Author:胡健
1、圖像平滑(smooth)也稱為“模糊處理”,最常見的smooth的使用方法是降低圖像上的噪聲或者失真。
2、圖像濾波
什么是圖像濾波呢?就是在盡量保留圖像細節特征的條件下對目標圖像的噪聲進行抑制。
圖像濾波的目的就是消除圖像的噪聲和抽出對象的特征。圖像濾波的要求是:不能損壞圖像的重要特征信息(如輪廓和邊緣)。還須要使得濾波處理后的圖像更加清晰。
對於平滑濾波來說,他的目的有兩類:(1)、模糊(2)、消噪
空間域內的平滑濾波採用平均法,就是求鄰近像素域內的平均亮度值,所以鄰域的大小與平滑的效果直接相關,鄰域越大,平滑的效果越好,可是須要注意的是,鄰域過大的話,平滑處理會使得邊緣信息損失得越大。
從而使輸出的圖像變得模糊。
那濾波器是什么呢?
我們能夠將濾波器想象成一個包括加權系數的窗體,當使用這個濾波器平滑處理圖像時,就把這個窗體放在圖像之上。透過這個窗體來看我們得到的圖像。
以下是一些濾波器:
方框濾波–> boxblur函數來實現 –>線性濾波
均值濾波(鄰域平均濾波)–> blur函數 –>線性濾波
高斯濾波–>GaussianBlur函數 –>線性濾波
中值濾波–>medianBlur函數 –>非線性濾波
雙邊濾波–>bilateralFilter函數 –>非線性濾波
-- PART A 線性濾波器介紹 --
–>什么叫做線性濾波器呢?
線性濾波器經常使用於剔除輸入信號中不想要的頻率或者從很多頻率中選擇一個想要的頻率。
以下是幾種常見的線性濾波器:
(1)、同意低頻率通過的低通濾波器
(2)、同意高頻率通過的高通濾波器
(3)、同意一定區域的頻率通過的帶通濾波器
(4)、阻止一定范圍內的頻率而且同意其它頻率通過的帶阻濾波器
(5)、僅僅改變相位的全通濾波器
(6)、阻止一個狹窄頻率范圍通過的特殊帶阻濾波器,陷波濾波器
–>關於濾波和模糊
濾波是將信號中的特定波段頻率過濾掉的操作,是為了抑制和防止干擾的措施。
比方高斯濾波。能夠分為高斯低通濾波和高斯高通濾波,這個要看高斯函數。低通就是模糊。高通就是銳化。
高斯濾波就是指用高斯函數作為濾波函數的濾波操作。
相同的。高斯模糊就是高斯低通濾波,高斯銳化就是高斯高通濾波。
—–>關於方框濾波
void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT )
—->參數介紹
第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象就可以。該函數對通道是獨立處理的。且能夠處理隨意通道數的圖片。但須要注意。待處理的圖片深度應該為CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之中的一個。
第二個參數。OutputArray類型的dst,即目標圖像,須要和源圖片有一樣的尺寸和類型。
第三個參數。int類型的ddepth。輸出圖像的深度,-1代表使用原圖深度,即src.depth()。
第四個參數,Size類型(對Size類型稍后有解說)的ksize。內核的大小。一般這樣寫Size( w,h )來表示內核的大小( 當中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第五個參數,Point類型的anchor。表示錨點(即被平滑的那個點)。注意他有默認值Point(-1,-1)。
假設這個點坐標是負值的話,就表示取核的中心為錨點,所以默認值Point(-1,-1)表示這個錨點在核的中心。
第六個參數,bool類型的normalize。默認值為true,一個標識符。表示內核是否被其區域歸一化(normalized)了。
第七個參數,int類型的borderType,用於判斷圖像外部像素的某種邊界模式。有默認值BORDER_DEFAULT,我們一般不去管它。
–>均值濾波
均值濾波是最簡單的一種濾波操作,輸出圖像的每個像素是窗體內的輸入圖像相應的像素的平均值,也就是歸一化后的方框濾波,它的實現也是使用方框濾波來實現的。
實現算法:對於每個像素點。用窗體內的平均像素來替換。
可是均值濾波會破壞圖像的細節。從而使得圖像變得模糊。
void blur(InputArray src, OutputArraydst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
第一個參數,InputArray類型的src,輸入圖像。即源圖像。填Mat類的對象就可以。
該函數對通道是獨立處理的,且能夠處理隨意通道數的圖片,但須要注意,待處理的圖片深度應該為CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之中的一個。
第二個參數,OutputArray類型的dst,即目標圖像,須要和源圖片有一樣的尺寸和類型。比方能夠用Mat::Clone,以源圖片為模板,來初始化得到如假包換的目標圖。
第三個參數。Size類型(對Size類型稍后有解說)的ksize,內核的大小。一般這樣寫Size( w,h )來表示內核的大小( 當中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第四個參數。Point類型的anchor。表示錨點(即被平滑的那個點)。注意他有默認值Point(-1,-1)。假設這個點坐標是負值的話,就表示取核的中心為錨點,所以默認值Point(-1,-1)表示這個錨點在核的中心。
第五個參數,int類型的borderType。用於判斷圖像外部像素的某種邊界模式。有默認值BORDER_DEFAULT,我們一般不去管它。
–>高斯濾波
高斯濾波是一種線性濾波。高斯濾波就是對整幅圖像進行加權平均的過程,每個像素點的值都由其本身和鄰域內的其它像素值經過加權平均后得到的。他的詳細操作方式為:
用一個模板(卷積,掩模)掃描圖像中的每個像素點。用模板確定的鄰域內的像素的加權平均值取替換模板中心像素點的值。
void GaussianBlur(InputArray src,OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, intborderType=BORDER_DEFAULT )
· 第一個參數,InputArray類型的src,輸入圖像,即源圖像。填Mat類的對象就可以。它能夠是單獨的隨意通道數的圖片,但須要注意,圖片深度應該為CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之中的一個。
· 第二個參數,OutputArray類型的dst,即目標圖像,須要和源圖片有一樣的尺寸和類型。比方能夠用Mat::Clone。以源圖片為模板,來初始化得到如假包換的目標圖。
· 第三個參數。Size類型的ksize高斯內核的大小。當中ksize.width和ksize.height能夠不同。但他們都必須為正數和奇數。
或者,它們能夠是零的,它們都是由sigma計算而來。
· 第四個參數,double類型的sigmaX,表示高斯核函數在X方向的的標准偏差。
· 第五個參數。double類型的sigmaY。表示高斯核函數在Y方向的的標准偏差。若sigmaY為零。就將它設為sigmaX。假設sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height計算出來。
· 為了結果的正確性着想。最好是把第三個參數Size,第四個參數sigmaX和第五個參數sigmaY所有指定到。
· 第六個參數。int類型的borderType,用於判斷圖像外部像素的某種邊界模式。有默認值BORDER_DEFAULT。我們一般不去管它。
以下是二維高斯函數:
/* 這是進行方框濾波操作的函數,也就是boxFilter Author:hujian Time:2016/4/5 */
void cv::boxFilter( InputArray _src, OutputArray _dst, int ddepth,
Size ksize, Point anchor,
bool normalize, int borderType )
{
CV_OCL_RUN(_dst.isUMat(), ocl_boxFilter(_src, _dst, ddepth, ksize, anchor, borderType, normalize))
//src是操作的圖形矩陣
//
Mat src = _src.getMat();
//得到原始圖像的類型和深度等信息
//
int stype = src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype);
//-1代表使用原始圖像的深度,所以給他賦值為原始圖像的深度吧
//
if( ddepth < 0 )
ddepth = sdepth;
//創建和初始化輸出圖像
//
_dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );
//然后一如既往的我們僅僅操作dst
//
Mat dst = _dst.getMat();
if( borderType != BORDER_CONSTANT && normalize && (borderType & BORDER_ISOLATED) != 0 )
{
if( src.rows == 1 )
ksize.height = 1;
if( src.cols == 1 )
ksize.width = 1;
}
//調用濾波引擎,開始濾波操作
//
Ptr<FilterEngine> f = createBoxFilter( src.type(), dst.type(),
ksize, anchor, normalize, borderType );
f->apply( src, dst );
}
上面的boxfilter用到了一個叫做濾波引擎的東西。也就是FilterEngine,如今我們來分析一下這個引擎。
class FilterEngine
{
public:
//! the default constructor
FilterEngine();
//! the full constructor. Either _filter2D or both _rowFilter and _columnFilter must be non-empty.
FilterEngine(const Ptr<BaseFilter>& _filter2D,
const Ptr<BaseRowFilter>& _rowFilter,
const Ptr<BaseColumnFilter>& _columnFilter,
int srcType, int dstType, int bufType,
int _rowBorderType = BORDER_REPLICATE,
int _columnBorderType = -1,
const Scalar& _borderValue = Scalar());
//! the destructor
virtual ~FilterEngine();
//! reinitializes the engine. The previously assigned filters are released.
void init(const Ptr<BaseFilter>& _filter2D,
const Ptr<BaseRowFilter>& _rowFilter,
const Ptr<BaseColumnFilter>& _columnFilter,
int srcType, int dstType, int bufType,
int _rowBorderType = BORDER_REPLICATE,
int _columnBorderType = -1,
const Scalar& _borderValue = Scalar());
//! starts filtering of the specified ROI of an image of size wholeSize.
virtual int start(Size wholeSize, Rect roi, int maxBufRows = -1);
//! starts filtering of the specified ROI of the specified image.
virtual int start(const Mat& src, const Rect& srcRoi = Rect(0,0,-1,-1),
bool isolated = false, int maxBufRows = -1);
//! processes the next srcCount rows of the image.
virtual int proceed(const uchar* src, int srcStep, int srcCount,
uchar* dst, int dstStep);
//! applies filter to the specified ROI of the image. if srcRoi=(0,0,-1,-1), the whole image is filtered.
virtual void apply( const Mat& src, Mat& dst,
const Rect& srcRoi = Rect(0,0,-1,-1),
Point dstOfs = Point(0,0),
bool isolated = false);
//! returns true if the filter is separable
bool isSeparable() const { return !filter2D; }
//! returns the number
int remainingInputRows() const;
int remainingOutputRows() const;
int srcType;
int dstType;
int bufType;
Size ksize;
Point anchor;
int maxWidth;
Size wholeSize;
Rect roi;
int dx1;
int dx2;
int rowBorderType;
int columnBorderType;
std::vector<int> borderTab;
int borderElemSize;
std::vector<uchar> ringBuf;
std::vector<uchar> srcRow;
std::vector<uchar> constBorderValue;
std::vector<uchar> constBorderRow;
int bufStep;
int startY;
int startY0;
int endY;
int rowCount;
int dstY;
std::vector<uchar*> rows;
Ptr<BaseFilter> filter2D;
Ptr<BaseRowFilter> rowFilter;
Ptr<BaseColumnFilter> columnFilter;
};
盡管不明白,可是上面的源代碼有非常明白的凝視呢!
–> blur函數源代碼
//這是均值濾波的函數
//我們能夠看到它僅僅調用了方框濾波函數,然后將歸一化設置為true
//也就是說。均值濾波就是歸一化后的方框濾波
//
void cv::blur( InputArray src, OutputArray dst,
Size ksize, Point anchor, int borderType )
{
boxFilter( src, dst, -1, ksize, anchor, true, borderType );
}
–>高斯濾波源代碼分析
//以下就是高斯濾波函數源代碼
//
void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize,
double sigma1, double sigma2,
int borderType )
{
//得到原始圖像的信息,依據這些信息創建輸出函數
//
int type = _src.type();
Size size = _src.size();
_dst.create( size, type );
//處理特殊情況
//
if( borderType != BORDER_CONSTANT && (borderType & BORDER_ISOLATED) != 0 )
{
if( size.height == 1 )
ksize.height = 1;
if( size.width == 1 )
ksize.width = 1;
}
//這還能干嗎呢?僅僅是純粹為了優化而加了這句吧。
//
if( ksize.width == 1 && ksize.height == 1 )
{
//直接復制,沒什么能夠做的
_src.copyTo(_dst);
return;
}
//創建高斯函數內核,進行濾波
//注意沒有調用濾波引起啊!
可能是由於效率不如這樣做好吧。 Mat kx, ky; createGaussianKernels(kx, ky, type, ksize, sigma1, sigma2); sepFilter2D(_src, _dst, CV_MAT_DEPTH(type), kx, ky, Point(-1,-1), 0, borderType ); }
以下就是線性濾波函數的使用了->
//this file will contain the usage of linaer filter in opencv
//
//the first function is boxFilter
//
void usage_boxFilter()
{
Mat img = imread("./smimage/1.jpg");
namedWindow("BoxFilter_SRC");
namedWindow("BoxFilter_RES");
imshow("BoxFilter_SRC", img);
//filter in boxfilter
//
Mat res;
boxFilter(img, res, -1, Size(5, 5));
//show the res image
//
imshow("BoxFilter_RES", res);
waitKey(0);
}
上面的函數執行結果應該是原來的圖像經過boxfilter之后變得更見曖昧與朦朧了。
//usage of blur filter
//
void usage_blur()
{
//read the image and show the src image
//
Mat image = imread("./smimage/1.jpg");
namedWindow("SRC");
imshow("SRC", image);
Mat res;
blur(image, res, Size(5, 5));
namedWindow("RES");
imshow("RES", res);
waitKey(0);
}
上面的結果應該是更加曖昧了,和boxfilter好像沒什么區別
#define _Filter_Linear_
#ifdef _Filter_Linear_
//this file will contain the usage of linaer filter in opencv
//
//the first function is boxFilter
//
void usage_boxFilter()
{
Mat img = imread("./smimage/1.jpg");
namedWindow("BoxFilter_SRC");
namedWindow("BoxFilter_RES");
imshow("BoxFilter_SRC", img);
//filter in boxfilter
//
Mat res;
boxFilter(img, res, -1, Size(5, 5));
//show the res image
//
imshow("BoxFilter_RES", res);
waitKey(0);
}
//usage of blur filter
//
void usage_blur()
{
//read the image and show the src image
//
Mat image = imread("./smimage/1.jpg");
namedWindow("SRC");
imshow("SRC", image);
Mat res;
blur(image, res, Size(5, 5));
namedWindow("RES");
imshow("RES", res);
waitKey(0);
}
//usage of GaussianBlur
//
void usage_GaussianBlur()
{
//read the image and show the src image
//
Mat image = imread("./smimage/1.jpg");
namedWindow("SRC");
imshow("SRC", image);
Mat res;
GaussianBlur(image, res, Size(5,5),0,0);
namedWindow("RES");
imshow("RES", res);
waitKey(0);
}
//this is a sum function,and include boxfilter,blur,gaussianblur
//
Mat src_image, dst_image_box, dst_image_blur, dst_image_gauss;
int boxFilterValue = 3; //boxfilter
int blurValue = 3; //blur
int gaussianValue = 3;
//this is the callback function SET
//
void onBoxFilter(int v, void* d)
{
//do boxfilter
//
boxFilter(src_image, dst_image_box, -1, Size(boxFilterValue, boxFilterValue));
//show this image
//
imshow("BoxFilter", dst_image_box);
}
void onBlur(int v, void* d)
{
//do boxfilter
//
blur(src_image, dst_image_box,Size(blurValue,blurValue));
//show this image
//
imshow("Blur", dst_image_box);
}
void onGauss(int v, void* d)
{
//do boxfilter
//
GaussianBlur(src_image, dst_image_box,Size(gaussianValue, gaussianValue),0,0);
//show this image
//
imshow("GaussianBlur", dst_image_box);
}
void boxFilter_blur_gaussianblur()
{
//ok,imread the src image
//
src_image = imread("./smimage/1.jpg");
if (src_image.empty()){
printf("This is a empty image or file\n");
return;
}
//show the src image
//
imshow("SRC", src_image);
//boxfilter part
//
namedWindow("BoxFilter");
createTrackbar("Value", "BoxFilter",&boxFilterValue,30, onBoxFilter);
onBoxFilter(0, 0);
//blur part
//
namedWindow("Blur");
createTrackbar("Value", "Blur", &blurValue, 30, onBlur);
onBlur(0, 0);
//gaussian part
//
namedWindow("GaussianBlur");
createTrackbar("Value", "GaussianBlur", &gaussianValue, 30, onGauss);
onGauss(0, 0);
waitKey(0);
return;
}
#endif //end of filter linear part a
—PART B 非線性濾波器介紹—-
–>中值濾波
首先介紹的是中值濾波:就像他的名字一樣,他的思想就是用像素點鄰域中的灰度的中值來取代該像素點的灰度值,中值濾波在去除脈沖噪聲、椒鹽噪聲的同一時候還能保留圖像的邊緣細節。
那中值濾波和均值濾波之間的區別是什么呢?
中值濾波是用中值來替代目標像素,而均值則是用均值來替代。
所以非常明顯在均值濾波中。所有鄰域內的像素點都參與了計算,對輸出圖像產生了影響。可是中值濾波用的是中值來取代目標像素點,所以噪聲像素非常難參與到目標圖像中去。所以在去除噪聲方面,中值濾波更科學合理一些,可是這種代價是所花費的時間要比均值濾波要多。由於要做排序等處理。
–>雙邊濾波
這是一種能夠保留邊界特征去除噪聲的濾波器。濾波器由兩個函數構成,一個函數是由幾何空間距離來決定濾波器系數。還有一個則由像素差決定濾波器系數。
以下是雙邊濾波器的濾波函數:
當中,加權系數W(i,j,k,l)取決於定義域核和值域核的乘積。
定義域核表演示樣例如以下:
值域核表演示樣例如以下:
所以雙邊濾波權重系數可得例如以下:
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 …
//usage of medianblur
//
void usage_medianBlur()
{
Mat image = imread("./smimage/18.jpg");
imshow("SRC", image);
//medianblur
//
Mat res;
medianBlur(image, res, 3);
imshow("RES", res);
waitKey(0);
}
雙邊濾波:
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。
//usage of bilateralFilter
//
void usage_bilateralFilter()
{
Mat src = imread("./smimage/18.jpg");
imshow("SRC", src);
Mat res;
bilateralFilter(src, res, 20, 50, 10);
imshow("RES", res);
waitKey(0);
}
至此,初步平滑處理學習完畢了。