咱们常见的模糊算法好比均值模糊、高斯模糊等其基本的过程都是计算一个像素周边的的某个领域内,相关像素的某个特征值的累加和及对应的权重,而后获得结果值。好比均值模糊的各像素的权重是同样的,而高斯模糊的权重和像素距离中心点的距离成高斯分布。这样的过程是没法区分出图像的边缘等信息的,致使被模糊后的图像细节严重丢失,一种简单的改进方式就是设置某个阈值,当领域像素和中心点像素的差距大于阈值时,设置其权重很小,甚至为0,这样对于自己比较平滑的区域,和原始的算法区别不大,而对于像素值变化较为明显的边缘地带,则可以有效地保留原始信息,这样就能起到下降噪音的同时保留边缘的信息。html
在实际的处理,小半径的领域每每处理能力有限,处理的结果不慎理想,而随着半径的增长,算法的直接实现耗时成平方关系增加,传统的优化方式因为这个判断条件的增长,已经没法继续使用,为了解决速度问题,咱们能够采用基于直方图算法的优化,若是可以统计出领域内的直方图信息,上述的判断条件及权重计算就能够简单的用下述代码实现:算法
void Calc(unsigned short *Hist, int Intensity, unsigned char *&Pixel, int Threshold) { int K, Low, High, Sum = 0, Weight = 0; Low = Intensity - Threshold; High = Intensity + Threshold; if (Low < 0) Low = 0; if (High > 255) High = 255;
for (K = Low; K <= High; K++) { Sum += Hist[K] * K; Weight += Hist[K]; } if (Weight != 0) *Pixel = Sum / Weight; }
注意在for以前的越界判断。数据结构
在任意半径局部直方图类算法在PC中快速实现的框架一文中咱们已经实现了任意半径恒长时间的直方图信息的获取,所以算法的执行时间只于上for循环中的循环量有关,也就是取决于Threshold参数,当Threshold取得越大,则最终的效果就越接近标准的模糊算法(上述代码是接近均值模糊),而在实际有意义的算法应用中而只有Threshold每每要取得较小才有保边的意义,所以,计算量能够获得适度的控制。框架
若是要实现选择性的高斯模糊,则要在for循环中的权重项目中再乘以一个系数,固然这会增长必定的计算量。函数
咱们选择了一些其余保边滤波器的测试图像进行了测试,在效果上经过调整参数能获得至关不错的效果,举例以下:post
原图 结果图: 参数r =10, Threshold = 16测试
原图 结果图: 参数r =10, Threshold = 16优化
原图 结果图: 参数r =10, Threshold = 16url
原图 结果图: 参数r =10, Threshold = 40spa
在处理时间上,使用如上参数,在I3的笔记本电脑上测试,一幅1024*768的彩色图像使用时间约为250ms,若是考虑使用YUV颜色空间中只处理Y份量,则速度越能提高到100ms,在结果上,同一样参数的表面模糊比较,彷佛很相似,但比表面模糊速度快了近3倍。
附上工程函数的主要代码:
/// <summary> /// 实现图像选择性图像模糊效果,O(1)复杂度,最新整理时间 2015.8.1。 /// </summary> /// <param name="Src">须要处理的源图像的数据结构。</param> /// <param name="Dest">保存处理后的图像的数据结构。</param> /// <param name="Radius">指定模糊取样区域的大小,有效范围[1,127]。</param> /// <param name="Threshold">选项控制相邻像素色调值与中心像素值相差多大时才能成为模糊的一部分,色调值差小于阈值的像素被排除在模糊以外,有效范围[1,255]。</param> IS_RET __stdcall SelectiveBlur(TMatrix *Src, TMatrix *Dest, int Radius, int Threshold, EdgeMode Edge) { if (Src == NULL || Dest == NULL) return IS_RET_ERR_NULLREFERENCE; if (Src->Data == NULL || Dest->Data == NULL) return IS_RET_ERR_NULLREFERENCE; if (Src->Width != Dest->Width || Src->Height != Dest->Height || Src->Channel != Dest->Channel || Src->Depth != Dest->Depth || Src->WidthStep != Dest->WidthStep) return IS_RET_ERR_PARAMISMATCH; if (Src->Depth != IS_DEPTH_8U || Dest->Depth != IS_DEPTH_8U) return IS_RET_ERR_NOTSUPPORTED; if (Radius < 0 || Radius >= 127 || Threshold < 2 || Threshold > 255) return IS_RET_ERR_ARGUMENTOUTOFRANGE; IS_RET Ret = IS_RET_OK; if (Src->Data == Dest->Data) { TMatrix *Clone = NULL; Ret = IS_CloneMatrix(Src, &Clone); if (Ret != IS_RET_OK) return Ret; Ret = SelectiveBlur(Clone, Dest, Radius, Threshold, Edge); IS_FreeMatrix(&Clone); return Ret; } if (Src->Channel == 1) { TMatrix *Row = NULL, *Col = NULL; unsigned char *LinePS, *LinePD; int X, Y, K, Width = Src->Width, Height = Src->Height; int *RowOffset, *ColOffSet; unsigned short *ColHist = (unsigned short *)IS_AllocMemory(256 * (Width + 2 * Radius) * sizeof(unsigned short), true); if (ColHist == NULL) {Ret = IS_RET_ERR_OUTOFMEMORY; goto Done8;} unsigned short *Hist = (unsigned short *)IS_AllocMemory(256 * sizeof(unsigned short), true); if (Hist == NULL) {Ret = IS_RET_ERR_OUTOFMEMORY; goto Done8;} Ret = GetValidCoordinate(Width, Height, Radius, Radius, Radius, Radius, Edge, &Row, &Col); // 获取坐标偏移量 if (Ret != IS_RET_OK) goto Done8; ColHist += Radius * 256; RowOffset = ((int *)Row->Data) + Radius; ColOffSet = ((int *)Col->Data) + Radius; // 进行偏移以便操做 for (Y = 0; Y < Height; Y++) { if (Y == 0) // 第一行的列直方图,要重头计算 { for (K = -Radius; K <= Radius; K++) { LinePS = Src->Data + ColOffSet[K] * Src->WidthStep; for (X = -Radius; X < Width + Radius; X++) { ColHist[X * 256 + LinePS[RowOffset[X]]]++; } } } else // 其余行的列直方图,更新就能够了 { LinePS = Src->Data + ColOffSet[Y - Radius - 1] * Src->WidthStep; for (X = -Radius; X < Width + Radius; X++) // 删除移出范围内的那一行的直方图数据 { ColHist[X * 256 + LinePS[RowOffset[X]]]--; } LinePS = Src->Data + ColOffSet[Y + Radius] * Src->WidthStep; for (X = -Radius; X < Width + Radius; X++) // 增长进入范围内的那一行的直方图数据 { ColHist[X * 256 + LinePS[RowOffset[X]]]++; } } memset(Hist, 0, 256 * sizeof(unsigned short)); // 每一行直方图数据清零先 LinePS = Src->Data + Y * Src->WidthStep; LinePD = Dest->Data + Y * Dest->WidthStep; for (X = 0; X < Width; X++) { if (X == 0) { for (K = -Radius; K <= Radius; K++) // 行第一个像素,须要从新计算 HistgramAddShort(ColHist + K * 256, Hist); } else { HistgramSubAddShort(ColHist + RowOffset[X - Radius - 1] * 256, ColHist + RowOffset[X + Radius] * 256, Hist); // 行内其余像素,依次删除和增长就能够了 } Calc(Hist, LinePS[0], LinePD, Threshold); LinePS++; LinePD++; } } ColHist -= Radius * 256; // 恢复偏移操做 Done8: IS_FreeMatrix(&Row); IS_FreeMatrix(&Col); IS_FreeMemory(ColHist); IS_FreeMemory(Hist); return Ret; } else { TMatrix *Blue = NULL, *Green = NULL, *Red = NULL, *Alpha = NULL; // 因为C变量若是不初始化,其值是随机值,可能会致使释放时的错误。 IS_RET Ret = SplitRGBA(Src, &Blue, &Green, &Red, &Alpha); if (Ret != IS_RET_OK) goto Done24; Ret = SelectiveBlur(Blue, Blue, Radius, Threshold, Edge); if (Ret != IS_RET_OK) goto Done24; Ret = SelectiveBlur(Green, Green, Radius, Threshold, Edge); if (Ret != IS_RET_OK) goto Done24; Ret = SelectiveBlur(Red, Red, Radius, Threshold, Edge); if (Ret != IS_RET_OK) goto Done24; // 32位的Alpha不作任何处理,实际上32位的相关算法基本上是不能分通道处理的 CopyAlphaChannel(Src, Dest); Ret = CombineRGBA(Dest, Blue, Green, Red, Alpha); Done24: IS_FreeMatrix(&Blue); IS_FreeMatrix(&Green); IS_FreeMatrix(&Red); IS_FreeMatrix(&Alpha); return Ret; } }
测试源代码及工程下载地址(VS2010开发): SelectiveBlur.rar
****************************做者: laviewpbt 时间: 2015.8.1 联系QQ: 33184777 转载请保留本行信息**********************