目录
一、卷积
1、卷积概念
2、卷积如何工作
3、常见算子(卷积核 Kenel)
4、自定义卷积模糊
5、代码演示
二、卷积边缘
1、卷积边缘问题
2、处理边缘
3、相关的API说明
4、代码演示
一、卷积
1、卷积概念
(1)在OpenCV中,卷积是一种常用的图像处理操作,用于图像滤波、特征提取等任务。它基于滑动窗口的概念,通过将一个小的核Kenel(也称为滤波器)与图像进行逐像素的乘法和求和运算来实现。
— 卷积是图像处理中一个操作,是kernel在图像的每个像素上的操作。
— Kernel本质上一个固定大小的矩阵数组,其中心点称为锚点(anchor point)
(2)卷积操作可以理解为在图像上滑动一个小的核,并将核的每个元素与对应位置的图像像素值相乘,然后将所有乘积结果相加得到输出图像的对应像素值。这个过程可以简单地表示为:
output(x, y) = sum(kernel(i, j) * input(x+i, y+j))
其中,
output(x, y)
是输出图像的像素值,kernel(i, j)
是核的元素值,input(x+i, y+j)
是输入图像的像素值。
(3)卷积操作在图像处理中有多种应用,其中最常见的是图像滤波。通过选择不同的核,可以实现不同的滤波效果,例如平滑滤波、边缘检测等。卷积操作还可以用于图像特征提取,例如使用卷积神经网络(CNN)进行图像分类、目标检测等任务。
在OpenCV中,可以使用cv::filter2D
函数来进行卷积操作。该函数接受输入图像、核以及输出图像作为参数,并将卷积结果存储在输出图像中。
2、卷积如何工作
把kernel放到像素数组之上,求锚点周围覆盖的像素乘积之和(包括锚点),用来替换锚点覆盖下像素点值称为卷积处理。数学表达如下
公式讲解:
K(i,j):卷积核的大小
I里面的参数就是窗口的半径
两个方向X、Y方向上的求和
例子:从左到右,从上到下进行计算
Sum = 8x1+6x1+6x1+2x1+8x1+6x1+2x1+2x1+8x1
New pixel = sum / (m*n)
3、常见算子(卷积核 Kenel)
(1)Robert算子:又称“梯度算子”
(2)Sobel算子:中间2*2,更大,比Robert算子的差异更大,效果可能更明显了
(3)拉普拉斯算子
4、自定义卷积模糊
(1)filter2D方法
filter2D (
Mat src, //输入图像
Mat dst, // 模糊图像
int depth, // 图像深度32/8,不知道的就默认-1,系统也默认和src的深度一样
Mat kernel, // 卷积核/模板
Point anchor = Point(-1,-1) , // 锚点位置,3、5、7、9,或者默认自动寻找中心位置
double delta = 0 // 计算出来的像素+delta
)
其中 kernel是可以自定义的卷积核
5、代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv)
{
Mat src, dst;
int ksize = 0;
src = imread("test.jpg");
if (!src.data)
{
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "Custom Blur Filter Result";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
// Robert 算子 X 方向
//Mat Robert_x = (Mat_<int>(2, 2) << 1, 0, 0, -1);
//Mat mat_Robert_x;
//filter2D(src, mat_Robert_x, -1, Robert_x);
//imshow("Robert x", mat_Robert_x);
Robert 算子 Y 方向
//Mat Robert_y = (Mat_<int>(2, 2) << 0, 1, -1, 0);
//Mat mat_Robert_y;
//filter2D(src, mat_Robert_y, -1, Robert_y);
//imshow("Robert y", mat_Robert_y);
// Sobel X 方向
Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);
imshow("Sobel X", dst);
// Sobel Y 方向
Mat yimg;
Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
filter2D(src, yimg, -1, kernel_y, Point(-1, -1), 0.0);
imshow("Sobel Y", yimg);
// 拉普拉斯算子
//Mat kernel_y = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
//filter2D(src, dst, -1, kernel_y, Point(-1, -1), 0.0);
//imshow("拉普拉斯", dst);
waitKey(0);
// 自定义卷积模糊
//int c = 0;
//int index = 0;
//while (true)null
//{
// c = waitKey(500);
// if ((char)c == 27) // ESC
// {
// break;
// }
// ksize = 5 + (index % 8) * 2;
// Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F) / (float)(ksize * ksize);
// filter2D(src, dst, -1, kernel, Point(-1, -1));
// index++;
// imshow(OUTPUT_WIN, dst);
//}
return 0;
}
效果展示:
(1)Robert算子,在X与Y方向上呈现出差异性
(2)Sobel算子:相对与Robert算子,差异会明显一些
(3)拉普拉斯算子:碎发也没看到了
二、卷积边缘
1、卷积边缘问题
卷积边缘问题:图像卷积的时候边界像素,不能被卷积操作。
原因:在于边界像素没有完全跟kernel重叠,所以当3x3滤波时候有1个像素的边缘没有被处理,5x5滤波的时候有2个像素的边缘没有被处理。
2、处理边缘
在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在 四周各填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之 后再去掉这些边缘。
openCV中默认的处理方法是: BORDER_DEFAULT,此外 常用的还有如下几种:
- BORDER_CONSTANT – 填充边缘用指定像素值
- BORDER_REPLICATE – 填充边缘像素用已知的边缘像素值
- BORDER_WRAP – 用另外一边的像素来补偿填充
3、相关的API说明
给图像添加边缘API:copyMakeBorder
copyMakeBorder(
- Mat src, // 输入图像
- Mat dst, // 添加边缘图像
- int top, // 边缘长度,一般上下左右都取相同值,
- int bottom,
- int left,
- int right,
- int borderType // 边缘类型
- Scalar value
)
4、代码演示
增加边缘的四种策略,都适用于什么场景,如何处理卷积的边缘。
先认识下:GaussianBlur()
GaussianBlur
函数用于对图像进行高斯模糊操作。它可以有效地去除图像中的噪声,并平滑图像的细节。void GaussianBlur (
InputArray src, // 输入图像,可以是单通道或多通道图像
OutputArray dst, // 输出图像,与输入图像具有相同的尺寸和类型
Size ksize, // 高斯核的大小,用
Size(w, h)
表示。它必须是正奇数,例如(3, 3)、(5, 5)等。double sigmaX, // 高斯核在X方向上的标准差
double sigmaY = 0, // 高斯核在Y方向上的标准差。如果为0,则默认使用
sigmaX
的值int borderType = BORDER_DEFAULT // 边界处理方式,默认为
BORDER_DEFAULT
);
int main(int argc, char** argv)
{
Mat src, dst;
src = imread("test.jpg");
if (!src.data)
{
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "Border Demo";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
/*
int top = (int)(0.05*src.rows);
int bottom = (int)(0.05*src.rows);
int left = (int)(0.05*src.cols);
int right = (int)(0.05*src.cols);
RNG rng(12345);
int borderType = BORDER_DEFAULT;
int c = 0;
while (true)
{
c = waitKey(500);
// ESC
if ((char)c == 27)
break;
if ((char)c == 'r')
borderType = BORDER_REPLICATE;
else if((char)c == 'w')
borderType = BORDER_WRAP;
else if((char)c == 'c')
borderType = BORDER_CONSTANT;
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
copyMakeBorder(src, dst, top, bottom, left, right, borderType, color);
imshow(OUTPUT_WIN, dst);
}
*/
// 上面的代码可以直接用下面接口替换
GaussianBlur(src, dst, Size(5, 5), 0, 0);
imshow(OUTPUT_WIN, dst);
waitKey(0);
return 0;
}
效果展示: