Reinhard经典色彩迁移算法

本文详细介绍了Reinhard色彩迁移算法的原理与实现过程,包括如何在lαβ颜色空间中通过调整图像的均值和方差来实现色彩迁移,以及具体的代码实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下文中,
  目标图像即为要进行染色的图像
  源图像即为色彩源

  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

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值