下文中,
目标图像即为要进行染色的图像
源图像即为色彩源
Reinhard等人根据lαβ颜色空间中各通道互相不关联的特点,提出了一组适用于各颜色分量的色彩迁移公式,较好的实现了彩色图像之间的色彩迁移。基本思想就是根据着色图像的统计分析确定一个线性变换,使得目标图像和源图像在lαβ空间中具有同样的均值和方差。
因此需要计算两幅图像的均值和标准方差。假设 l、a、b 是 目标图像 lαβ 通道的数据,L、A、B 分别是 结果图像 lαβ通道的值,ml、ma、mb 和 ml’、ma’、mb’ 分别是的 目标图像 和 源图像 的 lαβ 通道的均值,nl、na、nb 和 nl’、na’、nb’ 表示它们的标准方差。
首先,将 目标图像 lαβ 通道的数据减掉 目标图像 的 lαβ 通道的均值
L1 = l – ml
A1 = a – ma
B1 = b – mb
再将得到的新数据按比例放缩,其放缩系数是两幅图像标准方差的比值(源图像的标准方差 : 目标图像的标准方差)
L’ = (nl’ / nl)* L1
A’ = (na’ / na)* A1
B’ = (nb’ / nb)* B1
将得到的 L’、A’、B’分别加上 目标图像 lαβ 通道的均值,得到最终数据
L = L’ + ml’
A = A’ + ma’
B = B’ + mb’
整理后得到目标图像与源图像之间的像素关系表达:(目标图像lab 空间的像素 = (源标准差 / 目标标准差)* (目标像素 - 目标均值)+ 源均值)
L = (nl’ / nl)* (l – ml) + ml’
A = (na’ / na)* (a – ma) + ma’
B = (nb’ / nb)* (b – mb) + mb’
事实上这组式子表示的就是一个线性方程,以两幅图像的标准方差的比值作为斜率,两幅图像各个通道的均值作为一个点。这个简单的线性变换保证了目标图像和着色图像在lαβ颜色空间中具有相同的均值和方差。将变换后l、α、β的值作为新图像三个通道的值,然后显示的时候再将这三个通道转换为RGB值进行显示。
图 reinhard算法效果图
Reinhard 等人的色彩迁移算法的优点是实现简单,且运行效率很高。但该算法由于是整体色彩迁移,因此它对全局颜色基调单一的图像的有着良好的迁移效果。而对于颜色内容丰富的图像,则效果并不那么明显。一般解决方式是引入人机交互选取样本块的方法,同时还要求用户指定样本块之间的对应关系。这样就给用户增加了许多繁琐的交互。当图像的色彩比较复杂时,用户是无法手工精确地选取样本块的,此时,该算法也将失去作用。
参考了该文章的代码后,自己修改并补全了的代码。结果图如下:



代码:
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
bool TranRein(uchar* lpDIBBits, int lmageWidth, int lmageHeight, uchar* lpDIBBits2, int lmageWidth2, int lmageHeight2, uchar* lpDIBBits3);
void AverageVariance(double* lpLab, int Width, int Height, double& al, double& aa, double& ab, double& vl, double& va, double& vb);
void RgbToLab(uchar R, uchar G, uchar B, double& l, double& a, double& b);
void LabToRgb(double l, double a, double b, uchar& R, uchar& G, uchar& B);
void mat2Sample(Mat& src, uchar* sample, int nWidth, int nHeight);
void sample2Mat(Mat&src, int nWidth, int nHeight, uchar* sample);
int main()
{
Mat src = imread("4.jpg", 1);
int ImageHeight = src.rows;
int ImageWidth = src.cols;
uchar* lpDIBBits = new uchar[3 * ImageHeight * ImageWidth];
mat2Sample(src, lpDIBBits, ImageWidth, ImageHeight);
imshow("src", src);
Mat dst = imread("3.jpg", 1);
int ImageHeight2 = dst.rows;
int ImageWidth2 = dst.cols;
uchar* lpDIBBits2 = new uchar[3 * ImageHeight2 * ImageWidth2];
mat2Sample(dst, lpDIBBits2, ImageWidth2, ImageHeight2);
imshow("dst", dst);
uchar* lpDIBBits3 = new uchar[3 * ImageHeight2 * ImageWidth2];
TranRein(lpDIBBits, ImageWidth, ImageHeight, lpDIBBits2, ImageWidth2, ImageHeight2, lpDIBBits3);
Mat result(ImageHeight2, ImageWidth2, CV_8UC3);
sample2Mat(result, ImageWidth2, ImageHeight2, lpDIBBits3);
imshow("result", result);
waitKey(0);
}
bool TranRein(uchar* lpDIBBits, int lmageWidth, int lmageHeight, uchar* lpDIBBits2, int lmageWidth2, int lmageHeight2, uchar* lpDIBBits3)
{
//控制循环
int i;
int j;
long long nindex;
//a为均值,v为方差,分别为源图像和目标图像的lab通道的均值和方差
double al, aa, ab, vl, va, vb, al2, aa2, ab2, vl2, va2, vb2;
//lab空间的矩阵 1源图像,2目标图像,3结果图像
double* lpImageLab = new double[lmageWidth*lmageHeight * 3];
double* lpImageLab2 = new double[lmageWidth2*lmageHeight2 * 3];
double* lpImageLab3 = new double[lmageWidth2*lmageHeight2 * 3];
//源图像转换为lab,并求lab的均值及标准差
for (j = 0; j < lmageHeight; j++)
{
for (i = 0; i < lmageWidth; i++)
{
nindex = (j*lmageWidth + i);
RgbToLab(lpDIBBits[nindex * 3 + 2], lpDIBBits[nindex * 3 + 1], lpDIBBits[nindex * 3 + 0],
lpImageLab[nindex * 3 + 0], lpImageLab[nindex * 3 + 1], lpImageLab[nindex * 3 + 2]);
}
}
AverageVariance(lpImageLab, lmageWidth, lmageHeight, al, aa, ab, vl, va, vb);
//目标图像转换为lab,并求lab的均值及标准差
for (j = 0; j < lmageHeight2; j++)
{
for (i = 0; i < lmageWidth2; i++)
{
nindex = (j*lmageWidth2 + i);
RgbToLab(lpDIBBits2[nindex * 3 + 2], lpDIBBits2[nindex * 3 + 1], lpDIBBits2[nindex * 3 + 0],
lpImageLab2[nindex * 3 + 0], lpImageLab2[nindex * 3 + 1], lpImageLab2[nindex * 3 + 2]);
}
}
AverageVariance(lpImageLab2, lmageWidth2, lmageHeight2, al2, aa2, ab2, vl2, va2, vb2);
nindex = 0;
for (j = 0; j < lmageHeight2; j++)
{
for (i = 0; i < lmageWidth2; i++)
{
/*nindex = (j*lmageWidth + i);
lpImageLab3[nindex * 3 + 0] = (lpImageLab[nindex * 3 + 0] - al) * vl2 / vl + al2;
lpImageLab3[nindex * 3 + 1] = (lpImageLab[nindex * 3 + 1] - aa) * va2 / va + aa2;
lpImageLab3[nindex * 3 + 2] = (lpImageLab[nindex * 3 + 2] - ab) * vb2 / vb + ab2;*/
lpImageLab3[nindex] = (lpImageLab2[nindex++] - al2) * vl / vl2 + al;
lpImageLab3[nindex] = (lpImageLab2[nindex++] - aa2) * va / va2 + aa;
lpImageLab3[nindex] = (lpImageLab2[nindex++] - ab2) * vb / vb2 + ab;
}
}
求结果图像的lab
//for (i = 0; i < lmageWidth*lmageHeight; i++)
//{
// lpImageLab3[i * 3 + 0] = (lpImageLab[i * 3 + 0] - al) * vl2 / vl + al2;
// lpImageLab3[i * 3 + 1] = (lpImageLab[i * 3 + 1] - aa) * va2 / va + aa2;
// lpImageLab3[i * 3 + 2] = (lpImageLab[i * 3 + 2] - ab) * vb2 / vb + ab2;
//}
//将结果图像的lab转换为RGB
for (j = 0; j < lmageHeight2; j++)
{
for (i = 0; i < lmageWidth2; i++)
{
nindex = (j*lmageWidth2 + i);
LabToRgb(lpImageLab3[nindex * 3 + 0], lpImageLab3[nindex * 3 + 1], lpImageLab3[nindex * 3 + 2],
lpDIBBits3[nindex * 3 + 2], lpDIBBits3[nindex * 3 + 1], lpDIBBits3[nindex * 3 + 0]);
}
}
return true;
}
void AverageVariance(double* lpLab, int Width, int Height, double& al, double& aa, double& ab, double& vl, double& va, double& vb)
{
double suml = 0;
double suma = 0;
double sumb = 0;
double lsuml = 0;
double lsuma = 0;
double lsumb = 0;
//分行求平均,避免和过大而溢出
for (int j = 0; j < Height; j++)
{
for (int i = 0; i < Width; i++)
{
lsuml += lpLab[(j*Width + i) * 3];
lsuma += lpLab[(j*Width + i) * 3 + 1];
lsumb += lpLab[(j*Width + i) * 3 + 2];
}
suml += lsuml / Width;
suma += lsuma / Width;
sumb += lsumb / Width;
lsuml = lsuma = lsumb = 0;
}
al = suml / Height;
aa = suma / Height;
ab = sumb / Height;
suml = suma = sumb = 0;
lsuml = lsuma = lsumb = 0;
for (int j = 0; j < Height; j++)
{
for (int i = 0; i < Width; i++)
{
lsuml += pow(lpLab[(j*Width + i) * 3] - al, 2);
lsuma += pow(lpLab[(j*Width + i) * 3 + 1] - aa, 2);
lsumb += pow(lpLab[(j*Width + i) * 3 + 2] - ab, 2);
}
suml += lsuml / Width;
suma += lsuma / Width;
sumb += lsumb / Width;
lsuml = lsuma = lsumb = 0;
}
suml /= Height;
suma /= Height;
sumb /= Height;
vl = sqrt(suml);
va = sqrt(suma);
vb = sqrt(sumb);
/*for (int i = 0; i < Width*Height; i++)
{
suml += pow(lpLab[i * 3] - al, 2);
suma += pow(lpLab[i * 3 + 1] - aa, 2);
sumb += pow(lpLab[i * 3 + 2] - ab, 2);
}
suml /= (Width * Height);
suma /= (Width * Height);
sumb /= (Width * Height);
vl = sqrt(suml);
va = sqrt(suma);
vb = sqrt(sumb);*/
}
void RgbToLab(uchar R, uchar G, uchar B, double& l, double& a, double& b)
{
double L = 0.3811*R + 0.5783*G + 0.0402*B;
double M = 0.1967*R + 0.7244*G + 0.0782*B;
double S = 0.0241*R + 0.1288*G + 0.8444*B;
//若RGB值均为0,则LMS为0,防止数学错误log0
if (L != 0) L = log(L) / log(10);
if (M != 0) M = log(M) / log(10);
if (S != 0) S = log(S) / log(10);
l = (L + M + S) / sqrt(3);
a = (L + M - 2 * S) / sqrt(6);
b = (L - M) / sqrt(2);
}
void LabToRgb(double l, double a, double b, uchar& R, uchar& G, uchar& B)
{
l /= sqrt(3);
a /= sqrt(6);
b /= sqrt(2);
double L = l + a + b;
double M = l + a - b;
double S = l - 2 * a;
L = pow(10, L);
M = pow(10, M);
S = pow(10, S);
double dR = 4.4679*L - 3.5873*M + 0.1193*S;
double dG = -1.2186*L + 2.3809*M - 0.1624*S;
double dB = 0.0497*L - 0.2439*M + 1.2045*S;
//防止溢出,若求得RGB值大于255则置为255,若小于0则置为0
if (dR > 255) R = 255;
else if (dR < 0) R = 0;
else R = uchar(dR);
if (dG > 255) G = 255;
else if (dG < 0) G = 0;
else G = uchar(dG);
if (dB > 255) B = 255;
else if (dB < 0) B = 0;
else B = uchar(dB);
}
void mat2Sample(Mat& src, uchar* sample, int nWidth, int nHeight)
{
//uchar* temp;
long long nindex = 0;
for (int i = 0; i < nHeight; ++i)
{
//temp = src.ptr<uchar>(i);
for (int j = 0; j < nWidth; ++j)
{
///*sample[i*nWidth + j * 3] = temp[j * 3];
//sample[i*nWidth + j * 3 + 1] = temp[j * 3 + 1];
//sample[i*nWidth + j * 3 + 2] = temp[j * 3 + 2];*/
//sample[i*nWidth + j * 3] = src.at<Vec3b>(i, j)[0];
//sample[i*nWidth + j * 3 + 1] = src.at<Vec3b>(i, j)[1];
//sample[i*nWidth + j * 3 + 2] = src.at<Vec3b>(i, j)[2];
sample[nindex++] = src.at<Vec3b>(i, j)[0];
sample[nindex++] = src.at<Vec3b>(i, j)[1];
sample[nindex++] = src.at<Vec3b>(i, j)[2];
}
}
}
void sample2Mat(Mat&src, int nWidth, int nHeight, uchar* sample)
{
//uchar* temp;
long long nindex = 0;
for (int i = 0; i < nHeight; ++i)
{
//temp = src.ptr<uchar>(i);
for (int j = 0; j < nWidth; ++j)
{
/*src.at<Vec3b>(i, j)[0] = sample[i*nWidth + j * 3];
src.at<Vec3b>(i, j)[1] = sample[i*nWidth + j * 3 + 1];
src.at<Vec3b>(i, j)[2] = sample[i*nWidth + j * 3 + 2];*/
/*temp[j * 3] = sample[i*nWidth + j * 3];
temp[j * 3 + 1] = sample[i*nWidth + j * 3 + 1];
temp[j * 3 + 2] = sample[i*nWidth + j * 3 + 2];*/
src.at<Vec3b>(i, j)[0] = sample[nindex++];
src.at<Vec3b>(i, j)[1] = sample[nindex++];
src.at<Vec3b>(i, j)[2] = sample[nindex++];
}
}
}
之前写过的一个QT项目,里面有 Reinhard、Welsh和FCM色彩迁移算法的实现:https://download.csdn.net/download/qq_38701868/12034242
没下载积分的可百度网盘自取:
链接:https://pan.baidu.com/s/1kW98v9g_Nq1Yj6_YIHW3mQ
提取码:1u97