二、深入——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') {}
-
- return 0;
- }
-
- //-----------------------------【on_BoxFilter( )函数】------------------------------------
- // 描述:方框滤波操作的回调函数
- //-----------------------------------------------------------------------------------------------
- static void on_BoxFilter(int, void *)
- {
- //方框滤波操作
- boxFilter(g_srcImage, g_dstImage1, -1,Size( g_nBoxFilterValue+1, g_nBoxFilterValue+1));
- //显示窗口
- imshow("【<1>方框滤波】", g_dstImage1);
- }
-
- //-----------------------------【on_MeanBlur( )函数】------------------------------------
- // 描述:均值滤波操作的回调函数
- //-----------------------------------------------------------------------------------------------
- static void on_MeanBlur(int, void *)
- {
- blur(g_srcImage, g_dstImage2, Size( g_nMeanBlurValue+1, g_nMeanBlurValue+1),Point(-1,-1));
- imshow("【<2>均值滤波】", g_dstImage2);
-
- }
-
- //-----------------------------【on_GaussianBlur( )函数】------------------------------------
- // 描述:高斯滤波操作的回调函数
- //-----------------------------------------------------------------------------------------------
- static void on_GaussianBlur(int, void *)
- {
- GaussianBlur(g_srcImage, g_dstImage3, Size( g_nGaussianBlurValue*2+1,g_nGaussianBlurValue*2+1 ), 0, 0);
- imshow("【<3>高斯滤波】", g_dstImage3);
- }
-
-
- //-----------------------------【on_MedianBlur( )函数】------------------------------------
- // 描述:中值滤波操作的回调函数
- //-----------------------------------------------------------------------------------------------
- static void on_MedianBlur(int, void *)
- {
- medianBlur( g_srcImage, g_dstImage4, g_nMedianBlurValue*2+1 );
- imshow("【<4>中值滤波】", g_dstImage4);
- }
-
-
- //-----------------------------【on_BilateralFilter( )函数】------------------------------------
- // 描述:双边滤波操作的回调函数
- //-----------------------------------------------------------------------------------------------
- static void on_BilateralFilter(int, void *)
- {
- bilateralFilter( g_srcImage, g_dstImage5, g_nBilateralFilterValue, g_nBilateralFilterValue*2,g_nBilateralFilterValue/2 );
- imshow("【<5>双边滤波】", g_dstImage5);
- }
放出一些效果图。 原图: 方框滤波(盒式滤波):
均值滤波:
高斯滤波:
中值滤波(参数调的有些猛,妹子完全面目全非- -):
双边滤波(和原图差别不大,要仔细看才看得出效果):
OK,就放出这些吧,具体更多的运行效果大家就自己下载示例程序回去玩吧。
|