AI 图像处理链路往往从 JPEG 或 PNG 解码开始,而相机、设计软件导出的图片可能内嵌 Adobe RGB 或 Display P3 等 ICC 配置。训练框架中的 OpenCV、torchvision 等读取图像时默认只取像素矩阵,不读取或应用 ICC 配置。于是,同样一张照片在 Photoshop 里颜色饱满,进入模型后却变得灰暗,模型输出的结果又会把这种错误继续放大。要彻底解决,必须先承认色彩空间信息不是可有可无的元数据,而应作为图像内容的一部分参与所有处理。

一、色彩空间不一致的根源:色域与Gamma叠加
sRGB 与 Adobe RGB 都基于 RGB 三通道,但 Adobe RGB 在绿色和蓝绿色方向色域更大,能覆盖更多印刷色。sRGB 通常用于网络和普通显示,Adobe RGB 常用于摄影和打印。在色度图上,Adobe RGB 三角形面积约比 sRGB 大 35%,尤其在绿色区域。这种差异意味着相同的 RGB 数值在两者中代表不同的物理颜色。当 Adobe RGB 图像按 sRGB 解释时,原始色彩被压缩到较小色域,高饱和区域被裁剪,整体看起来发灰。
除色域外,两者通常使用不同的 Gamma 或传递函数。sRGB 使用约 2.2 的分段 Gamma,Adobe RGB 1998 使用 D65 白点和 2.2 Gamma。问题在于 ICC 转换需要同时处理矩阵和 tone reproduction curve。如果只做线性矩阵映射而忽略 Gamma 编码,转换结果存在亮度偏移。许多简化实现把像素从 Adobe RGB 到 sRGB 直接乘一个 3x3 矩阵,把非线性编码值当线性光处理,这是最常见错误之一。
常见的读取方式,例如 OpenCV 的 imread 或 PIL 直接访问像素,不会主动做色彩管理。PIL 打开图片时保留 ICC 信息在 img.info 中,但转成 numpy 数组后信息就丢失。如果下游模型接收的是 numpy 数组,色彩空间就已经固定为某个未声明假设,通常被当成 sRGB。训练时如果混入 Adobe RGB、Display P3 或未标记图片,模型学到的颜色映射会出现系统性偏差。
二、使用Pillow与ICC Profile完成可靠切换
Pillow 的 ImageCms 模块封装了 Little CMS,可以在不依赖外部工具的情况下完成 ICC 转换。最稳妥的做法是先读取图片内嵌的 ICC,再用 profileToProfile 转换到目标 sRGB。以下函数从路径加载任意图片,检测 ICC,如果是非 sRGB 则转换,否则保持不变。
from PIL import Image, ImageCms
import io
def open_as_srgb(path):
img = Image.open(path)
icc = img.info.get('icc_profile')
if icc is None:
return img.convert('RGB')
src_profile = ImageCms.ImageCmsProfile(io.BytesIO(icc))
dst_profile = ImageCms.createProfile('sRGB')
return ImageCms.profileToProfile(img, src_profile, dst_profile, outputMode='RGB')
这里没有把 Adobe RGB 简单映射成 sRGB 矩阵,而是通过 ICC 引擎处理色域映射和 Gamma。profileToProfile 的 renderingIntent 默认是 perceptual,也可以在调用时指定 ImageCms.Intent.RELATIVE_COLORIMETRIC 等。如果处理的是产品图或医学图像,建议使用 relative colorimetric 保持白点;如果是自然影像,perceptual 会保留更多视觉层次。注意图片若已内嵌 sRGB,重复转换会引入轻微舍入误差,因此最好先比较 ICC 特征。
如果后续使用 OpenCV 或 PyTorch,一定要先完成 ICC 转换再转数组。OpenCV 的 imread 会直接丢弃 ICC,所以更推荐 PIL 读取后转换,再转成 numpy。代码示例:
from PIL import Image, ImageCms
import io
import numpy as np
import cv2
def load_bgr_srgb(path):
img = open_as_srgb(path)
rgb = np.array(img)
return cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
未标记 ICC 但明确来自 Adobe RGB 的图片需要额外处理。比如团队约定导出时剥离 ICC,那么可以设置 source profile 为 Adobe RGB 并执行转换。但不要对所有无 ICC 图片都这样做,因为网络图片大多数是 sRGB,错误指定 Adobe RGB 会导致颜色过饱和。更稳健的策略是在数据入库时记录色彩来源,避免靠猜。
三、训练与推理链路中的色彩一致性策略
离线转换优先。训练集如果存在多种色彩空间,不应在每个 epoch 的 Dataset 里重复解析 ICC,这样既慢又容易因为多进程共享 profile 产生问题。正确做法是预处理阶段把全部图片统一转换为 sRGB,并重新保存为带 sRGB ICC 的 PNG 或高质量 JPEG。之后的训练读取链路只处理 sRGB 数据,不再进行色彩管理。这样能保证数据增强、归一化、模型输入都在同一个色彩语义下。
转换脚本可以批量遍历目录,并用多进程池加速。关键点是把 ICC 检测和转换函数保持纯函数,避免共享 ImageCms profile。以下是一个简化的批量处理示例:
import os
from PIL import Image, ImageCms
import io
def convert_file(src, dst):
img = Image.open(src)
icc = img.info.get('icc_profile')
if icc:
src_profile = ImageCms.ImageCmsProfile(io.BytesIO(icc))
dst_profile = ImageCms.createProfile('sRGB')
img = ImageCms.profileToProfile(img, src_profile, dst_profile, outputMode='RGB')
else:
img = img.convert('RGB')
img.save(dst, 'PNG', icc_profile=ImageCms.createProfile('sRGB').tobytes())
def batch_convert(folder):
for name in os.listdir(folder):
if name.lower().endswith(('.jpg', '.jpeg', '.png')):
src = os.path.join(folder, name)
dst = os.path.join(folder, 'rgb', name + '.png')
convert_file(src, dst)
推理端同样不能放松。线上服务收到的用户上传图片可能内嵌 Display P3 或 Adobe RGB。如果直接把像素交给模型,输出颜色会偏离用户预期。可以在服务入口统一做一次 open_as_srgb,并把检测结果记录到日志。对于需要返回 Adobe RGB 供打印或专业显示器使用的场景,推理结果应先按 sRGB 解释,再通过 ICC 转换到目标空间,而不是仅改文件扩展名。色彩空间转换和图像缩放、归一化之间也有顺序要求。正确的顺序是先转换色彩空间,再进行缩放,因为缩放会混合不同色度像素,若在错误空间缩放会引入额外色偏。
四、验证色彩转换是否生效:量化对比与监控
颜色变化不能只靠肉眼判断,因为不同显示器、亮度环境会干扰结论。可以通过色差 Delta E 来量化两张图在标准色彩空间中的差异。使用 skimage 或 python-colormath 可以计算 CIEDE2000 色差。通常若同一张 Adobe RGB 图在错误按 sRGB 显示时,色差可达 5 以上,而正确转换后与 Photoshop 参考结果应低于 2。代码示例:
from skimage.color import deltaE_ciede2000
from skimage import io as skio
import numpy as np
def color_diff(img1, img2):
lab1 = skio.rgb2lab(img1.astype(np.float32) / 255.0)
lab2 = skio.rgb2lab(img2.astype(np.float32) / 255.0)
return np.mean(deltaE_ciede2000(lab1, lab2))
除了色差,还可以观察 HSV 空间中的饱和度均值变化。Adobe RGB 转换到 sRGB 后,超色域颜色会被压缩,整体饱和度数值下降,但视觉上不应该出现色相偏移。用 OpenCV 将 RGB 转 HSV,再统计 S 通道均值,对比转换前后,应该看到饱和度变化主要来自色域映射,而不是异常。若色相 H 通道发生明显偏移,通常说明转换矩阵或 Gamma 处理错误。可以加入单元测试,用一张已知纯色 Adobe RGB 图片,检查转换后像素值是否在预期范围内。
最后建议在数据集管理层面保留原始文件。不要用转换后的 sRGB 覆盖原图,因为未来模型需求可能变成 Display P3 或线性空间。原始 ICC 与像素应同时归档。对于关键业务,可以在数据版本中增加色彩空间字段,例如 color_original 和 color_processed,便于追溯。
本质上,sRGB 和 Adobe RGB 切换不是简单的算法问题,而是流程问题。只要在读取、训练、推理、验证四个环节都显式传递色彩空间信息,AI 图像处理的颜色漂移就能得到控制。即使团队只用 sRGB,也需要明确声明并检查 ICC,否则默认假设很可能被某个第三方库打破。

