SSE图像算法优化系列十九:一种局部Gamma校正对比度增强算法及其SSE优化。
這是一篇2010年比較古老的文章了,是在QQ群里一位群友提到的,無聊下載看了下,其實也沒有啥高深的理論,抽空實現(xiàn)了下,雖然不高大上,還是花了點時間和心思優(yōu)化了代碼,既然這樣,就順便分享下優(yōu)化的思路和經(jīng)歷。
文章的名字為:Contrast image correction method,由于本人博客的后臺文件已經(jīng)快超過博客園所容許的最大空間,這里就不直接上傳文章了,大家可以直接點我提供的鏈接下載。
文章的核心就是對普通的伽馬校正做改進(jìn)和擴(kuò)展,一般來說,伽馬校正具有以下的標(biāo)準(zhǔn)形式:
其中I(i,j)為輸入圖像,O(i,j)為輸出圖像,γ為控制參數(shù),當(dāng)γ大于1時,圖像整體變亮,當(dāng)γ小于1大于0時,圖像整體變暗,γ小于0算法無意義。
這個算法對于圖像整體偏暗或整體偏亮?xí)r,通過調(diào)節(jié)參數(shù)γ可以獲得較為滿意的效果,但是如果圖像中同時存在欠曝或過曝的區(qū)域,同一個參數(shù)就無法同時滿意的效果了,因此,可引入一種γ隨圖像局部區(qū)域信息變化的算法來獲取更為滿意的效果,一種常用的形式如下:
Moroney在其論文Local colour correction using nonlinear masking提出了如下公式:
? 其中的mask獲取方式為:先對原圖進(jìn)行反色處理,然后進(jìn)行一定半徑的高斯模糊。
這樣做的道理如下:如果mask的值大于128,說明那個點是個暗像素同時周邊也是暗像素,因此γ值需要小于0以便將其增亮,mask值小于128,對應(yīng)的說明當(dāng)前點是個較亮的像素,且周邊像素也較亮,mask值為128則不產(chǎn)生任何變化,同時,mask值離128越遠(yuǎn),校正的量就越大,并且還有個特點就是純白色和純黑色不會有任何變化(這其實也是會產(chǎn)生問題的)。
如下圖所示,直觀的反應(yīng)了不同的mask值的映射結(jié)果。
??
簡單寫一段測試代碼,看看這個的效果如何:
int IM_LocalExponentialCorrection(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride) {unsigned char *Mask = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char));IM_Invert(Src, Mask, Width, Height, Stride); // Invert IntensityIM_ExpBlur(Mask, Mask, Width, Height, Stride, 20); // Blur for (int Y = 0; Y < Height; Y++){unsigned char *LinePS = Src + Y * Stride;unsigned char *LinePD = Dest + Y * Stride;unsigned char *LinePM = Mask + Y * Stride;for (int X = 0; X < Width; X++){LinePD[0] = IM_ClampToByte(255 * pow(LinePS[0] * IM_INV255, pow(2, (128 - LinePM[0]) / 128.0f))); // Moroney論文的公式LinePD[1] = IM_ClampToByte(255 * pow(LinePS[1] * IM_INV255, pow(2, (128 - LinePM[1]) / 128.0f)));LinePD[2] = IM_ClampToByte(255 * pow(LinePS[2] * IM_INV255, pow(2, (128 - LinePM[2]) / 128.0f)));LinePS += 3; LinePD += 3; LinePM += 3;}}free(Mask);return IM_STATUS_OK; }基本按照論文的公式寫的代碼,未做優(yōu)化,測試兩張圖片看看。
? ?
原圖1 ? ? ? ?Moroney論文的結(jié)果
似乎效果還不錯。
作為一種改進(jìn),Contrast image correction method一文作者對上述公式進(jìn)行了2個方面的調(diào)整,如下所示:
第一,高斯模糊的mask使用雙邊濾波來代替,因為雙邊濾波的保邊特性,這樣可以減少處理后的halo瑕疵。這沒啥好說的。
第二,常數(shù)2使用變量α代替,并且是和圖像內(nèi)容相關(guān)的,具體算式如下:
當(dāng)圖像的整體平均值小于128時,使用計算,當(dāng)平均值大于128時,使用計算,論文作者給出了這樣做的理由:對于低對比度的圖像,應(yīng)該需要較強烈的校正,因此α值應(yīng)該偏大,而對于有較好對比度的圖,α值應(yīng)該偏向于1,從而產(chǎn)生很少的校正量。
對于第二條,實際上存在很大的問題,比如對于我們上面進(jìn)行測試的原圖1,由于他上半部分為天空,下半部分比較暗,且基本各占一般,因此其平均值非常靠近128,因此計算出的α也非常接近1,這樣如果按照改進(jìn)后的算法進(jìn)行處理,則基本上圖像無什么變化,顯然這是不符合實際的需求的,因此,個人認(rèn)為作者這一改進(jìn)是不合理的,還不如對所有的圖像該值都取2,靠mask值來修正對比度。
那么對于彩色圖像,我們有兩種方法,一種是直接對RGB各分量處理,如上面的代碼所示,另外一種就是把他轉(zhuǎn)換到Y(jié)CBCR或者LAB或者YUV等空間,然后只處理亮度通道,最后在轉(zhuǎn)換到RGB空間,那么本文對我的有用的幫助就是提供了一個恢復(fù)色彩飽和度的方法。一般來說在對Y分量做處理后,再轉(zhuǎn)換到RGB空間,圖像會出現(xiàn)飽和度一定程度丟失的現(xiàn)象,看上去圖像似乎色彩不足。如下圖中間圖所示,因此,論文提出了下面的修正公式:
經(jīng)測試,這樣處理后的圖色彩還是很鮮艷的,和直接三通道分開處理的差不多(直接三通道分開處理有可能會導(dǎo)致嚴(yán)重偏色,而只處理Y則不會)。
????
原圖 ? 直接處理Y通道再轉(zhuǎn)換到RGB空間 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?改進(jìn)后的效果
我們貼出按照上述思路改進(jìn)后的代碼:
int IM_LocalExponentialCorrection(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride) {unsigned char *OldY = NULL, *Mask = NULL, *Table = NULL;OldY = (unsigned char *)malloc(Height * Width * sizeof(unsigned char));Mask = (unsigned char *)malloc(Height * Width * sizeof(unsigned char));IM_GetLuminance(Src, OldY, Width, Height, Stride); // 得到Y(jié)通道的數(shù)據(jù)IM_GuidedFilter(OldY, OldY, Mask, Width, Height, Width, IM_Max(IM_Max(Width, Height) * 0.01, 5), 25, 0.01f); // 通過Y通道數(shù)據(jù)處理得到255-Mask值unsigned char *NewY = Mask;for (int Y = 0; Y < Height * Width; Y++){NewY[Y] = IM_ClampToByte(255 * pow(OldY[Y] * IM_INV255, pow(2, (128 - (255 - Mask[Y])) / 128.0f)));}for (int Y = 0; Y < Height; Y++){unsigned char *LinePS = Src + Y * Stride;unsigned char *LinePD = Dest + Y * Stride;unsigned char *LinePO = OldY + Y * Width;unsigned char *LinePN = NewY + Y * Width;for (int X = 0; X < Width; X++, LinePS += 3, LinePD += 3, LinePO++, LinePN++){int Old = LinePO[0], New = LinePN[0];if (Old == 0){LinePD[0] = 0; LinePD[1] = 0; LinePD[2] = 0;}else{LinePD[0] = IM_ClampToByte((New * (LinePS[0] + Old) / Old + LinePS[0] - Old) >> 1);LinePD[1] = IM_ClampToByte((New * (LinePS[1] + Old) / Old + LinePS[1] - Old) >> 1);LinePD[2] = IM_ClampToByte((New * (LinePS[2] + Old) / Old + LinePS[2] - Old) >> 1);}}}free(OldY);free(Mask);return IM_STATUS_OK; }代碼并不復(fù)雜,基本就是按照公式一步一步編寫的,其中IM_GetLuminance和IM_GuidedFilter為已經(jīng)使用SSE優(yōu)化后的算法,對于本文一直使用的測試圖675*800大小的圖,測試時間大概再40ms,而上述兩個SSE的代碼耗時才5ms不到,因此,可以進(jìn)一步優(yōu)化。
第一個需要優(yōu)化的當(dāng)然就是那個NewY[Y]的計算過程了,里面的pow函數(shù)是非常耗時的,仔細(xì)觀察算式里只有兩個變量,切他們都是[0,255]范圍內(nèi)的,因此建立一個256*256的查找表就可以了,如下所示:
Table = (unsigned char *)malloc(256 * 256 * sizeof(unsigned char));for (int Y = 0; Y < 256; Y++){float Gamma = pow(2, (128 - (255 - Y)) / 128.0f);for (int X = 0; X < 256; X++){Table[Y * 256 + X] = IM_ClampToByte(255 * pow(X * IM_INV255, Gamma));}}for (int Y = 0; Y < Height * Width; Y++){NewY[Y] = Table[Mask[Y] * 256 + OldY[Y]];}free(Table);
速度一下子跳到了15ms,由于是查表,基本上無SSE優(yōu)化的發(fā)揮地方。
接著再看最后的飽和度校正部分的算法,核心代碼即:
LinePD[0] = IM_ClampToByte((New * (LinePS[0] + Old) / Old + LinePS[0] - Old) >> 1);LinePD[1] = IM_ClampToByte((New * (LinePS[1] + Old) / Old + LinePS[1] - Old) >> 1);LinePD[2] = IM_ClampToByte((New * (LinePS[2] + Old) / Old + LinePS[2] - Old) >> 1);注意到這里是以24位圖像為例的,其實24位圖像在進(jìn)行SSE優(yōu)化時有的時候比32位麻煩很多,因為32位一個像素4個字節(jié),一個SSE變量正好能容納4個像素,而24位一個像素3個字節(jié),很多時候要在編程時把他補充一個alpha,然后處理玩后在把這個alpha去掉。
對于本例,注意到還有特殊性,在處理一個像素時還涉及到對應(yīng)的Y分量的讀取,所以有增加了復(fù)雜性。
我們在看上下上面的公式,由于SSE沒有整數(shù)除法指令,通常情況下要進(jìn)行整除必須借助浮點版本的除法,因此必須有這種數(shù)據(jù)類型的轉(zhuǎn)換,另外,我們考慮把括號里的加法展開下,可以得到公式變?yōu)槿缦?#xff1a;
LinePD[0] = IM_ClampToByte((New * LinePS[0] / Old + LinePS[0] + New - Old) >> 1);這樣展開從C的角度來說不會產(chǎn)生什么大的性能差異,但是對于SSE編程卻有好處,注意到New和LinePS[0] 的最大只都不會超過255,因此兩者相乘也在ushort所能表達(dá)的范圍內(nèi),但是如果帶上原來的(LinePS[0] + Old) 則會超出ushort范圍,對于沒有超出USHORT類型的乘法,我們可以借助_mm_mullo_epi16一次性實現(xiàn)8個數(shù)據(jù)的乘法,然后在根據(jù)需要把他們擴(kuò)展位32位。
具體的優(yōu)化細(xì)節(jié)還有很多值得探討的,由于之前的很多系列文章里基本已經(jīng)講到部分優(yōu)化技巧,因此本文僅僅貼出最后這一塊的優(yōu)化代碼,具體細(xì)節(jié)有興趣的朋友可以自行去研究:
__m128i SrcV = _mm_loadu_epi96((__m128i *)LinePS);__m128i OldV = _mm_cvtsi32_si128(*(int *)LinePO);__m128i NewV = _mm_cvtsi32_si128(*(int *)LinePN);__m128i SrcV08 = _mm_unpacklo_epi8(SrcV, Zero);__m128i OldV08 = _mm_shuffle_epi8(OldV, _mm_setr_epi8(0, -1, 0, -1, 0, -1, 1, -1, 1, -1, 1, -1, 2, -1, 2, -1));__m128i NewV08 = _mm_shuffle_epi8(NewV, _mm_setr_epi8(0, -1, 0, -1, 0, -1, 1, -1, 1, -1, 1, -1, 2, -1, 2, -1));__m128i Temp08 = _mm_sub_epi16(_mm_add_epi16(SrcV08, NewV08), OldV08);__m128i Mul08 = _mm_mullo_epi16(SrcV08, NewV08);__m128i Value04 = _mm_div_epi32(_mm_unpacklo_epi16(Mul08, Zero), _mm_unpacklo_epi16(OldV08, Zero));__m128i Value48 = _mm_div_epi32(_mm_unpackhi_epi16(Mul08, Zero), _mm_unpackhi_epi16(OldV08, Zero));__m128i Value08 = _mm_srli_epi16(_mm_add_epi16(_mm_packus_epi32(Value04, Value48), Temp08), 1);__m128i SrcV12 = _mm_unpackhi_epi8(SrcV, Zero);__m128i OldV12 = _mm_shuffle_epi8(OldV, _mm_setr_epi8(2, -1, 3, -1, 3, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1));__m128i NewV12 = _mm_shuffle_epi8(NewV, _mm_setr_epi8(2, -1, 3, -1, 3, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1));__m128i Temp12 = _mm_sub_epi16(_mm_add_epi16(SrcV12, NewV12), OldV12);__m128i Mul12 = _mm_mullo_epi16(SrcV12, NewV12);__m128i Value12 = _mm_div_epi32(_mm_unpacklo_epi16(Mul12, Zero), _mm_unpacklo_epi16(OldV12, Zero));__m128i Value16 = _mm_srli_epi16(_mm_add_epi16(_mm_packus_epi32(Value12, Zero), Temp12), 1);_mm_storeu_epi96((__m128i*)LinePD, _mm_packus_epi16(Value08, Value16));這里充分運用的shuffle指令來實現(xiàn)各種需求。
優(yōu)化后速度可以提升到7ms左右。
? 本文最后的運行效果可下載測試:https://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar
位于菜單Enhance -->?LocalExponentialCorrection下。
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/Imageshop/p/9129162.html
總結(jié)
以上是生活随笔為你收集整理的SSE图像算法优化系列十九:一种局部Gamma校正对比度增强算法及其SSE优化。的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows安装Python3
- 下一篇: 开源应用框架BitAdminCore:更