中值濾波
中值濾波就是是基於排序統計理論的一種能有效抑制噪聲的非線性信號處理技術,它的基本原理是把數字圖像或數字序列中一點的值用該點的一個鄰域中各點值的中值代替,讓周圍的像素值接近的值,從而消除孤立的噪聲點。中值濾波一般使用模板的方法實現,對模板內的像素按照像素值的大小進行排序,生成單調上升(或下降)的二維數據序列,並使用下面的公式進行輸出:
g(x,y)=med{f(x-m,y-n),(m,n∈W)}
其中,f(x,y)表示原始的圖像,而g(x,y)表示處理後的圖像。
中值濾波一般使用二維模板,濾波窗口通常爲 3*3,5*5,7*7 區域,實際使用中,我們常常放大窗口長度,選取最合適的直到濾波效果滿意爲止,對於緩變長輪廓物體一般採用方形和圓形,對於尖角形一般採用十字形窗口。後面的程序採用 3*3 矩形區域。實現方法是通過從圖像中的某個採樣窗口取出奇數個數據進行排序。用排序後的中值取代要處理的數據即可。
中值濾波適用範圍
中值濾波對椒鹽噪聲的抑制效果好,在抑制隨機噪聲的同時能有效保護邊緣少受模糊。但它對點、線等細節較多的圖像卻不太合適。對中值濾波法來說,正確選擇窗口尺寸的大小是很重要的環節。一般很難事先確定最佳的窗口尺寸,需通過從小窗口到大窗口的中值濾波試驗,再從中選取最佳的。
在opencv下使用中值濾波
OpenCV函數 medianBlur 執行中值濾波操作:
/*************************************************************************
*
* 函數名稱:
* MedianFilter()
*
* 參數:
* LPSTR lpDIBBits - 指向源DIB圖像指針
* LONG lWidth - 源圖像寬度(象素數)
* LONG lHeight - 源圖像高度(象素數)
* int iFilterH - 濾波器的高度
* int iFilterW - 濾波器的寬度
* int iFilterMX - 濾波器的中心元素X座標
* int iFilterMY - 濾波器的中心元素Y座標
*
* 返回值:
* BOOL - 成功返回TRUE,否則返回FALSE。
*
* 說明:
* 該函數對DIB圖像進行中值濾波。
*
************************************************************************/
BOOL WINAPI MedianFilter(LPSTR lpDIBBits, LONG lWidth, LONG lHeight,
int iFilterH, int iFilterW,
int iFilterMX, int iFilterMY)
{
// 指向源圖像的指針
unsigned char* lpSrc;
// 指向要複製區域的指針
unsigned char* lpDst;
// 指向複製圖像的指針
LPSTR lpNewDIBBits;
HLOCAL hNewDIBBits;
// 指向濾波器數組的指針
unsigned char * aValue;
HLOCAL hArray;
// 循環變量
LONG i;
LONG j;
LONG k;
LONG l;
// 圖像每行的字節數
LONG lLineBytes;
// 計算圖像每行的字節數
lLineBytes = WIDTHBYTES(lWidth * 8);
// 暫時分配內存,以保存新圖像
hNewDIBBits = LocalAlloc(LHND, lLineBytes * lHeight);
// 判斷是否內存分配失敗
if (hNewDIBBits == NULL)
{
// 分配內存失敗
return FALSE;
}
// 鎖定內存
lpNewDIBBits = (char * )LocalLock(hNewDIBBits);
// 初始化圖像爲原始圖像
memcpy(lpNewDIBBits, lpDIBBits, lLineBytes * lHeight);
// 暫時分配內存,以保存濾波器數組
hArray = LocalAlloc(LHND, iFilterH * iFilterW);
// 判斷是否內存分配失敗
if (hArray == NULL)
{
// 釋放內存
LocalUnlock(hNewDIBBits);
LocalFree(hNewDIBBits);
// 分配內存失敗
return FALSE;
}
// 鎖定內存
aValue = (unsigned char * )LocalLock(hArray);
// 開始中值濾波
// 行(除去邊緣幾行)
for(i = iFilterMY; i < lHeight - iFilterH + iFilterMY + 1; i++)
{
// 列(除去邊緣幾列)
for(j = iFilterMX; j < lWidth - iFilterW + iFilterMX + 1; j++)
{
// 指向新DIB第i行,第j個象素的指針
lpDst = (unsigned char*)lpNewDIBBits + lLineBytes * (lHeight - 1 - i) + j;
// 讀取濾波器數組
for (k = 0; k < iFilterH; k++)
{
for (l = 0; l < iFilterW; l++)
{
// 指向DIB第i - iFilterMY + k行,第j - iFilterMX + l個象素的指針
lpSrc = (unsigned char*)lpDIBBits + lLineBytes * (lHeight - 1 - i + iFilterMY - k) + j - iFilterMX + l;
// 保存象素值
aValue[k * iFilterW + l] = *lpSrc;
}
}
// 獲取中值
* lpDst = GetMedianNum(aValue, iFilterH * iFilterW);
}
}
// 複製變換後的圖像
memcpy(lpDIBBits, lpNewDIBBits, lLineBytes * lHeight);
// 釋放內存
LocalUnlock(hNewDIBBits);
LocalFree(hNewDIBBits);
LocalUnlock(hArray);
LocalFree(hArray);
// 返回
return TRUE;
}
/*************************************************************************
*
* 函數名稱:
* GetMedianNum()
*
* 參數:
* unsigned char * bpArray - 指向要獲取中值的數組指針
* int iFilterLen - 數組長度
*
* 返回值:
* unsigned char - 返回指定數組的中值。
*
* 說明:
* 該函數用冒泡法對一維數組進行排序,並返回數組元素的中值。
*
************************************************************************/
unsigned char WINAPI GetMedianNum(unsigned char * bArray, int iFilterLen)
{
// 循環變量
int i;
int j;
// 中間變量
unsigned char bTemp;
// 用冒泡法對數組進行排序
for (j = 0; j < iFilterLen - 1; j ++)
{
for (i = 0; i < iFilterLen - j - 1; i ++)
{
if (bArray[i] > bArray[i + 1])
{
// 互換
bTemp = bArray[i];
bArray[i] = bArray[i + 1];
bArray[i + 1] = bTemp;
}
}
}
// 計算中值
if ((iFilterLen & 1) > 0)
{
// 數組有奇數個元素,返回中間一個元素
bTemp = bArray[(iFilterLen + 1) / 2];
}
else
{
// 數組有偶數個元素,返回中間兩個元素平均值
bTemp = (bArray[iFilterLen / 2] + bArray[iFilterLen / 2 + 1]) / 2;
}
// 返回中值
return bTemp;
}
CUDA並行條件下的中值濾波
從中值濾波的原理可以看出,平滑後圖像的每個像素的值只與原圖該點其鄰域的像素值有關,並且每個像素值的中值濾波處理算法是完全相同的。並且算法爲只涉及局部運算
的像素級處理,並且處理的數據量巨大,局部數據之間的相關性小,沒有涉及知識推理和人工干預,算法本身的並行化程度很高,並且 CUDA 的 SIMT(單指令多線程)並行流式程序處理模式在進行這樣的像素級的圖像處理時具有明顯的優勢。設計好內核運行函數,使平滑圖像中的每一個像素對應一個由一個線程產生。由此首先設計 kernel 函數MedianFilter 。
_global_ void MedianFilter(int In[N][N],int Out[N][N],int Width,int Height)
{
int window[9];
unsigned int x=blockIdx.x*blockDim.x+threadIdx.x;
unsigned int y=blockIdx.y*blockDim.y+threadIdx.y;
if(x>= Width && y>= Height) return;
window[0]=(y==0||x==0)?0:In[(y-1)* Width +x-1];
window[1]=(y==0)?0:In[(y-1)* Width +x];
window[2]=(y==0||x==DATA_W-1)? 0:In[(y-1)* Width +x+1];
window[3]=(x==0)? 0:In[y* Width +x-1];
window[4]= In[y* Width +x];
window[5]=(x==DATA_W-1)? 0:In[y* Width +x+1];
window[6]=(y==DATA_H-1||x==0)? 0:In[(y+1)* Width +x-1];
window[7]=(y==DATA_H-1)? 0:In[(y+1)* Width +x];
window[8]=(y==DATA_H-1||x==DATA_W-1)? 0:In[(y+1)* Width +x+1];
for (unsigned int j=0; j<5; ++j)
{
int min=j;
for (unsigned int l=j+1; l<9; ++l)
if (window[l] < window[min])
min=l;
const float temp=window[j];
window[j]=window[min];
window[min]=temp;
}
Out[y* Width + x]=window[4];
}
說明:
根據 CUDA 程序定義的語法,_global_ 修飾符聲明函數 MedianFilter 爲kernel 函數,表明此類函數在設備上執行,僅可通過宿主調用。函數 MedianFilter 有四個
形參:int In[N][N]定義了指向輸入的待處理的圖像數據指針, int Out[N][N] 定義了指向輸出的處理後的圖像數據指針,int Width 定義了待處理的圖像的寬度,int Height 定義
了待處理的圖像的高度。int window[9]表示爲線程定義了一個整形數組用於排序。int x和 y 求出當前線程的座標,確定我們用該線程在生成結果像素的座標。if 的判斷線程超出
邊界說明座標不合法,退出。window[0]到 window[8] 讀取當前點及其 8 鄰域像素值。for循環對當前點及其 8 鄰域像素值排序,最後輸出中值濾波結果。
主機端代碼如下
void main ( )
{
//讀入待銳化圖像文件並初始化主機;
//將圖像數據傳輸到設備;
dim3 dimBlock (16,16) ;
dim3 dimGrid((Width+dimBlock.x–1)/dimBlock.x,(Height+dimBlock.y–
1)/dimBlock.y);
clock_t begin = clock ( ) ;, end;
MedianFilter<<<dimGrid,dimBlock>>>(inlp,outlp,Width, Height);
cudaThreadSynchronize();
double cost;
end = clock();
cost = (double)(end - begin) / CLOCKS_PER_SEC;
}