【OpenCV】Retinex图像增强(SSR,MSR,MSRCR)

简介

1963年12月30日E. Land作为人类视觉的亮度和颜色感知的模型在俄亥俄州提出了一种颜色恒常知觉的计算理论——Retinex理论。Retinex是一个合成词,它的构成是retina(视网膜)+cortex(皮层)→ Retinex。40多年来,工作在IS&T、NASA的J. J. McCann和D. J. Jobson、Zia-Ur Rahman、G. A. Woodell等人模仿人类视觉系统发展了Retinex算法,从单尺度Retinex算法(single scale retinex, SSR)改进成多尺度加权平均的Retinex算法(multi-scale retinex, MSR),再发展成带彩色恢复的多尺度Retinex算法(multi-scale retinex with color restoration, MSRCR)。

Retinex 理论主要包含了两个方面的内容:物体的颜色是由物体对长波、中波和短波光线的反射能力决定的,而不是由反射光强度的绝对值决定的;物体的色彩不受光照非均性的影响,具有一致性。

(Retinex我简单理解为,拍摄得到的图像是反射图像与光照信息的组合,而增强要做的就是通过拍摄到的原图像,通过计算,去除光照信息,得到真实的反射图像 (个人理解可能有误))

主要思想

Retinex理论的基本假设是原始图像SS是光照图像LL和反射图像RR的乘积,即可表示为下式的形式:
S(x,y)=R(x,y)L(x,y) S(x,y) = R(x,y)\bullet L(x,y)
基于Retinex的图像增强的目的就是从原始图像SS中估计出光照LL,从而分解出RR,消除光照不均的影响,以改善图像的视觉效果,正如人类视觉系统那样。在处理中,通常将图像转至对数域,即
logS=logR+logL \log S = \log R+\log L
s=r+l s = r + l
Retinex方法的核心就是估测照度LL,从图像SS中估测LL分量,并去除L分量,得到原始反射分量RR,即:
l=f(s) l = f(s)
r=sf(s) r = s-f(s)
函数 f(x)f(x) 实现对照度L的估计

更多请参考:
dafydd_成: 图像去雾(二)Retinex图像增强算法

单尺度Retinex: SSR(Single Scale Retinex)

r(x,y)=logS(x,y)log[F(x,y)S(x,y)]r(x, y) = \log S(x, y) - \log [F(x, y) \otimes S(x, y) ]
F(x,y)=λe(x2+y2)c2F(x, y) = \lambda e^{\frac{-(x^2 + y^2)}{c^{2}}}
其中的 cc 是高斯环绕尺度,cc 值一般取值在80–100`之间,λ\lambda是一个尺度,它的取值必须满足下式:
F(x,y)dxdy=1\int \int F(x, y)dxdy = 1
这里的卷积是一个高斯卷积,按照公式计算得到 logR\log R后进行正规化,将像素值映射到 0-255范围内(计算时几乎没有人按logR\log R反求RR
更多请参考:
琦小虾:Retinex图像增强算法(SSR, MSR, MSRCR)详解及其OpenCV源码

多尺度Retinex: MSR(Multi-Scale Retinex)

r(x,y)=kKwk{logS(x,y)log[Fk(x,y)S(x,y)]}r(x, y) = \sum_{k}^{K}w_{k} \{ \log S(x, y) - \log [F_{k}(x, y) \cdot S(x, y) ] \}
式中,KK是高斯中心环绕函数的个数。当 K=1K=1 时,MSR退化为SSR。
通常来讲,为了保证兼有SSR高、中、低三个尺度的优点来考虑,KK取值通常为3,且有:
w1=w2=w3=13w_{1} = w_{2} = w_{3} = \frac{1}{3}
此外,实验表明,cic_i分别取15, 80, 200可以得到较好效果。
一般的Retinex算法对光照图像估计时,都会假设初始光照图像是缓慢变化的,即光照图像是平滑的。但实际并非如此,亮度相差很大区域的边缘处,图像光照变化并不平滑。所以在这种情况下,Retinuex增强算法在亮度差异大区域的增强图像会产生光晕。
另外MSR常见的缺点还有边缘锐化不足,阴影边界突兀,部分颜色发生扭曲,纹理不清晰,高光区域细节没有得到明显改善,对高光区域敏感度小等。

更多请参考:
琦小虾:Retinex图像增强算法(SSR, MSR, MSRCR)详解及其OpenCV源码

带颜色恢复的多尺度Retinex: (Multi-Scale Retinex with Color Restoration)

RMSRCRi(x,y)=Ci(x,y)RMSRi(x,y)R_{MSRCR_{i}}(x, y) = C_{i}(x, y) R_{MSR_{i}}(x, y)

Ci(x,y)=f[Ii(x,y)]=f[Ii(x,y)j=1NIj(x,y)]C_{i}(x, y) = f[I_{i}^{'}(x, y)] = f[\frac{I_{i}(x, y)}{ \sum_{j=1}^{N} I_{j}(x, y)}]

f[Ii(x,y)]=βlog[αIi(x,y)]=β{log[αIi(x,y)]log[j=1NIj(x,y)]}f[I_{i}^{'}(x, y)] = \beta \log [\alpha I_{i}^{'}(x, y)] = \beta \{ \log [\alpha I_{i}^{'}(x, y)] - \log [\sum_{j=1}^{N}I_{j}(x, y)] \}

其中参数说明如下:

Ii(x,y)I_{i}(x,y)表示第ii个通道的图像
CiC_i表示第ii个通道的彩色回复因子,用来调节3个通道颜色的比例;
f()f(·)表示颜色空间的映射函数;
ββ是增益常数;
αα是受控制的非线性强度;

MSRCR算法利用彩色恢复因子CC,调节原始图像中3个颜色通道之间的比例关系,从而把相对较暗区域的信息凸显出来,达到了消除图像色彩失真的缺陷。
处理后的图像局部对比度提高,亮度与真实场景相似,在人们视觉感知下,图像显得更加逼真。

但是MSRCR算法处理图像后,像素值一般会出现负值。所以从对数域 r(x,y)r(x, y) 转换为实数域 R(x,y)R(x, y) 后,需要通过改变增益Gain,偏差Offset对图像进行修正。使用公式可以表示为:
RMSRCRi(x,y)=GRMSRCRi(x,y)+OR_{MSRCR_{i}}(x, y)' = G\cdot R_{MSRCR_{i}}(x, y) + O
其中,GG 表示增益Gain,OO 表示偏差Offset。它们的值取决于软件中的算法实现。

更多请参考:
琦小虾:Retinex图像增强算法(SSR, MSR, MSRCR)详解及其OpenCV源码

OpenCV代码

#include <opencv2\opencv.hpp>
#include <math.h>

/********************************************************************************
单尺度Retinex图像增强程序
src为待处理图像
sigma为高斯模糊标准差
*********************************************************************************/
void SingleScaleRetinex (const cv::Mat &src, cv::Mat &dst, int sigma);

/********************************************************************************
多尺度Retinex图像增强程序
src为待处理图像
k为尺度参数
w为权重参数
sigma为高斯模糊标准差
*********************************************************************************/
void MultiScaleRetinex  (const cv::Mat &src, cv::Mat &dst, int k, std::vector<double> w, std::vector<double> sigmas);

/********************************************************************************
多尺度Retinex图像增强程序
src     为待处理图像
k       为尺度参数
w       为权重参数
sigma   为高斯模糊标准差
alpha   增益常数
beta    受控的非线性强度
gain    增益
offset  偏差
*********************************************************************************/
void MultiScaleRetinexCR(const cv::Mat &src, cv::Mat &dst, int k, std::vector<double> w, std::vector<double> sigmas, int alpha , int beta , int gain, int offset);

int main()
{

	cv::Mat src = cv::imread("D:\\CPP_Project\\OpenCV410\\OpenCV410\\image\\test_MSRCR.jpg", cv::IMREAD_COLOR);
	if (src.empty()) 
	{
		std::cout << "The image is empty" << std::endl;
		
		return 1;
	}
		
	cv::Mat res1 ,res2, res3;
	

	int k = 3;

	std::vector < double > w(k);
	w[0] = 0.333333333;
	w[1] = 0.333333333;
	w[2] = 0.333333334;

	std::vector<double> s(k);
	s[0] = 15;
	s[1] = 80;
	s[2] = 200;


	SingleScaleRetinex (src, res1, 200);

	MultiScaleRetinex  (src, res2, k, w, s);

	MultiScaleRetinexCR(src, res3, k, w, s, 1, 1, 1, 0);

	cv::imshow("org", src);
	cv::imshow("res1", res1);
	cv::imshow("res2", res2);
	cv::imshow("res3", res3);
	cv::waitKey(0);
	
	return 0;
}

void SingleScaleRetinex(const cv::Mat &src, cv::Mat &dst, int sigma)
{
	cv::Mat doubleI, gaussianI, logI, logGI, logR;

	src.convertTo(doubleI, CV_64FC3, 1.0, 1.0);                    //转换范围,所有图像元素增加1.0保证cvlog正常
	cv::GaussianBlur(doubleI, gaussianI, cv::Size(0,0), sigma);    //SSR算法的核心之一,高斯模糊,当size为零时将通过sigma自动进行计算
	cv::log(doubleI, logI);
	cv::log(gaussianI, logGI);
	logR = logI - logGI;                                           //Retinex公式,Log(R(x,y))=Log(I(x,y))-Log(Gauss(I(x,y)))
	cv::normalize(logR, dst, 0, 255, cv::NORM_MINMAX,CV_8UC3);     //SSR算法的核心之二,线性量化 (似乎在量化的时候没有谁会将 Log[R(x,y)]进行Exp函数的运算而直接得到R(x,y))
}


void MultiScaleRetinex(const cv::Mat &src, cv::Mat &dst, int k, std::vector<double> w, std::vector<double> sigmas) 
{
	cv::Mat doubleI, logI;
	cv::Mat logR = cv::Mat::zeros(src.size(),CV_64FC3);

	src.convertTo(doubleI, CV_64FC3, 1.0, 1.0);                    //转换范围,所有图像元素增加1.0保证cvlog正常
	cv::log(doubleI, logI);

	for (int i = 0; i < k; i++)
	{//Retinex公式,Log(R(x,y)) += w_k(Log(I(x,y))-Log(Gauss_k(I(x,y))))
		cv::Mat tempGI;
		cv::GaussianBlur(doubleI, tempGI, cv::Size(0, 0), sigmas[i]);
		cv::Mat templogGI;
		cv::log(tempGI, templogGI);
		logR += w[i]*(logI - templogGI);
	}

	cv::normalize(logR, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3);  //SSR算法的核心之二,线性量化 (似乎在量化的时候没有谁会将 Log[R(x,y)]进行Exp函数的运算而直接得到R(x,y))
}

//
void MultiScaleRetinexCR(const cv::Mat &src, cv::Mat &dst, int k, std::vector<double> w, std::vector<double> sigmas, int alpha, int beta, int gain, int offset)
{
	

	cv::Mat doubleIl, logI;
	cv::Mat logMSR = cv::Mat::zeros(src.size(), CV_64FC3);

	src.convertTo(doubleIl, CV_64FC3, 1.0, 1.0);                    //转换范围,所有图像元素增加1.0保证cvlog正常
	cv::log(doubleIl, logI);

	for (int i = 0; i < k; i++)
	{//Retinex公式,Log(R(x,y)) += w_k(Log(I(x,y))-Log(Gauss_k(I(x,y))))
		cv::Mat tempGI;
		cv::GaussianBlur(doubleIl, tempGI, cv::Size(0, 0), sigmas[i]);
		cv::Mat templogGI;
		cv::log(tempGI, templogGI);
		logMSR += w[i] * (logI - templogGI);
	}


	std::vector<cv::Mat> logMSRc(3);
	cv::split(logMSR, logMSRc);

	cv::Mat doubleI;
	src.convertTo(doubleI, CV_64FC3);

	std::vector<cv::Mat> doubleIc(3);
	cv::split(doubleI, doubleIc);


	cv::Mat sumDoubleIc =cv::Mat::zeros(doubleI.size(),CV_64FC1);
	for (int i = 0; i < doubleI.rows; i++)
	{
		for (int j = 0; j < doubleI.cols; j++)
		{
			sumDoubleIc.ptr<double>(i)[j] = doubleI.ptr<cv::Vec3d>(i)[j][0] + doubleI.ptr<cv::Vec3d>(i)[j][1] + doubleI.ptr<cv::Vec3d>(i)[j][2];
		}
	}


	std::vector<cv::Mat> divideDoubleIc(3);
	std::vector<cv::Mat> Cc(3);
	std::vector<cv::Mat> MSRCRc(3);
	cv::Mat tempResult;

	for (int i = 0; i < 3; i++)
	{
		cv::divide(doubleIc[i], sumDoubleIc, divideDoubleIc[i]);
		divideDoubleIc[i].convertTo(divideDoubleIc[i], CV_64FC1, 1.0, 1.0);
		cv::log(alpha * divideDoubleIc[i], Cc[i]);
		Cc[i] *= beta;
		MSRCRc[i] = Cc[i].mul(logMSRc[i]);
	}


	cv::merge(MSRCRc, tempResult);
	cv::normalize(tempResult, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章