目录
一、概述
为了加深自己的学习,针对图像滤波具体实现写了下面这篇帖子,卷积操作只是在此基础上做了旋转。滤波器(也称核)指的是由一幅图像根据像素点附近的区域计算得到一幅新图像的算法,用于去除噪声、边缘检测等诸多领域。滤波分为线性滤波和非线性滤波两大类。常见的线性滤波包括线性滤波、均值滤波、方框滤波等,非线性滤波包括中值滤波、双边滤波等。那么给定一个滤波器,我们如何对图像进行滤波操作呢,下面我们以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 // 边界外推方式
);
三、效果图
接口函数虽然简单,但是使用时往往忽略了底层的实现过程,为了便于自己理解,写了前面的实现过程,如有不对,恳请大家批评指正。