Skip to content

DogDogPaw/ml-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

14 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

멍멍포 ML Server

멍멍포 ν”Œλž«νΌμ˜ λΉ„λ¬Έ(Nose Print) νŠΉμ§• 벑터 μΆ”μΆœ 및 μœ μ‚¬λ„ 검증 μ„œλ²„μž…λ‹ˆλ‹€.

λͺ©μ°¨


κ°œμš”

ML ServerλŠ” λ°˜λ €λ™λ¬Όμ˜ μ½” 무늬(λΉ„λ¬Έ)λ₯Ό λΆ„μ„ν•˜μ—¬ κ³ μœ ν•œ νŠΉμ§• 벑터λ₯Ό μΆ”μΆœν•˜κ³ , μ €μž₯된 λ²‘ν„°μ™€μ˜ μœ μ‚¬λ„λ₯Ό κ²€μ¦ν•˜λŠ” λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€μž…λ‹ˆλ‹€.

핡심 κΈ°λŠ₯

  • νŠΉμ§• 벑터 μΆ”μΆœ: μ½” μ΄λ―Έμ§€μ—μ„œ κ³ μœ ν•œ μž„λ² λ”© 벑터 생성
  • μœ μ‚¬λ„ 검증: μƒˆ 이미지와 μ €μž₯된 벑터 비ꡐ (코사인 μœ μ‚¬λ„, μœ ν΄λ¦¬λ“œ 거리)
  • Pet DID 생성 지원: μΆ”μΆœλœ λ²‘ν„°μ˜ ν•΄μ‹œλ‘œ κ³ μœ ν•œ DID 생성

기술 μŠ€νƒ

  • Framework: FastAPI (Python)
  • Language: Python 3.11+
  • ML Runtime: ONNX Runtime
  • Model Format: ONNX (Open Neural Network Exchange)
  • Communication: gRPC (Protocol Buffers)
  • Storage: NCP Object Storage
  • Container: Docker
  • Orchestration: Kubernetes

μ•„ν‚€ν…μ²˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        API Gateway                               β”‚
β”‚                         (NestJS)                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β”‚ gRPC (:50052)
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        ML Server                                 β”‚
β”‚                        (FastAPI)                                 β”‚
β”‚                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚                    ONNX Runtime                           β”‚   β”‚
β”‚  β”‚                                                           β”‚   β”‚
β”‚  β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”‚
β”‚  β”‚    β”‚           embedder_model.onnx                    β”‚   β”‚   β”‚
β”‚  β”‚    β”‚                                                  β”‚   β”‚   β”‚
β”‚  β”‚    β”‚   Input: μ½” 이미지 (224x224 RGB)                 β”‚   β”‚   β”‚
β”‚  β”‚    β”‚   Output: νŠΉμ§• 벑터 (512-dim float array)        β”‚   β”‚   β”‚
β”‚  β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ NCP Object Storageβ”‚
                    β”‚                   β”‚
                    β”‚ nose-print-photo/ β”‚
                    β”‚   └── {petDID}/   β”‚
                    β”‚        β”œβ”€β”€ img.jpgβ”‚
                    β”‚        └── vec.npyβ”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚    PostgreSQL     β”‚
                    β”‚   (벑터 메타데이터)β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

gRPC API

μ„œλΉ„μŠ€ μ •μ˜

service NoseEmbedderService {
  // κ°•μ•„μ§€ μ½” μ΄λ―Έμ§€μ—μ„œ νŠΉμ§• 벑터 μΆ”μΆœ
  rpc ExtractNoseVector(NoseImageRequest) returns (NoseVectorResponse);

  // μƒˆ 이미지와 μ €μž₯된 이미지(PetDID) 비ꡐ
  rpc CompareWithStoredImage(CompareWithStoredImageRequest) returns (CompareVectorsResponse);

  // gRPC μ—°κ²° μƒνƒœ 확인
  rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
}

ExtractNoseVector - νŠΉμ§• 벑터 μΆ”μΆœ

μ½” μ΄λ―Έμ§€μ—μ„œ κ³ μœ ν•œ νŠΉμ§• 벑터(μž„λ² λ”©)λ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€.

Request:

message NoseImageRequest {
  bytes image_data = 1;      // 이미지 λ°”μ΄νŠΈ 데이터 (JPEG/PNG)
  string image_format = 2;   // 이미지 포맷 ("jpeg" λ˜λŠ” "png")
}

Response:

message NoseVectorResponse {
  repeated float vector = 1;           // νŠΉμ§• 벑터 (512차원 float λ°°μ—΄)
  int32 vector_size = 2;               // 벑터 차원 (512)
  bool success = 3;                    // 성곡 μ—¬λΆ€
  string error_message = 4;            // μ—λŸ¬ λ©”μ‹œμ§€
  optional MLErrorCode error_code = 5; // μ—λŸ¬ μ½”λ“œ
  optional bool retryable = 6;         // μž¬μ‹œλ„ κ°€λŠ₯ μ—¬λΆ€
}

μ‚¬μš© μ˜ˆμ‹œ:

# API Gatewayμ—μ„œ 호좜
response = await ml_service.ExtractNoseVector(
    NoseImageRequest(
        image_data=image_bytes,
        image_format="jpeg"
    )
)

# 벑터λ₯Ό keccak256 ν•΄μ‹œν•˜μ—¬ Pet DID 생성
vector_hash = keccak256(response.vector)
pet_did = f"did:ethr:besu:0x{vector_hash[:40]}"

CompareWithStoredImage - μ €μž₯된 이미지와 비ꡐ

μƒˆλ‘œ μ΄¬μ˜ν•œ 이미지와 기쑴에 λ“±λ‘λœ 펫의 비문을 λΉ„κ΅ν•˜μ—¬ μœ μ‚¬λ„λ₯Ό κ²€μ¦ν•©λ‹ˆλ‹€.

Request:

message CompareWithStoredImageRequest {
  string image_key = 1;   // μƒˆ 이미지 ν‚€ (NCP 경둜: nose-print-photo/{petDID}/{fileName})
  string pet_did = 2;     // 비ꡐ할 Pet DID
}

Response:

message CompareVectorsResponse {
  float similarity = 1;            // μ’…ν•© μœ μ‚¬λ„ 점수 (0.0 ~ 1.0)
  float cosine_similarity = 2;     // 코사인 μœ μ‚¬λ„ (0.0 ~ 1.0)
  float euclidean_distance = 3;    // μœ ν΄λ¦¬λ“œ 거리
  bool success = 4;                // 성곡 μ—¬λΆ€
  string error_message = 5;        // μ—λŸ¬ λ©”μ‹œμ§€
  int32 vector_size = 6;           // 벑터 차원
  optional MLErrorCode error_code = 7;
  optional bool retryable = 8;
}

μœ μ‚¬λ„ μž„κ³„κ°’:

  • similarity >= 0.85: 동일 펫으둜 인정
  • similarity >= 0.70: μΆ”κ°€ 검증 ν•„μš”
  • similarity < 0.70: λ‹€λ₯Έ 펫으둜 νŒμ •

HealthCheck - μ„œλΉ„μŠ€ μƒνƒœ 확인

Request:

message HealthCheckRequest {
  string service = 1;
}

Response:

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
    SERVICE_UNKNOWN = 3;
  }
  ServingStatus status = 1;    // μ„œλΉ„μŠ€ μƒνƒœ
  string message = 2;          // μƒνƒœ λ©”μ‹œμ§€
  string model_loaded = 3;     // λͺ¨λΈ λ‘œλ“œ μƒνƒœ ("true" / "false")
  string timestamp = 4;        // νƒ€μž„μŠ€νƒ¬ν”„
}

μ—λŸ¬ μ½”λ“œ

ν΄λΌμ΄μ–ΈνŠΈ μ—λŸ¬ (4xxx) - μž¬μ‹œλ„ λΆˆκ°€

μ½”λ“œ Enum μ„€λͺ…
ML_4001 INVALID_IMAGE μœ νš¨ν•˜μ§€ μ•Šμ€ 이미지
ML_4002 IMAGE_TOO_LARGE 이미지 크기 초과
ML_4003 INVALID_IMAGE_FORMAT μ§€μ›ν•˜μ§€ μ•ŠλŠ” 이미지 ν˜•μ‹
ML_4004 VECTOR_NOT_FOUND 벑터 데이터 μ—†μŒ
ML_4005 VECTOR_DIMENSION_MISMATCH 벑터 차원 뢈일치
ML_4006 INVALID_REQUEST 잘λͺ»λœ μš”μ²­

μ„œλ²„ μ—λŸ¬ (5xxx) - μž¬μ‹œλ„ κ°€λŠ₯

μ½”λ“œ Enum μ„€λͺ…
ML_5001 MODEL_NOT_LOADED λͺ¨λΈ λ―Έλ‘œλ“œ
ML_5002 INFERENCE_ERROR μΆ”λ‘  였λ₯˜
ML_5003 STORAGE_CONNECTION_ERROR μŠ€ν† λ¦¬μ§€ μ—°κ²° 였λ₯˜
ML_5004 INTERNAL_SERVER_ERROR λ‚΄λΆ€ μ„œλ²„ 였λ₯˜
ML_5005 SERVICE_UNAVAILABLE μ„œλΉ„μŠ€ λΆˆκ°€

λΉ„λ¬Έ 인식 ν”„λ‘œμ„ΈμŠ€

1. 펫 등둝 μ‹œ (졜초 등둝)

1. ν΄λΌμ΄μ–ΈνŠΈκ°€ μ½” 이미지 촬영 (2μž₯)
                    β”‚
                    β–Ό
2. API Gateway β†’ ML Server: ExtractNoseVector()
                    β”‚
                    β–Ό
3. ML Server: 이미지 μ „μ²˜λ¦¬ (224x224 λ¦¬μ‚¬μ΄μ¦ˆ, μ •κ·œν™”)
                    β”‚
                    β–Ό
4. ML Server: ONNX λͺ¨λΈ μΆ”λ‘  β†’ 512차원 νŠΉμ§• 벑터
                    β”‚
                    β–Ό
5. API Gateway: keccak256(vector) β†’ Pet DID 생성
                    β”‚
                    β–Ό
6. 벑터 및 이미지λ₯Ό NCP Object Storage에 μ €μž₯
   - nose-print-photo/{petDID}/image_1.jpg
   - nose-print-photo/{petDID}/image_2.jpg
   - nose-print-photo/{petDID}/vector.npy
                    β”‚
                    β–Ό
7. 블둝체인에 Pet DID 등둝 (PetDIDRegistry)

2. λΉ„λ¬Έ 검증 μ‹œ (본인 확인)

1. ν΄λΌμ΄μ–ΈνŠΈκ°€ μƒˆ μ½” 이미지 촬영
                    β”‚
                    β–Ό
2. 이미지λ₯Ό NCP에 μž„μ‹œ μ €μž₯
   - nose-print-photo/{petDID}/verify_{timestamp}.jpg
                    β”‚
                    β–Ό
3. API Gateway β†’ ML Server: CompareWithStoredImage()
                    β”‚
                    β–Ό
4. ML Server: μƒˆ μ΄λ―Έμ§€μ—μ„œ 벑터 μΆ”μΆœ
                    β”‚
                    β–Ό
5. ML Server: NCPμ—μ„œ μ €μž₯된 벑터 λ‘œλ“œ
                    β”‚
                    β–Ό
6. ML Server: μœ μ‚¬λ„ 계산
   - 코사인 μœ μ‚¬λ„
   - μœ ν΄λ¦¬λ“œ 거리
   - μ’…ν•© μœ μ‚¬λ„ 점수
                    β”‚
                    β–Ό
7. similarity >= 0.85 β†’ 검증 성곡
   similarity < 0.85  β†’ 검증 μ‹€νŒ¨

3. μ†Œμœ κΆŒ 이전 μ‹œ

1. μƒˆ λ³΄ν˜Έμžκ°€ 펫의 μ½” 이미지 촬영
                    β”‚
                    β–Ό
2. CompareWithStoredImage()둜 동일 펫 확인
                    β”‚
                    β–Ό
3. similarity >= 0.85 확인
                    β”‚
                    β–Ό
4. λΈ”λ‘μ²΄μΈμ—μ„œ μ†Œμœ κΆŒ 이전 (changeController)
                    β”‚
                    β–Ό
5. κΈ°μ‘΄ VC λ¬΄νš¨ν™”, μƒˆ VC λ°œκΈ‰

μ„€μΉ˜ 및 μ‹€ν–‰

사전 μš”κ΅¬μ‚¬ν•­

  • Python 3.11+
  • pip λ˜λŠ” poetry
  • ONNX Runtime
  • NCP Object Storage μ ‘κ·Ό κΆŒν•œ

μ„€μΉ˜

pip install -r requirements.txt

λͺ¨λΈ λ‹€μš΄λ‘œλ“œ

python download_model.py

Proto 파일 생성

python generate_proto.py

개발 λͺ¨λ“œ μ‹€ν–‰

uvicorn src.main:app --reload --host 0.0.0.0 --port 50052

ν”„λ‘œλ•μ…˜ μ‹€ν–‰

python -m grpc_tools.protoc -I./proto --python_out=./src --grpc_python_out=./src ./proto/nose_embedder.proto
python src/main.py

Docker

docker build -t ml-server .
docker run -p 50052:50052 ml-server

Kubernetes

kubectl apply -f k8s/

ν™˜κ²½ λ³€μˆ˜

# Server
GRPC_PORT=50052

# Model
MODEL_PATH=./embedder_model.onnx
MODEL_INPUT_SIZE=224

# NCP Object Storage
NCP_ACCESS_KEY=your-access-key
NCP_SECRET_KEY=your-secret-key
NCP_BUCKET_NAME=dogcatpaw-ml
NCP_ENDPOINT=https://kr.object.ncloudstorage.com

# Database
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=your-password
DB_DATABASE=ml_server

# Logging
LOG_LEVEL=info

ν”„λ‘œμ νŠΈ ꡬ쑰

dogcatpaw-ml-server/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ main.py                # gRPC μ„œλ²„ μ§„μž…μ 
β”‚   β”œβ”€β”€ service.py             # NoseEmbedderService κ΅¬ν˜„
β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ embedder.py        # ONNX λͺ¨λΈ 래퍼
β”‚   β”‚   └── preprocessor.py    # 이미지 μ „μ²˜λ¦¬
β”‚   β”œβ”€β”€ storage/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   └── ncp_storage.py     # NCP Object Storage ν΄λΌμ΄μ–ΈνŠΈ
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ similarity.py      # μœ μ‚¬λ„ 계산 ν•¨μˆ˜
β”‚   β”‚   └── vector_utils.py    # 벑터 μœ ν‹Έλ¦¬ν‹°
β”‚   └── proto/
β”‚       β”œβ”€β”€ nose_embedder_pb2.py
β”‚       └── nose_embedder_pb2_grpc.py
β”œβ”€β”€ k8s/                       # Kubernetes 배포 μ„€μ •
β”œβ”€β”€ embedder_model.onnx        # ONNX λͺ¨λΈ 파일
β”œβ”€β”€ download_model.py          # λͺ¨λΈ λ‹€μš΄λ‘œλ“œ 슀크립트
β”œβ”€β”€ generate_proto.py          # Proto μ½”λ“œ 생성 슀크립트
β”œβ”€β”€ requirements.txt           # Python μ˜μ‘΄μ„±
β”œβ”€β”€ Dockerfile
└── BUILD.md

λͺ¨λΈ 사양

embedder_model.onnx

ν•­λͺ© κ°’
Input Shape (1, 3, 224, 224)
Input Format RGB, float32, normalized [0, 1]
Output Shape (1, 512)
Output Format float32 embedding vector
Model Size ~50MB

μ „μ²˜λ¦¬ νŒŒμ΄ν”„λΌμΈ

def preprocess(image: bytes) -> np.ndarray:
    # 1. λ°”μ΄νŠΈ β†’ PIL Image
    img = Image.open(io.BytesIO(image))

    # 2. RGB λ³€ν™˜
    img = img.convert("RGB")

    # 3. λ¦¬μ‚¬μ΄μ¦ˆ (224x224)
    img = img.resize((224, 224), Image.LANCZOS)

    # 4. numpy λ°°μ—΄ λ³€ν™˜
    arr = np.array(img, dtype=np.float32)

    # 5. μ •κ·œν™” [0, 255] β†’ [0, 1]
    arr = arr / 255.0

    # 6. 채널 μˆœμ„œ λ³€κ²½ (HWC β†’ CHW)
    arr = arr.transpose(2, 0, 1)

    # 7. 배치 차원 μΆ”κ°€
    arr = np.expand_dims(arr, axis=0)

    return arr

API Gateway 연동

// API Gateway의 NoseEmbedderProxyService
@Injectable()
export class NoseEmbedderProxyService implements OnModuleInit {
  private noseService: NoseEmbedderServiceClient;

  constructor(@Inject('ML_GRPC_SERVICE') private client: ClientGrpc) {}

  onModuleInit() {
    this.noseService = this.client.getService<NoseEmbedderServiceClient>('NoseEmbedderService');
  }

  async extractVector(imageData: Buffer): Promise<NoseVectorResponse> {
    return firstValueFrom(
      this.noseService.ExtractNoseVector({
        image_data: imageData,
        image_format: 'jpeg',
      })
    );
  }

  async compareWithStored(imageKey: string, petDID: string): Promise<CompareVectorsResponse> {
    return firstValueFrom(
      this.noseService.CompareWithStoredImage({
        image_key: imageKey,
        pet_did: petDID,
      })
    );
  }
}

λΌμ΄μ„ μŠ€

MIT License

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors