如何基于纳米-透射率CSV数据生成LUT所需RGB曲线?
实现柯达琥珀滤镜色彩模拟:从光谱CSV到3D LUT的完整流程
1. 读取光谱CSV数据
首先读取存储波长与透射率的CSV文件,清洗并转换数据格式,保留400-700nm的可见光范围:
import pandas as pd import numpy as np # 读取无表头的CSV,手动指定列名 spectrum_df = pd.read_csv("amber_filter_spectrum.csv", header=None, names=["wavelength_nm", "transmittance_pct"]) # 将透射率百分比转为0-1的衰减系数 spectrum_df["transmittance"] = spectrum_df["transmittance_pct"] / 100.0 # 过滤可见光波段 spectrum_df = spectrum_df[(spectrum_df["wavelength_nm"] >= 400) & (spectrum_df["wavelength_nm"] <= 700)]
2. 光谱转CIE 1964 XYZ三色值
利用CIE 1964 10°标准观察者的颜色匹配函数(CMF),将透射光谱卷积计算为XYZ颜色空间值:
# 加载预计算的CIE 1964 10° CMF数据(400-700nm,1nm间隔) def load_cie1964_cmf(): wavelengths = np.arange(400, 701) # 实际应用中需替换为完整的CMF数组 x = np.load("cie1964_x.npy") y = np.load("cie1964_y.npy") z = np.load("cie1964_z.npy") return wavelengths, x, y, z cmf_wl, cmf_x, cmf_y, cmf_z = load_cie1964_cmf() # 插值输入光谱,匹配CMF的波长间隔 transmittance_interp = np.interp(cmf_wl, spectrum_df["wavelength_nm"], spectrum_df["transmittance"]) # 求和近似积分计算XYZ值 X = np.sum(transmittance_interp * cmf_x) Y = np.sum(transmittance_interp * cmf_y) Z = np.sum(transmittance_interp * cmf_z) # 以Y通道为基准归一化(模拟白光透过滤镜后的相对色彩) XYZ = np.array([X, Y, Z]) / Y if Y != 0 else np.array([0,0,0])
3. CIE XYZ转线性sRGB
通过标准转换矩阵将XYZ值转换为线性sRGB,裁剪超出色域的数值:
# CIE XYZ到线性sRGB的转换矩阵(D65光源) xyz_to_srgb = np.array([ [3.2406, -1.5372, -0.4986], [-0.9689, 1.8758, 0.0415], [0.0557, -0.2040, 1.0570] ]) # 计算线性sRGB值 linear_srgb = xyz_to_srgb.dot(XYZ) # 裁剪到0-1范围 linear_srgb = np.clip(linear_srgb, 0.0, 1.0)
4. 生成分离式RGB透射曲线
基于滤镜的线性sRGB衰减系数,生成输入-输出的RGB通道曲线,直观展示各颜色的衰减效果:
import matplotlib.pyplot as plt # 生成0-1的线性RGB采样点 input_vals = np.linspace(0, 1, 256) # 计算输出值(近似为输入值乘以对应通道的透射系数) output_r = input_vals * linear_srgb[0] output_g = input_vals * linear_srgb[1] output_b = input_vals * linear_srgb[2] # 绘制曲线 plt.figure(figsize=(10,6)) plt.plot(input_vals, output_r, color="#ff3333", label="Red Channel") plt.plot(input_vals, output_g, color="#33cc33", label="Green Channel") plt.plot(input_vals, output_b, color="#3333ff", label="Blue Channel") plt.xlabel("Input Linear RGB") plt.ylabel("Output Linear RGB") plt.title("Kodak Amber Filter RGB Transmission Curves") plt.legend() plt.grid(True) plt.show()
5. 制作线性转对数3D LUT立方体
生成n×n×n的3D LUT,实现线性RGB到对数RGB的色彩映射,模拟滤镜效果:
def generate_linear_to_log_lut(lut_size=33): steps = np.linspace(0, 1, lut_size) lut = np.zeros((lut_size, lut_size, lut_size, 3)) srgb_to_xyz = np.linalg.inv(xyz_to_srgb) log_offset = 1e-6 # 避免log(0)的错误 for i, r in enumerate(steps): for j, g in enumerate(steps): for k, b in enumerate(steps): # 线性sRGB转XYZ xyz = srgb_to_xyz.dot([r, g, b]) # 应用滤镜透射衰减 filtered_xyz = xyz * XYZ # 转回线性sRGB并裁剪 filtered_srgb = xyz_to_srgb.dot(filtered_xyz) filtered_srgb = np.clip(filtered_srgb, 0.0, 1.0) # 转换为log10并归一化到0-1 log_rgb = np.log10(filtered_srgb + log_offset) log_rgb = (log_rgb - np.log10(log_offset)) / (0 - np.log10(log_offset)) log_rgb = np.clip(log_rgb, 0.0, 1.0) lut[i,j,k] = log_rgb # 输出为.cube格式 with open("kodak_amber_linear_log_lut.cube", "w") as f: f.write("TITLE \"Kodak Amber Filter Linear to Log 3D LUT\"\n") f.write(f"LUT_3D_SIZE {lut_size}\n") for i in range(lut_size): for j in range(lut_size): for k in range(lut_size): r, g, b = lut[i,j,k] f.write(f"{r:.6f} {g:.6f} {b:.6f}\n") print("3D LUT已生成:kodak_amber_linear_log_lut.cube") # 生成33×33×33的LUT generate_linear_to_log_lut(lut_size=33)
关键注意事项
- 光谱精度:CSV波长间隔越密(如1nm),色彩转换精度越高,最低建议10nm间隔。
- CMF选择:必须使用CIE 1964 10°视场的颜色匹配函数,不要混淆CIE 1931 2°标准。
- LUT近似性:上述方法为视觉近似模拟,若需更高精度,需引入光谱重建算法解决RGB转光谱的多解问题。
- 对数参数:对数转换的底数(如log2)和归一化范围可根据影视标准(如Rec.709、ACES)调整。
内容的提问来源于stack exchange,提问作者NickG
相关产品推荐
相关产品推荐

