用任意线性滤波器做卷积

目录

一、概述

二、完整代码

三、效果图


一、概述

为了加深自己的学习,针对图像滤波具体实现写了下面这篇帖子,卷积操作只是在此基础上做了旋转。滤波器(也称核)指的是由一幅图像根据像素点附近的区域计算得到一幅新图像的算法,用于去除噪声、边缘检测等诸多领域。滤波分为线性滤波和非线性滤波两大类。常见的线性滤波包括线性滤波、均值滤波、方框滤波等,非线性滤波包括中值滤波、双边滤波等。那么给定一个滤波器,我们如何对图像进行滤波操作呢,下面我们以Sobel垂直梯度卷积核为例:

 在进行滤波操作过程中,将核当作一个滑块,滑过图像的每一个元素,核的锚点(红框位置)与图像对齐,对其进行加权,对应像素相乘再相加,如图所示,得到新的像素值。如果不进行填充,那么进行滤波操作后,图像的宽为:宽-(卷积核列数-1),高为:高-(卷积核行数-1)。但是我们在对图像操作过后,往往需要保持图像尺寸不变,避免信息丢失,那么就需要我们将核的锚点对准图像的第一个像素,而边界区域之外是没有像素值的,我们可以通过cv::copyMakeBorder()函数添加虚拟像素处理这个问题,copyMakeBorder()函数相关解释如下:                                 

cv::copyMakeBorder()就是一个为图像创建边框的函数,通过指定两幅图像,第一幅是源图像,第二幅是扩充之后的图像,同时指明填充方法,这个函数就会将第一幅图像填补后的结果保存在第二幅图像中。

void cv::copyMakeBorder(
    cv::InputArray     src,                     //原图像
    cv::OutputArray    dst,                     //输出图像
    int                top,                     //上边界扩充尺寸
    int                bottom,                  //下边界扩充尺寸
    int                left,                    //左边界扩充尺寸
    int                right,                   //右边界扩充尺寸
    int                borderType,              //像素填充方式
    const cv::Scalar&  value = cv::Scalar()     //用于常量边界(可选)
);

cv::BORDER_CONSTANT填充常量,cv::BORDER_WARP复制对边像素,cv::BORDER_REPLICATE复制边缘像素,cv::BORDER_REFLECT和cv::BORDER_REFLECT_ 101效果类似,都是通过镜像赋值扩展边界。

二、完整代码

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main() {
	Mat image = imread("input.jpg");
	cv::Mat average_kernal_3 = (Mat_<float>(3, 3) << 1, 2, 1,
		0, 0, 0,
		-1, -2, -1);
	cout << average_kernal_3.channels() << endl;
	int bios_x = (average_kernal_3.cols - 1) / 2;
	int bios_y = (average_kernal_3.rows - 1) / 2;
	//开始补全矩阵
	Mat complete_image;
	cv::copyMakeBorder(image, complete_image, bios_y, bios_y, bios_x, bios_x, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));
	complete_image.convertTo(complete_image, CV_32FC3);
	//滤波操作
	for (int chan = 0; chan < 3; chan++) {
		for (int i = 0; i < image.rows; i++) {
			for (int j = 0; j < image.cols; j++) {
				float sum = 0;
				for (int curr_rows = 0; curr_rows < average_kernal_3.rows; curr_rows++) {
					for (int curr_cols = 0; curr_cols < average_kernal_3.cols; curr_cols++) {
						float a = average_kernal_3.at<float>(curr_rows, curr_cols) * complete_image.at<Vec3f>(curr_rows + i, curr_cols + j)[chan];
						//	cout << a << endl;
						sum += a;
					}
				}
				complete_image.at<Vec3f>(i, j)[chan] = sum;
			}
		}
	}
	complete_image.convertTo(complete_image, CV_8UC3);
	imshow("output", complete_image);
	waitKey();
}

如果使用从cv::filter2D()这个接口函数,就更加简单了,函数参数解释如下:

cv::filter2D(

	cv::InputArray      src,               // 原图像
	cv::0utputArray     dst,               // 输出图像
	int          ddepth,                   // 输出图像深度(CV_8U)
	cv::InputArray kernel,                 //卷积核
	cv::Point anchor = cv::Point(-1, -1),  //锚点位置
	double delta = 0,                      //偏移
	int borderType = cv::BORDER DEFAULT    // 边界外推方式
);

三、效果图

接口函数虽然简单,但是使用时往往忽略了底层的实现过程,为了便于自己理解,写了前面的实现过程,如有不对,恳请大家批评指正。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值