本文作者:小嗷
微信公众号:aoxiaoji
吹比QQ群:736854977

小嗷再问一次自己,什么是线性滤波?
-
首先图像和核卷积是必须的。
-
均值的话,卷积以后的总数除以核的大小(如3*3=9,9个数相加后总数/9=所求的像素值)
-
高斯的话,先进行高斯函数的运算,然后把算出来的核核图像进行卷积。再加起来。
即: 核与图像卷积 + 核内数相加(不懂的话,自行看推荐文章。)
图像、相加、卷积、都是固定不变。自定义线性滤波,只能改变核(核也称元素结构),呵呵哒
- 大家可以参考第28篇代码有自定义核的内容
在本篇中,您将学习:
使用OpenCV函数filter2D()创建自己的线性过滤器。
本文你会找到以下问题的答案:
-
filter2D()
-
imgcodecs
-
char **argv是什么用法啊?argv[0] 什么意思啊?
2.1 理论
下面的解释属于Bradski和Kaehler的《学习OpenCV》一书(英文书)
2.1.1 相关性
在一般意义上,相关性是图像的每个部分和操作符(内核)之间的操作。
2.1.2 什么是内核?
内核本质上是一个固定大小的数值系数数组,以及该数组中通常位于中心的锚点。
(其实小嗷都不想写,这玩意说的N遍)
2.1.3 与内核的关联是如何工作的?
假设您希望知道映像(图像)中某个特定位置的结果值。相关值的计算方法如下:
-
将内核锚点放在一个确定的像素上,其余的内核将在图像中覆盖相应的本地像素。
-
将核系数乘以相应的图像像素值,并对结果进行求和。
-
将结果放置到输入图像中锚点的位置。
-
通过扫描整个图像的内核,对所有像素重复这个过程。
不得不说,相比小嗷的介绍,鬼佬的解释更专业更能装逼
用方程的形式表示上述过程,我们将得到:
看不懂公式正常,请点击第24篇自定义中值滤波处理的代码。

src.at<Vec3b>(i + 1, j + 1)[0]
src.at<Vec3b>(i + 1, j)[0]
src.at<Vec3b>(i, j + 1)[0]
I(XXX)代表就是锚点附近的值,K(i,j)代表核
幸运的是,OpenCV为您提供了函数filter2D(),因此您不必编写所有这些操作。
2.1.4 这个程序做什么?
-
加载一个图像
-
执行规范化的框过滤器。例如,对于大小为3的内核,内核为:
该程序将使用3、5、7、9和11大小的内核执行筛选操作。
- 过滤器输出(与每个内核一起)将在500毫秒内显示
3.1 filter2D
将图像与内核卷积。
该函数对图像应用任意线性滤波器。就地操作支持。当孔径位于图像外部,根据指定的边框模式插入离群像素值。该函数根据指定的边界模式插入离群点像素值。
这个函数实际上计算的是相关性,而不是卷积:
也就是说kernel并不是中心点的镜像,如果需要一个正真的卷积,使用函数flip()并将中心点设置为(kernel.cols - anchor.x - 1, kernel.rows - anchor.y -1).
该函数在大核(11x11或更大)的情况下使用基于DFT的算法,而在小核情况下使用直接算法(使用createLinearFilter()检索得到).
参数详情:
-
src:输入图像
-
dst:输出图像的大小和数量与src相同。
-
ddepth:目标图像深度,如果没写将生成与原图像深度相同的图像。原图像和目标图像支持的图像深度如下:
1src.depth() = CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F2src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F3src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F4src.depth() = CV_64F, ddepth = -1/CV_64F
当ddepth输入值为-1时,目标图像和原图像深度保持一致。
-
kernel:卷积核(或者是相关核),一个单通道浮点型矩阵。如果想在图像不同的通道使用不同的kernel,可以先使用split()函数将图像通道事先分开。
-
anchor:内核的基准点(anchor),其默认值为(-1,-1)说明位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点。
-
delta:在储存目标图像前可选的添加到像素的值,默认值为0
-
borderType:像素向外逼近的方法,默认值是BORDER_DEFAULT,即对全部边界进行计算。
3.2 imgcodecs
用于图片的读写。//2.4里没有这块(2.49)
OpenCV3开始图片、视频编解码从highgui模块分离出来,组成了imgcodecs和videoio。Linux环境下需要注意一下,其他没啥。
3.3 char **argv是什么用法啊?argv[0] 什么意思啊?
1int argc, char **argv 用于运行时,把命令行参数传入主程序。 2argc -- 命令行参数 总个数,包括 可执行程序名。 3argv[i] -- 第 i 个参数。 4argv[0] -- 可执行程序名。 5例如运行: 6abc.exe 7argc 等于 1, argv[0] 是 "abc.exe" 8例如运行: 9rec.exe 4 5.210argc 等于 3, argv[0] 是 "rec.exe", argv[1] 是 "4", argv[2] 是 "5.2".11主函数里若有:12int x;13float y;14char s[80];15strcpy(s,argv[0]); // 程序名存入 了 s16sscanf(argv[1],"%d",&x); // x 得到数值417sscanf(argv[2],"%f",&y); // y 得到数值 5.2

本篇文章的代码如下所示。
1//首先我们需要导入相关的包,例如图像处理imgpro和输入输出图片imgcodecs,highgui则是图形界面 2#include "opencv2/imgproc.hpp" 3#include "opencv2/imgcodecs.hpp" 4#include "opencv2/highgui.hpp" 5 6using namespace cv; 7 8int main(int argc, char** argv) 9{10 //声明变量11 Mat src, dst;12 Mat kernel;13 //Point锚点14 Point anchor;15 //像素值16 double delta;17 //图像深度18 int ddepth;19 //核的大小20 int kernel_size;21 //常量即: 窗口命名22 const char* window_name = "filter2D Demo"; 23 //设置imageName的名字等于argc大于等于2成立就argv[1],否则就输出图片24 const char* imageName = argc >= 2 ? argv[1] : "D://8line.jpg";2526 // 加载图片(选择彩色模式)27 src = imread(imageName, IMREAD_COLOR); // Load an image28 //如果为空就打印,退出29 if (src.empty())30 {31 printf(" Error opening image\n");32 printf(" Program Arguments: [image_name -- default ../data/lena.jpg] \n");33 return -1;34 }3536 // 为过滤器初始化参数37 anchor = Point(-1, -1);38 delta = 0;39 ddepth = -1;4041 //循环 - 每隔0.5秒过滤不同内核大小的图像42 int ind = 0;43 for (;;)44 {45 // 更新归一化方框过滤器的内核大小(均值)46 kernel_size = 3 + 2 * (ind % 5);47 //3*3矩阵,图像色彩是1,再除以948 kernel = Mat::ones(kernel_size, kernel_size, CV_32F) / (float)(kernel_size*kernel_size);4950 // 应用过滤器(原图、输出图、深度一样,卷积核,锚点中心,像素值,默认边界)51 filter2D(src, dst, ddepth, kernel, anchor, delta, BORDER_DEFAULT);52 imshow(window_name, dst);5354 char c = (char)waitKey(500);55 // Press 'ESC' to exit the program56 if (c == 27)57 {58 break;59 }6061 ind++;62 }6364 return 0;65}
源图:
效果图:(就是均值滤波(不停的扩大核的大小))
解释一下:
让我们来看看这个项目的总体结构:
加载一个图像
1const char* imageName = argc >=2 ? argv[1] : "../data/lena.jpg";2// Loads an image3src = imread( imageName, IMREAD_COLOR ); // Load an image4if( src.empty() )5{6 printf(" Error opening image\n");7 printf(" Program Arguments: [image_name -- default ../data/lena.jpg] \n");8 return -1;9}
初始化参数
1 // Initialize arguments for the filter2anchor = Point( -1, -1 );3delta = 0;4ddepth = -1;
循环
执行一个无限循环更新内核大小,并对输入图像应用线性过滤器。让我们更详细地分析一下:
- 首先我们定义过滤器将要使用的内核。这里是:
1 // Update kernel size for a normalized box filter2 kernel_size = 3 + 2*( ind%5 );3 kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
第一行是将kernel_size更新为范围内的奇数值:[3,11]。第二行实际上构建了内核。通过设置它的值与1′s矩阵填充和正常化把它除以元素的个数。
- 设置内核后,我们可以使用函数filter2D()生成过滤器:
1 // Apply filter2 filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
我们的程序将会实现一个while循环,每个500 ms的内核大小的过滤器将会在指定的范围内更新。
在编译上面的代码之后,您可以执行它,将图像的路径作为参数。结果应该是一个窗口,该窗口显示被规范化过滤器模糊的图像。内核大小每0.5秒就会发生变化。
-
本人是抱着玩一玩的心态,学习opencv(其实深度学习没有外界说的这么高深,小嗷是白板,而且有工作在身并且于代码无关)
-
大家可以把我的数学水平想象成初中水平,毕竟小嗷既不是代码靠吃饭又不是靠数学吃饭,毕业N年
-
写文章主要是为了后人少走点弯路,多交点朋友,一起学习
-
如果有好的图像识别群拉我进去QQ:631821577
-
就我一个白板,最后还是成的,你们别怕,慢慢来吧
分享可以无数次,转载成自己文章QQ邮箱通知一下,未经授权请勿转载。
邮箱:631821577@qq.com
QQ群:736854977
有什么疑问公众号提问,下班或者周六日回答,ths
感言
本文就是为了后面做【铺垫】,filter2D卷积函数 + Mat::one创建核(元素结构)
推荐文章:
18.图像处理之线性滤波(空间域/高低频/方框/均值/高斯) --- OpenCV从零开始到图像(人脸 + 物体)识别系列
17.图像处理之线性滤波(线性运算/卷积) --- OpenCV从零开始到图像(人脸 + 物体)识别系列
20.方差/标准差/数学期望/正态分布/高斯函数(数学篇)--- OpenCV从零开始到图像(人脸 + 物体)识别系列
24.双边滤波和中值滤波器(自定义中值滤波器去除椒盐噪声、exp含义)-- OpenCV从零开始到图像(人脸 + 物体)识别系列
28.击中击不中变换(二值图像/结构元素/集合/convertTo/saturate_cast/moveWindow)
32.色彩分割(inRange/cvtColor/iostream/max/min)涉及图像深度
代码链接:
https://pan.baidu.com/s/1wJnWJmseiJLE6ls0nQ6ehA
密码: ihyq
网友评论