Hair Removal로 접근했지만, 실제 문제는 Upper-Clothes Overwrite였다

ControlNet 기반 이미지 생성 파이프라인에서 마스크 정의를 재설계한 기술 회고

1. 문제 정의: Hair Removal 문제가 아니라 Garment Overwrite 문제였다

이번 작업의 시작점은 비교적 단순했다.
긴 머리카락이 상의 전면 일부를 가리고 있었고, 이를 자연스럽게 보정하는 것이 목표였다.
처음에는 이 문제를 hair removal로 해석했다. 즉, 머리카락 영역을 추출한 뒤 해당 영역을 inpainting으로 제거하거나 복원하면 해결될 것이라고 판단했다.

하지만 실제 디버깅 과정에서 확인한 것은, 이 문제를 hair removal로 정의하는 순간 파이프라인 전체가 잘못된 방향으로 흘러간다는 점이었다.

이번 작업의 실제 목적은 다음에 더 가까웠다.

  • 머리카락 자체를 정교하게 지우는 것
  • 머리카락 뒤에 있던 원래 옷을 그대로 복원하는 것

이 둘보다는,

  • 머리카락이 가린 상의 전면을 다시 생성해서
  • 최종 결과에서 상의가 머리카락보다 앞에 있는 것처럼 보이게 만드는 것

즉, 이번 문제는 제거 중심의 task가 아니라
upper-clothes overwrite / garment front regeneration에 가깝다.

이 차이를 정확히 구분하지 못하면 segmentation, mask generation, conditioning, compositing이 모두 어긋나게 된다.


2. 기존 파이프라인 구조: Hair-Centered Editing 관점의 흐름

당시 파이프라인은 전반적으로 hair-centered editing 구조에 가까웠다.
핵심 흐름은 대략 다음과 같았다.

  • hair mask 생성
  • 필요 시 SAM2 기반 refinement
  • face protect
  • Stable Diffusion inpainting
  • ControlNet conditioning
  • 후처리 및 합성

이 구조는 “헤어 영역을 편집하거나 생성하는 작업”에는 잘 어울린다.
실제로 hair segmentation, hair boundary refinement, face identity preservation 같은 작업에서는 꽤 자연스러운 구조다.

문제는 내가 해결하려는 대상이 더 이상 hair 자체가 아니었다는 점이다.
최종적으로 만들고 싶은 이미지는 머리카락이 없어진 상태의 상의 전면이었고,
이를 위해서는 hair 영역 자체보다 상의 전면의 재생성 대상 정의가 더 중요했다.

즉, 기존 파이프라인은 구조적으로 다음 질문에 최적화돼 있었다.

“머리카락을 어디까지 수정할 것인가?”

하지만 실제로 내가 던져야 했던 질문은 이쪽이었다.

“어떤 상의 영역을 새로 그려야 머리카락이 없어진 것처럼 보일까?”

겉보기에는 유사하지만, 생성 파이프라인에서는 완전히 다른 질문이다.


3. 초기 가정: Hair Mask만 정교하면 해결될 것이라는 착각

초기에는 비교적 직관적으로 생각했다.

  1. 머리카락 영역을 segmentation으로 정확하게 추출한다.
  2. 해당 영역을 refinement한다.
  3. 그 결과를 inpainting에 넣는다.
  4. ControlNet으로 구조를 보정한다.

이 접근은 표면적으로는 맞아 보인다.
실제로 이미지 위에 시각적으로 겹쳐져 있는 장애물이 머리카락이기 때문에,
그 영역만 정교하게 처리하면 문제가 해결될 것처럼 보이기 때문이다.

하지만 이 접근에는 근본적인 한계가 있었다.

  • 머리카락을 제거하는 것과
  • 머리카락이 없어진 것처럼 보이는 상의를 만드는 것은

동일한 작업이 아니다.

특히 긴 머리카락이 어깨선, 목선, 가슴 전면 일부를 넓게 가리고 있는 경우에는
단순한 hair removal이 아니라 가려진 의상 전면의 재구성이 핵심이 된다.

즉, “무엇을 삭제할 것인가” 중심의 마스킹은
“무엇을 다시 그릴 것인가” 중심의 생성 문제를 충분히 설명하지 못한다.


4. 핵심 오류 1: Visible Clothing Mask를 그대로 생성 마스크로 사용한 점

트러블슈팅 과정에서 가장 먼저 드러난 핵심 문제는
현재 사용 중인 생성 마스크가 visible clothing mask에 가까웠다는 점이다.

실제로 확인한 마스크는 다음과 같은 특징을 보였다.

  • 상의 마스크가 머리카락 실루엣을 따라 움푹 파여 있음
  • 머리카락이 걸친 부분은 옷 영역에서 제외됨
  • 즉, “현재 화면에서 보이는 옷”만을 기준으로 정의됨

이 마스크는 segmentation 관점에서는 합리적이다.
왜냐하면 segmentation 모델은 일반적으로 보이는 영역을 기준으로 클래스를 분리하기 때문이다.

하지만 생성 관점에서는 이 마스크가 오히려 문제를 만든다.
이 마스크를 생성에 그대로 쓰면 모델은 다음처럼 해석하게 된다.

  • 머리카락이 지나간 자리 자체는 구조적으로 유지해야 하는 영역
  • 옷은 그 경계를 피해 이어져야 하는 영역

그 결과 상의 전면이 하나의 연속된 면으로 재생성되지 못하고,
머리카락 실루엣을 기준으로 움푹 파인 구조가 계속 유지된다.

즉, visible mask는 “무엇이 지금 보이는가”를 알려줄 수는 있지만,
“무엇을 다시 그려야 하는가”를 정의해주지는 못한다.

이번 작업에서 진짜 필요했던 것은
hair occlusion을 무시한 amodal upper-clothes front mask였다.


5. 핵심 오류 2: Hair-Centered Mask Logic이 생성 목표를 제한한 점

기존 파이프라인의 핵심 로직은 hair-centered였다.
즉, 문제를 해석하는 기준점 자체가 hair mask에 맞춰져 있었다.

이 구조에서는 자연스럽게 다음과 같은 흐름이 만들어진다.

  • hair mask를 얼마나 정확히 뽑을 것인가
  • hair boundary를 얼마나 정교하게 다듬을 것인가
  • hair overlap을 얼마나 잘 분리할 것인가

하지만 이번 작업에서 중요한 것은 hair 자체의 정밀한 추출이 아니었다.
오히려 필요한 것은 머리카락과 겹친 상체 영역을 상의 쪽 regenerate 대상으로 귀속시키는 것이었다.

즉, 중심 질문이 바뀌어야 했다.

기존 질문

  • hair를 어디까지 분리할 것인가

필요한 질문

  • 상의 전면을 어디까지 다시 그릴 것인가

이 차이가 중요했던 이유는, 생성 모델은 결국 마스크가 정의한 영역을 기준으로 이미지를 다시 만들기 때문이다.
중심이 hair면 hair edit으로 흐르고, 중심이 clothes면 clothes overwrite로 흐른다.

이번 사례는 마스크 설계의 중심 객체가 바뀌면 태스크 해석 자체가 달라진다는 점을 보여줬다.


6. 핵심 오류 3: ControlNet Conditioning이 원본 Hair Edge를 다시 살릴 가능성

초기에는 ControlNet을 사용하고 있었기 때문에, 구조 보정은 어느 정도 해결될 수 있을 것이라고 기대했다.
실제로 ControlNet은 pose, edge, depth 같은 구조 조건을 강하게 반영하는 데 유리하다.

문제는 무슨 구조를 조건으로 넣고 있느냐였다.

edge 기반 conditioning을 사용할 경우, 원본 이미지에서 추출한 edge map에는 머리카락 실루엣도 함께 포함된다.
이 경우 내가 제거하고 싶었던 hair edge가 오히려 다시 생성 단계의 구조 조건으로 입력될 수 있다.

즉, ControlNet은 구조를 보정해 주는 도구지만,
그 구조가 잘못 정의되어 있으면 오히려 문제를 강화한다.

이번 사례에서 필요한 conditioning은 다음 성격에 가까워야 했다.

  • 원본 hair edge를 그대로 보존하는 conditioning
  • 머리카락 경계를 유지하는 conditioning

이 아니라,

  • upper body silhouette 중심 conditioning
  • clothes overwrite를 방해하지 않는 masked edge
  • hair silhouette를 덜 강조하는 구조 조건

즉, ControlNet의 유무보다 중요한 것은
ControlNet이 어떤 구조를 “보존해야 할 것”으로 인식하고 있느냐였다.


7. 핵심 오류 4: Post-Process를 메인 해결책처럼 바라본 점

후처리 단계는 흔히 마지막 품질 보정 장치처럼 느껴진다.
artifact cleanup, 경계 보정, 일부 잔상 제거 등에서 실제로 유효하기도 하다.

하지만 이번 작업에서는 후처리를 메인 해결책처럼 바라보면 안 됐다.
왜냐하면 현재 문제는 단순 artifact 문제가 아니라 재생성 대상 정의 자체가 잘못된 상태였기 때문이다.

즉, 메인 generation이 다음처럼 잘못 되어 있으면:

  • regenerate mask가 hair silhouette를 따라 파여 있고
  • conditioning이 hair edge를 보존하고 있고
  • composite 목적이 hair editing 관점에 묶여 있으면

후처리로 해결 가능한 범위를 이미 넘어선다.

후처리는 어디까지나 올바른 생성 결과를 조금 더 정리하는 단계이지,
생성 방향이 틀어진 파이프라인을 역전시키는 단계는 아니다.

이번 사례는 “후처리에서 어떻게든 잡겠지”라는 사고가 얼마나 위험한지도 보여줬다.


8. 해결 방향: Hair Removal에서 Upper-Clothes Overwrite로 파이프라인 재해석

문제를 다시 정의한 뒤, 해결 방향도 자연스럽게 달라졌다.
핵심은 파이프라인 전체를 hair removal 관점이 아니라 upper-clothes overwrite 관점으로 다시 해석하는 것이다.

구체적으로는 다음이 필요하다.

8.1 Regenerate Mask 재설계

최종 regenerate mask는 좁은 hair overlap 영역이 아니라,
상의 전면 전체 + 머리카락과 겹치는 상체 영역이 되어야 한다.

즉 마스크는 다음 조건을 만족해야 한다.

  • 양 어깨와 가슴 중앙부가 끊기지 않을 것
  • hair silhouette를 따라 움푹 파이지 않을 것
  • 목 아래 상의 전면이 하나의 연속된 면으로 정의될 것
  • hair overlap도 regenerate 대상에 포함될 것

8.2 Preserve 대상 축소

이번 작업에서 preserve의 핵심은 hair가 아니라 face다.
즉 face identity는 유지하되, hair 자체는 반드시 원형 유지할 필요가 없다.

이렇게 해야 generated clothes가 결과 이미지에서 더 앞 레이어처럼 보일 수 있다.

8.3 Conditioning 재설계

edge 기반 conditioning을 계속 쓴다면,
그 edge가 hair silhouette를 다시 강하게 보존하지 않도록 조정해야 한다.

즉 conditioning의 기준도
“원본 hair 구조 유지”가 아니라
“upper body structure 유지”로 바뀌어야 한다.

8.4 Composite 목적 변경

최종 합성도 “generated hair를 올리는 것”이 아니라
generated upper clothes를 front layer처럼 덮어쓰기 방향으로 바뀌어야 한다.

이 관점 전환이 이뤄져야 파이프라인 전체가 같은 목표를 향하게 된다.


9. 이번 트러블슈팅에서 얻은 기술적 교훈

이번 작업에서 얻은 교훈은 비교적 명확하다.

9.1 생성 태스크에서는 Segmentation Mask와 Generation Mask를 분리해서 생각해야 한다

segmentation은 보이는 영역을 분리하는 데 적합하다.
하지만 generation은 종종 보이지 않지만 다시 그려져야 하는 영역을 정의해야 한다.

즉 segmentation 결과를 그대로 generation mask로 가져오는 것은 위험할 수 있다.

9.2 문제 정의가 틀리면 모델 선택과 파라미터 튜닝은 한계가 있다

ControlNet, inpainting, face protect, 후처리 등은 모두 강력한 도구다.
하지만 regenerate 대상이 잘못 설정되어 있으면, 그 위의 튜닝은 구조적 한계를 벗어나기 어렵다.

9.3 “무엇을 제거할 것인가”보다 “무엇을 새로 그릴 것인가”가 더 중요할 수 있다

이번 이슈의 핵심 전환점은 바로 이 질문이었다.
머리카락을 어떻게 지울지가 아니라,
결국 어떤 상의 레이어를 다시 만들 것인지를 먼저 정의해야 했다.

9.4 Post-Process는 방향 수정이 아니라 미세 보정이다

메인 generation이 맞아야 후처리가 의미를 가진다.
후처리는 잘못된 regenerate target을 바로잡는 수단이 아니다.


10. 결론

이번 작업은 겉보기에는 단순한 hair occlusion 보정처럼 보였지만,
실제로는 문제 정의와 마스크 설계가 얼마나 파이프라인 전체를 좌우하는지를 보여준 사례였다.

처음에는 hair removal 문제라고 생각했다.
하지만 디버깅을 거치면서 실제 문제는 upper-clothes overwrite, 즉 상의 전면 재생성에 더 가까웠다는 점이 분명해졌다.

이 차이를 인식한 뒤에는 자연스럽게 다음이 보였다.

  • 왜 기존 마스크가 문제였는지
  • 왜 ControlNet이 해결해주지 못했는지
  • 왜 후처리에 기대면 안 되는지
  • 왜 segmentation 결과를 그대로 generation에 쓰면 안 되는지

앞으로 유사한 이슈를 다룰 때는,
먼저 눈에 띄는 가림 객체를 제거하는 관점보다
최종 이미지에서 어떤 레이어를 다시 생성해야 하는가를 기준으로 문제를 정의해야 한다.

이번 트러블슈팅은 그 기준을 다시 세우게 만든 사례였다.

GitHub: https://github.com/PracLee/job_hunting_mcp

1. 왜 이걸 만들게 되었는가

문제의 시작: "같은 서류를 6번 쓰는 고통"

취업 준비를 하면서, 채용 사이트에 이력서를 등록하는 과정이 가장 짜증나는 작업이었다.

원티드는 한 줄 소개 + 성과 중심 bullet + 기술 태그.
사람인은 표 기반 이력서 + 4문항 자기소개서.
잡코리아는 이력서 + 경력기술서 분리.
점핏은 기술 중심 프로필 + 프로젝트 카드.

같은 경력인데, 사이트마다 양식이 다르다.
결국 같은 내용을 6번 복사하고, 붙여넣고, 양식에 맞게 다시 정리하고 있었다.

그리고 공고마다 강조해야 할 포인트가 다르다.
백엔드 공고면 API/DB 경험을 앞에, AI 공고면 RAG/LLM 경험을 앞에.
이것까지 매번 수동으로 하고 있었다.

"이건 자동화할 수 있다" 라는 확신이 들었고, 그래서 이 프로젝트를 시작했다.

기존 도구들의 한계

미국 시장에는 resume optimizer 같은 도구가 많다.
하지만 한국 취업 시장에 맞는 도구는 거의 없었다.

  • 한국식 자기소개서 (지원동기/성장과정/입사후포부) 지원 ❌
  • 사람인/잡코리아/원티드 등 한국 채용 사이트 양식 지원 ❌
  • "경력기술서" 개념 자체가 미국 이력서와 다름
  • 한국어 기술 동의어 처리 ("자바" = "Java", "스프링부트" = "Spring Boot") ❌

한국 시장에 특화된 도구가 필요했다.


2. 어떻게 구상했는가

핵심 아이디어: "마스터 프로필 → 공고별/사이트별 변환"

전체 구상의 핵심은 단순하다:

이력서/경력기술서를 한 번만 입력
→ 마스터 프로필로 구조화
→ 공고에 맞게 강조 포인트 조정
→ 각 사이트 양식에 맞는 복붙 텍스트 생성

이 흐름을 MCP(Model Context Protocol) 서버로 구현하기로 했다.
MCP를 선택한 이유:

  1. Claude Desktop / Claude Code에서 바로 사용 가능 — 별도 UI 개발 불필요
  2. Tool 기반 아키텍처 — 기능을 모듈 단위로 쪼개기 좋음
  3. 로컬 실행 — 이력서 같은 개인정보를 외부 서버에 올리지 않아도 됨
  4. LLM 연동이 자연스러움 — 서류 작성 보조에 LLM을 바로 활용

아키텍처 결정

결정 선택 이유
실행 방식 로컬 실행 개인정보 보호 (이력서, 연락처 등)
LLM 각자 API키 또는 Ollama 비용 분산, 오프라인 가능
DB SQLite (better-sqlite3) 로컬 파일, 설치 불필요, 충분한 성능
공고 검색 어댑터 패턴 사이트별 API가 다르므로 추상화 필수
양식 변환 템플릿 패턴 LLM 없이도 기본 동작, LLM으로 보강 가능

"LLM 없이도 돌아가야 한다"

이 프로젝트에서 가장 중요하게 잡은 원칙이다.

  • 매칭: 규칙 기반 (기술 사전 + 가중치 계산) → LLM 불필요
  • 양식 변환: 템플릿 기반 변환 → LLM 불필요
  • 프로필 파싱: 정규식 + 섹션 분리 → LLM 불필요
  • 서류 생성: 여기만 LLM 사용 (문장 다듬기, 자소서 초안)

Ollama를 쓰면 API 키 없이도 전체 기능을 사용할 수 있다.


3. 지금까지 구현된 것

Phase 0: 프로젝트 셋업 + 16개 Tool 스켈레톤

  • MCP 서버 진입점 + StdioTransport 연결
  • 16개 Tool 전체 등록 (채용공고/프로필/매칭/서류/지원관리/면접)
  • SQLite DB + Repository 패턴
  • LLM 클라이언트 추상화 (Anthropic / OpenAI / Ollama)
  • 기술 사전 (80+ 항목, 한국어 동의어 포함)
  • Zod 기반 입력 검증

Phase 1-2: 프로필 파서 고도화 + 원티드 어댑터

  • 이력서 파서 (규칙 기반)
    • 한국 이력서 섹션 자동 분리 (인적사항/학력/경력/기술/프로젝트/자격증)
    • 기술스택 자동 추출 (tech-dictionary 연동)
    • 프로젝트별 성과/역할/기간 파싱
    • 경력 연차 자동 계산
  • 원티드 어댑터 (실시간 API)
    • 원티드 v4 API 연동
    • 검색 + 상세 조회
    • 공고 자동 정규화 + DB 캐싱
  • 공고 정규화 엔진
    • 주요업무/자격요건/우대사항/기술스택 자동 분류
    • 경력 범위 파싱 (3~5년, 5년 이상 등)
    • 직무 카테고리 자동 분류

Phase 3-4: 플랫폼 양식 템플릿 + resume_export 개선

  • 6개 플랫폼 양식 템플릿
    • 원티드: 한 줄 소개 + 성과 bullet + 기술 태그
    • 사람인: 표 기반 이력서 + 인적사항 + 문항별 자소서 구조
    • 잡코리아: 이력서 + 경력기술서 분리
    • 점핏: 기술 중심 프로필 + 프로젝트 카드
    • 로켓펀치: 링크드인 스타일 요약 + 경험 타임라인
    • 범용: 이메일 지원 / 일반 이력서용
  • resume_export 개선
    • LLM 없이 템플릿 기반 변환 (기본)
    • enhance_with_llm: true 옵션으로 LLM 문장 보강 (선택)
    • 공고 지정 시 맞춤 강조 포인트 조정

Phase 5: 채용 사이트 어댑터 추가

  • 사람인 어댑터 (Open API 기반)
    • 공식 API 연동 (SARAMIN_API_KEY 필요)
    • 직무 코드 / 지역 코드 매핑
    • 검색 + 상세 조회
  • 잡코리아 어댑터 (웹 파싱)
    • HTML 검색 결과 파싱
    • 상세 페이지 파싱 (주요업무/자격요건/우대사항)
  • 점핏 어댑터 (내부 API)
    • 기술 태그 기반 검색
    • API 실패 시 웹 검색 폴백
    • 상세 조회

테스트 현황

  • 63개 테스트 전체 통과
  • 테스트 분류:
    • 기술 사전 테스트 (동의어, 추출, 매칭)
    • 이력서 파서 테스트 (섹션 분리, 필드 추출)
    • 공고 정규화 테스트
    • 플랫폼 템플릿 테스트 (6개 플랫폼)
    • 매칭 스코어링 테스트
    • E2E 통합 테스트 (프로필→공고→매칭→양식변환→지원관리)

4. 현재 구현 상태 종합

어댑터 (공고 검색)

사이트 상태 방식 API 키
원티드 REST API (v4) 불필요
사람인 Open API 필요
잡코리아 웹 HTML 파싱 불필요
점핏 내부 API + 웹 폴백 불필요
로켓펀치 🔜 미구현 -

양식 변환 (이력서 복붙)

사이트 상태 특징
원티드 한 줄 소개 + 성과 bullet
사람인 표 기반 + 자소서 문항 구조
잡코리아 이력서 + 경력기술서 분리
점핏 기술 태그 중심 프로필
로켓펀치 링크드인 스타일
범용 이메일 지원용

Tool 구현 상태

Tool 구현 LLM 필요
jobs_search ✅ 완전 동작
jobs_get_detail ✅ 완전 동작
jobs_add ✅ 완전 동작
profile_parse_resume ✅ 규칙 기반 + LLM 보강 선택적
profile_get ✅ 완전 동작
match_score_job ✅ 규칙 기반
match_rank_jobs ✅ 규칙 기반
resume_tailor ✅ LLM 기반
resume_export ✅ 템플릿 + LLM 선택적 선택적
coverletter_brainstorm ✅ LLM 기반
coverletter_generate ✅ LLM 기반
portfolio_reorder ✅ LLM 기반
application_create ✅ 완전 동작
application_update_status ✅ 완전 동작
application_list ✅ 완전 동작
interview_prepare ✅ LLM 기반

16개 Tool 전부 구현 완료. LLM 없이도 10개 Tool이 완전 동작.


5. 핵심 기술적 결정과 이유

기술 사전 (tech-dictionary)

한국 채용공고에는 같은 기술이 여러 표기로 등장한다.

  • "자바", "Java", "JAVA" → 모두 같은 기술
  • "스프링부트", "Spring Boot", "SpringBoot" → 모두 같은 기술
  • "k8s", "쿠버네티스", "Kubernetes" → 모두 같은 기술

80개 이상의 기술과 한국어 동의어를 사전으로 관리하여,
매칭 정확도를 비약적으로 올렸다.

어댑터 패턴

각 채용 사이트의 API/HTML 구조가 완전히 다르다.

  • 원티드: 깔끔한 REST API
  • 사람인: 공식 Open API (키 필요)
  • 잡코리아: 공식 API 없음 → HTML 파싱
  • 점핏: 비공식 내부 API + 웹 폴백

SourceAdapter 인터페이스로 추상화하고,
search() / fetchDetail() / isAvailable()만 구현하면 새 사이트를 추가할 수 있게 설계했다.

템플릿 패턴 (양식 변환)

LLM에 양식 변환을 전부 맡기면:

  1. 응답 속도가 느림 (3~10초)
  2. 포맷이 매번 조금씩 다름
  3. API 비용 발생

그래서 규칙 기반 템플릿으로 1차 변환하고,
LLM은 선택적으로 문장을 다듬는 역할만 하도록 분리했다.


6. 남은 과제

단기 (다음 Phase)

  • 로켓펀치 어댑터 구현
  • 실 서비스 환경에서 크롤링 안정성 테스트
  • LLM 프롬프트 품질 개선 (한국어 경력기술서 문체)

중기

  • CLI 인터페이스 (MCP 없이도 직접 사용)
  • 이력서 PDF 파싱 (현재는 텍스트만)
  • 공고 변경 감지 (마감일 임박 알림)

장기

  • 비개발자 직무 확장 (디자이너, PM, 마케팅)
  • 면접 후기 크롤링 + 분석
  • 연봉 데이터 연동

7. 배운 것

  1. "불편함"이 가장 강한 동기다 — 같은 서류를 6번 쓰는 짜증이 없었으면 이 프로젝트는 없었다.
  2. 한국 시장 도구는 직접 만들어야 한다 — 미국 도구를 번역해서 쓸 수 없는 영역이 있다.
  3. LLM은 만능이 아니다 — 규칙 기반으로 해결 가능한 건 규칙 기반이 빠르고 정확하다.
  4. 어댑터 패턴의 위력 — 사이트마다 API가 달라도, 추상화 한 번이면 나머지 코드는 건드릴 필요 없다.
  5. MCP가 개인 도구 개발에 적합하다 — UI 없이 Claude에서 바로 도구를 쓸 수 있는 건 강력하다.

1. 주요 작업 내용

SD Inpainting 파이프라인 구축 및 배포 인프라 세팅 (3/20)

  • SD(Stable Diffusion) 기반 Inpainting 런타임과 배포 도구를 새로 구성했다. 파이프라인 코드(pipeline_sd_inpainting.py), 핸들러(handler_sd.py), face parsing/segface 모델 통합, SAM2 마스킹 유틸 등 약 13,700줄 규모의 코드를 세팅했다.
  • Legacy 헤어 파이프라인 지원 코드(pipeline_optimized.py, stylegan2, bald_proxy 등)를 import하여 기존 기능과의 호환성을 확보했다. 약 6,500줄 규모.
  • GitHub Actions 기반 CI/CD 워크플로우(build-sd-app, build-sd-base, deploy-sd)를 추가하여 Docker 빌드 및 RunPod 배포 파이프라인을 자동화했다.

앞머리(Bangs) 세그멘테이션 품질 개선 (3/23)

  • 숏컷 스타일에서 생성 마스크가 잘리는 아티팩트를 수정했다. 마스크 영역을 넓혀 cutoff 현상을 방지했다.
  • 얼굴 보호 마스크가 앞머리 영역까지 침범하는 문제를 발견하고, face protect mask 크기를 줄여 앞머리가 보존되도록 조정했다.
  • SAM2 프롬프트에 앞머리 positive point를 추가하여 이마 쪽 머리카락 커버리지를 개선했다.
  • SegFace 모델의 hair threshold를 0.5 → 0.3으로 낮춰 앞머리 감지 정확도를 높였다.

2. 잘한 점

  • SD Inpainting 런타임부터 CI/CD, Docker, 배포까지 엔드투엔드 인프라를 한 번에 구축한 것은 이후 빠른 반복 개발의 기반이 되었다.
  • 앞머리 세그멘테이션 이슈를 마스크 크기 → SAM2 프롬프트 → 모델 threshold 순서로 단계적으로 접근하여 체계적으로 해결했다.
  • PR 단위로 작업을 분리(SD 런타임 #1, develop 병합 #2)하여 코드 리뷰 단위를 관리했다.

3. 아쉬운 점 / 개선할 점

  • 앞머리 관련 수정이 4커밋에 걸쳐 진행되었는데, 각 커밋이 이전 수정의 부작용을 해결하는 형태였다. 사전에 마스크 파라미터 간 상호작용을 더 분석했다면 시행착오를 줄일 수 있었을 것이다.
  • Legacy 코드 import 시 빈 파일(0 bytes)이 다수 포함되어 있다. 실제 필요한 파일만 선별하여 가져왔으면 레포지토리가 더 깔끔했을 것이다.
  • 테스트 코드(test_runpod.py, test_ip.py)가 존재하지만 CI에서 자동 실행되는 구조가 아직 없다. 배포 전 자동 검증 단계 추가가 필요하다.

4. 다음 주 계획

  • 앞머리 외 다른 헤어스타일(긴 머리, 곱슬 등)에 대한 세그멘테이션 품질 검증 및 튜닝
  • CI 파이프라인에 테스트 자동화 단계 추가 검토
  • 배포 파이프라인 실제 운영 환경 검증

최종프로젝트 기획이 완성됬다.

  • 멀티 모달을 학습하기 위해서 "미용실 직원/고객을 위한 트렌드, 취향 분석 적용 앱"을 만들기로 했다.

지금까지의 상황 진척도

  • 로직은 사진 전송 -> 얼굴형 분석 + 마스킹 -> 마스킹 된 부위에 헤어 생성 이다.
  • 지금까지는 이미지 인식(분류)모델을 이것저것 만져보고있다.
  • 모델빌드가 오래걸리면 40분 가량 걸리는데, 이부분을 축소하는 방법을 찾아보고 있다.
  • 로컬에서 빌드하다가 용량이 터지는 경우가 4~5번 정도 있어서, docker hub로 build push 후, runpod에서 pull build 하는 방법을 사용했다.

현재까지 어려운점

  • 마스킹 모델의 성능이 머리카락이라는 얇은 물체? 객체?의 특성상 세밀하게 마스킹이 안되는 경우가 있었다.
  • 모델 변경 파라이터 변경등으로 수정하고 있는데, 위에서 언급한것과 같이, 한번 빌드시 최소 20분이라는 시간이 소요되기 때문에, 그점에서 시간적 소요가 크다.

지금까지 달성한 점

  • 일단 생성모델의 작동을 잘 하고있다.
  • 긴머리 -> 짧은 머리의 머리카락 변경을 요청하고 있는데, 정상적으로 잘 작동한다.

앞으로 해결해야할 점

  • 긴머리 -> 짧은 머리로 변환할때, 기존에 있던, 머리카락 부분을 다른 이미지로 대체를 못하는 경우가 있다.
  • 블러처리가 되는데, 이 부분을 어떻게 수정해야할지 감이오질 않는다...

드디어 시작된 최종프로젝트

  • 어느덧 교육은 마무리되었고 최종프로젝트가 시작됬다.
  • 기획부터 데이터선정, 모델선정, 모델학습, 웹배포까지 총괄하는 작업이다.
  • 지금까지는 내가 만들고 싶은걸 만들었다면, 이제는 적절한 절차를 통해 사업성이 타당한지, 시간내에 구현할 수 있는지, 지금 시장에 나와있는 앱이 있는지를 파악해서 기획서, 요구사항명세, WBS등을 꼼꼼히 고려하여 프로젝트를 진행해야한다.

멘토링..

  • 우리팀의 멘토는 M사의 멘토님이 이XX 멘토님이 되었다.
  • 지금까지 멘토링을 받아본 적이 없어서.. 좀 신기한 경험이였다. 확실히 실무자의 멘토링을 받으니 프로젝트의 가닥이 쉽게 잡혔다.
  • 어떤게 사업성이 있는 프로젝트인지 확실하게 피드백을 받았고, 어떻게 기획서를 써야하는지 배우고 앞으로의 방향성도 잡아주셔서 의미있는 멘토링이 되어서 배울게 많은 멘토링이였다.

우리의 프로젝트

  • ViT기술을 사용해 보려고 한다. 주제를 선정하고 보니 다른 전기수에서 이미 만들었던 프로젝트라 당황했다...!
  • 방향성을 다르게하고, 신기술을 더 접목을 시켜서 확실하게 구현을 해야겟다.

드디어 수업이 다 끝났다..

  • 강사님이 준비해오신 수업은 다 끝났고, 이제 최종프로젝트만 남았다.
  • 파이널 프로젝트 조가 나왔는데 참여에 적극성이 없으신 분이 있어서 좀 걱정이다.
  • 그 전에 4차 프로젝트 도 있는데 그건 뭐.. 다들 잘하니깐 금방 끝날것 같다.

최종프로젝트 기획

  • 일단 센터에서 정해준 몇개의 주제가 있엇는데, 한가지 대주제로 LLM 활용 내부고객 업무 효율성 향상을 위한 문서검색시스템을 선택했다.
  • 사용자가 학습할 도메인을 선택한 후 LLM을 통해 학습하는 플랫폼을 구현하는게 목적이다.
  • RAG데이터만 잘 가져오면 확장성은 무한한 프로젝트일것으로 보인다.

개인프로젝트

  • LLM을 이용한 TRPG 게임을 만드려고 한다.
  • 사용자가 배경을 정해도 되고, 미리 입력한 세계관으로 게임을 진행할 수 있게 할 예정이다.
  • 웹으로 간단히 구현이 가능할것 같고, 관련 자료를 찾아보는중이다.

코딩테스트

  • 일단 PCCE 는 레벨3를 달성했다. 레벨 4는 굳이...? 라는 느낌이라 이제 아마 PCCP를 준비할것 같다.
  • 인강은 다 안봤는데 다시 보기 시작해야할것 같다.
  • 이제 하루에 한문제씩은 꼭 풀어봐야지..!

이번주의 공부시간은 박살...

  • 일단 설이 있엇다.. 핑계지만 이번 주 공부시간은 박살.. 오랜만에 고향 친구들과 가족들을 만나느라 시간을 다 써버렷다..!
  • 뭔가 만드려고 계속 노력은 하는데, 저번 개인프로젝트가 망한 이후로 의욕이 감퇴된것 같다...
  • 새로운 의욕거리를 만들어야 겟다...!

설 주에 있던 일들...

  • LLM이 대중화 되었고 내가 LLM을 튜닝하는 공부를 한다고 하니, 다양하게 태클이 들어온다...
  • 본인 말이 다 맞는줄 아는 답답한 사람들과 말하느라 쉬었지만, 힘든 일정이였다....
    • LLM이 잘못된 정보를 자꾸 말한다고 하는데, LLM 환각 원인을 말해주고 최대한 환각이 안나오게 사용하는 법을 알려줘도 못믿는 사람들과 답답한 이야기를 이어나갔다.
    • 뭐 나도 설명하면서 다시 공부하게됬고, 어떻게 설명해야 사람들이 잘 알아들을수 있는지 알게되는 계기가 되었다.

돌아오는 주에 할 일들 정리

  • 일단 다시 알고리즘 공부 시작해야겟다..! 거의 2주간 놓고있었으니, 다시 감을 찾는데 오래 걸릴것 같다
  • 누나 집에서 다니니깐 다시 공부시간을 늘려야지 뭐...

사이드 프로젝트가 망했다…

  • 말 그대로 처참하게 망했다.
  • 현실적인 문제로 개발에 실패했고, 그 과정을 정리하며 회고해보려 한다.

만들려던 앱의 개요

누나가 임신성 당뇨를 겪고 있다. 특정 음식을 먹으면 공복 혈당과 식후 2시간 혈당을 비교했을 때, 어떤 재료(성분)가 들어간 음식에서 유독 혈당이 크게 치솟는 경우가 있었다.
그래서 그 “문제 재료”는 피하고, 혈당 관리에 도움이 되는 음식들을 기반으로 LLM이 레시피를 추천/생성해주는 혈당관리 앱을 만들려고 했다.

처음엔 단순히 LLM API를 호출하면 개발도 편하고 답변 품질도 좋을 거라 생각했다. 그리고 앱을 마켓에 올린 뒤 구글 애드(Ad)로 수익화하는 그림을 그렸다.

하지만 곧 현실적인 한계가 보였다.

  • API 방식은 운영비(토큰 비용) 가 계속 발생한다.
  • 단순 API 호출은 내가 하고 싶은 모델 파인튜닝 + 서비스와는 거리가 멀다.
  • 내 상황에서 금전적인 부담을 감당하기 어렵다는 결론에 가까워졌다.

그래서 “서버를 직접 운영하지 않아도 되는” Firebase(BaaS) 를 기반으로 가볍게 만들고 싶었다.

진행 과정

1) 모델 후보 선정

모델 후보는 크게 두 가지였다.

  1. EXAONE-3.5-2.4B: 한국어 답변 품질이 좋다.
  2. Qwen2.5 계열: 경량화 옵션이 다양하다.

2) 구조적 제약을 뒤늦게 인지

문제는, 내가 서버를 운영하지 않는 구조(Firebase 중심)에서는 튜닝한 sLLM을 서버에서 직접 돌리기 어렵다는 점이었다.
(서버를 따로 두자니 다시 운영비/관리 부담이 생긴다.)

그때 요즘 광고에서 흔히 보이는 문구가 떠올랐다.

“휴대폰에서 AI를 사용하세요.”

그래서 방향을 틀었다.

  • “그렇다면 모델을 더 경량화해서 'On-device LLM' 으로 돌리면 되지 않을까?”
  • 목표: 모바일 기기에서 로컬 추론이 가능한지 검증해보기

치명적인 문제

1) 로컬 테스트부터 이미 느렸다

최종 모델은 “로컬 기기에서 먼저 돌려보고 결정하자”로 정했다.

  • 먼저 EXAONE-3.5-2.4B 를 로컬에서 테스트했다.
    “2.4B면 그래도 금방 나오겠지”라고 생각했는데, 응답이 30분 이상 걸렸다.
    → 이 속도면 모바일에서 돌리는 건 사실상 불가능하다고 판단했다.

그래서 더 경량화된 옵션이 있는 Qwen2.5-1.5B-Instruct 를 선택했다.

  • Qwen2.5-1.5B도 로컬 테스트에서 답변까지 약 10분이 걸렸다.
    → 지금 생각하면 여기서 멈췄어야 했다.

2) 모바일 실험: 앱이 그냥 죽었다

그래도 “혹시 몰라서” 실제로 '앱 + 모델 탑재 형태'로 설치해서 실행해봤다.

결과는 단순했다.

  • 앱이 그냥 종료(크래시) 해버렸다.

성능/속도 문제가 아니라, '메모리/리소스/런타임' 안정성 단계에서 이미 버티지 못했다.
즉, “돌아가긴 하는데 느리다” 수준이 아니라 실행 자체가 성립하지 않는 환경이었다.

배운 점

  • 아무리 경량화된 모델이라도, 현재의 모바일 기기에서 LLM을 제대로 돌리는 건 아직 무리라는 걸 몸으로 배웠다.
    관련 연구도 이미 존재했다: 논문 링크
  • 개발에서 자주 하던 “일단 만들고 보자” 방식이, 이런 문제에서는 오히려 시간을 크게 낭비하게 만든다.
    다음부터는:
    • 선행 연구/사례/벤치마크를 먼저 확인하고
    • 실행 가능성을 검증한 뒤
    • 설계를 시작하는 편이 훨씬 효율적이라는 걸 느꼈다.
  • On-device를 전제로 하지 않고, 비용을 통제할 수 있는 방향부터 먼저 설계할 것 (예: 레시피 생성은 서버에서, 앱은 캐싱/프롬프트 최소화/사용량 제한 등)
  • 또는 LLM을 “생성”이 아니라 검색+요약(RAG) 기반의 제한된 답변으로 축소해서 리스크를 낮출 것

+ Recent posts