【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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章