MMD 主體感知調色(突出人物)
0. 結論先行
- MMD 舞台片想「突出人物」,兩條路:
- 光區塑形(推薦,乾淨):以人物為中心的柔光橢圓/徑向漸變——橢圓內人物提亮/暖/銳/輝光,橢圓外背景壓暗/冷/降飽和/輕微虛化;邊緣過渡很寬,幾乎看不到「圈」。
- 硬摳像分離(上限高但髒):幀差背景板取人物 mask,人物/背景分別調色再合成。難點是人物投影(cast shadow)會跟著人物一起動,和人物連成一片,mask 會帶上大片陰影 → 出現亮斑;強行腐蝕又啃翅膀羽毛。
- 只有成片時選 1;能從 Blender/MMD 重渲染帶 alpha 的角色層時選 2(質量最高)。
1. 素材與工具
| 東西 | 路徑 |
|---|---|
| 源片 | D:\mmd.mp4\0002-4956.mkv(完整 165.166s)/ 字幕\0001-4956 - 副本.mkv(截斷到 ~137s) |
| 調色腳本 | 字幕\build\grade_ffmpeg.py(光區/柔光)、grade_final5s.py(5s 成品參數)、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 生成柔光 mask(一次性 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 合成(懸浮人物 + 壓暗背景)
思路:人物調色版 + 背景調色版,用 mask 做 alpha 合併:
[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時 mask 是靜止流;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 | 柔光很寬,推薦做全片底片 |
在 5s 上試參數,滿意後直接套 30s/全片(ffmpeg 一次跑完,比逐幀 Python 快)。
3. 路線 B:硬摳像(需要更乾淨的 mask 時)
3.1 背景板
- 抽全片 1/2s 採樣幀,縮到 1280×540,逐像素中位數 = 背景板
bg_half.npy。 - 命令/復盤:
字幕\build\color_grade.py的build_background()。 - 已知問題:人物長期站在中間時,中位數會被人物「污染」(人物身上某些像素常常是人物)→ 幀差在軀幹處偏小 → mask 有洞。用
fill_holes或 bbox 兜底。
3.2 幀差 mask(1/4 解析度最快)
m = |frame_q - bg_q|.max(axis=2) > 38
m = MaxFilter(5)(外擴)→ largest connected component → 可選填洞/羽化
- 用於佔用分析/避讓(見 avoid-subtitles);用於調色時要再加「人物相似度」約束,否則投影/光斑混進來。
3.3 已知翻車點(本項目實測)
- 投影污染:人物影子跟著動,mask 連成一片 → 調色後人物周圍出現亮斑/色塊。解決:不要用幀差當精確 matte;要麼接受「連影子一起提亮」(很怪),要麼回到光區塑形。
- 中位背景污染:人物站樁處 mask 有洞 → 軀幹沒被調色,出現斑駁。解決:填洞/用 bbox。
- 8bit 色帶:大幅推色後天空/漸變會出色帶 → 加 1–3 級顆粒(ffmpeg
noise=alls=6:allf=t+u)或輸出 10bit/HEVC。 - 字幕對比度:底片壓暗後白字更清楚;若壓得太狠人物也變暗,需要再提人物或加 bloom。
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 幀:人物亮度和背景分離是否自然,光區邊緣有沒有明顯圈
- 近景/特寫:臉有沒有過曝、膚色是否偏
- 暗部有沒有死黑/色帶(8bit)
- 與字幕合看:白字是否夠清楚
- 輸出
ffprobe時長/幀率正確;10bit 輸出用-pix_fmt yuv420p10le -c:v libx265
6. 資源
- 5s 成品參數與對比:
字幕\輸出\調色測試\(A自然/B強化/成品方向 + 對比影片) - 30s 底片:
字幕\輸出\S4S6_30s\_base_30s.mp4(僅輕微壓暗,未做完整調色) - 之前嘗試過但被否的方向:哥特(
字幕\輸出\字幕設計\G1…G3)、FLARE 炸場、霓虹(字幕\輸出\字幕小樣)——留檔可參考參數。