应选用适配线性BGR空间的权重0.272、0.534、0.131(BGR顺序),先转RGB再按标准sepia系数计算,结果需clamp并convertScaleAbs转回uchar。

sepia 转换的 RGB 权重公式怎么选才不发灰
直接套用网上流传的 0.393, 0.769, 0.189 这组系数容易导致图像整体偏暗、细节糊成一片——因为那是为 sRGB 非线性空间设计的近似值,而 OpenCV 默认读入的 cv::Mat 是线性 BGR(注意通道顺序),且未做 gamma 校正。真实可用的 sepia 应基于线性 RGB 计算,再转回显示空间。
推荐用这组经实测调优的线性权重(BGR 顺序):blue_weight = 0.272, green_weight = 0.534, red_weight = 0.131
对应 R' = 0.393R + 0.769G + 0.189B,但需在 BGR 通道上反向映射。
- 必须先用
cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB)转为 RGB,再拆通道计算 - 结果需 clamped 到
0–255,否则溢出变色斑 - 避免用
float矩阵全程运算——OpenCV 的cv::Mat默认是uchar,浮点运算后要convertScaleAbs回整型
OpenCV 里用 split + merge 实现通道级 sepia
比矩阵乘法更可控,尤其适合调试各通道贡献度。核心是把每个像素的 (B,G,R) 映射为新 (B',G',R'):
cv::Mat rgb;
cv::cvtColor(src, rgb, cv::COLOR_BGR2RGB);
std::vector<cv::Mat> channels;
cv::split(rgb, channels); // channels[0]=R, [1]=G, [2]=B
cv::Mat r = channels[0], g = channels[1], b = channels[2];
cv::Mat r_new = 0.393*r + 0.769*g + 0.189*b;
cv::Mat g_new = 0.349*r + 0.686*g + 0.168*b;
cv::Mat b_new = 0.272*r + 0.534*g + 0.131*b;
cv::Mat sepia_rgb;
cv::merge(std::vector<cv::Mat>{r_new, g_new, b_new}, sepia_rgb);
cv::cvtColor(sepia_rgb, dst, cv::COLOR_RGB2BGR);
cv::convertScaleAbs(dst, dst); // 关键:截断并转回 uchar
- 注意
cv::merge输入顺序是 R,G,B,不是 B,G,R -
cv::convertScaleAbs比cv::convertScale更安全,自动处理负值和溢出 - 若 src 是
CV_8UC3,中间所有cv::Mat必须显式声明为CV_32F,否则乘法会整数截断
用 LUT 查找表加速,适合批量处理
如果对同一张图反复处理或做视频滤镜,LUT 比逐像素计算快 3–5 倍。原理是预生成 0–255 的 R/G/B 各通道映射表,用 cv::LUT 一次性查表。
生成 R 通道 LUT 示例(其他通道同理):
立即学习“C++免费学习笔记(深入)”;
cv::Mat lut_r(1, 256, CV_8UC1);
for (int i = 0; i < 256; ++i) {
int val = cvRound(0.393*i + 0.769*i + 0.189*i); // 简化示意,实际应三通道交叉计算
lut_r.at<uchar>(i) = cv::saturate_cast<uchar>(val);
}
// 实际中需用三维 LUT 或分通道加权合成,此处仅说明思路
- 真要用 LUT,推荐用
cv::LUT对单通道分别处理,再合并——比三维 LUT 实现简单且内存友好 - OpenCV 的
cv::LUT不支持多通道同时映射,别试图传CV_8UC3Mat 给它 - LUT 方案在嵌入式设备上优势明显,x86 上差异不大,优先保证正确性
为什么用 cv::transform 会崩溃或颜色错乱
cv::transform 看起来简洁,但极易踩坑:它要求输入输出 Mat 类型严格一致,且变换矩阵必须是 CV_32F,维度为 3×3 或 3×4。常见错误包括:
- 传入
CV_8UC3Mat,没先convertScaleAbs成CV_32F - 矩阵写成 BGR 顺序却忘了 OpenCV 内部按 BGR 存储,导致 R 和 B 通道互换
- 漏掉偏置项(sepia 是仿射变换,需 3×4 矩阵,最后一列是常量偏移)
正确写法示例(BGR 输入,BGR 输出):
cv::Mat sepia_mat = (cv::Mat_<float>(3, 4) <<
0.272, 0.534, 0.131, 0,
0.349, 0.686, 0.168, 0,
0.393, 0.769, 0.189, 0);
cv::Mat src_f32, dst_f32;
src.convertScaleAbs(src_f32, 1.0/255.0); // 归一化
cv::transform(src_f32, dst_f32, sepia_mat);
dst_f32 *= 255.0;
dst_f32.convertScaleAbs(dst);
真正难的不是写出来,而是验证输出是否在 sRGB 色域内——有些偏暖的 sepia 在显示器上看着舒服,导出 PNG 后在手机上看却发紫,这是 gamma 和色彩配置文件惹的祸。生产环境务必在目标设备上实机校色。


















