【OpenCV C++20 学习笔记】自定义线性滤波-filter2D

自定义线性滤波

原理

相关

线性滤波的是指就是相关,即计算图像中的每个部分和卷积核(kernel)的相关结果。

卷积核

卷积核本质上是一个固定大小的系数数组,数组中的某个元素被作为锚点(一般是数组的中心),如下图:

线性滤波操作

上面讲了线性滤波的实质就是计算相关,相关计算的具体步骤如下:

  1. 将卷积核的锚点放在某个目标像素上,卷积核的其他部分就会覆盖目标像素的邻近像素;
  2. 将卷积核上的系数与被覆盖的像素的值相乘,然后将积加总;
  3. 将加总的和赋予目标像素
  4. 对图像上的所有像素都应用以上步骤,直到每个像素都被当作目标像素进行了计算。

用数学公式来表示以上步骤:
H ( x , y ) = ∑ i = 0 M i − 1 ∑ j = 0 M j − 1 I ( x + i − a i , y + j − a j ) K ( i , j ) H(x,y)=\displaystyle \sum_{i=0}^{M_i-1} \displaystyle \sum_{j=0}^{M_j-1} I(x+i-a_i, y+j-a_j)K(i, j) H(x,y)=i=0∑Mi−1j=0∑Mj−1I(x+i−ai,y+j−aj)K(i,j)

  • H ( x , y ) H(x,y) H(x,y)为图像第x行、第y列像素,即目标像素,的计算结果;
  • K ( i , j ) K(i,j) K(i,j)为卷积核第i行,第j列的系数;
  • M i , M j M_i, M_j Mi,Mj分别为卷积核的总行数和总列数;
  • a i , a j a_i, a_j ai,aj分别为卷积核中锚点的行数和列数;
  • I ( x + i − a i , y + j − a j ) I(x+i-a_i, y+j-a_j) I(x+i−ai,y+j−aj)为目标像素的邻近像素。

虽然公式看上去有点复杂,但是好在OpenCV给我们提供了一个便利的API来实现线性滤波的算法。

API

在OpenCV中可以用filter2D()函数来实现线性滤波操作。其函数原型如下:

cpp 复制代码
void cv::filter2D(InputArray	src,
				OutputArray		dst,
				int				ddepth,
				InputArray		kernel,
				Point			anchor = Point(-1, -1),
				double			delta = 0,
				int				borderType = BORDER_DEFAULT)
  • src:输入图片
  • dst:输出图片,与输入图片有相同的尺寸和颜色通道数
  • ddepth:输出的数据格式,-1表示与原图保持同样的数据格式
  • kernel:卷积核数组
  • anchor:锚点坐标,默认为(-1, -1),即卷积核的中心
  • delta:可选的偏移值
  • borderType:图片边缘的扩充方式(由于卷积核的锚点无法覆盖图像边缘的像素,所以必须以某种方法扩充原图,使得锚点能够覆盖原有的边缘),默认为镜像扩充

实例

这里使用各种尺寸的归一化滤波作为例子。一个3*3的归一化滤波卷积核如下:
K = 1 3 ⋅ 3 [ 1 1 1 1 1 1 1 1 1 ] K=\frac{1}{3 \cdot 3} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix} K=3⋅31 111111111

使用著名的lena图片作为例子。

为了能够展示不同尺寸的卷积核带来的归一化滤波效果,这里使用了一个循环来进行迭代。在循环体内将卷积核的尺寸依次循环地设置为55, 7 7, 99, 1111, 13*13。具体代码如下:

cpp 复制代码
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;

int main() {
	Mat src{ imread("lena.jpg") };

	Point anchor{ Point(-1, -1) };	//锚点为卷积核中心
	double delta{ 0 };				//偏移值为0
	int ddepth{ -1 };				//输出数据类型与原图一致

	int ind{ 0 }, kernel_size;
	Mat kernel, dst;
	for (;;) {
		kernel_size = 3 + 2 * (ind % 5);	//卷积核的尺寸为5,7,9,11,13,不断循环
		kernel = Mat::ones(kernel_size, kernel_size, CV_32F)
			/ static_cast<float>(kernel_size * kernel_size);	//创建归一化滤波的卷积核

		//自定义滤波操作
		filter2D(src,
			dst,
			ddepth,
			kernel,
			anchor,
			delta,
			BORDER_DEFAULT);

		//显示结果图片
		imshow("filter2D", dst);
		char c = static_cast<char>(waitKey(500));
		if (c == 27)	//按ESC则退出
			break;

		ind++;
	}
}

效果如下:

  1. 5*5的归一化滤波结果
  2. 7*7归一化滤波结果
  3. 9*9归一化滤波结果
  4. 11*11归一化滤波结果
  5. 13*13归一化滤波结果

    可以看到卷积核越大,滤波后的图片越模糊。
相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习