---
id: ar-photo-booth
type: project
doc: portfolio
url: https://auejin.com/ko/projects/ar-photo-booth/portfolio/
lang: ko
---

# AR 사진관 — 실시간 AI 합성 포토부스

**2024–2026 · 국내 퍼리 컨벤션 7개 행사 운영 · 기획/개발/운영 단독 수행**

저장소: https://github.com/auejin/mirror-selfie

---

## 목차

1. [Executive Summary](#1-executive-summary)
2. [문제 정의](#2-문제-정의)
3. [시스템 아키텍처](#3-시스템-아키텍처)
4. [기술 하이라이트](#4-기술-하이라이트)
5. [프로덕트 기획과 반복](#5-프로덕트-기획과-반복)
6. [현장 운영과 브랜드 지속](#6-현장-운영과-브랜드-지속)
7. [정량 임팩트](#7-정량-임팩트)
8. [사고와 구조적 해결](#8-사고와-구조적-해결)
9. [한계와 로드맵](#9-한계와-로드맵)
10. [부록 — 기술 스택](#10-부록--기술-스택)

---

## 증거 등급 규약

이 문서의 모든 수치에는 근거 등급이 붙는다.

| 등급 | 의미 |
|---|---|
| `[검증]` | 코드·파일·git에서 직접 산출. 재현 명령이 존재 |
| `[운영기록]` | 운영자가 행사 가계부에 기록한 실측치. 제3자 재현 불가 |
| `[히스토리]` | git 커밋 이력에서 추론 |

등급이 낮다고 수치를 빼지 않았고, 높은 것처럼 쓰지도 않았다.

---

## 1. Executive Summary

**AR 사진관**은 웹캠 한 대로 전신 코스튬 착용자를 배경에서 분리하고, 캐릭터 일러스트
레이어 사이에 합성해 그 자리에서 인화하는 무인 키오스크다. 전 과정이 온디바이스에서
실행되며 네트워크를 필요로 하지 않는다.

2024년 8월부터 2026년 4월까지 **7개 컨벤션에서 703장**을 촬영했고 `[운영기록]`,
2026년 두 행사는 앱이 원본·마스크·합성본을 자동 저장해 **141세트가 남아 있다** `[검증]`.
1일 최장 가동 시간은 **7시간 53분**, 촬영 간격 중앙값은 **2분 13초**다 `[검증]`.

이 문서가 다루는 것은 모델 정확도가 아니라 **제약 조건 아래의 설계 판단**이다.
참가자가 화면을 조작할 수 없다는 제약이 프롬프트 자동 생성 구조를 만들었고,
행사장에 인터넷이 없다는 제약이 온디바이스 아키텍처를 만들었으며,
그 아키텍처가 결과적으로 프라이버시 정책과 일치했다.

---

## 2. 문제 정의

### 2.1 도메인 컨텍스트

> **퍼리 팬덤 / 퍼슈트 / 컨벤션에 대해**
>
> 퍼리 팬덤은 의인화된 동물 캐릭터를 중심으로 하는 창작·코스튬 문화다.
> 퍼슈트(fursuit)는 이 캐릭터를 구현한 전신 코스튬으로, 머리 전체를 덮는 조형 헤드와
> 인조모피 재질의 몸체로 구성된다. 국내에서는 연 여러 차례 컨벤션이 열리며,
> 참가자 상당수가 퍼슈트를 착용하고 참여한다.
>
> 이 문서에서 이 도메인이 중요한 이유는 문화적 맥락이 아니라 **기술적 성격** 때문이다.
> 퍼슈트는 인물 분리(human matting) 모델에게 자연 발생한 적대 샘플이다.

### 2.2 왜 만들었나

컨벤션 참가자들은 자신의 퍼슈트 사진을 남기고 싶어 하지만, 전문 촬영은 예약·대기·
후보정 리드타임을 수반한다. 그 자리에서 결과물을 받아가는 경험은 별개의 가치다.

상용 포토부스는 이 요구의 절반만 채운다. 촬영과 즉석 인화는 되지만
**참가자의 캐릭터와 어울리는 배경·소품 합성**이 불가능하다.
크로마키 부스는 배경 합성은 되지만 부스 면적, 설치 시간, 조명 통제를 요구하는데
컨벤션 부스는 그 어느 것도 통제할 수 없는 환경이다.

### 2.3 무엇이 어려웠나

**(a) 퍼슈트는 범용 인물 분리 모델을 무너뜨린다**

범용 모델이 의존하는 단서가 거의 전부 무효화된다:

| 모델이 기대하는 것 | 퍼슈트에서 실제 |
|---|---|
| 인체 통계에 맞는 실루엣 | 머즐·귀·꼬리로 윤곽이 이탈 |
| 선명한 피부/의복 경계 | 인조모피가 픽셀 단위로 반투명 |
| 무광 표면 | 광택 소재가 배경을 반사 |
| 얼굴·피부색 단서 | 전신 착용으로 전무 |
| 일관된 조명 | 컨벤션장 혼합 조명 |

**(b) 조작할 사람이 없다**

참가자는 조형 헤드를 쓰고 있어 시야가 제한되고 손 조작이 어렵다.
운영자는 다음 참가자를 응대 중이다. 즉 **파이프라인 어디에도 사람이 개입할 수 없다.**

**(c) 현장은 개발 환경의 가정을 전부 깬다**

인터넷 없음, 카메라가 여러 개로 잡힘, 창이 실수로 닫힘, 프린터가 용지를 뭄.
그리고 이 모든 일이 **대기열이 있는 상태에서** 발생한다.

**(d) 정답이 없는 품질 문제**

합성이 자연스러운지는 자동 지표로 측정되지 않는다. IoU가 높아도 귀 끝이 잘리면
참가자는 실망한다. 평가 함수가 사람뿐이라는 뜻이다.

---

## 3. 시스템 아키텍처

### 3.1 전체 구조

```mermaid
graph TB
    subgraph Entry["진입점"]
        Main["main.py · Application<br/>tkinter + cv2 듀얼 루프"]
    end
    subgraph Cam["카메라 계층"]
        ICamera["ICamera (ABC)"] --> WC["Webcam<br/>4K · OS별 백엔드 선택"]
    end
    subgraph AI["AI 파이프라인"]
        DEPTH["DepthEstimator<br/>Depth Anything V2 Small"]
        SEG["SAM2ImageProcessor<br/>SAM 2.1 Tiny"]
        DEPTH -->|"rough mask + smart points"| SEG
    end
    subgraph Comp["합성 계층"]
        MIRROR["MirrorCompositor<br/>레이어 합성 + 색 조화"]
        CV["OpenCVUtils<br/>UMat/numpy 추상화"]
        MIRROR --> CV
    end
    PRINTER["BasePrinter<br/>CUPS / Win32"]

    Main --> ICamera
    Main --> DEPTH
    Main --> SEG
    Main --> MIRROR
    Main --> PRINTER
```

### 3.2 세그멘테이션 파이프라인 — depth가 프롬프트를 만든다

```mermaid
flowchart LR
    A["RGB 프레임<br/>4K"] --> B["1024px 다운스케일"]
    B --> C["Depth Anything V2<br/>깊이 추정"]
    C --> D["suppress_depth_gradient<br/>바닥·벽 저주파 제거"]
    D --> E["get_rough_mask<br/>depth 0.7 + 중심가중 0.3<br/>→ Otsu → 연결성분"]
    E --> F["FPS 샘플링<br/>FG/BG 포인트"]
    E --> G["rough mask"]
    F --> H["SAM 2.1<br/>hybrid prompt"]
    G --> H
    H --> I["합집합"]
    G --> I
    I --> J["depth gradient<br/>엣지 정제"]
    J --> K["4K 업스케일 마스크"]
```

핵심은 **SAM 2가 요구하는 프롬프트를 depth가 대신 만든다**는 점이다.
SAM 2는 promptable 모델이지만, 무인 부스에는 프롬프트를 줄 사람이 없다.

### 3.3 스레딩 모델 — 프리뷰와 저장의 분리

```mermaid
sequenceDiagram
    participant T0 as 메인 스레드
    participant T3 as Detection 스레드
    participant D as 화면/저장

    loop 매 프레임
        T0->>T0: get_frame()
        T0->>T0: _pending_result 소비 (lock)
        alt 이전 detection 완료
            T0->>T3: _run_detection_async 시작
            T3->>T3: depth → points → SAM2 → 정제
            T3->>T0: _pending_result 적재 (lock)
        end
        T0->>D: 캐시 마스크로 합성 → imshow
    end

    Note over T0,D: 촬영 버튼
    T0->>T3: join(timeout=5s)
    T0->>T0: 현재 프레임 동기 재추론
    T0->>D: 픽셀 정확 합성본 저장 + 인쇄
```

프리뷰 경로와 저장 경로가 분리되어 있다. 프리뷰 마스크는 최대 1프레임 낡을 수 있고,
이것은 **의도적으로 허용한 비용**이다. 포즈를 잡는 중에는 오차가 인지되지 않는다.

### 3.4 레이어 합성 구조

```
배경 (background.png)
  └ 뒤 캐릭터 레이어      ← 피사체 뒤에 놓이는 일러스트
      └ 인물 컷아웃        ← 마스크로 분리된 참가자 (색 조화 적용)
          └ 공통 스티커
              └ 앞 캐릭터 레이어  ← 피사체를 가리는 일러스트
```

캐릭터 에셋을 **앞/뒤 두 장으로 나눠 그려야** 이 구조가 성립한다.
소프트웨어 아키텍처가 그림 작업 방식을 규정하고, 그림의 구도가 다시 세그멘테이션
파라미터(`center_ratio`)를 규정한다. 5장에서 다시 다룬다.

---

## 4. 기술 하이라이트

### 4.1 프롬프트 자동 생성 — 사람을 파이프라인에서 제거하기

**문제.** SAM 2는 점·박스·마스크 프롬프트를 받아 임의 객체를 분할한다. 클래스 학습이
필요 없다는 게 장점이지만, 뒤집으면 **누군가 프롬프트를 줘야 한다**는 뜻이다.
무인 부스에는 그 사람이 없다.

**해결.** 단안 깊이 추정을 프롬프트 생성기로 사용했다.

1. Depth Anything V2 Small로 깊이 맵 추정
2. `suppress_depth_gradient()` — 바닥·벽처럼 서서히 변하는 저주파 성분을 제거.
   가우시안 블러로 저주파를 뽑아 빼고 백분위 [2,98]로 재정규화
3. `get_rough_mask()` — 깊이 0.7 + 중심 가우시안 가중 0.3의 혼합 스코어에
   Otsu 임계화, 모폴로지 정리 후 **중심 연결성분만** 채택(바닥·벽 제거),
   LAB 색거리 기반 확장
4. rough mask 내부에서 **FPS(Farthest Point Sampling)** 로 전경 포인트를 뽑고,
   이미지 경계 8점을 배경 포인트로 고정
5. rough mask(logits) + 포인트를 SAM 2.1에 **hybrid prompt**로 투입

**트레이드오프.** 모델을 하나 더 얹어 지연이 늘었다. 대신 신장·포즈·코스튬 종류가
바뀌어도 사람 개입 없이 적응한다. 늘어난 지연은 4.2의 비동기 구조로 상쇄했다.

> 고정 좌표 프롬프트라는 더 싼 선택지가 있었다. 기각한 이유는 참가자 신장 편차와
> 포즈 자유도 때문이다. 부스의 가치는 참가자가 원하는 포즈를 취하는 데 있는데,
> 고정 프롬프트는 그 자유도를 그대로 실패율로 바꾼다.

*근거: `utils/depth.py`, `utils/segment.py`, `main.py:_run_auto_detect`,
커밋 `b7d9114`, `06c7856`, `75546e4`*

### 4.2 비동기 더블 버퍼 — 정확도를 필요한 순간에만

**문제.** Depth + SAM2 + 정제를 매 프레임 동기 실행하면 프리뷰가 끊긴다.
추론 주기를 늦추면 촬영 순간의 마스크가 **이전 포즈의 것**이 되어 결과물에서 팔이나
귀가 잘린다. 두 요구가 시간 척도가 다른데 같은 경로에 있었다.

**해결.** 경로를 분리했다.

- 메인 루프: 캐시 마스크로 즉시 합성 → `imshow`. detection 스레드가 놀고 있으면
  새 작업 투입, 완료된 결과는 lock 하에 소비
- 촬영 시: `_flush_detection()` — 진행 중 스레드를 join(timeout 5초)하고,
  대기 중 결과를 소비한 뒤, **현재 프레임에 대해 동기 추론을 1회 더 실행**하고
  그 마스크로 재합성해 저장

**트레이드오프.** 프리뷰 마스크가 1프레임 낡는다. 포즈를 잡는 중에는 인지되지 않고,
결정적 순간인 촬영에서만 정확도 비용을 지불한다.

**부수 효과.** 스레드가 6종(메인, 세그멘터 로딩, 깊이 로딩, detection, 에셋 로딩,
파일 쓰기, 인쇄)으로 늘면서 공유 상태 보호가 문제가 됐다. 특히
`_smart_points`/`_rough_mask` 캐시는 **lock이 아니라 구조적 직렬화**로 보호된다 —
같은 스레드 안에서만 읽고 쓰며, `_flush_detection`은 join 이후에만 실행된다.
이 판단은 코드만 봐서는 재구성되지 않으므로 `.agent/knowledge/threading-model.md`에
명문화했다.

*근거: `main.py:253-357`, `.agent/knowledge/threading-model.md`, 커밋 `6837615`, `3bf6ddf`*

### 4.3 해상도 분리 — 인쇄 품질과 실시간성 양립

인쇄에는 4K가 필요하지만 4K로 추론하면 실시간이 불가능하다. 저해상도 마스크를 그냥
확대하면 경계에 계단이 생겨 **인쇄물에서 즉시 보인다.**

마스크만 긴 축 1024px로 계산하고 픽셀은 4K를 유지했다. 업스케일 계단은 다음 항목에서 해결한다.

*근거: `main.py:33, 195-211`, 커밋 `026c54d`*

### 4.4 엣지 정제 — 깊이를 판별자로 재활용

**문제.** 업스케일 계단을 없애려 균일 블러를 걸면 귀 끝·손가락·꼬리 같은 실제 디테일도
함께 뭉개진다. 안 걸면 계단이 남는다. **어느 픽셀이 진짜 경계이고 어느 픽셀이 해상도
아티팩트인지 마스크만 봐서는 구분할 수 없다.**

**해결.** 이미 계산해둔 깊이 맵을 판별자로 재사용했다.

1. 깊이 맵에 Sobel을 걸어 gradient 크기를 구하고 0~1로 정규화 (강할수록 진짜 경계)
2. 마스크를 모폴로지 Close/Open으로 스무딩한 버전을 준비
3. erode/dilate 차집합으로 **경계 밴드**를 정의하고, 그 안에서만
   `원본 × edge_weight + 스무딩 × (1 - edge_weight)` 로 블렌딩
4. 최종 이진화 → 벡터풍 샤프 엣지 유지

깊이 불연속이 있는 곳은 원본 엣지가 보존되고, 평탄한 곳의 계단만 뭉개진다.
추가 모델 없이 기존 연산 결과를 재활용해 푼 것이 이 설계의 요점이다.

*근거: `main.py:374-425`, 커밋 `7b5a2b7`*

### 4.5 색 조화 — 3세대에 걸친 개선과 회귀 테스트

정답 이미지가 없는 문제다. 자동 지표로 검증할 수 없고, 행사장 조명은 매번 다르며,
인조모피의 반사 특성은 사람 피부와 다르다.

| 세대 | 방식 | 시점 |
|---|---|---|
| 1세대 | LAB 색공간에서 배경 통계에 맞춰 정합 | 2025-10 |
| 2세대 | Teal & Orange 시네마틱 LUT — 곡선 앵커 5점을 채널별로 보간해 톤커브 생성 | 2026-04-02 |
| 3세대 | Dynamic luma matching | 2026-04-04 |

**3세대의 요점**은 밝기를 선형으로 옮기지 않았다는 것이다.

- 목표 명도 = 피사체 L 평균 × 0.4 + 배경 L 평균 × 0.6, `[80, 180]`로 클립
- 감마 지수 = `log(목표_정규화) / log(현재_정규화)`, `[0.4, 2.5]`로 클립
- L 채널에 감마 적용

선형 오프셋은 어두운 부분을 뭉개거나 밝은 부분을 날린다. 감마 보정은 **계조를
보존하면서** 전체 명도를 옮긴다. 배경에 100%가 아니라 6:4로 맞춘 것은
"배경에 완전히 녹아들면 피사체가 죽는다"는 연출 판단이고, 두 클립은 극단 입력에서의
발산을 막는 가드레일이다.

**검증 방법.** `scripts/test_color_grading.py`가 동일 입력에 대해
**원본 / 1세대 / 2세대 / 3세대 4단 비교 이미지**를 생성한다. 자동 지표가 없는 문제를
사람 눈 회귀 테스트로 대체한 것이다. 새 알고리즘이 특정 조명에서 이전보다 나빠지는지를
눈으로 확인할 수 있다.

*근거: `utils/mirror.py:106-184`, `scripts/test_color_grading.py`,
커밋 `ba9489b` → `3e9355f` → `845d41e`*

### 4.6 크로스플랫폼 가속 추상화

`cv2.UMat`은 macOS에서 동작하지 않고, 스칼라-UMat 연산(`1.0 - mask`)은 Windows에서
실패한다. 이런 비대칭이 코드 전역에 흩어지면 플랫폼 하나가 조용히 깨진다.

`OpenCVUtils` 정적 메서드 계층으로 전 호출을 통과시키고,
**직접 `cv2.UMat()` 호출을 금지 규칙으로 명문화**했다.
PyTorch 디바이스는 MPS → CUDA → CPU 순 자동 선택.

| | Windows | macOS |
|---|---|---|
| OpenCV 가속 | `cv2.UMat` (OpenCL) | `np.ndarray` |
| PyTorch | CUDA → CPU | MPS → CPU |
| 카메라 백엔드 | `CAP_MSMF` | `CAP_AVFOUNDATION` |
| 인쇄 | Win32 API | `lpr` (CUPS) |

*근거: `utils/opencv.py`, `.agent/knowledge/cross-platform-patterns.md`,
커밋 `b8be00e`, `62a6f4e`*

### 4.7 암묵지를 에이전트가 읽는 규칙으로

스레드 6종이 모델·마스크 캐시·컴포지터 레이어를 공유하는 구조에서
"이 변수는 왜 lock이 필요 없는가"(같은 스레드 내 직렬화) 같은 판단은
코드만 봐서 재구성되지 않는다. 3개월 뒤의 본인도, AI 코딩 에이전트도 마찬가지다.

저장소에 `.agent/` 트리를 만들어 **총 1,371줄**을 작성했다 `[검증]`:

| 디렉터리 | 내용 | 줄 수 |
|---|---|---|
| `knowledge/` | 아키텍처, AI 파이프라인, 스레딩 모델, 크로스플랫폼 패턴 | 512 |
| `skills/` | SAM2 통합, 스레드 안전성, 크로스플랫폼 OpenCV, 컴포지터 레이어 | 459 |
| `workflows/` | 환경 설정, 실행, 캐릭터 추가, 카메라 추가, 모듈 리팩토링 | 400 |

`threading-safety` skill은 서술이 아니라 **검사 가능한 형태**다 —
"절대 금지 사항" 4항과 신규 기능 추가 시 체크리스트 5항으로 구성된다.

*근거: `.agent/` 전체, 커밋 `05ce587`, `3bf6ddf`, `358ab59`*

---

## 5. 프로덕트 기획과 반복

### 5.1 행사가 곧 릴리스 사이클이었다

git 커밋 61개를 행사 D-day 기준으로 분류하면 이렇게 나온다 `[히스토리]`:

| 행사 | D-day | D-30 ~ D-1 | D-day ~ D+7 |
|---|---|---|---|
| 2025 FUR:RAID | 2025-10-12 | 26 | 0 |
| 2026 HowlSoup | 2026-01-24 | 16 | 1 |
| 2026 FurryJoA | 2026-02-21 | 22 | 3 |
| 2026 FurstClass | 2026-04-04 | 2 | 1 |

**커밋 61개 중 61개(100%)가 행사 D-30 ~ D+7 구간 안에 있다.**

> **베이스레이트를 함께 밝힌다.** 저장소 기간은 2025-09-21 ~ 2026-04-04로 196일이고,
> 4개 행사의 D-30~D+7 창을 합치면 **126일(64%)** 을 덮는다. 즉 커밋이 무작위로
> 흩어져 있어도 64%는 이 구간에 들어간다. 실제는 100%이므로 유의미한 쏠림이지만,
> "100%"라는 숫자만 떼어 보면 실제보다 극적으로 읽힌다.
>
> 또한 **행사 D-day 중 확정 근거가 있는 것은 3건**이다
> (2024 FUR:RAID 파일명, 2026 FurryJoA·FurstClass 촬영 타임스탬프).
> 나머지는 아카이브 파일 수정시각과 행사 폴더명으로 추정했다.
> ±3일 오차로는 창 판정이 뒤집히지 않지만, 추정임을 밝혀둔다.

이 프로젝트에 "한가할 때 하는 개발"은 존재하지 않는다. 모든 코드가 다음 행사라는
외부 마감에 묶여 있었고, 그것이 우선순위 결정 방식이었다.

> **정직하게 쓰자면 이건 리스크 높은 방식이다.** 2026 HowlSoup D-1에는 9커밋이
> 들어갔고 그중에는 MODNet→SAM2 교체라는 아키텍처 변경이 있었다. 실패했으면
> 행사 당일 부스가 열리지 못했을 것이다. 완충 장치는 두 가지였다 —
> 롤백 가능한 단위로 쪼개 커밋했고, 이전 버전이 항상 동작하는 상태로 남아 있었다.
> 결과적으로 7회 모두 정상 운영됐지만, 이것을 좋은 프로세스라고 주장하지는 않는다.

### 5.2 무엇을 만들지보다 무엇을 안 만들지

기획 역량은 추가한 기능보다 **쳐낸 것**에서 드러난다. 이 저장소에는 그 증거가 있다.

**(a) 수동 편집 기능을 삭제했다** — 커밋 `d463b89` "remove manual editing"

초기에는 운영자가 마스크를 클릭으로 보정할 수 있었다. 정확도는 올라가지만
**촬영 1건당 운영자가 붙잡히는 시간**이 생긴다. 대기열이 있는 부스에서 이것은
처리량을 직접 깎는다. 자동 인식(`06c7856`)이 충분히 동작하는 것을 확인한 뒤
기능을 통째로 제거했다. 정확도 상한을 낮추고 처리량을 택한 결정이다.

**(b) 뎁스카메라를 도입했다가 제거했다** — `e42dc1a`(2025-10) → `b0e64e7`(2026-02)

8장 CASE 2에서 자세히 다룬다.

**(c) 완전 자동화를 고집하지 않았다** — 커밋 `9b7fe70`

캐릭터마다 구도가 다르니 피사체 중심 위치도 다르다. 이것을 자동 추정하려면
또 하나의 문제를 풀어야 한다. 대신 `CHARACTER_CONFIG`에 캐릭터별
`center_ratio_x/y`를 상수로 두었다. 9종 × 2개 값 = 18개 숫자로 끝나는 문제를
알고리즘으로 풀지 않았다.

### 5.3 사용자 피드백이 코드가 되는 경로

행사 아카이브에는 행사별 **후기 폴더가 194개 파일**로 남아 있다 `[검증]`.

| 행사 | 후기 파일 |
|---|---|
| 2024 FUR:RAID | 36 |
| 2024 HowlSoup | 28 |
| 2025 FurryJoA | 97 |
| 2026 HowlSoup | 10 |
| 2026 FurryJoA | 23 |

피드백이 코드로 이어진 경로가 커밋에 남아 있다:

| 현장에서 드러난 것 | 이어진 커밋 | 시점 |
|---|---|---|
| 행사장에 인터넷이 없다 | `de9d569` 오프라인 모드 | 2026 HowlSoup **D+0** |
| macOS가 아이폰을 카메라로 잡는다 | `b3d2b61` Continuity Camera 배제 | 2026 FurryJoA **D+2** |
| 현장 조명에서 합성이 뜬다 | `845d41e` dynamic luma matching | 2026 FurstClass **D+0** |
| 모델 로딩 중 크래시 | `25f3b6a` 로딩 버그 수정 | 2026 FurryJoA **D+0** |

**행사 당일에 커밋이 찍혔다는 것**은 부스를 운영하면서 문제를 발견하고 그 자리에서
고쳤다는 뜻이다. 이 4건이 현장 피드백 루프의 가장 직접적인 증거다.

### 5.4 수요를 보고 공급을 늘렸다

2024 HowlSoup 촬영본을 캐릭터별로 분류한 결과 `[검증]`:

| 순위 | 캐릭터 | 촬영 | 점유율 |
|---|---|---|---|
| 1 | A | 42 | 31.1% |
| 2 | B | 27 | 20.0% |
| 3 | C | 25 | 18.5% |
| 4 | D | 25 | 18.5% |
| 5 | E | 10 | 7.4% |
| 6 | F | 6 | 4.4% |

상위 2종이 51%, 하위 2종은 합쳐도 12%다. 이 분포는 단순 촬영 기록이 아니라
**어떤 에셋에 제작 시간을 더 쓸지 결정하는 제품 지표**다.

이후 캐릭터는 6종 → 9종으로 늘었고, 추가 시점은 전부 특정 행사 직전이다
(`6443616` "new characters for howlsoup 2026", `a812ad2` 캐릭터 1종 추가 — **D-2**).

### 5.5 소프트웨어가 그림을 규정하고, 그림이 파라미터를 규정한다

레이어 합성 구조(3.4절)는 캐릭터 에셋을 **앞/뒤 두 장으로 나눠 그려야** 성립한다.
동시에 그림의 구도가 결정되면 그에 맞춰 세그멘테이션의 `center_ratio`를 조정해야 한다.

디자이너와 개발자가 나뉘어 있다면 이 왕복은 며칠이 걸린다.
단독 수행이라 **왕복이 0**이었고, 그래서 행사 D-2에 새 캐릭터를 투입할 수 있었다.

품질 상한이 존재한다는 대가는 인정한다. 전문 일러스트레이터가 그렸다면 더 나았을
에셋이 있고, 전담 개발자가 있었다면 더 나은 구조가 있었을 것이다.
단독 수행은 **품질 상한을 감수하고 반복 속도를 얻는 교환**이었다.

---

## 6. 현장 운영과 브랜드 지속

### 6.1 3년 7회, 그리고 부스는 매번 열렸다

| 행사 | 시기 | 촬영 `[운영기록]` |
|---|---|---|
| FUR:RAID | 2024-08 | 80 |
| HowlSoup | 2024-12 | 120 |
| FurryJoA | 2025-02 | 64 |
| FUR:RAID | 2025-10 | 200 |
| HowlSoup | 2026-01 | 100 |
| FurryJoA | 2026-02 | 64 |
| FurstClass | 2026-04 | 75 |
| **합계** | **3년 7회** | **703** |

성장 추이로는 FUR:RAID 기준 80(2024-08) → 200(2025-10)으로 2.5배다.
다만 **인과 주장은 하지 않는다.** 행사 규모, 부스 위치, 운영 시간, 동시기 경쟁 부스가
통제되지 않았다. 확실한 것은 3년간 7회 연속 재참가했고 매회 부스를 유지했다는 사실이다.

### 6.2 부스 운영 실측

2026년 두 행사는 앱이 촬영 시 3종 파일을 자동 저장하므로 운영 지표가 직접 산출된다 `[검증]`:

| 지표 | 2026 FurryJoA | 2026 FurstClass |
|---|---|---|
| 촬영 건수 | 63 | 75 |
| 가동 시간 | 10:08 → 16:02 (**5h 53m**) | 08:29 → 16:22 (**7h 53m**) |
| 시간당 처리량 | 10.7건/h | 9.5건/h |
| 촬영 간격 p50 | **2분 13초** | **2분 31초** |
| 촬영 간격 p90 | 16분 46초 | 17분 25초 |
| 피크 | 15시 (18건) | 15시 (22건) |

> **이 수치가 증명하는 것과 증명하지 않는 것.** 위 가동 시간은 첫 촬영과 마지막 촬영의
> 타임스탬프 간격이다. 그 사이에 부스가 계속 촬영을 받았다는 것은 증명하지만,
> **프로그램이 한 번도 재시작되지 않았다는 것은 증명하지 않는다.**
> 중간에 크래시 후 재실행이 있었어도 타임스탬프 범위는 동일하게 나온다.
> "무중단"이 아니라 "가동 시간"으로 읽어야 정확하다.

**p50 약 2.5분**이 1팀당 실제 처리 시간이다 — 안내, 캐릭터 선택, 포즈, 촬영, 인쇄, 전달.
대기열이 있는 구간에서는 이것이 시스템의 처리 한계다.

p50과 p90의 격차(2.5분 vs 17분)는 시스템 불안정이 아니라 **수요의 시간대 편차**다.
개장 직후와 점심 전후가 한산하고 15시에 피크가 온다. 두 행사 모두 같은 패턴이다.

**FurstClass는 가계부 75장과 자동저장 75세트가 완전히 일치한다.**
가계부 기록 방식이 신뢰할 만하다는 교차검증 결과다.

### 6.3 부스 브랜딩을 직접 만들었다

행사 아카이브에는 소프트웨어 외의 산출물이 함께 남아 있다 `[검증]`:

부스 간판 · 메뉴판 · 문패 · A3 포스터 4종 · 명함 · 참가 카드 · 패널 ·
키오스크 버튼 UI 아이콘 · 캐릭터별 얼굴 아이콘 · 굿즈 · 부스컷 템플릿 ·
**한국어 운영 설명서**

연도별로 무엇이 추가됐는지 추적하면 부스가 어떻게 정교해졌는지 보인다:
2024 메뉴판 → 2025 참가 카드·패널 → 2026 A3 포스터 4종.

키오스크 화면의 캐릭터 선택 버튼 아이콘과 `main.py`의 `change_character()` 팝업이
대응한다. **디자인과 구현을 같은 사람이 왕복했다**는 증거다.

### 6.4 커뮤니티와의 관계 — 데이터를 얻을 자격

퍼슈트 사진은 커뮤니티에서 소유권·초상권 의식이 매우 높은 자산이다.
무단 수집은 기술적으로 쉽지만 커뮤니티 신뢰를 영구히 잃는다.

**수집 절차** `[운영기록]`

1. 퍼리 팬덤 커뮤니티 내에서 **트위터 공개 모집**
2. 사용 목적 사전 고지 — *AR 사진관 + 향후 개발할 퍼리·퍼슈트 팬덤용 AI 학습*
3. **구글 폼** opt-in 접수, 기증자가 본인 드라이브 링크 제출
4. 부스 참가자 촬영물은 **로컬 저장만, 외부 반출 없음**

**설계상 짚을 점 4가지**

- 크롤링이 아닌 **opt-in 공개 모집** — 수집 출처의 정당성
- **목적 고지에 미래 학습 용도까지 포함** — purpose specification
- 기증 데이터(학습용)와 참가자 촬영물(서비스 산출물)의 **취급 등급 분리**
- 참가자 촬영물 로컬 온리 원칙이 **온디바이스 추론 아키텍처와 일치**한다.
  클라우드 API를 쓰지 않은 것은 지연·비용 최적화이자 동시에 프라이버시 설계였다 —
  **하나의 아키텍처 결정이 두 요구를 함께 만족시킨 사례**다.

결과적으로 규모는 작지만 **사용 자격이 명확한 데이터셋**을 얻었다.
크롤링이었다면 며칠이면 될 일을 수개월에 걸쳐 했고, 그 대신 커뮤니티 신뢰를 얻었다.

> **이 문서도 그 정책을 지킨다.** 기증 이미지와 참가자 촬영물은 여기에 싣지 않았다.
> 결과물 예시가 적은 것은 문서의 결함이 아니라 위 정책을 문서 자신에게 적용한 결과다.

---

## 7. 정량 임팩트

### 7.1 요약

| 축 | 수치 | 등급 |
|---|---|---|
| 운영 | 3년 7회, 703장 | `[운영기록]` |
| 가동 | 1일 최장 가동 **7h 53m** (첫 촬영~마지막 촬영) | `[검증]` |
| 처리량 | **9.5 ~ 10.7건/시간**, 간격 p50 **2분 13초** | `[검증]` |
| 데이터 | 동의 기반 기증 **9명 1,289장** + 운영 자동수집 **141세트** | `[검증]` |
| 개발 | 커밋 61개 **전부** 행사 D-30~D+7 창 내 | `[히스토리]` |
| 코드 | Python **3,436줄** + 에이전트 지식문서 **1,371줄** | `[검증]` |
| 확장 | 캐릭터 6종 → 9종, 신규 투입 리드타임 **D-2** | `[검증]` |

### 7.2 유닛 이코노믹스 — 가계부 실측 `[운영기록]`

부스는 인쇄 1장당 **5,000원**을 받았다. 아래는 운영자 행사 가계부에 기록된 실지출·수입이며,
업계 추정치가 아니라 실제 거래 금액이다.

**장당 경제성**

| 항목 | 금액 |
|---|---|
| 판매가 | 5,000원 / 장 |
| 인화지 원가 (RP-54, 27,500원 / 54장) | **509원 / 장** |
| **장당 공헌이익** | **4,491원 (마진 90%)** |

**집계 범위 — 사진관 세그먼트만**

> 같은 부스에서 굿즈·아크릴·인형 등 **별도 상품군**도 함께 운영했다.
> 그쪽 제작비와 매출은 이 손익에서 **전부 제외**했다. 사진관의 수익성을 보려면
> 사진관의 직접비와 직접 매출만 대응시켜야 한다.
> 아래는 AR 사진관 단일 세그먼트의 손익이다.

**고정비**

| 항목 | 금액 | 성격 |
|---|---|---|
| **배경 그림 외주** | **1,000,000원** | 1회성. AR 합성 배경 원화 |
| 뎁스카메라 | 317,517원 | 1회성. CASE 2에서 제거 → **매몰비용** |
| 웹캠 (4K) | 206,520원 | 1회성 |
| 포토프린터 (염료승화형) | 186,990원 | 1회성 |
| 부스 인테리어 소품 | 73,000원 | 1회성 |
| **1회성 지출 소계** | **1,784,027원** | |
| 부스·행사 참가비 | 80,000원 × 7 = 560,000원 | 행사별. **공통비** (굿즈와 공유) |
| 입간판 재인쇄 | 16,000원 × 7 = 112,000원 | 행사별. 사진관 직접비 |

> **단일 최대 지출은 기술이 아니라 그림이었다.**
> 배경 원화 외주 100만원은 1회성 지출의 **56%**, 유료 인쇄 **223장분**으로,
> AI 장비 일체(711,027원)의 1.4배다. 파운데이션 모델은 무료로 쓸 수 있었지만
> **참가자가 그 앞에 서고 싶어지는 배경은 돈을 주고 사야 했다.**
> 이 프로젝트에서 기술은 가장 싼 부품이었다.

**손익분기 — 공통비 배부 기준에 따라 두 가지**

| 배부 기준 | 고정비 | 손익분기 | 누적 703장 대비 |
|---|---|---|---|
| A. 직접비만 (참가비는 공통비로 제외) | 1,896,027원 | **422장** | 60% |
| **B. 참가비 전액 배부 (기준값, 보수적)** | 2,456,027원 | **547장** | **78%** |

부스 면적·운영 시간의 대부분을 사진관이 썼으므로 B가 현실에 가깝고,
이 문서는 **B를 기준값으로 삼는다** — 유리한 쪽이 아니라 불리한 쪽을 기준으로 잡는다.

**손익분기까지 2년 5개월, 다섯 번째 행사가 걸렸다.**

| 회차 | 행사 | 촬영 | 누적 |
|---|---|---|---|
| 1 | 2024-08 FUR:RAID | 80 | 80 |
| 2 | 2024-12 HowlSoup | 120 | 200 |
| 3 | 2025-02 FurryJoA | 64 | 264 |
| 4 | 2025-10 FUR:RAID | 200 | 464 |
| **5** | **2026-01 HowlSoup** | **100** | **564** ← 547장 돌파 |
| 6 | 2026-02 FurryJoA | 64 | 628 |
| 7 | 2026-04 FurstClass | 75 | 703 |

행사별 반복 비용(참가비 + 입간판 96,000원)만 보면 **21장**이면 회수된다.
행사별 촬영이 64~200장이므로 **7개 행사 전부가 그 행사의 반복 비용은 넉넉히 넘겼다.**
즉 **매 행사는 처음부터 흑자였고, 초기 투자 회수에 5회가 걸린 구조**다.
이것이 "매번 적자를 감수하고 버텼다"가 아니라 "매번 남는데 초기 투자가 컸다"는 뜻이며,
3년 7회 연속 운영이 가능했던 실제 이유다.

> **폐기한 뎁스카메라는 317,517원, 유료 인쇄 71장분이었다.**
> AI 장비 예산(711,027원)의 **45%**, 1회성 지출 전체의 18%다.
> CASE 2에서 "정확도만 보고 도입하고 현장 운영 리스크를 의사결정에 넣지 않았다"고 쓴
> 판단 착오의 **금전적 크기가 이것**이다.
> 이 장비를 사지 않았다면 손익분기는 547장이 아니라 **476장**, 즉 한 행사 빨랐다.

**누적 손익**

촬영본은 인쇄 오류(5% 미만)를 제외하고 전량 유료 인쇄로 이어졌다 `[운영기록]`.
오류분은 인화지를 소모하지만 매출이 없으므로 보수 시나리오에 반영했다.

매출은 유료 인쇄 668~703장 × 5,000원 = **3,339,000 ~ 3,515,000원**,
인화지비는 소모 703~738장 × 509원 = **358,000 ~ 376,000원**이다.

| 배부 기준 | 순손익 (보수) | 순손익 (상한) |
|---|---|---|
| A. 직접비만 | +1,067,000원 | +1,261,000원 |
| **B. 참가비 전액 배부 (기준값)** | **+507,000원** | **+701,000원** |

**기준값 순손익 약 51만 ~ 70만원.** 3년 7회를 운영하고 남은 금액이다.
인쇄 성공률이 95%를 넘어 두 시나리오의 폭이 20만원 이내로 좁다 —
마진이 얇을수록 **인쇄 신뢰성이 곧 손익**이었다는 뜻이다.
실패율이 20%였다면 순손익은 절반 이하로 떨어진다.

**집계 범위에서 제외한 것과 그 이유**

| 제외 항목 | 이유 |
|---|---|
| 굿즈·아크릴·인형 제작비 | **별도 상품군.** 자체 매출이 있으므로 사진관 손익에 섞으면 왜곡 |
| 퍼슈트 운반비·이동 잡비 | **증분원가가 아님.** 부스를 열지 않았어도 컨벤션에 참가했을 것이므로 사진관 때문에 추가로 발생한 비용이 아니다 |
| 인건비 | 아래 참조 |

사진관 세그먼트의 **직접비는 위 표에 열거된 항목으로 집계가 닫혀 있다.**
추가로 드러날 항목을 남겨두지 않았다.

**이 숫자를 읽을 때의 단서 2가지 — 반드시 함께 볼 것**

1. **인건비가 0으로 잡혀 있다.** 기획·개발·에셋 제작·현장 운영이 전부 무보수 자기노동이다.
   개발 시간만 커밋 61개 분량이고, 여기에 시장 인건비를 넣으면 **순손익은 즉시 음수가 된다.**
   3년간 남은 51만~70만원은 노동의 대가가 아니라 **현금흐름이 마이너스가 아니었다는 사실**뿐이다.
2. 이 부스의 목적은 수익이 아니었다. 위 수치는 **"3년간 자기 돈을 태우지 않고 지속
   가능했다"** 는 것을 보여주는 것이지 사업성을 주장하는 것이 아니다.
   지속 가능성이야말로 7회 연속 운영을 가능하게 한 조건이다.

---

### 7.3 대체비용 환산 (참고)

> 아래는 실지출이 아니라 **대안과의 비교**다. 모든 가정을 명시하고 범위로 제시한다.

**① 상용 포토부스 렌탈 대체**

7회 × 50만~80만원 = **350만 ~ 560만원** 상당.

*가정*: 각 행사 1일 운영, 렌탈에 무제한 인화·스태프 포함, 부가세 별도.
단가는 국내 공개 시세 기준([심플큐브](https://simplecube.net/pricing/) 50만원~,
[포토키오스크 렌탈](https://www.yubi.co.kr/www/kiosk_photo/photo2/rental) 최소 80만원).

*한계 — 중요*: 상용 렌탈은 **캐릭터별 AR 합성이라는 핵심 기능을 제공하지 않는다.**
따라서 이것은 동등 비교가 아니라 **참고치**다. 기능 동등성을 요구하면 커스텀 개발
견적이 되어 훨씬 커지지만, 그 값은 검증할 수 없으므로 싣지 않는다.

**② 후보정 리드타임 제거**

수동 합성 기준 1장당 5~15분(누끼 + 레이어 합성 + 색보정)을 가정하면
703장 × 5~15분 = **59 ~ 176시간**.

*가정*: 포토샵 숙련자가 캐릭터 레이어 템플릿을 갖춘 상태. 장당 5분(낙관) ~ 15분(보수).
이 범위는 **저자의 가정이며 출처 기반 수치가 아니다.**

*핵심*: 시간 절약보다 **리드타임 자체의 제거**가 본질이다. 수동 후보정은 아무리 빨라도
참가자가 그 자리에서 받아갈 수 없다. 이 시스템의 가치는 인건비 절감이 아니라
**"즉석에서 받아간다"는 경험이 성립하게 만든 것**이다.

**③ 캐릭터 확장 한계비용**

물리 세트·조형물 방식에서 캐릭터 1종 추가는 제작·운반·보관 비용을 수반한다.
이 시스템에서는 **PNG 2장 + 설정 3줄**이면 된다.
증거: 커밋 `a812ad2`가 행사 **D-2에 캐릭터를 추가**했고 정상 운영됐다.
물리 방식에서 D-2 신규 캐릭터 투입은 불가능하다.

7.2의 세그먼트 직접비는 집계가 닫혀 있으므로, 위 대체비용 환산만이 추정 영역이다.

---

## 8. 사고와 구조적 해결

### CASE 1 · 범용 매팅 모델이 퍼슈트에서 무너지다

**증상** — 사람 대상으로 잘 동작하던 매팅 모델이 퍼슈트 착용자에게서 실루엣을 크게
잘라먹거나 배경을 인물로 오인했다.

**현장 영향** — 결과물 품질이 참가자마다 들쭉날쭉해졌다. 어떤 참가자는 만족하고
어떤 참가자는 실망하는 상태는 부스 신뢰도에 직접 타격이다.

**원인** — 2.3(a)의 5가지. 범용 모델이 의존하는 단서가 거의 전부 무효화된다.

**선택지와 트레이드오프**

| 안 | 장점 | 단점 | 판단 |
|---|---|---|---|
| 데이터로 해결 (파인튜닝) | 도메인 정확도 우수 | 재학습·라벨링·버전관리 부채를 1인이 부담 | 1차 채택 |
| 구조로 해결 (사람 개념 제거) | 유지보수 거의 없음, 일반화 | 도메인 오차 잔존 | 2차 채택 |
| 물리로 해결 (크로마키) | 확실 | 부스 면적·설치 시간·조명 통제 필요 | 기각 |

**해결** — 1차로 커뮤니티 기증 이미지 기반 MODNet 파인튜닝 + HITL 반복 검증을 운영했다.
이후 2차로 depth 기반 전경 분리 + SAM2 promptable 조합으로 전환했다(`b7d9114`).

**전환한 이유가 핵심이다.** 파인튜닝이 부정확해서가 아니다. 파인튜닝은 모델 하나를
만드는 일이 아니라 **데이터 수집·라벨링·재학습·버전관리·기증자 관계 전체를 1인이
3년간 떠안는 결정**이다. 새 퍼슈트 유형이 나올 때마다 이 사이클을 다시 돌려야 한다.
"정확도가 더 높은 쪽"이 아니라 **"1인이 지속 가능한 쪽"** 을 택했다.

**재발 방지** — "사람"이라는 사전지식을 파이프라인에서 제거해 어떤 코스튬에도
일반화되도록 했다. 잔여 오차는 캐릭터별 상수 18개로 흡수(`9b7fe70`).

### CASE 2 · 뎁스카메라 도입, 그리고 4개월 뒤 제거

**증상** — 뎁스카메라 도입(`e42dc1a`, 2025-10)은 분리 정확도를 실제로 높였다.
문제는 정확도가 아니라 현장이었다.

**현장 영향** — 장비 추가 운반, USB 대역폭, OS별 드라이버, 카메라 정렬 유지.
그리고 결정적으로 **그 장비가 고장나면 부스 전체가 멈추는** 단일 장애점이었다.

**원인** — 정확도 최적화만 보고 도입했고, **현장 운영 리스크를 의사결정에 넣지 않았다.**
이것이 진짜 실수였다.

**금전적 크기** — 이 장비는 **317,517원**으로 AI 장비 예산(711,027원)의 **45%**,
유료 인쇄 **71장분**이었다. 4개월 만에 파이프라인에서 제거했으므로 전액 매몰비용이다.
사지 않았다면 프로젝트 손익분기는 547장이 아니라 **476장** — 회수가 한 행사 빨랐다.
판단 착오를 정성적으로 서술하지 않고 숫자로 남겨두는 이유는, **이 크기를 기억해야
다음에 같은 실수를 안 하기 때문**이다.

**해결** — Depth Anything V2 Small로 전면 대체(`b0e64e7`, 2026-02).
잃은 정확도는 SAM2 하이브리드 프롬프트로 보전했다.

**재발 방지** — 이후 하드웨어 추가 결정 시
**"이 장비가 고장나면 부스가 멈추는가"** 를 먼저 묻게 됐다.
정확도를 내주고 운영 리스크를 제거하는 교환을 명시적 판단 기준으로 채택했다.

### CASE 3 · 프리뷰 부드러움 vs 저장본 정확도

**증상** — AI 파이프라인을 매 프레임 동기 실행하니 프리뷰가 끊겼다.
추론 주기를 늦추면 촬영 순간의 마스크가 이전 포즈의 것이 되어 저장본이 어긋났다.

**현장 영향** — 참가자가 포즈를 잡는 동안 화면이 끊겨 조정이 어렵거나,
결과물에서 팔·귀가 잘렸다. **인쇄되어 나온 뒤에는 되돌릴 수 없다.**

**원인** — 두 요구가 서로 다른 시간 척도를 갖는데 같은 경로에서 처리했다.
프리뷰는 연속적 부드러움이, 저장은 단일 시점 정확도가 필요하다.

**해결** — 비동기 detection + 더블 버퍼. 촬영 시에만 동기 flush(`6837615`). 4.2절 참조.

**재발 방지** — 스레드 6종의 공유 상태 보호 규약을
`.agent/knowledge/threading-model.md`에 명문화하고, 신규 기능 체크리스트를
`.agent/skills/threading-safety/`에 규칙화했다(`3bf6ddf`).

### CASE 4 · 현장 환경이 만드는 실패 모드

**증상** — ① 행사장에 인터넷이 없어 모델 자동 다운로드 실패
② macOS가 아이폰을 Continuity Camera로 잡아 엉뚱한 카메라 선택
③ 운영 중 버튼창을 실수로 닫으면 조작 불가
④ 프린터가 준비되지 않았는데 인쇄 버튼이 눌림

**현장 영향** — 전부 **대기열이 있는 상태에서 발생한다.** 참가자가 지켜보는 앞에서
부스가 멈춘다.

**원인** — 개발 환경의 암묵적 가정(인터넷 있음, 웹캠 하나, 창 안 닫음, 프린터 항상 준비)이
현장에서 전부 깨진다.

**해결**

| 실패 모드 | 대응 | 커밋 |
|---|---|---|
| 네트워크 부재 | 오프라인 모드 + 모델 다운로드 사전 스크립트 분리 | `de9d569`, `cb375ba` |
| 카메라 오인식 | USB 카메라 존재 시 Continuity Camera 자동 배제 | `b3d2b61` (**D+2**) |
| 창 닫힘 | 닫힘 감지 후 자동 재생성 | `main.py:138-146` |
| 프린터 미준비 | 3초 폴링 + 버튼 게이팅 | `main.py:561-579` |

**재발 방지** — 각 실패 모드를 크래시-재시작이 아니라 **개별 복구 경로로 흡수**했다.
코드 복잡도가 올라가는 대가를 치렀고, 이를 `.agent/skills/`의 규칙 문서로 상쇄했다.
원클릭 실행 스크립트 3종(`run.command`/`run.bat`/`run.ps1`)으로 운영자 인계 경로도 확보했다.

### CASE 5 · 커뮤니티가 맡긴 데이터를 잃다 — **미해결**

**증상** — 공개 모집으로 **21명**의 기증을 받았으나 현재 **9명분(1,289장)** 만 남아 있다.

**원인** — **수집 역량은 있었으나 보존 정책이 없었다.**
무료 저장소 용량 한계, 백업·생애주기 정책 부재, 수집 즉시 로컬 미러링 미실시.
기증자 재요청이 가능한 연락 채널 관리도 체계화되어 있지 않았다.

**잃은 것** — 기증자 기준 **57%**. 단순 파일이 아니라 **커뮤니티가 신뢰해서 맡긴 자산**이며,
재수집하려면 같은 사람들에게 다시 요청해야 한다. 기술적 손실보다 이쪽이 무겁다.

**지금이라면**

- 수집 즉시 로컬 미러링 + 콜드 스토리지 이중화
- 체크섬 기반 인벤토리
- 기증 대장(기증자 · 수량 · 수집일 · 동의 범위) 별도 관리
- 용량 임계 알림

**재발 방지 — 미해결.** 현존 9명분에 대한 이중화도 아직 적용되지 않았다.
로드맵 최우선 항목으로 이관한다.

---

## 9. 한계와 로드맵

### 9.1 알려진 한계

| 한계 | 설명 |
|---|---|
| **정량 품질 지표 부재** | 세그멘테이션 정확도를 IoU 등으로 측정한 적이 없다. 품질 판정이 전부 육안이었다. `photos/`의 141세트 raw+mask 쌍이 있으므로 지금은 측정 가능하다 |
| **기증 데이터 미이중화** | CASE 5. 남은 9명분도 백업 정책이 없다 |
| **파인튜닝 파이프라인 미보존** | 학습 코드·설정이 저장소 밖에 있어 재현 불가 |
| **`main.py` 단일 클래스 597줄** | Application이 카메라·AI·합성·UI·인쇄를 모두 보유. 리팩토링 가이드는 `.agent/workflows/refactor-module.md`에 작성해뒀으나 미실행 |
| **자동화 테스트 없음** | `scripts/test_*.py`는 비교 이미지 생성기이지 단위 테스트가 아니다 |
| **에이전트 문서 stale** | 브랜치 변경 후 일부 문서에 옛 브랜치명 언급이 남아 있다 |
| **2024–2025 운영 지표 부재** | 자동 저장 기능이 2026-02에 추가되어, 이전 5개 행사는 스루풋 실측이 없다 |
| **성장 추이의 교란 변수** | 행사 규모·부스 위치·운영 시간이 통제되지 않아 촬영 수 증가를 시스템 개선 효과로 귀속할 수 없다 |

### 9.2 로드맵

**우선순위 1 — 데이터 보존** (CASE 5 해소)
로컬 미러링 + 콜드 스토리지 이중화, 체크섬 인벤토리, 기증 대장 정비.

**우선순위 2 — 품질 측정 체계**
`photos/` 141세트에 대해 수동 GT 마스크를 일부 만들고 IoU·경계 F1을 측정.
색 조화 3세대의 개선을 육안이 아닌 수치로 확인.

**우선순위 3 — 구조 개선**
`main.py` 책임 분리, 세그멘테이션 파이프라인 단위 테스트, 에이전트 문서 갱신.

**우선순위 4 — 기능 확장**
다인 촬영(현재는 단일 피사체 중심 가정), 동영상/GIF 출력, 참가자 셀프 조작 UI.

---

## 10. 부록 — 기술 스택

### 모델

| 모델 | 역할 | 크기 |
|---|---|---|
| SAM 2.1 Hiera Tiny | promptable 세그멘테이션 (mask + points) | ~149 MB |
| Depth Anything V2 Small | 단안 깊이 추정 → 프롬프트 자동 생성 | HuggingFace snapshot |
| MediaPipe Selfie Segmenter / Pose Landmarker | 초기 실험 잔재. 다운로드 스크립트에만 남아 있고 현 파이프라인에서 import되지 않음 | — |

### 런타임

`Python 3.11` · `PyTorch` (MPS / CUDA / CPU 자동 선택) · `OpenCV`(UMat/OpenCL) ·
`transformers` · `NumPy` · `tkinter` · `uv`

### 플랫폼 대응

| | Windows | macOS |
|---|---|---|
| OpenCV 가속 | `cv2.UMat` (OpenCL) | `np.ndarray` |
| PyTorch | CUDA → CPU | MPS → CPU |
| 카메라 백엔드 | `CAP_MSMF` | `CAP_AVFOUNDATION` |
| 인쇄 | Win32 API | `lpr` (CUPS) |

### 하드웨어

4K USB 웹캠 · 염료승화형 포토프린터 · 노트북 (GPU 가속 선택)

### 저장소 구성

```
main.py                 Application (597줄)
camera/                 ICamera ABC + Webcam
utils/
  depth.py              DepthEstimator, rough mask, smart points  (602줄)
  segment.py            SAM2ImageProcessor, hybrid prompt          (498줄)
  mirror.py             MirrorCompositor, 색 조화                  (259줄)
  opencv.py             UMat/numpy 추상화                          (149줄)
  printer.py            OS별 프린터 팩토리                          (250줄)
scripts/                모델 다운로드, 세그멘테이션/색보정 비교 테스트
asset/                  캐릭터 앞·뒤 레이어 + 배경 + 스티커 (PNG 23종)
.agent/                 knowledge 4 / skills 4 / workflows 5 (1,371줄)
```

---

## 표기 원칙

- 캐릭터 명칭은 타인 소유분을 포함하므로 전부 익명 라벨로 표기했다.
- 기증자·사진작가 등 제3자 식별 정보는 기재하지 않았다.
- 기증 이미지와 부스 참가자 촬영물은 동의 범위가 학습 용도이므로 이 문서에 싣지 않았다.
- 날짜는 연-월 수준까지만 표기했다.
