通常我們生活中遇到的影像,無論是jpg、還是png或者bmp格式,一般都是8位的(每個通道的畫素值範圍是0-255),但是隨著一些硬體的發展,在很多行業比如醫療、紅外、航拍等一些場景下,擁有更寬的量化範圍的影像也越來越常見,比如10位(頻寬1024)、12位(頻寬4096)、14位(頻寬16384)以及16位(頻寬32768)的影像,當然還有以浮點數儲存的高動態影像(hdr格式的那種),但是目前大部分的顯示器還是隻支援8點陣圖像的顯示,因此,對於這一類影像,一個很重要的問題就是如何將他們的資料量化到0到255之間,而且儘量的保留更多的細節資訊,這也就是常見的HDR到LDR的過程。 在我前面的部落格裡其實也有講到這方面的資訊,本文再嘗試將直方圖均衡化引入到這個過程中。
首先,我們統一一下由一組ushort資料(頻寬是10、12、14、16的Raw影像,都可以用ushort資料型別表示)直接量化為8位顯示的函式,這樣我們的處理就可以集中在原始的ushort資料經過演算法處理後得到新的ushort資料的過程。這個函式簡單如下所示:
// 這個只是個輔助用來顯示的函式
int IM_ConvetUshortToByte(unsigned short *Src, unsigned char *Dest, int Width, int Height, int Stride, int WindowWidth, int WindowLevel)
{
int Channel = Stride / Width;
int Min = WindowLevel - WindowWidth / 2;
int Max = WindowLevel + WindowWidth / 2;
if (Min < 0) Min = 0;
if (Max > 65535) Max = 65535;
int Diff = Max - Min;
if (Diff == 0)
{
memset(Dest, Max, Height * Stride * sizeof(unsigned char));
}
else
{
unsigned char Table[65536];
for (int X = 0; X < 65536; X++)
{
if (X < Min)
Table[X] = 0;
else if (X > Max)
Table[X] = 255;
else
Table[X] = IM_ClampToByte((X - Min) * 255 / Diff);
}
for (int Y = 0; Y < Height; Y++)
{
unsigned short *LinePS = Src + Y * Width * Channel;
unsigned char *LinePD = Dest + Y * Stride;
for (int X = 0; X < Width * Channel; X++)
{
LinePD[X] = Table[LinePS[X]];
}
}
}
return IM_STATUS_OK;
}
其中的WindowWidth表示窗寬,WindowLevel表示窗位,這個其實是藉助了醫學影像上的一些概念,對於普通的RAW影像,比如12位,我們通常就認為WindowWidth = 1 << 12 = 4096,而WindowLevel則就為窗寬的一半。
一般來說,RAW影像中的資料每一行是沒有冗餘量的,即沒有BMP點陣圖中所謂的掃描行對齊的概念。所以可以直接遍歷每一個資料。
那麼我們來看看如何把普通的直方圖均衡化演算法利用到RAW影像中來。
以灰度圖為例,如果已經統計了影像的直方圖,則直方圖均衡化的新的隱射曲線由以下程式碼獲取:
for (int Y = 0, Num = 0; Y < 256; Y++)
{
Num = Num + Histgram[Y];
Table[Y] = (unsigned char)(((float)Num * 255) / (Width * Height)); // 注意(float)強制轉換的位置,否則對於大圖就溢位了,2014.11.1修正
}
簡單的,擴充套件到ushort型別的RAW影像,我們也可以用以下的類似程式碼搞定:
for (int Y = 0, Num = 0; Y < WindowWidth; Y++)
{
Num = Num + Histgram[Y];
Table[Y] = (unsigned short)(((float)Num * WindowWidth) / (Width * Height)); // 注意(float)強制轉換的位置,否則對於大圖就溢位了,2014.11.1修正
}
關於這個WindowWidth,我覺得一般可以由兩種方法得到,一個是我們已經知道了這個硬體生成的影像是多少位的,常見的就是10、12、14、16等,這個時候WindowWidth可以直接指定,而如果只有RAW資料,一種方式就是根據資料的最大值來確定WindowWidth,即取大於最大值的2的整數次冪的那個值。
如下程式碼所示:
ushort MaxValue = IM_GetMaxValue(ImageData, ImageWidth * ImageHeight * ImageChannel);
int ProperWindowWidth = 1;
while (ProperWindowWidth < MaxValue)
ProperWindowWidth *= 2;
WindowWidth = ProperWindowWidth;
WindowLevel = WindowWidth / 2;
這個簡單的資料範圍的調整,我們看下一些效果:
a、RAW資料直接ConvetUshortToByte的8位結果圖 b、直方圖均衡後的RAW資料轉換為8位的效果圖 c、對8位的a圖直接在直方圖均衡後的結果圖
通過比較可以看到確實還是有明顯的增強效果的,但是似乎有過曝的現象。
我們可以仿照一種強化的基於區域性直方圖裁剪均衡化的對比度調節演算法 或者限制對比度自適應直方圖均衡化演算法原理、實現及效果 文中的方法將區域性直方圖均衡化引入到16位中,嘗試看看效果是否有改善,這裡不多談,只說下我遇到的幾個問題。
一個是在ClipHistogram 這個函式的過程中,我們發現往往會出現這個函式陷入死迴圈的結果,特備是對於12位以上的影像,因此,這個可能需要其他的一些改進方案。
二個是我們還可以學習【演算法隨記四】自動色階、對比度、直方圖均衡等演算法的一些小改進 一文中的getWeightedValue函式,即對獲取的直方圖資料開平方,起到一定的壓縮作用,這個可以明顯的改善上述的曝光效果。
另外,同樣的道理,在區域性演算法裡,還可以不用直方圖均衡化演算法,可以使用任何其他的基於直方圖的調整基數,比如自動色劑等等。
a、RAW資料直接ConvetUshortToByte的8位結果圖 b、區域性壓縮直方圖均衡後的RAW資料轉換為8位的效果圖
很明顯這樣處理後的效果要好很多。細節、曝光等等都較為合適。
關於16位RAW影像,本人開發了一個簡易的增強和處理程式,可在 https://files.cnblogs.com/files/Imageshop/Optimization_Demo_16.rar下載測試。
其他相關連結:
【16位RAW影像處理一】:基於Fast Bilateral Filtering 演算法的 High-Dynamic Range(HDR) 影像顯示技術。
【16位RAW影像處理二】:一種自適應對數對映的高對比度影像顯示技術及其速度優化。
如果想時刻關注本人的最新文章,也可關注公眾號: