视频色彩分级(评估 → 校正 → 风格 → 选择 → LUT + 应用)
从头编写分级代码,在真实素材上生成多个不同的命名风格,通过明确的人类选择或委派的代理判断来选定一个风格,然后烘焙出一个便携式.cube LUT并将其应用到整个片段。无需预设包。
核心理念——先校正,后风格化。 每个风格都是 校正基础 + 创意层:
- 校正基础 使素材变得“诚实”——对于平坦的LOG/HLG素材,它是log转Rec.709转换;对于已经是Rec.709的素材,它是白平衡中和 + 曝光 + 轻度对比。所有风格共享这个基础,因此“中性”始终有意义,而风格之间则通过“意图”来区分。
- 创意层 是叠加在基础之上的风格(暖色/冷色/青橙/鲜艳/褪色……)。
肤色是质量基准。 对于人物——尤其是深色皮肤——teal_orange 和 cool_desat 通常会投射出绿色/灰色的偏色。首先在面孔上评判每个风格;保持中间调温暖,将色调推入阴影/高光/背景,优先使用 vibrance 而不是原始的 saturation。不要将白平衡一路推向中性,否则皮肤会失去活力。
要求:环境变量 PATH 中包含 ffmpeg/ffprobe,以及安装了 numpy + Pillow 的 Python。
契约
- 每个风格 =
base + creative layer;该基础由所有风格共享。 - 所有风格渲染在同一帧代表性帧上(公平、可控制的比较),带有标签,全分辨率。
- 完整应用使用
-c:a copy(音频从不重新编码 → A/V 同步一致)并保持源文件的时长、帧率、尺寸。 - 在交付前完成风格选择。当用户要求选择时,使用
human模式;当用户委派选择或要求自主运行时,使用agent模式,无需暂停。两种模式下,均需在持久化计划中保留选择理由。
项目协议工作流
将持久化决策存储在 work/color-grade/grade-plan.json 中:
{
"schema_version": 1,
"target": "base-video",
"base": "eq=contrast=1.05",
"looks": [{"name": "clean", "chain": "null"}],
"selected_look": "clean",
"selection_mode": "agent",
"selection_rationale": "中性校正保留了自然的肤色和原始光源。",
"selected_lut": "../../final/selected-color-look.cube",
"evidence_refs": ["media:source"]
}
默认使用 base-video,这样内容卡片和字幕不会被重新着色。只有在对已合成像素进行有意分级时,composite 才有效。读取共享的 work/understand/media.json 和视觉证据。生成 review/02-color-grade/choose-color-look.jpg 和 review/02-color-grade/skin-tone-check.jpg;可选生成简短的 selected-look-preview.mp4。将决策和证据记录到 review/02-color-grade/selected-look.md 中。
在用户明确选择后,将 selection_mode 设置为 human。当用户委派决策或请求不间断的工作流时,将其设置为 agent,并保守地选择,以肤色、高光保留和中性平衡为优先事项。始终编写非空的 selection_rationale;在 agent 模式下不要暂停。
将选定的 LUT 烘焙到 final/selected-color-look.cube。当该 LUT 被交付时,在分级计划中设置 selected_lut,以便共享渲染器使用该确切文件。烘焙后的完整风格 LUT 已经包含校正基础;渲染器必须单独应用 lut3d,而不是 base + lut3d。
在 based_on 中记录操作的精确输入修订版本,当计划或选定的输入发生变化时增加其整数 revision,仅当审核文件完成后才将操作检查设置为 pass,并贡献完整的协议字段:
{
"target": {"sequence": "main", "scope": "base-video"},
"effects": {
"changes_timeline": false,
"changes_geometry": false,
"changes_video_pixels": true,
"changes_audio": false,
"adds_track": null
},
"check": {
"status": "pass",
"report": "../review/02-color-grade/selected-look.md"
},
"render": {
"kind": "video-filter",
"target": "base-video",
"plan": "color-grade/grade-plan.json"
}
}
共享渲染器验证 selected_look 存在,在时间线变换之后、叠加层之前应用分级,并一次性地编码最终视频:
python skills/video-understand/scripts/build_render_plan.py .
python skills/video-understand/scripts/render_project.py work/render/render-plan.json
独立兼容性输入
- 源视频。
- 一个
looks.json(包含base和命名的looks);从looks.example.json开始,并根据assess.py报告的内容重新调整base。
所有现有脚本继续接受传统的 looks.json。gradelib.load_spec() 也接受标准的 grade-plan.json,因此在迁移期间,联系卡片、片段、LUT 和独立应用命令保持兼容。
looks.json 模式
{
"base": "<校正基础:log转Rec.709转换,或针对普通素材的白平衡+曝光+对比度>",
"looks": [
{ "name": "clean_neutral", "label": "1 · 干净中性", "desc": "仅校正", "chain": "eq=contrast=1.02" },
{ "name": "teal_orange", "label": "4 · 青橙风格", "desc": "电影感", "chain": "colorbalance=...,eq=..." }
// 一个风格的完整 ffmpeg 滤镜 = base + "," + chain。设置 "prepend_base": false 以单独使用 chain。
]
}
工作流
-
评估——切勿盲目分级。
python scripts/assess.py SOURCE.mp4 --out work/报告 log 与 Rec.709 的对比、白平衡色偏(signalstats U/V 与 128 对比)、曝光(Y),选取一个代表性帧(
work/assess/frame.png),并建议基础调整。相应地重新调整looks.json中的base(例如,暖色偏色 → 增加一点蓝色;log 素材 → 将 log 转 709 转换放入base中)。- 代表性帧是根据中位数亮度选取的,而不是根据面部。 在单次拍摄的 talking-head 上,这通常会落在好的面部帧上,但在以 B-roll 为主/多镜头素材上,可能会选取一个无面部帧——然后
face_skin_check.png就无用了(肤色是质量基准)。使用--frame-time SECONDS强制指定一个已知的面部时刻。 - 白平衡/曝光统计信息来自大约在时长的 10% 处的一个约 10 秒的
signalstats窗口——对于恒定光照的素材效果不错;对于光照变化的素材,使用--win-start SECONDS/--win-dur SECONDS来移动/延长窗口。
- 代表性帧是根据中位数亮度选取的,而不是根据面部。 在单次拍摄的 talking-head 上,这通常会落在好的面部帧上,但在以 B-roll 为主/多镜头素材上,可能会选取一个无面部帧——然后
-
渲染风格以供选择。
python scripts/render_looks.py work/assess/frame.png looks.json --out work/looks生成
work/looks/looks_compare.png(原始画面 + 每种风格,带标签,全分辨率)和face_skin_check.png(放大的皮肤条)。查看这两个文件。 皮肤是基准——调整或丢弃任何使皮肤变灰/变绿的风格,然后重新渲染。主体偏离中心?添加--face-crop W:H:X:Y。 将这些 PNG 视为工作中间文件;将批准的审核副本发布为review/02-color-grade/choose-color-look.jpg和review/02-color-grade/skin-tone-check.jpg。 -
(可选) 动态预览 排名前 2-3 的风格,以便看到其动态效果(保留音频):
python scripts/make_clips.py SOURCE.mp4 looks.json --out work/clips \ --pick clean_neutral,warm_filmic,teal_orange --with-original --ss 148 --t 8 -
选择一个风格。 仅当用户要求选择时才呈现联系卡片并等待。否则在 agent 模式下选择它并继续执行,无需决策暂停。在交付前写入
selected_look、selection_mode和selection_rationale。 -
选定后——烘焙 LUT 并应用到整个片段。
python scripts/bake_lut.py looks.json --name CHOSEN \ --out final/selected-color-look.cube --verify-frame work/assess/frame.png python scripts/apply_grade.py SOURCE.mp4 --looks looks.json --name CHOSEN --out out/graded.mp4 # 或使用交付的 LUT 应用:--lut final/selected-color-look.cubeapply_grade.py验证时长/音频是否保持不变,打印白平衡偏移(U/V 趋向 128),并导出一个检查帧以供目测。- 该应用会将视频重新编码一次(有损);音频使用
-c:a copy。 质量/时间由--crf(默认 18)和--preset(默认slow)设置。对于小型/短片片段,slow没问题;但对于 1080p/4K 的长时长素材,这是一个巨大的、无声的时间成本——对于交付,降级到--preset medium(对于快速预览则使用veryfast),仅在最终交付时使用slow。 - 将交付的视频与交付的 LUT 匹配: 滤镜链(
--name)和烘焙后的 33³.cube(--lut)在陡峭的曲线上可能存在高达约 5/255 的差异(bake_lut.py会报告这个max|d|)。对于大多数工作来说是不可见的,但如果你要交付.cube并且希望渲染的视频与其精确匹配,请使用--lut final/selected-color-look.cube而不是--name来应用。
- 该应用会将视频重新编码一次(有损);音频使用
设计说明 / 注意事项
- 对干净剪辑进行分级,然后在分级后的素材之上合成叠加层。 如果你必须对一个已经包含烧录字幕/卡片的视频进行分级,请不要相信
assess.py:它的白平衡/曝光统计信息和帧选取会受到图形的影响(深色顶部/底部遮幅会拉低 Y 值,明亮的字幕文本/青色强调色会扭曲 U/V 值,帧选取也可能落在字幕密集的帧上)——你会在校正叠加层,而不是素材。重复使用在干净素材上调谐的base,并预期分级会对叠加层产生影响,因此只使用微妙的/校正性的风格(clean_neutral);teal_orange/vintage_faded会明显地改变文本/强调色的颜色。 - 一个风格/LUT 覆盖所有相关片段。 一旦选定了风格,在所有相关片段(例如,干净剪辑 + 带叠加层的版本)上重复使用相同的
looks.json/.cube——不要为每个片段重新运行两阶段的选择过程。 - 先校正,后风格化。 基础是对 Deak's S-Log3→Rec.709 步骤的泛化。对于普通素材,基础是白平衡+曝光+对比度;对于 log 素材,将转换放在那里。
- 皮肤优先。
teal_orange/cool_desat是在深色主体上常见的皮肤问题来源——保持中间调温暖,对阴影/高光/背景进行色调处理,使用vibrance。不要完全中和 U/V 值。 - 标签是通过 ffmpeg 叠加的 PIL PNG——永不使用
drawtext。 在 Windows 上,drawtext的fontfile路径(驱动器冒号)不可靠。字体在gradelib.py顶部设置。 - 驱动器冒号路径会破坏需要路径的 ffmpeg filtergraph 选项(如
lut3d、metadata=print:file=)。脚本以cwd(当前工作目录)等于文件所在文件夹的方式运行 ffmpeg,并通过基本名称引用它。任何新的需要路径的滤镜都应保持此模式。 - 输出文件会放置在你传入的
--out目录中(持久化),而不是系统临时目录。 - 面部/皮肤条使用固定的居中的中上部裁剪,而不是面部检测。 如果主体没有大致居中,请向
render_looks.py传递--face-crop W:H:X:Y,否则皮肤条将显示错误的区域。 apply_grade.py的验证检查帧位于时长的 20% 处——与选择风格时所使用的帧不同的一帧。如果那个 20% 位置的点是无面部的,请在另一帧上目测皮肤(或者向assess.py传递--frame-time,以便选定的帧是已知的面部时刻)。- LUT 烘焙是精确的,因为这里的每个滤镜都是每像素点操作:
bake_lut.py将一个 33³ 的单位网格通过实际的滤镜链推送,并与之进行自检(mean|d|应远低于 1/255;几个 level 的max|d|仅仅是插值结果)。 - 交付前始终进行选择。 人类选择和委派的代理选择都是有效的,但计划必须记录哪个发生了以及原因。