为什么底片反转后人脸会发青?C-41 橙色色罩消除原理与着色器实现
所有让人脸发青的反转工具都犯了同一个错:把密度空间的问题当成线性问题来解。这里给出推导和着色器代码。
把翻拍图丢进任意通用修图软件,点一下「反相」,人脸就会发青。常见诊断是「白平衡不对」——这是错的,所以你拉色温滑块永远调不准。这层色偏是厂商故意做进胶片里的,而清除它需要的是除法,不是减法。
一、片基为什么是橙色的
彩色负片的青、品红染料在光谱上并不纯净,会吸收本不该吸收的波段(副吸收)。放任不管,从负片还原出的所有颜色都会被污染。C-41 通用的解法是彩色偶合剂:未参与反应的偶合剂保持黄橙色,形成一层与被补偿的副吸收形状完全一致的正性色罩。那层橙色是内建的校正滤镜,不是缺陷。
由此得到的结论
冲洗完成的 C-41 负片是被刻意做成非中性的。任何假设片基中性的反转算法,从第一步就在解另一道题。
二、线性反相「1 − RGB」的陷阱
胶片对光的响应是指数型的。光学密度定义为 D = log₁₀(1 / T)(T 为透过率),而乳剂对曝光呈线性的是密度而非透过率。用 255 - x 反转 8 位 sRGB 值等于反转透过率:橙色分量被反转成青色留在画面里,处于透过率压缩端的暗部则被压成一块死板的藏蓝。
三、在密度空间里减掉色罩
在密度空间里,去掉片基就是一次普通减法:D_image = D_pixel − D_base。两边取指数后,减法变成除法 T_image = T_pixel / T_base——这正是着色器要「除以」取样片基色而不是减去它的原因。之后一次幂运算再把乳剂的非线性响应还回来。
vec3 inverted = pow(max(vec3(0.0), 1.0 - (pixel / mask)), vec3(u_filmCurve));
inverted = clamp((inverted - u_black) / (u_white - u_black), 0.0, 1.0);
inverted = pow(inverted, vec3(1.0 / u_gamma));pixel / mask——密度空间中对橙色片基的减法。u_filmCurve(约 1.45–1.60)——还原 S 形乳剂响应,而非平直斜坡。u_black/u_white——色阶拉伸,让直方图铺满又不截断。u_gamma(1.8)——补偿显示传递函数;缺了它中间调会偏暗发闷。
四、把 `mask` 取准:40×40 加离群点剔除
整套校正只依赖每个通道一个数值,因此一颗灰尘就能毁掉一整帧。NegaScan 会在未曝光片基(齿孔间隙或帧间空隙)取 40×40 区域求均值,并在求均值前剔除超过 1.5 倍标准差的样本,无需手工蒙版即可排除颗粒、灰尘与划痕。
五、自己跑一遍
与工作台完全相同的着色器,运行在你自己的 GPU 上。拖动分隔条,或把自己的负片拖进来看看你那卷的表现。
如果一个反转工具说不出它把哪些像素当作片基,那它就是在猜——而这份猜测的后果,要你每一帧亲手去修。
继续阅读