一、引言
本人初次接觸HDR方面的知識,有描述不正確的地方煩請見諒。
為方便文章描述,引用部分百度中的文章對HDR圖像進行簡單的描述。
高動態范圍圖像(High-Dynamic Range,簡稱HDR),相比普通的圖像,可以提供更多的動態范圍和圖像細節,根據不同的曝光時間的LDR(Low-Dynamic Range)圖像,利用每個曝光時間相對應最佳細節的LDR圖像來合成最終HDR圖像,能夠更好的反映人真實環境中的視覺效果。
現實真正存在的亮度差,即最亮的物體亮度,和最小的物體亮度之比為108, 而人類的眼睛所能看到的范圍是105左右,但是一般的顯示器,照相機能表示的只有256種不同的亮度,計算一般的顯示器,照相機能表示的只有256種不同的亮度機在表示圖象的時候是用8bit(256)級或16bit(65536)級來區分圖象的亮度的,但這區區幾百或幾萬無法再現真實自然的光照情況。HDR文件是一種特殊圖形文件格式,它的每一個像素除了普通的RGB信息,還有該點的實際亮度信息。普通的圖形文件每個象素只有0 -255的灰度范圍,這實際上是不夠的。想象一下太陽的發光強度和一個純黑的物體之間的灰度范圍或者說亮度范圍的差別,遠遠超過了256個級別。因此,一張普通的白天風景圖片,看上去白雲和太陽可能都呈現是同樣的灰度/亮度,都是純白色,但實際上白雲和太陽之間實際的亮度不可能一樣,他們之間的亮度差別是巨大的。因此,普通的圖形文件格式是很不精確的,遠遠沒有紀錄到現實世界的實際狀況。而HDR格式則記錄了很廣范圍內的亮度信息。
但是最終,HDR圖像要在顯示器中顯示,還是需要對其數據進行處理的,如何處理即能充分利用這些數據,又能使得圖像的顯示盡量不丟失細節,是多年來不少圖像工作者研究的重點。
簡單的說,就是現在有一堆離散的數據,數據的分布范圍可能很廣,如何把這些離散的數據隱射到0到255之間。
二、相關算法的實現
最簡單的當然是線性隱射,先算出離散數據的最大值和最小值,然后將數據線性的拉升至0到255之間,這種直接的操作往往無法得到滿意的效果,會導致大量細節丟失,表現在視覺上就是一大塊黑色或者一大塊白色的,如下圖所示:
、
上面兩幅圖要么是暗部太暗,要么是亮部太亮,整體對比度太強,導致細節信息大量丟失。
針對這一問題,很多人提出了不少相當不錯的解決方案,比如基於全局操作符的,其中本文作者實現其中的基於快速雙邊濾波技術的HDR顯示過程。
本文對應的參考論文地址: Fast Bilateral Filtering for the Display of High-Dynamic-Range Images
論文的細路很簡單,首先他將原始的HDR數據分解成兩個層:base layer 和 detail layer,然后降低base layer的對比度,不改變detail layer的數據,在將這兩層合並。
其中:base layer的數據用 HDR原始數據進行雙邊濾波獲取。
算法的簡單流程入下所示:
1、input intensity= 1/61*(R*20+G*40+B)
2、r=R/(input intensity), g=G/input intensity, B=B/input intensity
3、log(base)=Bilateral(log(input intensity))
4、log(detail)=log(input intensity)-log(base)
5、log (output intensity)=log(base)*compressionfactor+log(detail) - log_absolute_scale
6、R output = r*10^(log(output intensity)), etc.
上述過程中的變量compressionfactor,log_absolute_scale原文作者的建議取值為:
compressionfactor = targetContrast/(max(log(base)) - min(log(base))) 對於很多圖像,targetContrast使用log(5)能獲得較為理想的值。
而log_absolute_scale= max(log(base))*compressionfactor;
在進行雙邊的時候,SigmaS一般取值為0.02*Max(Width,Height)比較合適,而SigmaR取值0.4較為理想(這里是指數據量化到了0-1之間的)。
所以都取優化的參數,則上述過程可以自動進行。
作者提到上述log操作都是以10為底進行的,我覺得以e為底實際效果也沒啥區別的。
三、效果
按照這個思路編制程序后,確實能取得很不錯的效果,比如上述兩幅圖像,按照前面講的參數取值,解碼后得到的圖像如下:

可見,圖像的細節較為完美的體現出來了。
當然,自動的參數不一定能調處最好的效果,比如還是這兩幅圖,手工選擇一些參數,可以調出如下效果:

特別是第一幅圖,很有種蒙太奇的感覺。
在看看幾張長出現在論文中的圖像的結果:



線性解碼圖 雙邊濾波解碼圖
有些圖線性解碼啥都看不到,雙邊濾波解碼后細節表現的就很清晰了。
HDR格式的原始數據的解碼可以借助FreeImage來實現,FreeImage似乎已經講這些數據量化到了0和1之間(不一定正確)。一段簡單的實現代碼如下:
public Bitmap LoadHdrFormFreeImage(string FileName) { Bitmap Bmp = null; FREE_IMAGE_FORMAT fif = FREE_IMAGE_FORMAT.FIF_UNKNOWN; ; if (FreeImage.IsAvailable() == true) { fif = FreeImage.GetFileType(FileName, 0); if (fif != FREE_IMAGE_FORMAT.FIF_HDR) { MessageBox.Show("不是Hdr格式的圖像."); return null; } fif = FreeImage.GetFIFFromFilename(FileName); FIBITMAP Dib = FreeImage.Load(fif, FileName, FREE_IMAGE_LOAD_FLAGS.DEFAULT); uint Bpp = FreeImage.GetBPP(Dib); if (Bpp != 96) { MessageBox.Show("無法支持的Hdr格式."); FreeImage.Unload(Dib); return null; } uint Width = FreeImage.GetWidth(Dib); // 圖像寬度 uint Height = FreeImage.GetHeight(Dib); // 圖像高度 uint Stride = FreeImage.GetPitch(Dib); // 圖像掃描行的大小,必然是4的整數倍 IntPtr Bits = FreeImage.GetBits(Dib); float* Data = (float*)Bits; int Speed, Index; byte* Pixel; float Value; if (RawData != null) Marshal.FreeHGlobal((IntPtr)RawData); RawData = (float*)Marshal.AllocHGlobal((int)Width * (int)Height * 3 * sizeof(float)); CopyMemory(RawData, Data, (int)Width * (int)Height * 3 * sizeof(float)); Bmp = new Bitmap((int)Width, (int)Height, PixelFormat.Format24bppRgb); BitmapData BmpData = Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); Pixel = (byte*)BmpData.Scan0; for (int Y = 0; Y < Height; Y++) { Speed = Y * BmpData.Stride; Index = Y * (int)Width * 3; for (int X = 0; X < Width; X++) { Value = (Data[Index + 2] * 255); if (Value > 255) Value = 255; else if (Value < 0) Value = 0; Pixel[Speed] = (byte)Value; Value = (Data[Index + 1] * 255); if (Value > 255) Value = 255; else if (Value < 0) Value = 0; Pixel[Speed + 1] = (byte)Value; Value = (Data[Index + 0] * 255); if (Value > 255) Value = 255; else if (Value < 0) Value = 0; Pixel[Speed + 2] = (byte)Value; Index += 3; Speed += 3; } } FreeImage.Unload(Dib); Bmp.UnlockBits(BmpData); Bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); return Bmp; } else return null; }
以上采用的是線性解碼。
附一個解碼的調用程序:http://files.cnblogs.com/Imageshop/ReadHdrTest.rar
更多的源碼可參考:http://people.csail.mit.edu/sparis/code/src/tone_mapping
http://people.csail.mit.edu/fredo/PUBLI/Siggraph2002/
一些常見的用於測試的HDR圖像可以從這里下載:http://www.pauldebevec.com/Research/HDR/
同樣,這種 tone mapping算法也可以用在普通的RGB圖像上,效果如下所示:

原圖 增加base layer的對比度

原圖 降低base layer的對比度
對於普通的整體偏暗的圖像,該方式也能取得相當理想的效果,一些測試結果如下:



很多其他的算法也能起到類似上述的效果,不過他們一般很容易產生halo現象。
相信對於偏亮的普通照片,也可以有同樣的處理能力(未找到合適的測試圖片)。

*********************************作者: laviewpbt 時間: 2013.11.18 聯系QQ: 33184777 轉載請保留本行信息************************
