咱們常見的模糊算法好比均值模糊、高斯模糊等其基本的過程都是計算一個像素周邊的的某個領域內,相關像素的某個特徵值的累加和及對應的權重,而後獲得結果值。好比均值模糊的各像素的權重是同樣的,而高斯模糊的權重和像素距離中心點的距離成高斯分佈。這樣的過程是沒法區分出圖像的邊緣等信息的,致使被模糊後的圖像細節嚴重丟失,一種簡單的改進方式就是設置某個閾值,當領域像素和中心點像素的差距大於閾值時,設置其權重很小,甚至爲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 轉載請保留本行信息**********************