通常我们生活中遇到的图像,无论是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图像处理二】:一种自适应对数映射的高对比度图像显示技术及其速度优化。