本文为转载
颜色,这样就可以构造一个较小的直方图用来加速,但是由于过渡量化会对结果带来一定的瑕疵。因此作者又用了一个平滑的过程。 最后和LC不同的是,作者的处理时在Lab空间进行的,而由于Lab空间和RGB并不是完全对应的,其量化过程还是在RGB空间完成的。
我们简单看看这个量化过程,对于一幅彩色图像,减少其RGB各分量的值,可以用Photoshop的色调分离功能直接看到其结果,如下所示:
原图:共有64330种颜色 色调分离 结果图:共有1143种颜色
(上图由于保存为JPG格式了,你们下载分析后实际颜色的数量肯定会有所不同了)。
对于上面的图,似乎觉得量化后区别不是特别大,但是我们在看一个例子:
原图:172373种颜色 结果图:共有1143种颜色
这种转换后的区别就比较大了,这就是作者说的瑕疵。
在作者的附带代码中,有这个算法的实现,我只随便看了下,觉得写的比较复杂, 于是我自己构思了自己的想法。
可以肯定的一点就是,为了加快处理速度必须降低图像的彩色信息量,但是我得控制这个降低的程度,那么我想到了我最那首的一些东西:图像的位深处理。在我的Imageshop中,可以将24位真彩色图像用尽量少的视觉损失降低为8位的索引图像。因此,我的思路就是这样,但是不用降低位深而已。
那么这个处理的第一步就是找到彩色图像的中最具有代表性的颜色值,这个过程可以用8叉树实现,或者用高4位等方式获取。 第二,就是在量化的过程中必须采用相关的抖动技术,比如ordered dither或者FloydSteinberg error diffuse等。更进一步,可以超越8位索引的概念,可以实现诸如大于256的调色板,1024或者4096都是可以的,但是这将稍微加大计算量以及编码的复杂度。我就采用256种颜色的方式。量化的结果如下图:
原图:172373种颜色 结果图:共有256种颜色
可以看到256种颜色的效果比上面的色调分离的1143种颜色的视觉效果还要好很多的。
从速度角度考虑,用8叉树得到调色板是个比较耗时的过程,一种处理方式就是从原图的小图中获取,一半来说256*256大小的小图获取的调色板和原图相比基本没有啥区别,不过这个获取小图的插值方式最好是使用最近邻插值:第一:速度快;第二:不会产生新的颜色。
最后,毕竟处理时还是有视觉损失和瑕疵,在我的算法最后也是对显著性图进行了半径为1左右的高斯模糊的。
贴出部分代码:
////// 实现功能: 基于全局对比度的图像显著性检测 /// 参考论文: 2011 CVPR Global Contrast based salient region detection Ming-Ming Cheng /// http://mmcheng.net/salobj/ /// 整理时间: 2014.8.3 /// /// 需要进行检测的图像数据,只支持24位图像。 /// 输出的显著性图像,也是24位的。 /// 输入的彩色数据的对应的灰度数据。 /// 输入图像数据的高度。 /// 图像的扫描行大小。 ///在Lab空间进行的处理,使用了整形的LAB转换,采用抖动技术将图像颜色总数量降低为256种,在利用直方图计算出显著性查找表,最后采用高斯模糊降低量化后的颗粒感。 void __stdcall SalientRegionDetectionBasedonHC(unsigned char *Src, unsigned char *SaliencyMap, int Width, int Height, int Stride) { int X, Y, XX, YY, Index, Fast, CurIndex; int FitX, FitY, FitWidth, FitHeight; float Value; unsigned char *Lab = (unsigned char *) malloc(Height * Stride); unsigned char *Mask = (unsigned char *) malloc(Height * Width); float *DistMap = (float *) malloc(Height * Width * sizeof(float)); float *Dist = (float *)malloc(256 * sizeof(float)); int *HistGram = (int *)malloc(256 * sizeof(int)); GetBestFitInfoEx(Width, Height, 256, 256, FitX, FitY, FitWidth, FitHeight); unsigned char *Sample = (unsigned char *) malloc(FitWidth * FitHeight * 3); InitRGBLAB(); for (Y = 0; Y < Height; Y++) RGBToLAB(Src + Y * Stride, Lab + Y * Stride, Width); Resample (Lab, Width, Height, Stride, Sample, FitWidth, FitHeight, FitWidth * 3, 0); // 最近邻插值 RGBQUAD *Palette = ( RGBQUAD *)malloc( 256 * sizeof(RGBQUAD)); GetOptimalPalette(Sample, FitWidth, FitHeight, FitWidth * 3, 256, Palette); ErrorDiffusionFloydSteinberg(Lab, Mask, Width, Height, Stride, Palette, true); // 先把图像信息量化到较少的范围内,这里量化到256种彩色 memset(HistGram, 0, 256 * sizeof(int)); for (Y = 0; Y < Height; Y++) { CurIndex = Y * Width; for (X = 0; X < Width; X++) { HistGram[Mask[CurIndex]] ++; CurIndex ++; } } for (Y = 0; Y < 256; Y++) // 采用类似LC的方式进行显著性计算 { Value = 0; for (X = 0; X < 256; X++) Value += sqrt((Palette[Y].rgbBlue - Palette[X].rgbBlue)*(Palette[Y].rgbBlue - Palette[X].rgbBlue) + (Palette[Y].rgbGreen- Palette[X].rgbGreen)*(Palette[Y].rgbGreen - Palette[X].rgbGreen) + (Palette[Y].rgbRed- Palette[X].rgbRed)*(Palette[Y].rgbRed - Palette[X].rgbRed)+ 0.0 ) * HistGram[X]; Dist[Y] = Value; } for (Y = 0; Y < Height; Y++) { CurIndex = Y * Width; for (X = 0; X < Width; X++) { DistMap[CurIndex] = Dist[Mask[CurIndex]]; CurIndex ++; } } Normalize(DistMap, SaliencyMap, Width, Height, Stride); // 归一化图像数据 GuassBlur(SaliencyMap, Width, Height, Stride, 1); // 最后做个模糊以消除分层的现象 free(Dist); free(HistGram); free(Lab); free(Palette); free(Mask); free(DistMap); free(Sample); FreeRGBLAB(); }
上述方式比直接的Bruce-force的实现方式快了NNNN倍,比原作者的代码也快一些。并且效果基本没有啥区别。
原图 HC结果,用时20ms 直接实现:150000ms 原作者的效果
我做的HC和原作者的结果有所区别,我没仔细看代码,初步怀疑是不是LAB空间的处理不同造成的,也有可能是最后的浮点数量化到[0,255]算法不同造成的。
三:AC算法
参考论文:Salient Region Detection and Segmentation Radhakrishna Achanta, Francisco Estrada, Patricia Wils, and Sabine SÄusstrunk 2008 , Page 4-5
这篇论文提出的算法的思想用其论文的一句话表达就是:
saliency is determined as the local contrast of an image region with respect to its neighborhood at various scales.
具体实现上,用这个公式表示:
以及:
其实很简单,就是用多个尺度的模糊图的显著性相加来获得最终的显著性。关于这个算法的理论分析,FT算法那个论文里有这样一段话:
Objects that are smaller than a filter size are detected ompletely, while objects larger than a filter size are only artially detected (closer to edges). Smaller objects that are well detected by the smallest filter are detected by all three filters, while larger objects are only detected by the larger filters. Since the final saliency map is an average of the three feature maps (corresponding to detections of he three filters), small objects will almost always be better highlighted.
这个算法编码上也非常简单:
////// 实现功能: saliency is determined as the local contrast of an image region with respect to its neighborhood at various scales /// 参考论文: Salient Region Detection and Segmentation Radhakrishna Achanta, Francisco Estrada, Patricia Wils, and Sabine SÄusstrunk 2008 , Page 4-5 /// 整理时间: 2014.8.2 /// /// 需要进行检测的图像数据,只支持24位图像。 /// 输出的显著性图像,也是24位的。 /// 输入的彩色数据的对应的灰度数据。 /// 输入图像数据的高度。 /// 图像的扫描行大小。 /// inner region's radius R1。 /// outer regions's min radius。 /// outer regions's max radius。 /// outer regions's scales。 ///通过不同尺度局部对比度叠加得到像素显著性。 void __stdcall SalientRegionDetectionBasedonAC(unsigned char *Src, unsigned char *SaliencyMap, int Width, int Height, int Stride, int R1, int MinR2, int MaxR2, int Scale) { int X, Y, Z, Index, CurIndex; unsigned char *MeanR1 =(unsigned char *)malloc( Height * Stride); unsigned char *MeanR2 =(unsigned char *)malloc( Height * Stride); unsigned char *Lab = (unsigned char *) malloc(Height * Stride); float *DistMap = (float *)malloc(Height * Width * sizeof(float)); InitRGBLAB(); for (Y = 0; Y < Height; Y++) RGBToLAB(Src + Y * Stride, Lab + Y * Stride, Width); // 注意也是在Lab空间进行的 memcpy(MeanR1, Lab, Height * Stride); if (R1 > 0) // 如果R1==0,则表示就取原始像素 BoxBlur(MeanR1, Width, Height, Stride, R1); memset(DistMap, 0, Height * Width * sizeof(float)); for (Z = 0; Z < Scale; Z++) { memcpy(MeanR2, Lab, Height * Stride); BoxBlur(MeanR2, Width, Height, Stride, (MaxR2 - MinR2) * Z / (Scale - 1) + MinR2); for (Y = 0; Y < Height; Y++) { Index = Y * Stride; CurIndex = Y * Width; for (X = 0; X < Width; X++) // 计算全图每个像素的显著性 { DistMap[CurIndex] += sqrt( (MeanR2[Index] - MeanR1[Index]) * (MeanR2[Index] - MeanR1[Index]) + (MeanR2[Index + 1] - MeanR1[Index + 1]) * (MeanR2[Index + 1] - MeanR1[Index + 1]) + (MeanR2[Index + 2] - MeanR1[Index + 2]) * (MeanR2[Index + 2] - MeanR1[Index + 2]) + 0.0) ; CurIndex++; Index += 3; } } } Normalize(DistMap, SaliencyMap, Width, Height, Stride, 0); // 归一化图像数据 free(MeanR1); free(MeanR2); free(DistMap); free(Lab); FreeRGBLAB(); }
核心就是一个 boxblur,注意他也是在LAB空间做的处理。
以上检测均是在R1 =0 , MinR2 = Min(Width,Height) / 8 . MaxR2 = Min(Width,Height) / 2, Scale = 3的结果。
4、FT算法
参考论文: Frequency-tuned Salient Region Detection, Radhakrishna Achantay, Page 4-5, 2009 CVPR
这篇论文对显著性检测提出了以下5个指标:
1、 Emphasize the largest salient objects.
2、Uniformly highlight whole salient regions.
3、Establish well-defined boundaries of salient objects.
4、Disregard high frequencies arising from texture, noise and blocking artifacts.
5、Efficiently output full resolution saliency maps.
而起最后提出的显著性检测的计算方式也很简答 :
where I is the mean image feature vector, I!hc (x; y) is the corresponding image pixel vector value in the Gaussian blurred version (using a 55 separable binomial kernel) of the original image, and || *|| is the L2 norm.
这个公式和上面的五点式如何对应的,论文里讲的蛮清楚,我就是觉得那个为什么第一项要用平局值其实直观的理解就是当高斯模糊的半径为无限大时,就相当于求一幅图像的平均值了。
这篇论文作者提供了M代码和VC的代码,但是M代码实际上和VC的代码是不是对应的, M代码是有错误的,他求平均值的对象不对。
我试着用我优化的整形的LAB空间来实现这个代码,结果和原作者的效果有些图有较大的区别,最后我还是采用了作者的代码里提供的浮点版本的RGBTOLAB。
相关参考代码如下:
////// 实现功能: 基于Frequency-tuned 的图像显著性检测 /// 参考论文: Frequency-tuned Salient Region Detection, Radhakrishna Achantay, Page 4-5, 2009 CVPR /// http://ivrgwww.epfl.ch/supplementary_material/RK_CVPR09/ /// 整理时间: 2014.8.2 /// /// 需要进行检测的图像数据,只支持24位图像。 /// 输出的显著性图像,也是24位的。 /// 输入的彩色数据的对应的灰度数据。 /// 输入图像数据的高度。 /// 图像的扫描行大小。 ///在Lab空间进行的处理,但是不能用库中的整形RGBLAB颜色函数,必须用原始的浮点数处理。不然很多结果不明显,原因未知。 void __stdcall SalientRegionDetectionBasedOnFT(unsigned char *Src, unsigned char *SaliencyMap, int Width, int Height, int Stride) { int X, Y, XX, YY, Index, Fast, CurIndex, SrcB, SrcG, SrcR, DstB, DstG, DstR; float *Lab = (float *) malloc(Height * Stride * sizeof(float)); float *DistMap = (float *) malloc(Height * Width * sizeof(float)); float MeanL = 0, MeanA = 0, MeanB = 0; for (Y = 0; Y < Height; Y++) RGBToLABF(Src + Y * Stride, Lab + Y * Stride, Width); // 浮点类型的数据转换 for (Y = 0; Y < Height; Y++) { Index = Y * Stride; for (X = 0; X < Width; X++) { MeanL += Lab[Index]; MeanA += Lab[Index + 1]; MeanB += Lab[Index + 2]; Index += 3; } } MeanL /= (Width * Height); // 求LAB空间的平均值 MeanA /= (Width * Height); MeanB /= (Width * Height); GuassBlurF(Lab, Width, Height, Stride, 1); // use Gaussian blur to eliminate fine texture details as well as noise and coding artifacts for (Y = 0; Y < Height; Y++) // 网站的matlab代码的blur部分代码不对 { Index = Y * Stride; CurIndex = Y * Width; for (X = 0; X < Width; X++) // 计算像素的显著性 { DistMap[CurIndex++] = (MeanL - Lab[Index]) * (MeanL - Lab[Index]) + (MeanA - Lab[Index + 1]) * (MeanA - Lab[Index + 1]) + (MeanB - Lab[Index + 2]) * (MeanB - Lab[Index + 2]) ; Index += 3; } } Normalize(DistMap, SaliencyMap, Width, Height, Stride); // 归一化图像数据 free(Lab); free(DistMap); }
检测效果如下图:
五、四种算法的综合比较
通过一些试验图像,我到时觉得4种算法,FT的效果最为明显,举例如下:
原图 FT(50ms) AC(25ms)
LC(2ms) AC(23ms)
只有FT检测出了那个叶。
LC AC
六、下一步工作
这里我研究的几种显著性分析都是很简单很基础的算法,实现起来也比较方便,现在还有很多效果显著但是算法比较复杂的论文,等有空或者有能力的是在去看看他们。在这显著性分析只是很多其他处理的第一步,有了这个基础,我也想看看后续的分割或者再感知缩放方面的应用吧。
http://files.cnblogs.com/Imageshop/salientregiondetection.rar
做了一个测试集。