辦公室今天停電,幸好本本還有電,同事們好多都去打麻將去了,話說麻將這東西玩起來也還是有味的,不過我感覺我是輸了不舒服,贏了替輸的人不舒服,所以干脆拜別麻壇四五年了,在辦公室一個人整理下好久前的一片論文的思想,和萬千世界里有緣人共同分享下資源了。
論文的名字是<Adaptive Logarithmic Mapping For Displaying High Contrast Scenes>,相關的PDF文檔可以在百度上下載到,翻譯成中文的意思是一種顯示高對比度場景的自適應對數映射算法,也是一篇很古老的算法文章的,看了下好像是2003年的,在Opencv 3.0中已經提供了這個算法的實現了,但是其實現的細節我覺得寫的真惡心(我覺得Opencv所有的算法寫的都惡心,饒了一堆彎才到算法的中點,感覺大的工程都是這樣的),其算法位於OpenCV3.0\opencv\sources\modules\photo\src\tonemap文件中,我在實現這個算法時時參考了另外一個非常有名的開源軟件:luminance hdr, 這個軟件最新的版本集成了11中用於HDR顯示的算法。過完年我要再次好好的看看這個軟件的代碼了。
話說回本文的重點,由於對論文的理解不是很深刻,部分內容僅以翻譯為主。
一、應用背景
簡單的說,就是我們認為顯示給人眼看的亮度值Ld和場景亮度值Lw之間存在如下的關系:
其中Lmax為場景的最大亮度,這個映射關系式能夠保證無論真實亮度范圍有多大,最亮的部分總能映射為1(白色),而其他的亮度也能平緩的變化。雖然這個算式對一些圖能獲得較為滿意的結果,但是我們也發現有些圖的亮度壓縮太過了,一些高對比度的內容也丟失了。
二、自適應對數映射
那么論文提出的色調映射方案遵循了下面幾條規則:(1)不管原始數據如何分散,他必須都能輸出連續的結果。(2)它應該具有自適應性和可擴展性,他必須能顯示出場景的物理本質同時不得引入對比度反轉和光暈。總的亮度也必須忠實於實際的內容。(3)算法也需要對用戶友好,也就是說在大部分情況下需要能自動實現,少數情況需要調節一些比較直觀的參數。
1、把場景亮度映射到圖像亮度
輸出圖像的整體亮度主要是由場景的亮度特性決定的,所以找到一個從場景亮度到輸出圖像亮度之間的初始縮放因子很重要,這就類似於在攝影中曝光設置決定了所拍攝的圖片的最終效果一樣。現在的鏡頭都提供的不同的自動曝光選項,比如center- weighted, center-spot以及 matrix-metering等。同樣,本文提出兩種不同的方法適合於不同的用途。對於靜態圖片或者當用戶不直接和場景交互,我們基於所有像素的亮度信息計算出場景的對數平均值作為初始縮放因子,對於需要交互的場合,縮放因子不是固定值,而是使用亮度的對數信息的高斯模糊后的結果,通常高斯模糊核能覆蓋場景的15%范圍即可以,當然也可以調整這個范圍。
2、對比度調整
本文提出的核心的最具特色的色調映射函數就是根據每個像素的信息來自適應的調整函數中的對數基(從2到10)。這從本質上提供了很好的對比度和細節信息,同時能對高亮度值進行很大程度上的壓縮。原則上,一個更寬或者更窄的對數基也可以使用,但是實際上,我們沒有任何理由去使用它。當對數基小於2時,其值迅速增加,導致曝光調整很困難。另一方面,當對數基大於10時,亮度壓縮的量很小,導致這個圖片丟失了太多的對比度。同時,我們也觀察到了高對數基時的一些顏色偏移現象。
如下左圖所示,左圖時基於2對數基的,有圖是基於10對數基的(全圖),很明顯,兩幅圖的對比度和亮度區別很明顯,但是他們都沒有給出非常令人滿意的結果。
為了實現不同像素不同的對數基以及像素的連續性的要求,我們參考了 Perlin和Hoffert的偏置對數函數,該函數是紋理分析的的標准工具並且在計算機視覺上廣為應用。偏置函數定義在單位區間[0,1]之間的power函數,有一個參數b,直接決定了輸出值的大小,具體形式如下:
其曲線如上右圖所示。
2.3 算法細節
一般數據是基於RGB空間的,我們首先將數據轉換到XYZ空間,其中的Y分量反應了每個像素的亮度值。我們首先基於Y分量計算出對數的最大亮度Lwmax和平均亮度Lwa。然后把公式(3)帶入公式(1)並做適當的闊啊站,得到最終的計算顯示亮度的公式如下所示:
相比於論文,上述公式后半部分是我自己添加上去的,主要是為了解釋方便。
我們首先看下算式中的,很明顯,他的取值范圍是2到10之間,這和論文前面的描述是一致的。然后底部的Log10也保證了整個算式的區間范圍。
然后我們知道,對數計算式有如下特性:
因此把公式(4)的后半部分展開就到了論文的結果。
式中有多了個參數Ldmax,這個值表示顯示設備的最大顯示能力,對於普通的CRT顯示器,我們直接取值為100。
論文后面還有關於Gamma校正的內容,那些都是輔助的了,實際上沒啥意思,論文核心的就是上述公式。
三:具體實現即細節注意:
具體實現代碼可以完美的參考luminance hdr,關鍵是要注意一些數據的范圍要映射到0和1之間才能處理,特別是論文有些地方其實沒有講的特別清晰,比如在論文里有這樣一句話:The XYZ luminance component Y of each pixel (Lw for world luminance) and the maximum luminance of the scene Lwmax are divided by the world adaptation luminance Lwa and eventually multiplied by an exposure factor set by the user。這里其實沒有明確的說Lwa是什么(整篇論文都沒有說)其實就是上面講的對數平均值,還有最后直接用公式計算得到的Ld一般情況下是很小的,如何處理讓其顯示也是值得講究的。我這里貼出對公式(4)計算的核心代碼:
void ComputeScaleTable(float ScaleTable[], float Bias, float Saturation, float MaxLum, float AvLum) { float Divider, BiasP, NormalY, Interpol, NewLum; MaxLum = MaxLum / AvLum; // normalize maximum luminance by average luminance(divided by the world adaptation luminance Lwa) Divider = log10f(MaxLum + 1.0f); // 論文公式(4)左半部分的除數 BiasP = logf(Bias) / log(0.5); // 公式(3)中的上指標 for (int Index = 0; Index < 256; Index++) // Normal tone mapping of every pixel { // The XYZ luminance component Y of each pixel (Lw for world luminance) and the maximum luminance of the scene // Lwmax are divided by the world adaptation luminance Lwa and eventually multiplied by an exposure factor set by the user. NormalY = (Index / 255.0) / AvLum; // divided by the world adaptation luminance Lwa Interpol = logf(2.0f + powf(NormalY / MaxLum, BiasP) * 8.0f); // 論文公式(4)右半部分的除數 NewLum = (logf(NormalY + 1.0f) / Interpol) / Divider; // 論文公式(4) ScaleTable[Index] = powf(NewLum / (Index / 255.0 + 1e-4), Saturation); } }
以上代碼是針對8位圖像的,上面的/255.0就是歸一化到[0,1]范圍的作用。但是最后一行的NewLum / (Index / 255.0 + 1e-4)你們能理解是什么意思嗎?
最后一行代碼的Saturation的作用見<Gradient domain high dynamic range compression>一文,當大於1時,圖像越飽和也越亮,小於1是圖像變暗。
雖然論文描述的算法本意是用到HDR這種高動態范圍的圖像的,但是實際上我目前也只實現了8位的LDR的代碼,但是對於8位的圖像,特別是偏暗的圖像還是有很好的增強的效果的,對於正常的圖像,一般也不會出現特別不好的效果。
對於常態的圖片,一般也能起到一定的視覺增強效果:
關於速度優化方面,如果是針對8位圖像,則中間的很多浮點計算可以用查表代替,而XYZ和RGB空間轉化,我前面一篇博客已經提到可以用SSE快速實現。處理1080P的圖大概需要20ms。
也曾嘗試不轉到XYZ空間,直接提取出亮度信息,然后直接在RGB空間處理,似乎效果也還可以,但是有可能會出現較多的偏色。
明年有時間把這個算法擴展到16位圖像上取看看,有什么效果。
8位測試工程:https://files.cnblogs.com/files/Imageshop/TonemapDrago.rar