가상 피팅에서 사용자가 바지만 입히고 싶은데 결과에 신발이나 상의까지 같이 따라오는 경우가 있다. 겉으로 보면 생성 모델이 틀린 것처럼 보인다. 하지만 이번 ThatzFit 실험에서 더 먼저 봐야 했던 문제는 모델 성능이 아니라 입력 범위였다.
원본 의류 이미지에 후드, 신발, 배경, 인물 일부가 같이 들어오자 provider는 "입힐 옷"과 "참고할 착장"을 구분해야 했다. 이 구분을 provider에게 맡기면 바지만 입혀야 하는 요청도 전체 착장 변환처럼 해석될 수 있다. human parser로 바지 영역만 남겼을 때 target garment identity가 더 선명해졌다.
요약
- 문제: 사용자는 바지만 입히고 싶은데 원본 의류 이미지에 상의, 신발, 배경이 섞이면 provider가 target garment와 참고 착장을 혼동할 수 있었다.
- 판단: provider를 바꾸기 전에 "입힐 의류 범위"를 먼저 좁혀야 했다.
- 결과: masked input은 garment identity 점수를
2.0 -> 4.0, overall 점수를2.8 -> 3.7로 올렸지만 edge/detail 품질은 아직 불안했다. - 배운 점: segmentation은 생성 품질을 자동으로 올리는 장치가 아니라, downstream 모델이 "어디까지 입혀야 하는지" 오해할 여지를 줄이는 장치다.
상황
ThatzFit의 가상 피팅은 모델 이미지와 의류 이미지를 외부 생성 모델(provider)에 넘겨 결과를 만든다. 사용자가 올리는 의류 이미지는 항상 깔끔한 상품 컷이 아니다. 바지만 입히고 싶은데 원본 이미지 안에 상의, 신발, 사람 몸, 배경이 같이 있으면 provider는 어디까지를 목표 의류(target garment)로 봐야 하는지 추론해야 한다.
이번 실험의 질문은 "외부 생성 모델이 나쁜가?"가 아니었다. "provider가 보기 전에 우리가 target garment 범위를 더 명확히 줄 수 있는가?"였다.
문제
테스트 입력은 바지 이미지였지만 원본에는 검은 후드, 신발, 인물 일부, 배경이 같이 있었다. 사용자의 의도는 바지만 입히는 것이다. 하지만 이 이미지를 그대로 provider에 넘기면 "바지"와 "바지를 입은 사람의 착장"이 한 장에 섞인다.
그 결과 provider는 바지만 target garment로 볼 수도 있고, 후드나 신발까지 포함한 착장을 함께 참고할 수도 있다. 사용자는 바지만 바꾸고 싶은데 결과가 신발이나 상의까지 건드리면, 문제는 단순한 생성 품질이 아니라 target garment scope의 실패에 가깝다.
그래서 확인하고 싶었던 질문은 두 가지였다.
- 로컬 human parser가 ThatzFit의 실제 입력 이미지에서 바지 영역을 충분히 빠르고 안정적으로 잡는가?
- 마스킹된 바지만 provider에 넘기면 "입힐 대상"이 바지로 더 분명해지는가?
처음 생각한 가설
가설은 단순했다. 생성 모델이 보기 전에 의류 후보를 바지만 남긴 투명 PNG로 줄이면, provider가 후드나 신발까지 target garment처럼 해석하는 오해를 줄일 수 있다.
다만 반대 가능성도 있었다. 마스크 경계가 거칠거나 얇은 디테일이 날아가면 생성 모델이 오히려 바지의 형태를 덜 잘 이해할 수 있다. 그래서 이번 실험은 backend default 변경이 아니라 별도 worktree와 Jupyter notebook에서만 진행했다.
확인한 증거
실험은 기존 backend 작업트리를 건드리지 않기 위해 별도 worktree에서 진행했다.
- 환경: macOS arm64, Python 3.12.3, torch 2.12.0, MPS 사용 가능
이번 실험에서는 Hugging Face의 fashn-ai/fashn-human-parser를 사용했다. fashion/virtual try-on 용도로 공개된 SegFormer 기반 human parsing 모델이고, top, dress, skirt, pants, feet 같은 semantic class를 반환한다. 이 글의 초점은 모델 자체 성능 비교가 아니라, segmentation 결과로 provider에 넘기는 target garment 범위를 좁힐 수 있는지였다.
먼저 human parser의 단독 실행 시간을 측정했다. 캐시가 된 반복 실행(warm run) 기준으로 MPS가 CPU보다 훨씬 빨랐다.
| device | cached load | first inference | warm avg |
|---|---|---|---|
| CPU | 0.87s | 1.23s | 1.02s |
| MPS | 0.93s | 0.45s | 0.26s |
첫 실행(cold run)에서 CPU 모델 로드가 114초까지 나왔는데, 이는 weight 다운로드와 캐시 영향이다. 실제 서비스형 실행 판단에는 캐시 이후 수치를 보는 것이 맞다. 또 패키지 기본값은 macOS에서 MPS를 자동 선택하지 않으므로 device를 명시해야 한다.
테스트 이미지의 class 분포는 다음과 같았다.
| class | area |
|---|---|
| pants | 45.84% |
| background | 45.17% |
| top | 5.35% |
| feet | 3.59% |
단순히 가장 큰 클래스(class)를 고르면 배경(background)을 선택할 수 있으므로, 실험에서는 top, dress, skirt, pants처럼 의류 클래스(garment class) 후보 안에서 가장 큰 영역을 선택했다. 이 샘플에서는 pants가 선택됐고, 바지 실루엣은 충분히 잡혔다. 대신 키링 같은 얇은 액세서리, 손과 포켓 주변, 발과 밑단 경계는 의류 매팅(garment matting) 수준으로 정밀하지 않았다.
해결한 방식
노트북에서는 같은 모델 이미지에 대해 두 가지 변형(variant)을 만들었다.
baseline_original: 후드, 신발, 인물 일부가 포함된 원본 의류 이미지를 그대로 provider에 전달masked_largest_garment: human parser가 선택한pants영역만 투명 PNG로 만들어 provider에 전달
Provider 실행 시간은 baseline이 약 14.8초, masked variant가 약 25.2초였다. 이 차이는 단일 실행 결과라 외부 모델 지연(provider latency) 변동까지 포함한다. 로컬 전처리 자체는 MPS warm 기준 약 0.26초였고, 노트북에서 기록된 segmentation 구간은 약 0.72초였다.
수동 평가는 아래처럼 기록했다. 점수는 1-5 기준이다. 표에는 숫자만 남기고, 판단 메모는 아래에 분리했다.
| Variant | Garment identity | Body realism | Edge quality | Artifact control | Overall |
|---|---|---|---|---|---|
| Original input | 2.0 | 4.0 | 4.0 | 2.0 | 2.8 |
| Masked input | 4.0 | 3.5 | 3.5 | 3.5 | 3.7 |
평가 메모
baseline_original: provider가 원본을hoodie + pants착장으로 해석했다. 바지는 자연스럽지만 사용자가 기대한pants only범위에는 실패했다.masked_largest_garment: 흰 티와 신발은 더 잘 보존되고 바지만 입히는 방향으로 개선됐다. 다만 발과 밑단이 변형되고 checkerboard 배경이 남았다.
배운 점
이 실험에서 마스킹은 효과가 있었다. 핵심은 이미지 품질을 자동으로 올리는 것이 아니라 provider가 해석할 garment 범위를 줄여주는 데 있었다. 원본을 그대로 넣으면 provider가 후드나 신발까지 착장 정보로 끌고 들어올 수 있다. 바지만 남긴 입력은 "입힐 대상은 바지"라는 계약을 훨씬 분명하게 만들었다.
하지만 바로 기본 기능으로 넣기에는 이르다. human parser는 의미론적 분할(semantic segmentation)에는 충분하지만 픽셀 단위 매팅(matting) 도구는 아니다. 또한 provider 결과가 실제 alpha가 아니라 checkerboard 배경을 rasterize해서 반환한 점은 후처리까지 같이 봐야 한다.
다음에는 이렇게 판단한다
비슷한 가상 피팅 이슈를 다시 보면 바로 provider를 바꾸지 않는다. 먼저 입력 이미지 안에서 목표 의류와 참고 착장이 섞여 있는지 본다. 사용자가 바지만 원했는데 신발이나 상의까지 결과에 영향을 준다면, 모델 교체보다 target garment scope를 좁히는 전처리가 먼저다.
다음 단계는 backend에 바로 붙이는 것이 아니라, 대표 의류 3-5개로 같은 비교를 반복하는 것이다. masked variant가 계속 이기면 feature flag 뒤에 숨긴 실험 기능으로 넣는다.
후보 flow는 이렇다.
- provider가 지원 대상이 아니면 skip한다.
VIRTUAL_FITTING_GARMENT_MASKING_ENABLED가 꺼져 있으면 skip한다.- singleton parser를
asyncio.to_thread나 bounded executor로 실행한다. - 허용된 garment class 중 가장 큰 영역만 남긴다.
- garment area가 기준보다 작으면 원본 bytes로 fallback한다.
- performance step에
garment_segmentation을 남긴다. - masked PNG bytes를 direct-bytes runner에 전달한다.
이번 실험의 결론은 “계속 실험할 가치가 있다”이다. 마스킹은 목표 의류 범위(target garment scope)를 통제하는 데 유효했다. 다만 운영 기능(production feature)으로 만들려면 여러 입력에서 반복 검증하고, 신발/상의 보존 여부와 removebg 후처리 결과까지 같이 비교해야 한다.
