MMD 주체 인식 색보정(댄서 돋보이게 하기)
0. 결론부터
- MMD 무대 영상에서 "댄서를 돋보이게" 하려면 두 가지 방법이 있습니다:
- 광역 성형(권장, 깔끔): 댄서 중심의 부드러운 조명 타원/방사형 그라데이션 – 타원 내부는 댄서를 밝게/따뜻하게/선명하게/블룸 처리하고, 타원 외부 배경은 어둡게/차갑게/채도 낮춤/약간 블러 처리합니다. 가장자리 전환이 매우 넓어 '원'이 거의 보이지 않습니다.
- 하드 키잉 분리(상한은 높지만 더러움): 프레임 차분(프레임 차이)으로 배경판을 생성하여 캐릭터 마스크를 추출하고, 주체와 배경을 각각 보정한 후 합성합니다. 난제는 캐릭터의 투영 그림자(cast shadow)가 캐릭터와 함께 움직여 캐릭터와 하나로 연결된다는 점입니다. 이로 인해 마스크가 넓은 그림자 영역을 포함하게 되어 → 밝은 블롭(얼룩)이 나타납니다. 강제로 침식(erode)하면 날개나 머리카락까지 깎여 나갑니다.
- 최종 결과물만 필요한 경우 방법 1을 선택하고, Blender/MMD에서 알파 채널이 포함된 캐릭터 레이어를 다시 렌더링할 수 있는 경우 방법 2를 선택합니다(품질이 가장 높음).
1. 자료 및 도구
| 항목 | 경로 |
|---|---|
| 원본 영상 | D:\mmd.mp4\0002-4956.mkv (전체 165.166초) / 자막\0001-4956 - 복사본.mkv (~137초로 잘림) |
| 색보정 스크립트 | 자막\build\grade_ffmpeg.py (광역/부드러운 조명), grade_final5s.py (5초 완성품 파라미터), color_grade.py (구형 프레임 차분 키잉), gothic_art.py (정적 프레임 고딕 색보정) |
| 검증 스크립트 | avoid_layout.py (점유율 분석), avoid_side2.py (점유율+bbox) |
| ffmpeg | C:\Users\35530\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-9.0-full_build\bin\ffmpeg.exe |
| Python | C:\Users\35530\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\python\python.exe (PIL/numpy, cv2/딥러닝 없음) |
2. 방법 A: 광역 성형(이 프로젝트의 기본 방식)
2.1 부드러운 조명 마스크 생성(일회성 PNG)
import numpy as np
from PIL import Image
W,H = 2560,1080
yy,xx = np.mgrid[0:H,0:W].astype(np.float32)
cx,cy = 0.515,0.565 # 댄서 중심 (2560x1080 세로 구도 기준, 대략 이 위치; 다른 영상이면 프레임 추출하여 확인)
rx,ry = 0.245,0.47 # 반폭/반높이 (비율); 너무 작으면 원이 보이고, 너무 크면 분리 효과가 없음
q = ((xx-W*cx)/(W*rx))**2 + ((yy-H*cy)/(H*ry))**2
mask = np.clip(1.0-q,0,1)**0.55 # 0.5–0.7이 부드러움
Image.fromarray((mask*255).astype(np.uint8)).save("_mask_final.png")
참고 완성품: 자막\출력\색보정테스트\_mask_final.png, 자막\출력\자막샘플\_mask_final.png.
2.2 ffmpeg 합성 (댄서 강조 + 배경 압축)
아이디어: 댄서 색보정 버전 + 배경 색보정 버전, 마스크로 알파 합성:
[0:v]split=2[sv][bv];
[sv]eq=brightness=0.050:contrast=1.06:saturation=1.15,
colorbalance=rs=0.02:bs=-0.02:rm=0.05:bm=-0.06:rh=0.02:bh=-0.02,
split=2[sm][sh];[sh]gblur=sigma=14[blur];
[sm][blur]blend=all_mode=screen:all_opacity=0.34[sub];
[sub]format=yuva420p[suba];
[1:v]format=gray[gm];[suba][gm]alphamerge[subj];
[bv]eq=brightness=-0.045:contrast=1.07:saturation=0.87,
colorbalance=rm=-0.03:bm=0.05,gblur=sigma=0.6[bg];
[bg][subj]overlay=0:0,format=yuv420p[v]
명령어:
& $ff -v error -y -ss 36.5 -t 30 -i src.mkv -loop 1 -i _mask_final.png `
-filter_complex "<위 graph>" -map "[v]" -t 30 `
-c:v libx264 -preset medium -crf 16 -pix_fmt yuv420p OUT.mp4
참고:
-loop 1 -i mask.png시 마스크는 정적 스트림입니다.overlay후-t로 길이를 제한하세요. 파라미터 의미:bg brightness음수=압축,saturation<1=채도 낮춤,bm>0=배경이 파란색(차가움)으로 치우침,gblur=약간 블러;subj의 brightness/채도/따뜻한 색상/blend screen으로 블룸 효과.
2.3 실제 측정된 파라미터 세트 (2560×1080, 자주색 무대 MMD)
| 단계 | bg brightness | bg sat | subj gain | subj sat | bloom | vignette | 효과 |
|---|---|---|---|---|---|---|---|
| A 자연 | −0.035 | 0.82 | +0.030 | 1.08 | 0.20 | 0.30 | 댄서를 살짝 강조, 분위기 거의 변화 없음 |
| B 강화 | −0.075 | 0.70 | +0.055 | 1.16 | 0.34 | 0.42 | 명확한 분리, 광역 가장자리가 약간 보임 |
| 최종 방향 | −0.085 | 0.68 | +0.050 | 1.15 | 0.30 | 0.38 | 부드러운 조명이 매우 넓음, 전체 영상 기본으로 권장 |
5초 컷에서 파라미터를 테스트하고 만족하면 바로 30초/전체 영상에 적용합니다 (ffmpeg 한 번에 실행, 프레임별 Python보다 빠름).
3. 방법 B: 하드 키잉 (더 깨끗한 마스크가 필요할 때)
3.1 배경판
- 전체 영상에서 1/2초마다 샘플 프레임을 추출하여 1280×540으로 축소, 픽셀별 중간값(median) = 배경판
bg_half.npy. - 명령어/복기:
자막\build\color_grade.py의build_background(). - 알려진 문제: 댄서가 중앙에 오래 서 있으면 중간값이 댄서에 의해 "오염"됩니다 (댄서의 특정 픽셀이 자주 나타남) → 프레임 차분 시 몸통 부분에서 값이 작아짐 → 마스크에 구멍이 생깁니다.
fill_holes또는 bbox로 보완.
3.2 프레임 차분 마스크 (1/4 해상도가 가장 빠름)
m = |frame_q - bg_q|.max(axis=2) > 38
m = MaxFilter(5) (확장) → largest connected component → 선택적으로 구멍 채우기/페더링
- 점유율 분석/회피에 사용됨 (avoid-subtitles 참조); 색보정에 사용할 때는 "댄서 유사도" 제약 조건을 추가해야 함, 그렇지 않으면 그림자/광블롭이 섞여 들어옴.
3.3 알려진 실패 지점 (이 프로젝트에서 실제 측정)
- 투영 그림자 오염: 댄서 그림자가 따라 움직여 마스크가 하나로 연결됨 → 색보정 후 댄서 주변에 밝은 블롭/색상 덩어리 발생. 해결: 프레임 차분을 정밀 매트로 사용하지 말 것. "그림자까지 함께 밝게" (매우 어색함)를 받아들이거나 광역 성형으로 돌아갈 것.
- 중간 배경 오염: 댄서가 정지한 구간에서 마스크에 구멍이 생김 → 몸통 부분이 색보정되지 않아 얼룩덜룩해짐. 해결: 구멍 채우기/bbox 사용.
- 8비트 밴딩: 색상을 크게 조정한 후 하늘/그라데이션에서 밴딩이 발생할 수 있음 → 1–3 단계의 노이즈 추가 (ffmpeg
noise=alls=6:allf=t+u) 또는 10bit/HEVC 출력. - 자막 대비: 배경을 어둡게 하면 흰색 글자가 더 선명해짐; 너무 어둡게 하면 댄서도 함께 어두워지므로 댄서를 다시 밝게 하거나 블룸을 추가해야 함.
4. 고딕/네온 등 스타일화 색보정 (정적 프레임용)
- 고딕 레드블랙 (사용자에게 기각됨, 기록만 보관):
자막\build\gothic_art.py의gothic_grade(): 압축 0.42–0.52, 채도 낮춤 0.30–0.42, 중간조 보라/빨강, 강한 비네트, 안개. - 테마 색상 버전 (사용자가 최종적으로 원했던): 흰색/뼈색 위주 + 라벤더 보라/마젠타 핑크 블룸, 배경은 진한 보라색/검정으로 압축;
gothic_art.py의 색보정 상수 참조 (BLOOD/LAV/PINK/VIOLET). - 네온 버전:
neon_wide.py(ASS로 직접 네온 그리기, 색보정 불필요). - 빠른 색상 적용:
eq+colorbalance+vignette세트만으로 보라색 무대를 모든 색조로 변경 가능:- 차가운 청록:
colorbalance=bs=0.10:bm=0.12:bm...(파란색 오프셋), 채도 낮춤; - 따뜻한 잿불/불:
colorbalance=rs=0.10:rm=0.08,eq=saturation=1.1.
- 차가운 청록:
5. 검증 체크리스트
- 3–5프레임 추출: 댄서 밝기와 배경 분리가 자연스러운지, 광역 가장자리에 눈에 띄는 원이 없는지 확인
- 클로즈업/특수 촬영: 얼굴이 과다 노출되지 않았는지, 피부색이 편향되지 않았는지 확인
- 어두운 부분에 완전 검정/밴딩(8비트)이 없는지 확인
- 자막과 함께 보기: 흰색 글자가 충분히 선명한지 확인
- 출력
ffprobe길이/프레임 속도 정확; 10비트 출력은-pix_fmt yuv420p10le -c:v libx265사용
6. 리소스
- 5초 완성품 파라미터 및 비교:
자막\출력\색보정테스트\(A자연/B강화/최종방향 + 비교 비디오) - 30초 기본:
자막\출력\S4S6_30s\_base_30s.mp4(약간 압축만, 완전한 색보정 아님) - 이전에 시도했지만 기각된 방향: 고딕 (
자막\출력\자막디자인\G1…G3), FLARE 폭발 효과, 네온 (자막\출력\자막샘플) – 기록용으로 파라미터 참조 가능.