Skip to content

OpenCV 中使用 CCM

来自本人笔记: https://github.com/masterAllen/LearnOpenCV/blob/main/docs/7.3.md

先只简单写一下 CCM 概念:CCM 是从相机 Raw 图到最终出图的一个流程,本质上就是对各个像素的 RGB 做一个矩阵变换。因为 CCM 前的各个像素是属于相机线性颜色空间,要转为如 sRGB 这种颜色空间,需要做一些变换。让其尽可能的解决目标颜色空间,如尽可能让拍色卡拍出的红色、绿色接近目标颜色空间规定的色卡红色、绿色。

在 OpenCV 中,mcc 和 ccm 这两个模块负责这个功能,而且处理流程还是很线性的,非常易懂。非常推荐直接看我当时做的实验代码 test_ccm.ipynb

1. 检测图片中的色卡

用 mcc.CCheckerDetector 类即可,创建一个对象后,对图片执行 process,然后使用 getListColorCheckergetBestColorChecker 就能返回结果,前者是返回一堆列表,后者是返回这个列表里面得分最高(即最可能的)条目。这个条目是 mcc.CChecker 类,后面会用到,现在就可以理解成一个卡片信息类。

值得一提的是,不仅仅支持常见 24 色卡,还支持检测 SG140 和 VINYL18 这两种色卡,在 process 传入对应选项即可。但为了方便讲述,后面统一就写处理 24 色卡时候的情况了。

执行上面操作后,得到了最可能的条目,即 mcc.CChecker 类的一个对象,假设叫做 now_checker

detector = cv2.mcc.CCheckerDetector.create()

# 也可以用神经网络模型来检测,检测的时候参数要设置为 useNet
# detector.setNet()
# isCheck = detector.process(src, chartType=cv2.mcc.MCC24, useNet=True)

# 三种种类,MCC24、SG140、VINYL18
isCheck = detector.process(src, chartType=cv2.mcc.MCC24)

if isCheck:
    # getList 返回数组,getBest 则返回数组里面最有可能的
    # checkers = detector.getListColorChecker()
    now_checker = detector.getBestColorChecker()

2. 可选:画出色卡位置

可以使用 mcc.CCheckerDraw 来画出检测到的色卡, 也是创建一个对象(传入上面检测到的 now_checker 作为参数),然后调用 draw 画图即可(也要传入原始图片,这样才能在图片上画出色卡的框框来)。

# CCheckerDraw 画图,就这两个方法;create 这里的 color 和 thickness 都是默认值,不指定也可以
cdraw_src = np.copy(src)
cdraw = cv2.mcc.CCheckerDraw.create(now_checker, color=(0, 250, 0), thickness=2)
cdraw.draw(cdraw_src)

3. 查看检测出的色卡的一些信息

上面所提到,得到了 mcc.CChecker 类的一个对象,为了方便将其叫做 now_checker。此时就可以去 OpenCV 文档中查看这个类的各个方法了,全是 getset 方法,本质上就是查看或者修改这张检测出的色卡的信息。基本没啥好说的,如 getBox 是返回四个点,即整张色卡的四角;getColorCharts 是返回 96 个点,即 24 色卡中各个色块的四角。

但是要重点强调 getChartsRGB 的结果,返回 \(72 \times 5\) 的 ndarray:72 是 24 色卡中各自三个颜色,颜色顺序是 RGB;5 是分别表示 p_size, average, stddev, max, min,其中 p_size 是色卡大小(所以每个色卡 RGB 中第一个数都是一样的),其他四个好理解。

# getChartsRGB -> (72, 5);24*3,各自表示 p_size, average, stddev, max, min,这里就只拿 average 即可
src_rgb = now_checker.getChartsRGB()

4. 准备好做 CCM 的一些参数

下面准备求矩阵了。ccm.ColorCorrectionModel 负责这个做 CCM 这个功能。创建对象的时候需要传入校正前 24 色卡的颜色信息,如前所述,now_checker.getChartsRGB 可得到色卡的各个颜色信息。返回是 \(72 \times 5\) 的 ndarray,我们只要取 average 即可(五列中第二列),即各个色块的平均 RGB 值。

但是注意,有三个要求:大小需要变为 (24, 1, 3);范围需要变为 [0, 1];排序需要是 RGB。所以需要多做一下处理!!

# 重要:CCM 模型需要传入当前拍摄色卡的颜色,要求是:(24,1,3)、[0,1]、RGB排列(已满足)
src_rgb = src_rgb[:, 1].reshape((24, 1, 3)) / 255
ccm_model = cv2.ccm.ColorCorrectionModel(src_rgb, constcolor=cv2.ccm.COLORCHECKER_MACBETH)

然后就可以设置一些参数了,具体查阅 OpenCV 文档。强烈推荐用 Imatest 这个软件实现做一遍 CCM 流程,里面的一些参数设置都能和 OpenCV 对应上。

# 设置参数,有许多参数,这里就简单写一个意思意思(其实这个也不用写,默认就是 3x3)
ccm_model.setCCM_TYPE(cv2.ccm.CCM_3x3)

参数的话,随便写一点(这个还是需要对 CCM 有一点熟悉,所以这也是为什么我推荐用 Imatest 先去做几次 CCM):

1. setWeightList: 不同色卡的权重
2. setWeightCoeff: 亮度 L* 的指数大小,默认是 0,相当于对权重影响都是 1,即不考虑亮度。
3. setColorSpace: 目标颜色空间,默认 ccm.COLOR_SPACE_SRGB
4. setCCMType: 矩阵大小是 3x3 还是 4x3,默认 ccm.CCM_3x3
5. setLinear: 对输入的 RGB 做什么处理,因为 CCM 是在线性空间做的,OpenCV 做了一些详细介绍

  • 假如是原始 RAW 图下检测到的 RGB 值,那么它本身是线性的,设为 ccm.LINEARIZATION_IDENTITY
  • 假如是传入 JPG 文件检测到的 RGB 值,那么需要反变换到线性空间,方法有两种 Gamma 和 PolyFit:
    • Gamma: ccm.LINEARIZATION_GAMMA;此时可根据 setLinearGamma 设 gamma 值,默认 2.2
    • PolyFit: ccm_LINERIZATION_xxPOLYFIT(xx共四种);此时可根据 setLinearDegree 设置 degree 值,默认 3

PolyFit 的四种: COLORPOLYFIT GRAYPOLYFIT COLORLOGPOLYFIT GRAYLOGPOLYFIT

5. 算出矩阵,校正图片

没啥说的,run 算矩阵,getCCM 得出矩阵,infer 校正图片。只不过 infer 校正图片是输入和输出都要是 0-1 和 RGB 排列,所以要多做一点处理罢了。直接看代码即可:

ccm_model.run()
ccm_matrix = ccm_model.getCCM()

# 可以获取校正前后的 RGB;校正的 Loss 等
# print(ccm_model.getLoss(), ccm_model.get_dst_rgbl())

# 校正图片。很重要:输入和输出图片都是 [0, 1] 和 RGB 排列!
out = ccm_model.infer(cv2.cvtColor(src, cv2.COLOR_BGR2RGB)/255)
out = cv2.cvtColor( (out * 255).clip(0, 255).astype(np.uint8), cv2.COLOR_RGB2BGR )

Comments