MMD Subjektbewusste Farbkorrektur (Person hervorheben)
0. Fazit vorweg
- MMD-Bühnenvideos, die „Person hervorheben“ möchten, haben zwei Wege:
- Lichtzonenmodellierung (empfohlen, sauber): Eine weiche Lichtellipse / radialer Verlauf um die Person – innerhalb der Ellipse wird die Person aufgehellt/warm/scharf/glühend, außerhalb der Hintergrund abgedunkelt/kalt/entsättigt/leicht unscharf; die Übergänge sind sehr weich, fast unsichtbar.
- Harte Keying-Trennung (hohe Qualität aber schmutzig): Frame-Differenz zum Hintergrundbild, um eine Personenmaske zu erhalten, Person und Hintergrund getrennt farblich bearbeiten und wieder zusammensetzen. Die Schwierigkeit ist der Schlagschatten (cast shadow) der Person, der sich mitbewegt und mit der Person verbunden ist; die Maske enthält dann große Schattenflächen → helle Flecken; aggressives Erodieren frisst wiederum Flügel/Federn.
- Nur bei Endproduktion Weg 1 wählen; wenn eine Alpha-Ebene der Figur aus Blender/MMD gerendert werden kann, Weg 2 (höchste Qualität).
1. Material und Werkzeuge
| Gegenstand | Pfad |
|---|---|
| Quellvideo | D:\mmd.mp4\0002-4956.mkv (komplett 165,166s) / Untertitel\0001-4956 - Kopie.mkv (abgeschnitten auf ~137s) |
| Grading-Skripte | Untertitel\build\grade_ffmpeg.py (Lichtzone/weiches Licht), grade_final5s.py (5s-Endprodukt-Parameter), color_grade.py (alte Version Frame-Differenz-Matte), gothic_art.py (statischer Frame Gothic-Grading) |
| Validierungsskripte | avoid_layout.py (Belegungsanalyse), avoid_side2.py (Belegung+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, kein cv2/Deep Learning) |
2. Weg A: Lichtzonenmodellierung (Standardvorgehen dieses Projekts)
2.1 Erstellen einer weichen Lichtmasken (einmalig 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 # Personenmitte (bei 2560x1080 Hochformat etwa hier; bei anderen Videos Frame extrahieren zur Bestätigung)
rx,ry = 0.245,0.47 # Halbbreite / Halbhöhe (Anteil); zu klein → sichtbarer Kreis, zu groß → keine Trennung
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 recht weich
Image.fromarray((mask*255).astype(np.uint8)).save("_mask_final.png")
Referenz-Endprodukte: Untertitel\Ausgabe\Farbtest\_mask_final.png, Untertitel\Ausgabe\Untertitel-Muster\_mask_final.png.
2.2 ffmpeg-Synthese (schwebende Person + abgedunkelter Hintergrund)
Idee: Personen-Grading-Version + Hintergrund-Grading-Version, mit der Maske als Alpha-Blending:
[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]
Befehl:
& $ff -v error -y -ss 36.5 -t 30 -i src.mkv -loop 1 -i _mask_final.png `
-filter_complex "<obiger Graph>" -map "[v]" -t 30 `
-c:v libx264 -preset medium -crf 16 -pix_fmt yuv420p OUT.mp4
Hinweis: Bei
-loop 1 -i mask.pngist die Maske ein statischer Stream; nach demoverlaymit-tdie Länge begrenzen. Parameterbedeutung: bg brightness negativ = abdunkeln, saturation<1 = entsättigen,bm>0= Hintergrund bläulich (kalt),gblur= leichte Unschärfe; bei subj brightness/Sättigung/warme Töne/blend screenfür Glow.
2.3 Gemessene Parameterstufen (2560×1080, lila Bühne MMD)
| Stufe | bg brightness | bg sat | subj gain | subj sat | bloom | vignette | Effekt |
|---|---|---|---|---|---|---|---|
| A natürlich | −0,035 | 0,82 | +0,030 | 1,08 | 0,20 | 0,30 | Leichte Aufhellung der Person, fast keine Änderung der Atmosphäre |
| B verstärkt | −0,075 | 0,70 | +0,055 | 1,16 | 0,34 | 0,42 | Deutliche Trennung, Lichtzonenrand leicht sichtbar |
| Endprodukt-Richtung | −0,085 | 0,68 | +0,050 | 1,15 | 0,30 | 0,38 | Sehr weiches Licht, empfohlen für das gesamte Untervideo |
Parameter auf 5s testen, bei Zufriedenheit direkt auf 30s/Ganzvideo anwenden (ffmpeg läuft einmal durch, schneller als Frame-für-Frame mit Python).
3. Weg B: Harte Keying (für sauberere Masken)
3.1 Hintergrundbild
- Aus dem gesamten Video jede 1/2s einen Frame ziehen, auf 1280×540 skalieren, pixelweise Median = Hintergrundbild
bg_half.npy. - Befehl/Rückschau:
Untertitel\build\color_grade.py'sbuild_background(). - Bekanntes Problem: Wenn die Person lange in der Mitte steht, wird der Median durch die Person „verschmutzt“ (manche Pixel der Person sind oft die Person) → Frame-Differenz ist am Rumpf zu klein → Maske hat Löcher. Mit
fill_holesoder Bbox abfangen.
3.2 Frame-Differenz-Maske (1/4 Auflösung am schnellsten)
m = |frame_q - bg_q|.max(axis=2) > 38
m = MaxFilter(5) (expandieren) → größte verbundene Komponente → optional Löcher füllen/weichzeichnen
- Verwendet für Belegungsanalyse/Ausweichen (siehe avoid-subtitles); für Farbkorrektur muss zusätzlich eine „Personenähnlichkeits“-Einschränkung hinzugefügt werden, sonst mischen sich Schatten/Lichtflecken ein.
3.3 Bekannte Fehlerquellen (in diesem Projekt getestet)
- Schattenkontamination: Der Schatten der Person bewegt sich mit, die Maske verschmilzt → nach Farbkorrektur helle Flecken/Farbblöcke um die Person. Lösung: Frame-Differenz nicht als präzise Matte verwenden; entweder akzeptieren, dass der Schatten mit aufgehellt wird (seltsam), oder zurück zur Lichtzonenmodellierung.
- Median-Hintergrundverschmutzung: Maske hat Löcher an Stellen, wo die Person stand → Rumpf wird nicht farblich bearbeitet, erscheint fleckig. Lösung: Löcher füllen / mit Bbox.
- 8-Bit-Banding: Starke Farbverschiebungen führen zu Farbbändern in Himmel/Verläufen → 1–3 Stufen Rauschen hinzufügen (ffmpeg
noise=alls=6:allf=t+u) oder 10bit/HEVC ausgeben. - Untertitelkontrast: Abgedunkeltes Untervideo macht weiße Schrift deutlicher; wenn zu stark abgedunkelt auch die Person dunkler wird, muss die Person wieder aufgehellt oder mit Bloom versehen werden.
4. Gothic/Neon etc. Stilisierte Farbkorrektur (für statische Frames)
- Gothic Rot-Schwarz (vom Benutzer bereits abgelehnt, nur zur Dokumentation):
Untertitel\build\gothic_art.py'sgothic_grade(): abdunkeln um 0,42–0,52, entsättigen auf 0,30–0,42, Mitten lila/rot, starke Vignette, Nebel. - Themenfarbversion (die der Benutzer letztendlich wollte): Weiß/Elfenbeinweiß als Hauptfarbe + Lavendel/Magentarosa Glow, Hintergrund in tiefes Schwarz-Lila; siehe Farbkonstanten in
gothic_art.py(BLOOD/LAV/PINK/VIOLET). - Neon-Version:
neon_wide.py(ASS zeichnet Neon direkt, keine Farbkorrektur nötig). - Schnelle Farbanpassung: Die drei Werkzeuge
eq+colorbalance+vignettereichen, um eine lila Bühne in jeden Farbton zu drücken:- Kalt-Blau:
colorbalance=bs=0.10:bm=0.12:bm...(Blau verschieben), Sättigung reduzieren; - Warm-Glut:
colorbalance=rs=0.10:rm=0.08,eq=saturation=1.1.
- Kalt-Blau:
5. Validierungscheckliste
- 3–5 Frames ziehen: Ist die Trennung von Personenhelligkeit und Hintergrund natürlich, gibt es einen sichtbaren Rand der Lichtzone?
- Nahaufnahme/Porträt: Ist das Gesicht überbelichtet, ist der Hautton verfälscht?
- Gibt es in dunklen Bereichen Tiefschwarz/Farbbänder (8bit)?
- Zusammen mit Untertiteln prüfen: Ist die weiße Schrift deutlich genug?
- Ausgabe mit
ffprobeauf korrekte Dauer/Bildrate prüfen; 10bit-Ausgabe mit-pix_fmt yuv420p10le -c:v libx265
6. Ressourcen
- 5s-Endproduktparameter und Vergleich:
Untertitel\Ausgabe\Farbtest\(A natürlich/B verstärkt/Endprodukt-Richtung + Vergleichsvideo) - 30s-Untervideo:
Untertitel\Ausgabe\S4S6_30s\_base_30s.mp4(nur leicht abgedunkelt, keine vollständige Farbkorrektur) - Früher versuchte, aber abgelehnte Richtungen: Gothic (
Untertitel\Ausgabe\Untertiteldesign\G1…G3), FLARE-Überbelichtung, Neon (Untertitel\Ausgabe\Untertitel-Muster) – zur Dokumentation mit Parameterreferenz.