我們常見的模糊算法基本的過程都是計算一個像素周邊的的某個領域内,相關像素的某個特征值的累加和及對應的權重,然後得到結果值。這樣的過程是無法區分出圖像的邊緣等資訊的,一種簡單的改進方式就是設定某個門檻值,當領域像素和中心點像素的差距大于門檻值時,設定其權重很小,甚至為0,這樣對于本身比較平滑的區域,和原始的算法差別不大,而對于像素值變化較為明顯的邊緣地帶,則能夠有效地保留原始資訊。
我們常見的模糊算法比如均值模糊、高斯模糊等其基本的過程都是計算一個像素周邊的的某個領域内,相關像素的某個特征值的累加和及對應的權重,然後得到結果值。比如均值模糊的各像素的權重是一樣的,而高斯模糊的權重和像素距離中心點的距離成高斯分布。這樣的過程是無法區分出圖像的邊緣等資訊的,導緻被模糊後的圖像細節嚴重丢失,一種簡單的改進方式就是設定某個門檻值,當領域像素和中心點像素的差距大于門檻值時,設定其權重很小,甚至為0,這樣對于本身比較平滑的區域,和原始的算法差別不大,而對于像素值變化較為明顯的邊緣地帶,則能夠有效地保留原始資訊,這樣就能起到降低噪音的同時保留邊緣的資訊。
在實際的處理,小半徑的領域往往處理能力有限,處理的結果不慎理想,而随着半徑的增加,算法的直接實作耗時成平方關系增長,傳統的優化方式由于這個判斷條件的增加,已經無法繼續使用,為了解決速度問題,我們可以采用基于直方圖算法的優化,如果能夠統計出領域内的直方圖資訊,上述的判斷條件及權重計算就可以簡單的用下述代碼實作:
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循環中的權重項目中再乘以一個系數,當然這會增加一定的計算量。
我們選擇了一些其他保邊濾波器的測試圖像進行了測試,在效果上通過調整參數能得到相當不錯的效果,舉例如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL1YzNzMzNxMDNwMDOxEDMvwFOwUTMwIzLcNTOykDNz8CX1EDMyc2bsJ2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
原圖 結果圖: 參數r =10, Threshold = 16
原圖 結果圖: 參數r =10, Threshold = 16
原圖 結果圖: 參數r =10, Threshold = 16
原圖 結果圖: 參數r =10, Threshold = 40
在處理時間上,使用如上參數,在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 轉載請保留本行資訊**********************