【OpenCV入門教程之九】 非線性濾波專場 中值濾波 雙邊濾波
正如我們上一篇文章中講到的,線性濾波可以實現很多種不同的影象變換。然而非線性濾波,如中值濾波器和雙邊濾波器,有時可以達到更好的實現效果。鄰域運算元的其他一些例子還有對二值影象進行操作的形態學運算元,用於計算距離變換和尋找連通量的半全域性運算元。
先上一張截圖:
一、理論與概念講解——從現象到本質
1.1非線性濾波概述
之前的那篇文章裡,我們所考慮的濾波器都是線性的,即兩個訊號之和的響應和他們各自響應之和相等。換句話說,每個畫素的輸出值是一些輸入畫素的加權和,線性濾波器易於構造,並且易於從頻率響應角度來進行分析。
其實在很多情況下,使用鄰域畫素的非線性濾波也許會得到更好的效果。比如在噪聲是散粒噪聲而不是高斯噪聲,即影象偶爾會出現很大的值的時候。在這種情況下,用高斯濾波器對影象進行模糊的話,噪聲畫素是不會被去除的,它們只是轉換為更為柔和但仍然可見的散粒。
這就到了中值濾波登場的時候了。
1.2中值濾波
中值濾波(Median filter)是一種典型的非線性濾波技術,基本思想是用畫素點鄰域灰度值的中值來代替該畫素點的灰度值,該方法在去除脈衝噪聲、椒鹽噪聲的同時又能保留影象邊緣細節,.
中值濾波是基於排序統計理論的一種能有效抑制噪聲的非線性訊號處理技術,其基本原理是把數字影象或數字序列中一點的值用該點的一個鄰域中各點值的中值代替,讓周圍的畫素值接近的真實值,從而消除孤立的噪聲點,對於斑點噪聲(speckle noise)和椒鹽噪聲(salt-and-pepper noise)來說尤其有用,因為它不依賴於鄰域內那些與典型值差別很大的值。中值濾波器在處理連續影象窗函式時與線性濾波器的工作方式類似,但濾波過程卻不再是加權運算。
中值濾波在一定的條件下可以克服常見線性濾波器如最小均方濾波、方框濾波器、均值濾波等帶來的影象細節模糊,而且對濾除脈衝干擾及影象掃描噪聲非常有效,也常用於保護邊緣資訊, 儲存邊緣的特性使它在不希望出現邊緣模糊的場合也很有用,是非常經典的平滑噪聲處理方法。
●中值濾波與均值濾波器比較
中值濾波器與均值濾波器比較的優勢:在均值濾波器中,由於噪聲成分被放入平均計算中,所以輸出受到了噪聲的影響,但是在中值濾波器中,由於噪聲成分很難選上,所以幾乎不會影響到輸出。因此同樣用3x3區域進行處理,中值濾波消除的噪聲能力更勝一籌。中值濾波無論是在消除噪聲還是儲存邊緣方面都是一個不錯的方法。
中值濾波器與均值濾波器比較的劣勢
:中值濾波花費的時間是均值濾波的5倍以上。
顧名思義,中值濾波選擇每個畫素的鄰域畫素中的中值作為輸出,或者說中值濾波將每一畫素點的灰度值設定為該點某鄰域視窗內的所有畫素點灰度值的中值。
例如,取3 x 3的函式窗,計算以點[i,j]為中心的函式窗畫素中值步驟如下:
(1) 按強度值大小排列畫素點.
(2) 選擇排序畫素集的中間值作為點[i,j]的新值.
這一過程如圖下圖所示.
一般採用奇數點的鄰域來計算中值,但如果畫素點數為偶數
時,中值就取排序畫素中間兩點的平均值.採用大小不同鄰域的中值濾波器的結果如圖。
中值濾波在一定條件下,可以克服線性濾波器(如均值濾波等)所帶來的影象細節模糊,
而且對濾除脈衝干擾即影象掃描噪聲最為有效。在實際運算過程中並不需要影象的統計特
性,也給計算帶來不少方便。但是對一些細節多,特別是線、尖頂等細節多的影象不宜採用
中值濾波。
1.3雙邊濾波
雙邊濾波(Bilateral filter)是一種非線性的濾波方法,是結合影象的空間鄰近度和畫素值相似度的一種折衷處理,同時考慮空域資訊和灰度相似性,達到保邊去噪的目的。具有簡單、非迭代、區域性的特點。
雙邊濾波器的好處是可以做邊緣儲存(edge preserving),一般過去用的維納濾波或者高斯濾波去降噪,都會較明顯地模糊邊緣,對於高頻細節的保護效果並不明顯。雙邊濾波器顧名思義比高斯濾波多了一個高斯方差sigma-d,它是基於空間分佈的高斯濾波函式,所以在邊緣附近,離的較遠的畫素不會太多影響到邊緣上的畫素值,這樣就保證了邊緣附近畫素值的儲存。但是由於儲存了過多的高頻資訊,對於彩色影象裡的高頻噪聲,雙邊濾波器不能夠乾淨的濾掉,只能夠對於低頻資訊進行較好的濾波。
在雙邊濾波器中,輸出畫素的值依賴於鄰域畫素值的加權值組合:
而加權係數w(i,j,k,l)取決於定義域核和值域核的乘積。
其中定義域核表示如下(如圖):
定義域濾波對應圖示:
值域核表示為:
值域濾波:
兩者相乘後,就會產生依賴於資料的雙邊濾波權重函式:
二、深入——OpenCV原始碼分析溯源
首先讓我們一起領略medianBlur()函式的原始碼,其於…\opencv\sources\modules\imgproc\src\smooth.cpp的第1653行開始。
//-----------------------------------【medianBlur()函式中文註釋版原始碼】-------------------------// 程式碼作用:進行中值濾波操作的函式 // 說明:以下程式碼為來自於計算機開源視覺庫OpenCV的官方原始碼 // OpenCV原始碼版本:2.4.8 // 原始碼路徑:…\opencv\sources\modules\imgproc\src\smooth.cpp // 原始檔中如下程式碼的起始行數:1653行// 中文註釋by淺墨 //--------------------------------------------------------------------------------------------------------void cv::medianBlur( InputArray _src0,OutputArray _dst, int ksize ){//拷貝形參Mat資料到臨時變數,用於稍後的操作 Mat src0 = _src0.getMat(); _dst.create( src0.size(), src0.type() ); Mat dst = _dst.getMat(); //處理特定的ksize值的不同情況 if( ksize <= 1 ) { src0.copyTo(dst); return; } CV_Assert( ksize% 2 == 1 ); //若之前有過HAVE_TEGRA_OPTIMIZATION優化選項的定義,則執行巨集體中的tegra優化版函式並返回#ifdef HAVE_TEGRA_OPTIMIZATION if (tegra::medianBlur(src0, dst, ksize)) return;#endif bool useSortNet = ksize == 3 || (ksize == 5#if !CV_SSE2//若CV_SSE2為假,則執行巨集體中語句 && src0.depth() > CV_8U#endif );//開始正式進行濾波操作 Mat src; if( useSortNet ) { if( dst.data != src0.data ) src = src0; else src0.copyTo(src); //根據不同的位深,給medianBlur_SortNet函式取不同的模板型別值 if( src.depth() == CV_8U ) medianBlur_SortNet<MinMax8u, MinMaxVec8u>( src, dst, ksize ); else if( src.depth() == CV_16U ) medianBlur_SortNet<MinMax16u, MinMaxVec16u>( src, dst, ksize ); else if( src.depth() == CV_16S ) medianBlur_SortNet<MinMax16s, MinMaxVec16s>( src, dst, ksize ); else if( src.depth() == CV_32F ) medianBlur_SortNet<MinMax32f, MinMaxVec32f>( src, dst, ksize ); else CV_Error(CV_StsUnsupportedFormat, ""); return; } else { cv::copyMakeBorder( src0, src, 0, 0, ksize/2, ksize/2, BORDER_REPLICATE); int cn = src0.channels(); CV_Assert( src.depth() == CV_8U && (cn == 1 || cn == 3 || cn ==4) ); double img_size_mp = (double)(src0.total())/(1 << 20); if( ksize <= 3 + (img_size_mp < 1 ? 12 : img_size_mp < 4 ? 6 :2)*(MEDIAN_HAVE_SIMD && checkHardwareSupport(CV_CPU_SSE2) ? 1 : 3)) medianBlur_8u_Om( src, dst, ksize ); else medianBlur_8u_O1( src, dst, ksize ); }}
仔細閱讀原始碼我們可以發現,正式進入濾波操作時,根據影象不同的位深,我們會給medianBlur_SortNet函式模板取不同的模板型別值,或者呼叫medianBlur_8u_Om或medianBlur_8u_O1來進行操作。
上面我們剛說到,medianBlur_SortNet 是一個函式模板,其原始碼於smooth.cpp的1439行開始,由於其函式體很長,我們在此只貼出它的函式宣告。
template<class Op, class VecOp>static void medianBlur_SortNet( constMat& _src, Mat& _dst, int m );
另外,bilateralFilter函式的原始碼也比較冗長,在D:\Program Files\opencv\sources\modules\imgproc\src\smooth.cpp原始碼檔案中。
從1714行到2273行都是。我們在這裡只給出路徑,和一張概況圖,大家有興趣自己去看原始碼。
再提一點,smooth.cpp原始碼的第2275行到2552行是OpenCV中自適應雙邊濾波器(adaptiveBilateralFilter)的原始碼,有興趣和精力的童鞋可以去探究探究。
三、淺出——API函式快速上手
3.1中值濾波——medianBlur函式
medianBlur函式使用中值濾波器來平滑(模糊)處理一張圖片,從src輸入,而結果從dst輸出。
且對於多通道圖片,每一個通道都單獨進行處理,並且支援就地操作(In-placeoperation)。
C++: void medianBlur(InputArray src,OutputArray dst, int ksize)
引數詳解:
- 第一個引數,InputArray型別的src,函式的輸入引數,填1、3或者4通道的Mat型別的影象;當ksize為3或者5的時候,影象深度需為CV_8U,CV_16U,或CV_32F其中之一,而對於較大孔徑尺寸的圖片,它只能是CV_8U。
- 第二個引數,OutputArray型別的dst,即目標影象,函式的輸出引數,需要和源圖片有一樣的尺寸和型別。我們可以用Mat::Clone,以源圖片為模板,來初始化得到如假包換的目標圖。
- 第三個引數,int型別的ksize,孔徑的線性尺寸(aperture linear size),注意這個引數必須是大於1的奇數,比如:3,5,7,9 ...
呼叫範例:
//載入原圖 Mat image=imread("1.jpg"); //進行中值濾波操作 Mat out; medianBlur( image, out, 7);
用上面三句核心程式碼架起來的完整程式程式碼:
//-----------------------------------【程式說明】----------------------------------------------// 說明:【中值濾波medianBlur函式的使用示例程式】// 開發所用OpenCV版本:2.4.8// 2014年4月3 日 Create by 淺墨//------------------------------------------------------------------------------------------------ //-----------------------------------【標頭檔案包含部分】---------------------------------------// 描述:包含程式所依賴的標頭檔案//----------------------------------------------------------------------------------------------#include "opencv2/core/core.hpp"#include"opencv2/highgui/highgui.hpp"#include"opencv2/imgproc/imgproc.hpp" //-----------------------------------【名稱空間宣告部分】---------------------------------------// 描述:包含程式所使用的名稱空間//----------------------------------------------------------------------------------------------- using namespace cv; //-----------------------------------【main( )函式】--------------------------------------------// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始//-----------------------------------------------------------------------------------------------int main( ){ //載入原圖 Mat image=imread("1.jpg"); //建立視窗 namedWindow("中值濾波【原圖】" ); namedWindow("中值濾波【效果圖】"); //顯示原圖 imshow("中值濾波【原圖】", image ); //進行中值濾波操作 Mat out; medianBlur( image, out, 7); //顯示效果圖 imshow("中值濾波【效果圖】" ,out ); waitKey(0 ); }
執行效果圖(孔徑的線性尺寸為7):
3.2 雙邊濾波——bilateralFilter函式
用雙邊濾波器來處理一張圖片,由src輸入圖片,結果於dst輸出。
C++: void bilateralFilter(InputArray src, OutputArraydst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
- 第一個引數,InputArray型別的src,輸入影象,即源影象,需要為8位或者浮點型單通道、三通道的影象。
- 第二個引數,OutputArray型別的dst,即目標影象,需要和源圖片有一樣的尺寸和型別。
- 第三個引數,int型別的d,表示在過濾過程中每個畫素鄰域的直徑。如果這個值我們設其為非正數,那麼OpenCV會從第五個引數sigmaSpace來計算出它來。
- 第四個引數,double型別的sigmaColor,顏色空間濾波器的sigma值。這個引數的值越大,就表明該畫素鄰域內有更寬廣的顏色會被混合到一起,產生較大的半相等顏色區域。
- 第五個引數,double型別的sigmaSpace座標空間中濾波器的sigma值,座標空間的標註方差。他的數值越大,意味著越遠的畫素會相互影響,從而使更大的區域足夠相似的顏色獲取相同的顏色。當d>0,d指定了鄰域大小且與sigmaSpace無關。否則,d正比於sigmaSpace。
- 第六個引數,int型別的borderType,用於推斷影象外部畫素的某種邊界模式。注意它有預設值BORDER_DEFAULT。
呼叫程式碼示範如下:
//載入原圖 Mat image=imread("1.jpg"); //進行雙邊濾波操作 Mat out; bilateralFilter( image, out, 25, 25*2, 25/2 );
用一個完整的示例程式把bilateralFilter函式熟悉一下:
執行效果圖:
//-----------------------------------【程式說明】----------------------------------------------// 說明:【雙邊濾波bilateralFilter函式的使用示例程式】// 開發所用OpenCV版本:2.4.8// 2014年4月3 日 Create by 淺墨//------------------------------------------------------------------------------------------------ //-----------------------------------【標頭檔案包含部分】---------------------------------------// 描述:包含程式所依賴的標頭檔案//----------------------------------------------------------------------------------------------#include "opencv2/core/core.hpp"#include"opencv2/highgui/highgui.hpp"#include"opencv2/imgproc/imgproc.hpp" //-----------------------------------【名稱空間宣告部分】---------------------------------------// 描述:包含程式所使用的名稱空間//----------------------------------------------------------------------------------------------- using namespace cv; //-----------------------------------【main( )函式】--------------------------------------------// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始//-----------------------------------------------------------------------------------------------int main( ){ //載入原圖 Mat image=imread("1.jpg"); //建立視窗 namedWindow("雙邊濾波【原圖】" ); namedWindow("雙邊濾波【效果圖】"); //顯示原圖 imshow("雙邊濾波【原圖】", image ); //進行雙邊濾波操作 Mat out; bilateralFilter( image, out, 25, 25*2, 25/2 ); //顯示效果圖 imshow("雙邊濾波【效果圖】" ,out ); waitKey(0 ); }
四、綜合示例——在實戰中熟稔
依然是每篇文章都會配給大家的一個詳細註釋的博文配套示例程式,把這篇文章中介紹的知識點以程式碼為載體,展現給大家。
這個示例程式中可以用軌跡條來控制各種濾波(方框濾波、均值濾波、高斯濾波、中值濾波、雙邊濾波)的引數值,通過滑動滾動條,就可以控制影象在各種平滑處理下的模糊度,有一定的可玩性。廢話不多說,上程式碼吧:
//-----------------------------------【程式說明】----------------------------------------------// 程式名稱::《【OpenCV入門教程之九】非線性濾波專場:中值濾波、雙邊濾波 》 博文配套原始碼// 開發所用IDE版本:Visual Studio 2010// 開發所用OpenCV版本: 2.4.8// 2014年4月8日 Create by 淺墨//------------------------------------------------------------------------------------------------ //-----------------------------------【標頭檔案包含部分】---------------------------------------// 描述:包含程式所依賴的標頭檔案//----------------------------------------------------------------------------------------------#include <opencv2/core/core.hpp>#include<opencv2/highgui/highgui.hpp>#include<opencv2/imgproc/imgproc.hpp>#include <iostream> //-----------------------------------【名稱空間宣告部分】---------------------------------------// 描述:包含程式所使用的名稱空間//----------------------------------------------------------------------------------------------- using namespace std;using namespace cv; //-----------------------------------【全域性變數宣告部分】--------------------------------------// 描述:全域性變數宣告//-----------------------------------------------------------------------------------------------Mat g_srcImage,g_dstImage1,g_dstImage2,g_dstImage3,g_dstImage4,g_dstImage5;int g_nBoxFilterValue=6; //方框濾波核心值int g_nMeanBlurValue=10; //均值濾波核心值int g_nGaussianBlurValue=6; //高斯濾波核心值int g_nMedianBlurValue=10; //中值濾波引數值int g_nBilateralFilterValue=10; //雙邊濾波引數值 //-----------------------------------【全域性函式宣告部分】--------------------------------------// 描述:全域性函式宣告//-----------------------------------------------------------------------------------------------//軌跡條回撥函式static void on_BoxFilter(int, void *); //方框濾波static void on_MeanBlur(int, void *); //均值塊濾波器static void on_GaussianBlur(int, void *); //高斯濾波器static void on_MedianBlur(int, void *); //中值濾波器static void on_BilateralFilter(int, void*); //雙邊濾波器 //-----------------------------------【main( )函式】--------------------------------------------// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始//-----------------------------------------------------------------------------------------------int main( ){ system("color 5E"); //載入原圖 g_srcImage= imread( "1.jpg", 1 ); if(!g_srcImage.data ) { printf("Oh,no,讀取srcImage錯誤~!\n"); return false; } //克隆原圖到四個Mat型別中 g_dstImage1= g_srcImage.clone( ); g_dstImage2= g_srcImage.clone( ); g_dstImage3= g_srcImage.clone( ); g_dstImage4= g_srcImage.clone( ); g_dstImage5= g_srcImage.clone( ); //顯示原圖 namedWindow("【<0>原圖視窗】", 1); imshow("【<0>原圖視窗】",g_srcImage); //=================【<1>方框濾波】========================= //建立視窗 namedWindow("【<1>方框濾波】", 1); //建立軌跡條 createTrackbar("核心值:", "【<1>方框濾波】",&g_nBoxFilterValue, 50,on_BoxFilter ); on_MeanBlur(g_nBoxFilterValue,0); imshow("【<1>方框濾波】", g_dstImage1); //===================================================== //=================【<2>均值濾波】========================== //建立視窗 namedWindow("【<2>均值濾波】", 1); //建立軌跡條 createTrackbar("核心值:", "【<2>均值濾波】",&g_nMeanBlurValue, 50,on_MeanBlur ); on_MeanBlur(g_nMeanBlurValue,0); //====================================================== //=================【<3>高斯濾波】=========================== //建立視窗 namedWindow("【<3>高斯濾波】", 1); //建立軌跡條 createTrackbar("核心值:", "【<3>高斯濾波】",&g_nGaussianBlurValue, 50,on_GaussianBlur ); on_GaussianBlur(g_nGaussianBlurValue,0); //======================================================= //=================【<4>中值濾波】=========================== //建立視窗 namedWindow("【<4>中值濾波】", 1); //建立軌跡條 createTrackbar("引數值:", "【<4>中值濾波】",&g_nMedianBlurValue, 50,on_MedianBlur ); on_MedianBlur(g_nMedianBlurValue,0); //======================================================= //=================【<5>雙邊濾波】=========================== //建立視窗 namedWindow("【<5>雙邊濾波】", 1); //建立軌跡條 createTrackbar("引數值:", "【<5>雙邊濾波】",&g_nBilateralFilterValue, 50,on_BilateralFilter); on_BilateralFilter(g_nBilateralFilterValue,0); //======================================================= //輸出一些幫助資訊 cout<<endl<<"\t嗯。好了,請調整滾動條觀察影象效果~\n\n" <<"\t按下“q”鍵時,程式退出~!\n" <<"\n\n\t\t\t\tby淺墨"; while(char(waitKey(1))!= 'q') {}