-
수만 장 가족사진에 AI가 메타데이터를 입히는 과정 — Immich + VLM 파이프라인 해부IT 2026. 3. 18. 23:00
사진을 Immich에 올리면 끝일까? 26,841장의 가족사진을 Immich에 넣은 뒤, 진짜 작업이 시작되었다. 사진 한 장에 붙는 메타데이터가 어떤 종류가 있고, 각각 어떻게 만들어지며, 지금 어디까지 진행되었는지 정리한다.
사진 한 장에 붙는 정보들
Immich에 사진을 업로드하면 여러 겹의 정보가 사진 위에 쌓인다. 크게 네 가지 레이어로 나눌 수 있다.
하나씩 살펴보자.
Layer 1: EXIF — 카메라가 남기는 기본 정보
사진 파일 안에 원래 들어 있는 정보다. Immich가 새로 만드는 게 아니라, 카메라(또는 스마트폰)가 촬영 시점에 기록한 것을 Immich가 읽어서 DB에 저장한다.
항목 예시 활용 촬영일시 2023-08-15 14:32:07 타임라인 정렬, "이 날의 추억" GPS 좌표 37.5665°N, 126.9780°E 지도 뷰에 사진 표시 카메라 모델 Galaxy S26 기기별 필터링 해상도 4032×3024 썸네일 생성 비율 파일 크기 4.2 MB 스토리지 관리 처리 시점: 업로드 즉시. 사진이 Immich에 들어오는 순간 EXIF 파서가 돌아간다. 별도 AI가 필요 없으므로 수만 장도 수 분 내에 완료된다.
현재 상태: 전량 완료. 어렸을 때 찍은 사진부터 최신 사진까지 타임라인이 자동 구성되어 있다.
Layer 2: CLIP — "레고 블록을 쌓는 아이" 검색이 되는 이유
CLIP(Contrastive Language-Image Pre-training)은 이미지와 텍스트를 같은 벡터 공간에 매핑하는 모델이다. Immich는 업로드된 사진마다 CLIP 벡터를 생성해서 PostgreSQL(pgvector)에 저장한다.
작동 원리:
1. 사진 업로드 → Immich ML 컨테이너가 CLIP 모델로 이미지 임베딩 생성
2. 사용자가 "해변에서 뛰는 아이"를 검색 → 텍스트 임베딩 생성
3. 이미지 벡터와 텍스트 벡터의 코사인 유사도 계산 → 가장 비슷한 사진 반환GPU 가속 효과:
작업 CPU GPU 비고 CLIP 인덱싱 수 일 4분 "레고 블록을 쌓는 아이" 검색 가능 이 차이는 DGX Spark의 Blackwell GPU(SM 121)에 맞춰 ONNX Runtime을 직접 빌드한 덕분이다. 기본 제공되는 CPU 모드에서는 26,000장 인덱싱에 며칠이 걸리지만, GPU 모드에서는 4분 만에 끝난다.
현재 상태: 26,841장 전량 인덱싱 완료. Immich 검색창에서 한국어/영어 자연어 검색이 즉시 동작한다.
Layer 3: InsightFace — 474개 얼굴 클러스터에서 60명 식별
Immich에 내장된 InsightFace(buffalo_l) 모델이 얼굴 인식을 담당한다. 크게 세 단계로 작동한다.
1단계 — 얼굴 감지(Detection)
사진에서 얼굴 영역을 찾아 바운딩 박스를 그린다. 단체 사진이라면 한 장에서 여러 얼굴이 감지된다.
2단계 — 임베딩(Embedding)
감지된 얼굴마다 512차원 벡터를 생성한다. 이 벡터가 "이 얼굴의 고유한 특징"을 숫자로 표현한 것이다.
3단계 — 클러스터링 + 이름 태깅
비슷한 벡터끼리 자동으로 묶는다(클러스터링). 같은 사람의 얼굴은 벡터가 가까우므로 하나의 클러스터로 모인다. 여기까지는 자동이고, 각 클러스터에 이름을 붙이는 건 수동이다. Immich의 People 탭에서 클러스터를 클릭하고 "지민"이라고 이름을 입력하면, 그 이후로 같은 클러스터의 모든 사진에 이름이 적용된다.
GPU 가속 효과:
작업 CPU GPU 비고 얼굴 감지 수 일 8분 가족앨범에서 얼굴 추출 얼굴 클러스터링 수 일 10분 인물별 자동 분류 현재 상태:
- 감지된 얼굴: 5,944장에서 발견
- 클러스터 수: 474개 (한 사람이 여러 클러스터로 나뉘기도 함)
- 이름 확인 완료: 60명 (가족, 친척, 지인)
- 미확인 클러스터: 62개왜 한 사람이 여러 클러스터로 나뉠까? 아이의 경우 성장에 따라 얼굴이 크게 변하기 때문이다. 예를 들어 가장 많이 등장하는 "수현"은 159개 클러스터, "도윤"은 124개 클러스터로 나뉘어 있다. 어릴 때의 얼굴과 지금의 얼굴이 다르게 인식되는 것이다. Immich에서는 이런 클러스터들을 수동으로 하나의 인물로 병합할 수 있다.
가장 많이 등장하는 인물 TOP 5 (개인정보 보호를 위해 가명 사용):
이름* 사진 수 클러스터 주요 활동 수현 1,354장 159개 여행, 가족사진 도윤 1,331장 124개 여행, 가족식사 지민 921장 66개 가족사진, 성장 기록 서연 221장 27개 가족 모임 하준 164장 1개 가족 모임 * 표의 이름은 모두 가명입니다.
Layer 4: VLM — 사진마다 한국어 설명을 써주는 AI
가장 최근에 추가한 레이어다. Immich 내장 모델은 아무리 좋아도 사진의 "맥락"을 문장으로 설명해주지는 않는다. CLIP은 검색용 벡터를 만들 뿐이고, InsightFace는 얼굴만 본다. "가족이 제주도 해변에서 모래성을 쌓고 있다"는 정보는 어디에도 없다.
이걸 해결하기 위해 VLM(Vision-Language Model)을 Immich 외부에서 별도로 돌리고 있다.
사용 모델: Qwen2.5-VL 32B (Ollama로 로컬 실행, 21.2GB)
분석 과정:
VLM 프롬프트가 요청하는 정보:
{ "description": "가족이 해변에서 모래성을 쌓고 있다. 아이가 삽으로 모래를 퍼 올리고 있다.", "people_count": 3, "location_type": "실외", "scene": "해변 놀이", "tags": ["가족", "바다", "모래성", "여름"] }이 정보가 Immich에 저장되는 방식:
-description→ 사진의 설명(Description) 필드에 직접 기록
-tags→vlm:가족,vlm:바다같은 접두어를 붙여 Immich 태그로 부착
-scene→vlm:scene:해변 놀이태그로 부착
-location_type→vlm:실외태그로 부착왜 Immich 내장이 아니라 외부에서 돌릴까?
Immich의 내장 ML은 CLIP과 InsightFace에 최적화되어 있다. VLM처럼 사진을 보고 문장을 생성하는 모델은 기본 제공하지 않는다. 그래서 Ollama로 Qwen2.5-VL을 로컬에 띄우고, Immich REST API를 통해 결과를 주입하는 구조를 별도로 만들었다.
현재 진행 상황:
항목 수치 전체 이미지 26,736장 VLM 처리 완료 1,720장 (6.4%) 성공 1,709장 실패 11장 (0.6%) 스킵 (기존 설명 있음) 37장 처리 속도 약 1분/장 남은 예상 시간 약 16일 1분/장이 느리게 느껴질 수 있지만, 32B 파라미터 VLM이 사진을 보고 한국어 문장을 생성하는 작업이다. GPU 메모리를 21GB 점유하면서 24시간 쉬지 않고 돌아가고 있다. 실패율 0.6%는 대부분 VLM이 JSON이 아닌 자연어로 응답한 경우로, 파싱 실패다. 사진 자체의 문제는 아니다.
새 사진이 들어오면 — 자동 파이프라인
사진 관리는 한 번 세팅하고 끝나는 게 아니다. 앞으로도 계속 사진이 추가될 텐데, 매번 수동으로 처리할 수 없다. 그래서 자동 파이프라인을 구축했다.
자동 인입 경로 (inbox → Immich)
Immich 모바일 앱으로 촬영한 사진은 자동 백업된다. 하지만 카카오톡이나 메신저로 받은 사진, 다른 기기에서 옮긴 사진은
inbox폴더에 넣으면 된다.매일 새벽 3시 cron이 실행되면:
- inbox 스캔 → 미디어 파일 탐색
- 무결성 검증 → 깨진 파일 필터링 (PIL/ffprobe)
- 중복 검사 → SHA-1 해시로 Immich 기존 사진과 비교
- 업로드 → Immich REST API로 전송
- ML 트리거 → CLIP + InsightFace 자동 실행
- VLM 분석 → 미처리 사진에 한국어 설명 추가
중복 검사가 있으므로 같은 사진을 두 번 넣어도 안전하다. Immich 내부 체크섬과 동일한 SHA-1 base64 인코딩을 사용한다.
VLM 처리 추적 — 태그 기반 resume
VLM 분석이 중간에 중단되더라도 문제없다. 처리 완료된 사진에는
vlm:processed태그가 붙기 때문에, 다음 실행 시 이미 태그가 있는 사진은 건너뛴다. 마찬가지로 기존에 description이 있는 사진도 스킵한다.동시 실행 방지를 위해
fcntl파일 락을 사용한다. cron이 VLM을 시작하려 하는데 이미 진행 중이면 자동으로 스킵된다.각 레이어의 검색 활용
이 네 가지 레이어가 모이면, 한 장의 사진을 다양한 방식으로 찾을 수 있다.
검색 방법 사용 레이어 예시 날짜로 찾기 EXIF "2023년 8월 사진" 장소로 찾기 EXIF (GPS) 지도에서 제주도 클릭 자연어 검색 CLIP "케이크 앞에서 촛불 부는 아이" 인물로 찾기 InsightFace "지민이 나온 사진" 태그로 필터 VLM vlm:scene:생일파티설명 텍스트 검색 VLM description에서 "모래성" 검색 CLIP만으로도 자연어 검색이 되지만, VLM 설명이 추가되면 더 정교한 검색이 가능해진다. 예를 들어 CLIP은 "음식"과 관련된 사진을 찾아주지만, VLM은 "삼겹살 파티"인지 "생일 케이크"인지까지 구분해준다.
16일 후의 미래
현재 VLM 분석은 6.4% 진행되었다. 26,736장 중 1,720장이 한국어 설명을 갖고 있다. 남은 25,016장은 약 16일 뒤에 완료된다.
모든 사진에 설명이 붙으면:
- "제주도에서 돌하르방 앞에서 찍은 가족사진" 같은 구체적인 검색이 가능해진다
- 사진별 장면 분류(여행, 생일, 졸업, 식사 등)가 완성되어 자동 앨범 구성의 재료가 된다
- 인물 인식과 결합하면 "지민이 나온 생일파티 사진" 같은 복합 검색도 된다자동화 파이프라인이 갖춰진 덕분에, 앞으로 새 사진이 추가되어도 동일한 4단계를 자동으로 거치게 된다. 한 번 세팅하면 사진이 알아서 똑똑해지는 구조다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
gogcli에서 gws로: REST API → CLI → AI Agent, 도구의 진화를 따라가다 (0) 2026.03.17 DGX Spark에서 Immich로 가족앨범 GPU 가속 관리하기 (1) 2026.03.16 DGX Spark에서 ONNX Runtime GPU 빌드 성공기 — 8번의 실패와 1번의 성공 (0) 2026.03.16 왜 MoE 아키텍처가 등장했나? - Trinity 모델 툴콜링 이슈에서 출발한 탐구 (0) 2026.03.15 Claude Code Remote Control 실전 가이드 - 서버의 AI를 모바일에서 이어 쓰기 (0) 2026.03.15 블록체인에서 AI 개발까지 - Proof of Work는 어떻게 진화했나 (0) 2026.03.15 AI 에이전트에게 자율권을 얼마나 줄 것인가 - HITL Policy 설계 (0) 2026.03.14 Claude Code가 PR을 만들 수 있는 이유 - GitHub CLI와 API의 구조부터 이해하기 (0) 2026.03.14 LLM tool calling, '지원'한다면서요? — 스펙과 현실 사이의 간극 (1) 2026.03.14 NVIDIA DGX부터 ASUS Ascent GX10, MSI EdgeXpert까지 - AI 서버 시장이 바뀌고 있다 (0) 2026.03.13