人物避讓字幕(完全/盡量不擋人)
0. 兩種目標要分清
- 「完全不擋」:每個字框與每一幀人物 mask 的交集 = 0。代價是字只能放兩側/頂部窄欄,字號受限。
- 「盡量不擋」:允許壓翅膀邊緣/背景,但臉和身體必須乾淨。可以繼續用大字風格(S4/S6 等),只檢查近景幀。
本技能預設做「完全不擋」並給出可驗證資料;如果使用者說「炸裂/大字/藝術字」,大概率是後者。
1. 核心演算法(快版:只求 bbox)
不再逐像素求精確 matte,只需要人物包圍盒,用 1/4 解析度算,極快:
- 背景板
bg_half.npy(1280×540,見 subject-color-grade 的 2.1/3.1)。 - 每幀縮到 640×270,與背景板做差:
d = |frame-bg|.max(axis=2),閾值 38(低閾值會把 LOVE 燈牌/光斑算進來)。 MaxFilter(5)膨脹 → 最大連通域(BFS,640×270 上很快)→ 取 bbox ×4 還原到全解析度。- 按歌詞句視窗把每幀 bbox 併起來(一句內人物會動,用整句最寬範圍),得到每句
bounds = (left,right,top,bottom)。 - 排版:文字列放在
left-50左側 /right+50右側 / 頂部 / 底部;字號按可用寬度收縮。 - 逐幀複核:把每個字框映射回 640×270,與每幀 mask 求交集,報告 worst hit;不為 0 就整體縮字號再驗,直到 0。
2. 可直接複用的實現
字幕\build\avoid_side2.py(推薦,完整版:pass1 測 bbox → layout → verify → 渲染 → 關鍵幀):
$py='C:\Users\35530\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\python\python.exe'
& $py '字幕\build\avoid_side2.py'
它固定處理舊片(0001-4956)的 15.4s 片段;換片/換段時改這些常數:
SRC/OUT/T0,T1CUES(每句起止,相對片段)EL裡的字號/位置候選(L/R列、y、fs)LEFT_MARGIN/右側可用寬度邏輯 產物:NEON_avoid_15s.ass+ 渲染影片 + 關鍵幀表。 舊版(合成演示):avoid_demo.py、avoid_debug.py、avoid_layout.py(佔用熱區圖分析_佔位與選位.jpg)。
3. 本專案的實測資料(舊片 0:51–1:06.4)
- 固定底部排版:6/6 句壓到人物,人物區域遮擋率 7%–12%。
- 側欄避讓:0/6 句壓到人物,單句遮擋 ≤0.2%;逐幀複核 0 像素。
- 每句可用側欄(舊片,全 2560 寬):左欄約 0–500px,右欄約 2100–2560px,具體每句不同,必須現算。
- 人物在畫面中央、翅膀展開時佔寬約 48%–82%,近景/特寫時幾乎佔滿 → 特寫幀只能把字放上下角。
4. 排版策略(不擋人時也好看)
- 主歌詞放左欄、點綴詞放右欄,形成「左右對唱」的觀感;
- 頂部放長句(
MAKING EVERYBODY GO之類),底部只留小字/等寬資訊; - 每句只有一個「視覺主角」,其餘詞做小字,避免整句全大導致側欄放不下;
- 字框估算要保守:ASCII 寬 ≈ fs×0.66、CJK ≈ fs×1.06;留 10–14px pad。
5. 驗證清單
- 每句視窗的 bbox 是否漏掉翅膀尖/頭頂(看
分析_佔用區域.jpg) - 逐幀複核 worst hit = 0(腳本會列印)
- 抽 3–4 幀看:字是否貼邊/被裁、是否壓到小指/翅膀
- 近景/特寫幀單獨抽查
- 若允許「盡量不擋」,記錄哪些幀壓到了翅膀(可接受)
6. 已知坑
- 背景 LOL 燈牌/光斑會被幀差誤判 → 閾值 38 + 最大連通域通常能去掉;仍不行就加空間先驗(人物在中央)。
- 人物投影可能被當成人物(bbox 變大)→ 對避讓是安全的(你會避開影子),但會讓側欄更窄。
- 濾鏡
MaxFilter在 640×270 上 ≈ 外擴 8px 全解析度,複核時不要再額外放大 pad。 - 若換了新影片/新機位,所有 bounds 都要重算,不能複用舊片數字。