一、原始的灰度世界算法
灰度世界算法(Gray World)是以灰度世界假設為基礎的,該假設認為對於一幅有着大量色彩變化的圖像, R、 G、 B 三個分量的平均值趨於同一個灰度K。一般有兩種方法來確定該灰度。
(1)直接給定為固定值, 取其各通道最大值的一半,即取為127或128;
(2)令 K = (Raver+Gaver+Baver)/3,其中Raver,Gaver,Baver分別表示紅、 綠、 藍三個通道的平均值。
算法的第二步是分別計算各通道的增益:
Kr=K/Raver;
Kg=K/Gaver;
Kb=K/Baver;
算法第三步為根據Von Kries 對角模型,對於圖像中的每個像素R、G、B,計算其結果值:
Rnew = R * Kr;
Gnew = G * Kg;
Bnew = B * Kb;
對於上式,計算中可能會存在溢出(>255,不會出現小於0的)現象,處理方式有兩種。
a、 直接將像素設置為255,這可能會造成圖像整體偏白。
b、 計算所有Rnew、Gnew、Bnew的最大值,然后利用該最大值將將計算后數據重新線性映射到[0,255]內。實踐證明這種方式將會使圖像整體偏暗,建議采用第一種方案。
一般來說,灰度世界算法的效果還是比較好的呢,並且該算法的執行速度非常之快,目前也存在了不少對該算法進行改進的效果,有時間我在整理一下。
原圖 處理后的圖
二、完美反射算法
當初寫這個代碼的時候的一些參考文獻一下子也找不到了,就從已經寫好的代碼中描述下該算法的過程吧。
原理:完美全反射理論perfect Reflector假設圖像上最亮點就是白點,並以此白點為參考對圖像進行自動白平衡,最亮點定義為R+G+B的最大值,具體編碼步驟如下:
(1)計算每個像素的R\G\B之和,並保存到一臨時內存塊中。
for (Y = 0; Y < Height; Y++) { Pointer = bmp.Pointer + Y * Stride; for (X = 0; X < Width; X++) { Sum = (short)(*(Pointer) + *(Pointer + 1) + *(Pointer + 2)); // R+G+B HistRGB[Sum]++; *SumP = (short)Sum; Pointer += 3; SumP++; } }
(2)按R+G+B值的大小計算出其前10%或其他Ratio的白色參考點的的閾值T。
for (Y = 767; Y >= 0; Y--) { Sum += HistRGB[Y]; if (Sum > Width * Height * Ratio / 100) { Threshold = Y; break; } }
(3)遍歷圖像中的每個點,計算其中R+G+B值大於T的所有點的R\G\B分量的累積和的平均值。
for (Y = 0; Y < Height; Y++) { Pointer = bmp.Pointer + Y * Stride; for (X = 0; X < Width; X++) { if (*SumP > Threshold) { AvgB += *Pointer; AvgG += *(Pointer + 1); AvgR += *(Pointer + 2); // 為獲得增益做准備 Amount++; } Pointer += 3; SumP++; } } AvgB /= Amount; AvgG /= Amount; AvgR /= Amount;
(4)對每個點將像素量化到[0,255]之間。
for (Y = 0; Y < Height; Y++) { Pointer = bmp.Pointer + Y * Stride; for (X = 0; X < Width; X++) { Blue = *Pointer * MaxValue / AvgB; // 另外一種算法需要先計算不抑制重新計算的RGB的范圍,然后求RGB的最大值,如果最大值大於255,則所有的結果都要除以最大值在乘以255,但實際表明該算法、 不合適; Green = *(Pointer + 1) * MaxValue / AvgG; Red = *(Pointer + 2) * MaxValue / AvgR; if (Red > 255) Red = 255; else if (Red < 0) Red = 0; // 這里需要判斷,因為RGB空間所有的顏色轉換到YCbCr后,並不是填充滿了0-255的范圍的,反轉過去就會存在一些溢出的點。 if (Green > 255) Green = 255; else if (Green < 0) Green = 0; // 編譯后應該比三目運算符的效率高 if (Blue > 255) Blue = 255; else if (Blue < 0) Blue = 0; *Pointer = (byte)Blue; *(Pointer + 1) = (byte)Green; *(Pointer + 2) = (byte)Red; Pointer += 3; } }
原圖 Ratio=10% Ratio=2%
從效果上看,該算法應該比灰度世界的效果要好些,但是也還是受到Ratio這個參數的影像。特別是第二個圖片,過高的Ration導致圖片過於泛白。這個問題可以還是最后量化的哪一步引起的,我會抽空再研究一下其他的量化方式,盡量降低Ration的影響。
針對上述的第二步,看到很多matlab和VC的代碼,有很多人居然先用快速排序對累加后的數據進行排序,然后再取其10%的最大值,對圖像的數據進行排序,可能就是再快速的排序都快不起來吧,看到這,也許全國人民都笑了。
三、動態閾值算法
參考論文:A Novel Automatic White Balance Method For Digital Still Cameras
同經典的一些算法相同,算法分為兩個步驟:白點檢測和白點調整。
白點檢測:
(1)為了增強算法的魯棒性,原文將圖像分成12部分,其中寬高比為4:3,關於這一點,我認為不合理,對圖像不是通用的,后文再說。
(2)計算每個區域的Cb\Cr分量的平均值Mb/Mr。
(3)按下式計算每個區域的Cb\Cr分量的絕對差的累積值Db/Dr:
上式中N為每個區域的像素數。
(4)如果Db/Dr的值偏小,則我們忽略這一塊,因為這表明這一塊的顏色分布比較均勻,而這樣的局部對於白平衡不好。這個偏小的准則我們稍微再談。
(5)統計對於除了符合第四條的的其他區域的Mb/Mr/Db/Dr的平均值作為整幅圖像的Mb/Mr/Db/Dr值。
關於這一條,原文的話是:The final Mb、Mr、Db、Dr are obtained by taking the average of those regions that pass this additional step。
我在實際中做的時候就是分別對每塊進行的,似乎效果也還不錯。
(6)按下述規則初步確定哪些點是屬於白色參考點:
(7)對於初步判斷已經屬於白色參考點的像素,按大小取其亮度值為前10%的位最終確定的白色參考點。
白點調整:
(1)計算白色參考點亮度值的平均值Raver,Gaver,Baver,(各通道分開計算)。
(2)按照以下各式計算每個通道的增益:
式中,Ymax就是YCbCr顏色空間中Y分量的在整幅圖像中的最大值。
(3)按照以下各式計算最終每個通道的顏色值:
其中R/G/B為在原始的顏色空間中的值,注意這里要進行溢出檢測的。
簡單的談下白點檢測的分塊操作吧,原文把圖像分成4*3的12快,這樣做事針對於我們很多數碼照片是這個比例的,如果通用,我覺得應該用每個塊的大小來控制,比如每塊為 100*100個像素。
這個算法的效果如下:
原圖 塊大小50*50 塊大小100*100
上三圖表明:1、該算法效果非常好;2、對塊大小不太敏感,因此非常適合於自動化操作。
關於RGB到YCbCr的快速轉換,可以參考:顏色空間系列3: RGB和YCbCr顏色空間的轉換及優化算法
由於在上述鏈接的文章中,YCbCr顏色空間已經被轉換到[0,255],因此白色檢測部分的第(6)條中的sign函數就不需要了。
同樣,提供個編譯好的文件給有興趣研究該算法的朋友看看效果:
http://files.cnblogs.com/Imageshop/AutoWhiteBalance.zip
后記:
針對動態閾值法,很多朋友反映如果YCbCr的值量化在0到255之間的話會出現所有的像素都會被式(6)初步判斷為白色參考點。這樣前期的工作就失去了意義,算法就變成了類似於完美反射算法那了,稍微有點不同的地方就是兩者選擇兩點的准則有所不同。雖然這樣做的最終結果還算不錯,但確實和論文的本意像違背了,后面經過實踐,如果把YCbCr的值量化在-127到128之間,式6中的Sgn同樣適用,則初步判斷為白點的數會大量的減少,對於同一個圖片,同一個參數兩個算法的最終的效果比較如下:
更新后的下載鏈接依舊如下,以增加了修正后的功能。
***************************作者: laviewpbt 時間: 2013.4.20 聯系QQ: 33184777 轉載請保留本行信息*************************