前言
無意中看到了一篇比較老的論文,Adaptive median filters: new algorithms and results。感興趣的可以下載下來看看。主要就是提出了一種自適應中值濾波算法,這個算法是很經典的中值濾波算法的改進版本,自動選擇濾波器的大小,以追求更好的效果。原理十分簡單,后面都盡量簡短地進行說明。
中值濾波的思想就是比較一定領域內的像素值的大小,取出其中值作為這個領域的中心像素新的值。假設對一定領域內的所有像素從小到大進行排序,如果存在孤立的噪聲點,比如椒鹽噪聲(椒噪聲——較小的灰度值,呈現的效果是小黑點;鹽噪聲——較大的灰度值,呈現的效果是小白點),那么從小到大排序的這個數組中,那些孤立的噪聲一定會分布在兩邊(要么很小,要么很大),這樣子取出的中值點可以很好地保留像素信息,而濾除了噪聲點的影響。 中值濾波器受濾波窗口大小影響較大,用于消除噪聲和保護圖像細節,兩者會存在沖突。如果窗口較小,則能較好地保護圖像中的一些細節信息,但對噪聲的過濾效果就會打折扣;反之,如果窗口尺寸較大則會有較好的噪聲過濾效果,但也會對圖像造成一定的模糊效果,從而丟失一部分細節信息。另外,如果在濾波窗口內的噪聲點的個數大于整個窗口內像素的個數,則中值濾波就不能很好的過濾掉噪聲。
在噪聲密度不是很大的情況下(根據經驗,噪聲的出現的概率小于0.2),使用中值濾波的效果不錯。但是當噪聲出現的概率比較高時,原來的中值濾波算法就不是很有效了。只有增大濾波器窗口尺寸,盡管會使圖像變得模糊。 使用自適應中值濾波器的目的就是,根據預設好的條件,動態地改變中值濾波器的窗口尺寸,以同時兼顧去噪聲作用和保護細節的效果。 下面是自適應中值濾波器算法的詳細描述: 預先定義好以下符號:
S x y Sxy :濾波器的作用區域,濾波器窗口所覆蓋的區域,該區域中心點為圖像中第y行第x列個像素點;Z m i n Zmin :S x y Sxy 中最小的灰度值;Z m a x Zmax :S x y Sxy 中最大的灰度值;Z m e d Zmed :S x y Sxy 中所有灰度值的中值;Z x y Zxy :表示圖像中第y行第x列個像素點的灰度值;S m a x Smax :S x y Sxy 所允許的最大窗口尺寸;
自適應中值濾波器分為以下兩個過程,A 和B : A : 1. A1 = Z m e d Zmed - Z m i n Zmin 2. A2 = Z m e d Zmed - Z m a x Zmax 3. 如果A 1 > 0 A1>0 且 A 2 < 0 A2<0 ,則跳轉到B 4. 否則,增大窗口的尺寸 5. 如果增大后的尺寸≤ ≤ S m a x Smax ,則重復A 6. 否則,直接輸出Z m e d Zmed B : 1. B1 = Z x y Zxy - Z m i n Zmin 2. B2 = Z x y Zxy - Z m a x Zmax 3. 如果B 1 > 0 B1>0 且 B 2 < 0 B2<0 ,則輸出Z x y Zxy 4. 否則輸出Z m e d Zmed
直觀解釋
在自適應中值濾波算法中,A 步驟里面會先判斷是否滿足Z m i n < Z m e d < Z m a x Zmin<Zmed<Zmax 。這一步驟實質是判斷當前區域的中值點是否是噪聲點,通常來說是滿足Z m i n < Z m e d < Z m a x Zmin<Zmed<Zmax 這個條件的,此時中值點不是噪聲點,跳轉到B ;考慮一些特殊情況,如果Z m e d = Z m i n Zmed=Zmin 或者Z m e d = Z m a x Zmed=Zmax ,則認為是噪聲點,應該擴大窗口尺寸,在一個更大的范圍內尋找一個合適的非噪聲點,隨后再跳轉到B ,否則輸出的中值點是噪聲點; 接下來考慮跳轉到B 之后的情況:判斷中心點的像素值是否是噪聲點,判斷條件為Z m i n < Z x y < Z m a x Zmin<Zxy<Zmax ,原理同上,因為如果Z x y = Z m i n Zxy=Zmin 或者Z x y = Z m a x Zxy=Zmax ,則認為是噪聲點。如果不是噪聲點,我們可以保留當前像素點的灰度值;如果是噪聲點,則使用中值替代原始灰度值,濾去噪聲。
程序實現
程序中定義了產生椒噪聲和鹽噪聲函數,以及中值濾波和自適應中值濾波的函數。 程序很基礎,不做贅述。
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
void saltNoise(cv::Mat img,
int n)
{
int x, y;
for (
int i =
0 ;i < n /
2 ;i++){x =
std ::rand() % img.cols;y =
std ::rand() % img.rows;
if (img.type() == CV_8UC1){img.at<uchar>(y, x) =
255 ;}
else if (img.type() == CV_8UC3){img.at<cv::Vec3b>(y, x)[
0 ] =
255 ;img.at<cv::Vec3b>(y, x)[
1 ] =
255 ;img.at<cv::Vec3b>(y, x)[
2 ] =
255 ;}}
}
void pepperNoise(cv::Mat img,
int n)
{
int x, y;
for (
int i =
0 ;i < n /
2 ;i++){x =
std ::rand() % img.cols;y =
std ::rand() % img.rows;
if (img.type() == CV_8UC1){img.at<uchar>(y, x) =
0 ;}
else if (img.type() == CV_8UC3){img.at<cv::Vec3b>(y, x)[
0 ] =
0 ;img.at<cv::Vec3b>(y, x)[
1 ] =
0 ;img.at<cv::Vec3b>(y, x)[
2 ] =
0 ;}}
}
uchar medianFilter(cv::Mat img,
int row,
int col,
int kernelSize)
{
std ::
vector <uchar> pixels;
for (
int y = -kernelSize /
2 ;y <= kernelSize /
2 ;y++){
for (
int x = -kernelSize /
2 ;x <= kernelSize /
2 ;x++){pixels.push_back(img.at<uchar>(row + y, col + x));}}sort(pixels.begin(), pixels.end());
auto med = pixels[kernelSize*kernelSize /
2 ];
return med;
}
uchar adaptiveMedianFilter(cv::Mat &img,
int row,
int col,
int kernelSize,
int maxSize)
{
std ::
vector <uchar> pixels;
for (
int y = -kernelSize /
2 ;y <= kernelSize /
2 ;y++){
for (
int x = -kernelSize /
2 ;x <= kernelSize /
2 ;x++){pixels.push_back(img.at<uchar>(row + y, col + x));}}sort(pixels.begin(), pixels.end());
auto min = pixels[
0 ];
auto max = pixels[kernelSize*kernelSize -
1 ];
auto med = pixels[kernelSize*kernelSize /
2 ];
auto zxy = img.at<uchar>(row, col);
if (med > min && med < max){
if (zxy > min && zxy < max)
return zxy;
else return med;}
else {kernelSize +=
2 ;
if (kernelSize <= maxSize)
return adaptiveMedianFilter(img, row, col, kernelSize, maxSize);
else return med;}
}
int main()
{
int minSize =
3 ;
int maxSize =
7 ;cv::Mat img;img = cv::imread(
"lena.bmp" );cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);cv::imshow(
"src" , img);saltNoise(img,
40000 );pepperNoise(img,
40000 );cv::imshow(
"noise" , img);cv::Mat temp = img.clone();cv::Mat img1;cv::copyMakeBorder(img, img1, maxSize /
2 , maxSize /
2 , maxSize /
2 , maxSize /
2 , cv::BorderTypes::BORDER_REFLECT);
for (
int j = maxSize /
2 ;j < img1.rows - maxSize /
2 ;j++){
for (
int i = maxSize /
2 ;i < img1.cols - maxSize /
2 ;i++){img1.at<uchar>(j, i) = adaptiveMedianFilter(img1, j, i, minSize, maxSize);}}cv::imshow(
"adaptiveMedianFilter" , img1);cv::Mat img2;
int kernelSize =
3 ;cv::copyMakeBorder(temp, img2, kernelSize /
2 , kernelSize /
2 , kernelSize /
2 , kernelSize /
2 , cv::BorderTypes::BORDER_REFLECT);
for (
int j = kernelSize /
2 ;j < img2.rows - kernelSize /
2 ;j++){
for (
int i = kernelSize /
2 ;i < img2.cols - kernelSize /
2 ;i++){img2.at<uchar>(j, i) = medianFilter(img2, j, i, kernelSize);}}cv::imshow(
"medianFilter" , img2);cv::waitKey();cv::destroyAllWindows();
return 0 ;
}
結果截圖
原始圖像和添加椒鹽噪聲后的圖像。 其實截圖中看不出很明顯的區別,在自己電腦上運行后看結果會清楚點。可以看到使用普通中值濾波的結果相比另一個會模糊一些,且局部仍然會有一小的噪聲點。圖像邊緣多出來的區域不好做中值濾波處理保留了原始圖像,所以邊緣那一圈仍然有噪聲。
總結
以上是生活随笔 為你收集整理的自适应中值滤波及实现 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。