欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenCV3学习笔记(6):图像卷积操作,filter2D()

程序员文章站 2023-12-23 17:54:15
...

0.数字图像处理中卷积

数字图像是一个二维的离散信号,对数字图像做卷积操作其实就是利用卷积核(卷积模板)在图像上滑动,将图像点上的像素灰度值与对应的卷积核上的数值相乘,然后将所有相乘后的值相加作为卷积核中间像素对应的图像上像素的灰度值,并最终滑动完所有图像的过程。

OpenCV3学习笔记(6):图像卷积操作,filter2D()
这张图可以清晰的表征出整个卷积过程中一次相乘后相加的结果:该图片选用3*3的卷积核,卷积核内共有九个数值,所以图片右上角公式中一共有九行,而每一行都是图像像素值与卷积核上数值相乘,最终结果-8代替了原图像中对应位置处的1。这样沿着图片一步长为1滑动,每一个滑动后都一次相乘再相加的工作,我们就可以得到最终的输出结果。除此之外,卷积核的选择有一些规则:
1)卷积核的大小一般是奇数,这样的话它是按照中间的像素点中心对称的,所以卷积核一般都是3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2。
2)卷积核所有的元素之和一般要等于1,这是为了原始图像的能量(亮度)守恒。其实也有卷积核元素相加不为1的情况,下面就会说到。
3)如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
4)对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。

参考博客(理解图像卷积操作的意义,里面也罗列了一些常用的卷积核):https://blog.csdn.net/chaipp0607/article/details/72236892

1.边界补充问题

上面的图片说明了图像的卷积操作,但是他也反映出一个问题,如上图,原始图片尺寸为7*7,卷积核的大小为3*3,当卷积核沿着图片滑动后只能滑动出一个5*5的图片出来,这就造成了卷积后的图片和卷积前的图片尺寸不一致,这显然不是我们想要的结果,所以为了避免这种情况,需要先对原始图片做边界填充处理。在上面的情况中,我们需要先把原始图像填充为9*9的尺寸。
常用的区域填充方法有,边界复制、镜像填充、快填充等。

以filter2D默认的BORDER_DEFAULT(等效于BORDER_REFLECT_101)为例,文档描述为(gfedcb|abcdefgh|gfedcba),也就是说这是镜像填充的,画个示意图(圈起来的为原本的数据,外部为卷积计算用的补充边界):

OpenCV3学习笔记(6):图像卷积操作,filter2D()

参考博客:https://blog.csdn.net/qq_32846595/article/details/79057040

2.filter2D的使用

filter2D将图像与卷积核做卷积,函数原型如下:

void cv::filter2D(
  InputArray src,    //源图像Mat对象
  OutputArray dst,   //目标图像Mat对象
  int ddepth,        //目标图像的深度,一般填-1即可,表示源图像与目标图像深度相同。
  InputArray kernel, //卷积核,一个单通道浮点型矩阵。如果想在图像不同的通道使用不同的kernel,可以先使用split()函数将图像通道事先分开。
  Point anchor = Point(-1, -1),     //内核的基准点,其默认值为(-1,-1)说明位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点
  double delta = 0,  //在储存目标图像前可选的添加到像素的值,默认值为0,不填就是默认值。
  int borderType = BORDER_DEFAULT   //边界填充的类型,默认值是BORDER_DEFAULT,文档说明为gfedcb|abcdefgh|gfedcba镜像填充的。
)

我们先用一个3*3的数据矩阵和一个3*3的卷积核来测试下他的计算过程:

	//一个3*3的Mat图像
	Mat src_mat = (Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
	Mat out_mat;
	//一个3*3的Mat卷积核
	Mat kernal_mat = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	filter2D(src_mat, out_mat, -1, kernal_mat);
	std::cout << "src:\n" << src_mat << std::endl;
	std::cout << "kernal:\n" << kernal_mat << std::endl;
	std::cout << "out:\n" << out_mat << std::endl;

 输出如下:

OpenCV3学习笔记(6):图像卷积操作,filter2D()

首先我们使用BORDER_DEFAULT规则对边界进行补充得到:

OpenCV3学习笔记(6):图像卷积操作,filter2D()

那么第一行第一列就这么算(卷积核中心对齐该单元格,卷积核矩阵每个点对应相乘后求累加和):

OpenCV3学习笔记(6):图像卷积操作,filter2D()

结果为:-7,因为我们的图像像素点存储类型目前设置为uchar,范围是0-255,所以截取后等于0;

同理,第三行第一列的计算:

OpenCV3学习笔记(6):图像卷积操作,filter2D()

结果为:11

可以看出,这里的计算规则是没有用到相邻单元格已经处理过的数据的,每次计算用的原始图。

下面再拿实际图片来测试:

	//测试下图片的效果
	Mat src_image = imread("F:/Src/libingbing.jpg");
	Mat temp_image;
	//原图太大了,缩放下
	resize(src_image, temp_image, cv::Size(int(src_image.cols * 0.3), int(src_image.rows * 0.3)));
	Mat out_iamge1;
	//边缘锐化卷积核
	Mat kernal_mat1 = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	filter2D(temp_image, out_iamge1, -1, kernal_mat1);

	Mat out_iamge2;
	//高斯平滑卷积核
	Mat kernal_mat2 = (Mat_<float>(3, 3) <<
		1 / 16.0f, 2 / 16.0f, 1 / 16.0f,
		2 / 16.0f, 4 / 16.0f, 2 / 16.0f,
		1 / 16.0f, 2 / 16.0f, 1 / 16.0f);
	filter2D(temp_image, out_iamge2, -1, kernal_mat2);
	imshow("原图", temp_image);
	imshow("filter2D边缘锐化", out_iamge1);
	imshow("filter2D高斯平滑", out_iamge2);

效果如下:

OpenCV3学习笔记(6):图像卷积操作,filter2D()

(一些常用的卷积核可以参考网上的博客或者书籍,如博客

参考文档:https://docs.opencv.org/master/d2/de8/group__core__array.html

参考博客:https://blog.csdn.net/qq_32846595/article/details/79057040

参考博客:https://blog.csdn.net/chaipp0607/article/details/72236892

3.手动计算(暂略)

本来想自己手动写下实现过程,不过人比较懒,就复制粘贴好了,不过这个和filter2D实现有区别,可以参考下(没有填充边界):

OpenCV3学习笔记(6):图像卷积操作,filter2D()

先让我们了解下基本函数:

Mat.ptr<uchar>(int i=0) 获取像素矩阵的指针,索引i表示第几行,从0开始计行数。

获得当前行指针const uchar*  current= myImage.ptr<uchar>(row );

获取当前像素点P(row, col)的像素值 p(row, col) =current[col]

saturate_cast<uchar>(-100),返回 0。

saturate_cast<uchar>(288),返回255

lsaturate_cast<uchar>(100),返回100

代码如下:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace cv;
using namespace std;

int main()
{
	//读取图片
	Mat raw = imread("F:/Src/libingbing.jpg");
	Mat src, dsr;
	//原图太大了,缩放下
	resize(raw, src, cv::Size(int(raw.cols * 0.3), int(raw.rows * 0.3)));
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	imshow("input image", src);

	int cols = (src.cols - 1) * src.channels();
	int offsetx = src.channels(); //通道数
	int rows = src.rows;

	dsr = Mat::zeros(src.size(), src.type());
	for (int row = 1; row < (rows - 1); row++)
	{
		const uchar* previous = src.ptr<uchar>(row - 1);
		const uchar* current = src.ptr<uchar>(row);
		const uchar* next = src.ptr<uchar>(row + 1);
		uchar* output = dsr.ptr<uchar>(row);

		for (int col = offsetx; col < cols; col++)
		{
			output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetx] +
				current[col + offsetx] + previous[col] + next[col]
				));
		}
	}
	namedWindow("current image demo", CV_WINDOW_AUTOSIZE);
	imshow("current image demo", dsr);
	waitKey(0);

	return 0;
}

效果如下:

OpenCV3学习笔记(6):图像卷积操作,filter2D()

参考博客:https://blog.csdn.net/weixin_42126427/article/details/106008578

 

相关标签: OpenCV filter2D

上一篇:

下一篇: