色彩轉換的實作,公式是參考 Color Transfer between Images 這篇2001年的論文


前言

心情廢文:

去年年底開始寫論文的關係,變得非常忙,一方面要盡快完成核心程式,一方面又要著手撰寫論文(教授還指定要用英文啊…),blog就漸漸停擺了!

由於最近需要把心思放在論文生產上,又不想繼續冷凍blog,所以就想到可以記錄下論文用到的一些影像處理的技術,也能順便整理自己的思緒,回頭去好好看自己用了哪些東西、實作細節,好好的把論文寫完QQ

概念

色彩轉換跟色彩空間的轉換不太一樣,色彩轉換是指把A影像的色彩映射到B影像上,讓B影像的色彩呈現的與A影像一致

Example: 把日出變日落

原圖
ColorTransfer1

參考圖
ColorTransfer2

結果
ColorTransfer3

PS. 我找到論文上的圖然後直接跑結果XD。事實證明經典論文都是說真的,神奇的數學轉換!

雖然色彩轉換與色彩空間轉換不一樣,卻也有用到色彩空間轉換

一般描述影像的RGB色彩空間並不能很好的讓我們調整色彩變化,調整RGB的值會讓整張影像色偏,且用RGB描述色彩對人類而言並不直覺

人類視覺上會注意的,像是影像的明亮度、飽和度,前人透過人眼能觀察到的幾個影像變化,制定了一些較能描述色彩的色彩空間,所以很多影像處理習慣上把色彩空間從RGB先轉換到這些能描述色彩變化的空間

而色彩轉換的原論文就是先把兩張要處理的影像從RGB轉換到Lab (也有人是RGB轉到HSI)

Lab Color Space

Lab中的L是指亮度(0-100),a代表紅綠軸(0-1),b代表黃藍軸(0-1),因為L分量匹配人類對亮度的感知,只要修改a和b就能做精確的顏色平衡,或是修改L來調整亮度

另一個色彩空間Luv跟Lab很類似,但影像處理用Lab較多,跟顯示相關的系統則用Luv較多,因為Luv對混和不同顏色的光特別有用

RGB轉換到Lab的公式網路上找都有,先將RGB轉到CIEXYZ,再從XYZ轉到Lab,公式比較複雜,且因為有浮點數運算,有一點精度問題

這邊就不自幹這個轉換的實作了,俗話說不要重建車子的輪子嘛,直接利用OpenCV提供的轉換就好囉

色彩轉換公式

原論文的式子是拆開來的,有點難以理解,總結來說原理就是

將來源圖片的變異量變成和目標圖片一樣, 再加上來源圖像的平均值, 即可算出和來源圖像顏色分布相似的目標圖像像素值

我的公式寫法跟論文有一點點不同,原本論文的描述我覺得不太直覺,所以稍微移項了一下,數學本質還是沒變

我把要用的來源影像叫做source,要映射的影像叫做target

mean就是平均值,var為變異量

目的是算出結果圖的Lab值,公式為targetLab減去targetLab的平均值後,乘上sourceLab與targetLab的變異量比值,最後加上sourceLab的平均值

outputLab = (targetLab - targetLab_mean) * (sourceLab_var / targetLab_var) + sourceLab_mean

實作上需要注意的是以上的公式需要分別套用在Lab三個通道上,也就是每個通道都要各自算得到結果,然後再合併每個通道

最後記得先做正規化再轉回RGB空間,不然會變得很奇怪! 當初卡這個問題卡了一天才發現

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "opencv2/opencv.hpp"
#include <iostream>
#include <cmath>
#include <windows.h>

using namespace std;
using namespace cv;
Mat colorTransfer(Mat source, Mat target) {
/* RGB空間轉到lab空間 */
Mat sourceLab, targetLab;
cvtColor(source, sourceLab, CV_BGR2Lab);
cvtColor(target, targetLab, CV_BGR2Lab);

/* 轉換為浮點數型態計算 (必須) */
sourceLab.convertTo(sourceLab, CV_32FC3);
targetLab.convertTo(targetLab, CV_32FC3);

/* Lab三個通道都要計算平均值與變異數(標準差) */
Scalar sourceLab_mean, targetLab_mean;
Scalar sourceLab_var, targetLab_var;

// 變異數: 量測所有資料到平均數的平均距離, var^2 = (Xi - M)^2 / N

// The function meanStdDev calculates the mean and the standard deviation M of array elements independently for each channel
meanStdDev(sourceLab, sourceLab_mean, sourceLab_var);
meanStdDev(targetLab, targetLab_mean, targetLab_var);

// 分離lab三個通道成每一個獨立的通道
Mat sourceLab_split[3], targetLab_split[3];
split(sourceLab, sourceLab_split);
split(targetLab, targetLab_split);

/* Color Transfer: output = (targetLab - targetLab_mean) * (sourceLab_var / targetLab_var) + sourceLab_mean */

// target lab 每個通道減去自身平均值, 乘上來源與自身的變異數比值, 再加上來源平均值
for (int i = 0; i < 3; i++){
targetLab_split[i] = (targetLab_split[i] - targetLab_mean[i]) * (sourceLab_var[i] / targetLab_var[i]) + sourceLab_mean[i];
}

// 合併每個通道, 從lab空間轉回RGB空間
Mat output;
merge(targetLab_split, 3, output);

// 轉回unsigned 8bits 8UC3
output.convertTo(output, CV_8UC3);
cvtColor(output, output, CV_Lab2BGR);

return output;
}