有次客户要求把一批素材调成"电影感"。我当时的理解就是调调饱和度、对比度,于是用了
eq滤镜:
bash ffmpeg -i in.mp4 -vf "eq=contrast=1.1:saturation=1.2" out.mp4调完发过去,回复是"不对,我要的是那种感觉"。我也说不清哪里不对——画面确实变艳了,但就是不像样片。
后来对方发来一个
.cube文件,说"套这个 LUT"。我用lut3d一加载,立刻就是那个效果。那一刻我才明白:调色不是"把饱和度拉高",而是一套完整的色彩映射。但直接套 LUT 也踩了坑:素材是 Log 拍的,直接套普通 LUT 出来灰蒙蒙;在 8bit 上调色出现了色带。这篇把这些都写下来。
TL;DR:LUT 是一张"输入颜色 → 输出颜色"的查找表,
.cube是通用格式。ffmpeg 用-vf lut3d=file=xxx.cube。四个要点:Log 素材必须先转换到显示色彩空间(Log 画面看起来灰,直接套风格 LUT 是错的);在 10bit 上调色再输出(8bit 上做色彩映射会产生色带);顺序是"曝光 → 白平衡 → 对比/饱和 → 风格 LUT → 输出转换";LUT 太重就用blend混合原图来减强度。验证用waveform(波形图)和vectorscope(矢量图),别只靠眼睛。
目录
- 一、调色的三个层次
- 二、LUT 到底是什么
- 三、ffmpeg 里怎么用 LUT
- 四、Log 素材:最常见也是最大的坑
- 五、位深:为什么 8bit 上调色会断层
- 六、正确的调色顺序
- 七、LUT 太重怎么办:强度控制
- 八、性能
- 九、验证:用示波器看,别只靠眼睛
- 十、坑清单
一、调色的三个层次
| 层次 | 工具 | 做什么 |
|---|---|---|
| 基础校正 | eq(亮度/对比度/饱和度/gamma)、colorlevels(色阶)、colorbalance(白平衡) |
把画面"修正常"(曝光准、白平衡准) |
| 曲线调整 | curves |
精细控制各通道的映射曲线 |
| 风格化 | LUT(lut3d) |
一次性套用一套完整的色彩风格 |
大多数人以为的"调色"只是第一层——调饱和度和对比度。这能对画面产生影响,但做不到"电影感",因为那是一整套非线性、跨通道的色彩映射(比如把暗部往青蓝偏、亮部往暖橙偏,同时压缩高光)。
这种复杂的映射用 eq 参数是表达不出来的,只能靠 LUT。
二、LUT 到底是什么
LUT = Look-Up Table(查找表):给定输入颜色,查表得到输出颜色。
| 类型 | 结构 | 能表达什么 |
|---|---|---|
| 1D LUT | 每个通道一张一维表(R、G、B 各自独立映射) | 只能做 gamma、通道独立的调整 |
| 3D LUT | 三维立方体(比如 33³ = 35937 个采样点) | 任意的色彩映射(包括跨通道、饱和度联动) |
| HALD CLUT | 把 3D LUT 编码成一张图片 | 同上,用图片方式传递 |
我们用的都是 3D LUT,因为它能表达完整的色彩变换。
.cube 文件格式(文本,可以直接打开看):
# Generated by ...
LUT_3D_SIZE 33
DOMAIN_MIN 0.0 0.0 0.0
DOMAIN_MAX 1.0 1.0 1.0
0.000000 0.000000 0.000000 ← 输入 (0,0,0) → 输出 (0,0,0)
0.030303 0.000000 0.000000
...
LUT_3D_SIZE 33 表示每边 33 个采样点(常见的还有 17、25、65)。采样点越多越精确,但文件越大、计算越慢。33³ 是最常见的平衡点。
重要认知:LUT 是"死的"映射。它不知道你的素材是什么样——如果素材本身曝光不准、白平衡偏了,套 LUT 只会把错误一起放大。所以先做基础校正,再套 LUT。
三、ffmpeg 里怎么用 LUT
基本用法
ffmpeg -i input.mp4 -vf "lut3d=file=filmic.cube" -c:v libx264 -crf 18 output.mp4
用第二个输入的方式(更灵活)
ffmpeg -i input.mp4 -i filmic.cube \
-filter_complex "[0:v][1:v]lut3d[out]" \
-map "[out]" -map 0:a -c:v libx264 -crf 18 -c:a copy output.mp4
插值方式
lut3d 有个 interp 参数,决定"落在采样点之间的颜色怎么算":
-vf "lut3d=file=filmic.cube:interp=tetrahedral"
| 取值 | 说明 |
|---|---|
nearest |
最近邻,最快,可能有色阶 |
trilinear |
三线性插值,默认,质量和速度平衡 |
tetrahedral |
四面体插值,质量最好,慢一点 |
建议用 tetrahedral——尤其是对渐变(天空)多的素材,三线性插值可能产生轻微的色带。
其他 LUT 相关滤镜
| 滤镜 | 用途 |
|---|---|
lut1d |
应用 1D LUT |
lut3d |
应用 3D LUT |
haldclut |
用 HALD 图片做 LUT(有些 LUT 以图片形式分发) |
lut / lutrgb / lutyuv |
用表达式自定义映射(适合简单的数学变换) |
四、Log 素材:最常见也是最大的坑
这是我踩的第一个坑。
Log 曲线(Sony S-Log、Canon C-Log、Panasonic V-Log、ARRILogC)是相机为了保留更多动态范围设计的:把传感器记录的亮度用一条对数曲线"压扁"存储。
所以 Log 素材看起来是灰的、低对比度、低饱和度——这是正常的,信息都在,只是需要"展开"。
错误的做法:直接给 Log 素材套一个"风格 LUT"(比如某个电影模拟 LUT)。
原因:那种风格 LUT 的设计前提是"输入是 Rec.709 的正常画面"。你喂给它一个灰扑扑的 Log 画面,出来的结果就是对比度不够、颜色发闷。
正确的流程:
Log 素材
↓ ① 转换到 Rec.709(这一步由"技术 LUT"或色彩空间转换完成)
Rec.709 正常画面
↓ ② 基础校正(曝光/白平衡)
校正后画面
↓ ③ 套风格 LUT
成片
第①步有两种做法:
做法 A:用厂商提供的"Log → Rec.709"技术 LUT(推荐,最准)
ffmpeg -i log_footage.mp4 \
-vf "lut3d=file=SLog3_to_Rec709.cube:interp=tetrahedral" \
-c:v libx264 -crf 18 rec709.mp4
做法 B:用 zscale 做色彩空间转换(没有 LUT 文件时)
ffmpeg -i log_footage.mp4 \
-vf "zscale=t=linear:npl=100,zscale=p=709:t=709:m=709:r=tv" \
-c:v libx264 -crf 18 rec709.mp4
(HDR 的 PQ/HLG 素材转 SDR 需要 tone mapping,项目里 HDR 那篇讲过,这里不重复。)
两步合并(技术转换 + 风格 LUT):
ffmpeg -i log_footage.mp4 \
-vf "lut3d=file=SLog3_to_Rec709.cube,lut3d=file=filmic.cube" \
-pix_fmt yuv420p10le \
-c:v libx264 -crf 18 output.mp4
滤镜链按顺序执行——先技术转换,后风格 LUT,这个顺序不能反。
五、位深:为什么 8bit 上调色会断层
第二个坑。
8bit 意味着每个通道只有 256 个可能的值。LUT 的映射是"连续"的,但输出必须四舍五入到 256 个档位之一——在渐变区域(天空、墙面、皮肤),相邻档位之间就会出现可见的跳变,也就是色带(banding)。
Log 素材尤其严重:它原本的动态范围被压缩在 8bit 里,展开之后每个档位代表的亮度跨度很大,色带非常明显。
解决:在 10bit(或更高)上调色,最后再输出目标位深。
ffmpeg -i input.mp4 \
-vf "lut3d=file=filmic.cube,format=yuv420p10le" \
-c:v libx264 -pix_fmt yuv420p10le -crf 20 output_10bit.mp4
如果最终要 8bit(兼容性考虑),输出时加抖动(dither)把量化误差打散:
ffmpeg -i input.mp4 \
-vf "lut3d=file=filmic.cube,format=yuv420p10le" \
-c:v libx264 -pix_fmt yuv420p \
-vf "zscale=d=error_diffusion" \ # 注意:不要写两个 -vf
-crf 20 output_8bit.mp4
(正确写法是把所有滤镜放一个 -vf 里):
ffmpeg -i input.mp4 \
-vf "lut3d=file=filmic.cube,format=yuv420p10le,zscale=d=error_diffusion,format=yuv420p" \
-c:v libx264 -crf 20 output_8bit.mp4
抖动的效果:色带变成一层细微的噪点,人眼对噪点的容忍度远高于对色带的——这是"用不明显的噪声换取没有明显色带"的经典手法。
判断有没有色带:看渐变区域(天空、虚化背景),或者用 histogram 滤镜看直方图是否出现"梳齿"。
六、正确的调色顺序
我现在的标准流程(ffmpeg 滤镜链从前到后):
1. 曝光/色阶 colorlevels / eq=brightness
2. 白平衡 colorbalance / colorchannelmixer
3. 对比度/饱和度 eq=contrast:saturation
4. 曲线(可选) curves
5. 风格 LUT lut3d
6. 位深/范围转换 format + zscale
7. 锐化(可选) unsharp
为什么是这个顺序:
- 前面的步骤是"修正",要在"风格化"之前完成(风格 LUT 会放大一切);
- 位深转换放最后(中间过程保持高精度);
- 锐化放最后(锐化色彩映射后的画面,而不是之前)。
完整示例:
ffmpeg -i log.mp4 \
-vf "lut3d=file=SLog3_to_Rec709.cube,\
colorlevels=rimin=0.02:gimin=0.02:bimin=0.02:rimax=0.98:gimax=0.98:bimax=0.98,\
colorbalance=rs=0.03:gs=0.0:bs=-0.03:rm=0.02:gm=0.0:bm=-0.02,\
eq=contrast=1.05:saturation=1.05,\
lut3d=file=filmic.cube:interp=tetrahedral,\
format=yuv420p10le" \
-c:v libx264 -pix_fmt yuv420p10le -crf 20 -preset slow \
-c:a copy output.mp4
注意多行 -vf 的写法:用反斜杠换行时,行首不要有空格(否则会被当成滤镜名的一部分导致解析失败)。这是很常见的错误。
七、LUT 太重怎么办:强度控制
第三个坑:很多风格 LUT 是为特定素材设计的,套到你的素材上可能"太重"(比如暗部压得太死、肤色偏得太厉害)。
ffmpeg 的 lut3d 没有内建的强度参数,但可以用 blend 滤镜把调色结果和原图混合:
ffmpeg -i input.mp4 \
-filter_complex "\
[0:v]split[a][b];\
[a]lut3d=file=filmic.cube[lut];\
[lut][b]blend=all_mode=normal:all_opacity=0.6[out]" \
-map "[out]" -map 0:a -c:v libx264 -crf 18 -c:a copy output.mp4
解释:
split把原图复制成两份;- 一份套 LUT;
blend把"调色后"和"原图"按all_opacity=0.6混合——0.6 就是 LUT 强度;all_mode=normal表示上层(第一个输入)以不透明度 0.6 覆盖下层。
强度参考:
| 强度 | 效果 |
|---|---|
| 0.3~0.5 | 轻微风格化(更自然) |
| 0.6~0.8 | 明显的风格(常用) |
| 1.0 | 完整 LUT(可能过重) |
我的习惯:先套 1.0 看效果,然后逐步降到 0.7 左右——大多数情况下 0.6~0.8 比 1.0 好看,因为保留了素材本身的色彩特征。
八、性能
lut3d 是逐像素操作,成本不低。实测(1080p,8 核):
| 配置 | 处理速度 | 说明 |
|---|---|---|
| 8bit + trilinear | 45 fps | 快 |
| 10bit + trilinear | 28 fps | |
| 10bit + tetrahedral | 16 fps | 质量最好,慢 |
| 4K 10bit + tetrahedral | 4 fps | 很慢,要有耐心 |
优化建议:
- LUT 尺寸:33³ 够用,别用 65³(慢 4 倍,肉眼差别极小);
- 插值:不追求极致用 trilinear;
- 先缩放在调色前还是后:如果需要输出 720p,先缩放再调色(像素少,快得多)——画质差异可忽略;
- 调色和编码合并成一条命令(避免中间落盘:调色输出 raw 再编码会非常慢且占磁盘)。
不要先调色输出一个无损中间文件再编码——1080p 10bit 无损中间文件每分钟几个 GB,磁盘和 IO 都是灾难。一条命令做完。
九、验证:用示波器看,别只靠眼睛
ffmpeg 自带示波器滤镜,这是我后来养成的习惯(输出一张图,看数据而不是凭感觉):
波形图(waveform):看亮度分布和曝光
ffmpeg -i input.mp4 -vf "waveform=m=1:i=0.1:filter=lowpass,scale=1024:512" \
-frames:v 1 waveform.png
- 波形压在顶部 → 过曝;
- 压在底部 → 欠曝;
- 理想是分布在中下部(暗部有细节、亮部不溢出)。
矢量图(vectorscope):看色彩和饱和度
ffmpeg -i input.mp4 -vf "vectorscope=m=color:i=0.1,scale=512:512" \
-frames:v 1 vectorscope.png
- 点越靠中心 → 饱和度越低;
- 越靠边缘 → 越饱和(可能溢出,广播标准有上限);
- 皮肤色调应该落在"肤色线"附近(矢量图上通常有标记)。
直方图:
ffmpeg -i input.mp4 -vf "histogram=level_height=256:display_mode=stack" \
-frames:v 1 hist.png
对比调色前后:把原片和成片的波形图并排看,能立刻确认"暗部有没有被压死""亮部有没有溢出"。这比盯着屏幕猜靠谱得多——眼睛会被整体观感欺骗,示波器不会。
一个实用技巧:抽几个代表性帧(暗场景、亮场景、人物特写)各做一次示波器,批量处理时作为验收依据。
十、坑清单
- 用 eq 调饱和度当调色 → 只能做很有限的调整。风格化必须用 LUT。
- 给 Log 素材直接套风格 LUT → 灰蒙蒙。先做 Log → Rec.709 转换。
- 8bit 上调色 → 色带。在 10bit 上调色,输出 8bit 时加抖动。
- 两个
-vf参数 → 后者覆盖前者,前面的滤镜被丢掉。所有滤镜写在一个-vf里。 - 多行
-vf行首有空格 → 解析失败(空格被当成滤镜名)。 - LUT 文件路径含空格或特殊字符 → 要转义(
file='my lut.cube')。 - LUT 插值用 nearest → 渐变区域色阶明显。用 tetrahedral 或 trilinear。
- 先调色后缩放 → 慢(对 4K 像素做调色)。先缩放。
- 先调色输出中间文件再编码 → 磁盘爆掉。一条命令做完。
- LUT 套太重 → 用 blend 混合原图减强度(0.6~0.8)。
- 不看示波器只靠眼睛 → 主观判断容易被"变艳了"误导。看波形图和矢量图。
- 忽略色彩范围(tv/pc) → limited 和 full 混用会导致发灰或者过曝。确认
color_range。 - HDR 素材当普通素材调色 → 需要 tone mapping,不然高光全白。
- 批量套同一个 LUT → 不同镜头的曝光不同,套同样 LUT 效果不一致。理想是分镜头调,至少要抽查。
- 用网上的 LUT 不看授权 → 很多 LUT 是商业授权的。确认能不能商用。
- 调色后直接
-c:a copy但视频流参数变了 → 这没问题(音频独立),但要确认封装兼容性。
最后说说这次之后我对"调色"的理解变化。
以前我觉得调色是"审美活"——是摄影师/调色师的领域,做技术的插不上手。做完这批之后我明白:调色的审美部分确实在人工,但它的工程部分(色彩空间、位深、LUT 格式、验证方法)完全是技术活,而且后者决定了前者的下限。
具体来说:
- 色彩空间搞错 → 再好的 LUT 也出不来正确结果;
- 位深不够 → 一定会出色带,跟 LUT 好坏无关;
- 没有验证手段 → 你不知道自己的调整是不是"修好了"还是"修过头了"。
所以现在遇到调色需求,我的第一句话是"素材是什么色彩空间、什么位深、有没有 Log",而不是"你想要什么风格"。前者决定了能不能做对,后者只是参数选择。
还有一点:示波器是性价比最高的学习工具。把同一段素材套不同 LUT、不同强度,各出一张波形图并排看,很快就能建立"参数变化 → 画面变化"的直觉。比看十篇教程有用。