OpenCV学习 基础图像操作(十):卷积与卷积算子

卷积

卷积定义

简单来说就是一个kernel在图像上进行加权求和,用公式可表示为

H(x,y) = \sum _{i = 0} ^{M_i-1} \sum _{j = 0} ^{M_j-1}I(x+i-a_i,y+j-a_j)K(i,j)

卷积核的锚点指的是这个卷积核更新输出的点,一般为卷积核的中心点;

卷积的步长为卷积核在图片上移动一次的距离。

卷积边界问题

当卷积核移动到图像相边界时,卷积核会有部分位置超出原图的边界,此时无法进行运算,需要将原图像的边界扩充后才能运算。

扩充方式

BORDER_DEFAULT:用已知边缘镜像填充

BORDER_CONSTANTP:用指定像素填充边缘

BORDER_REPLICATE:用最边缘的像素填充边缘

BORDER_WRAP:用另一边的像素填充边缘

BORDER_ISOLATED:用0填充边缘

卷积算子

一阶算子

Rebert

Roberts 算子又称为交叉微分算子,它是基于交叉差分的梯度算法,通过局部差分计算检测边缘线条。常用来处理具有陡峭的低噪声图像,当图像边缘接近于正 45 度或负 45 度时,该算法处理效果更理想。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。

G_x = \begin{bmatrix} +1 & 0 \\ 0 & -1 \end{bmatrix}    G_y=\begin{bmatrix} 0 & +1\\ -1 & 0 \end{bmatrix}

prewitt

Prewitt 是一种图像边缘检测的微分算子,其原理是利用特定区域内像素灰度值产生的差分实现边缘检测。由于 Prewitt 算子采用 3*3 模板对区域内的像素值进行计算,而Robert算子的模板是 2*2,故 Prewitt 算子的边缘检测结果在水平方向和垂直方向均比 Robert 算子更加明显,Prewitt算子适合用来识别噪声较多,灰度渐变的图像。

G_x = \begin{bmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1\end{bmatrix}   G_y = \begin{bmatrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1\end{bmatrix}

经典Prewitt算子认为:凡灰度新值大于或等于阈值的像素点都是边缘点。即选择适当的阈值T,若P(i,j)≥T,则(i,j)为边缘点,P(i,j)为边缘图像。这种判定是欠合理的,会造成边缘点的误判,因为许多噪声点的灰度值也很大,而且对于幅值较小的边缘点,其边缘反而丢失了。

  Prewitt算子对噪声有抑制作用,抑制噪声的原理是通过像素平均,但是像素平均相当于对图像的低通滤波,所以Prewitt算子对边缘的定位不如Roberts算子。

Sobel

Sobel算子是一种用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导。该算子用于计算图像明暗程度近似值。根据图像边缘旁边明暗程度把该区域内超过某个数的特定点记为边缘。Sobel 算子在Prewitt算子的基础上增加了权重的概念,认为相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓。

Sobel算子的边缘定位更准确,常用于噪声较多,灰度渐变的图像。

G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1\end{bmatrix}   G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1\end{bmatrix}

Sobel 算子根据像素点上下,左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘,对噪声具有平滑作用,提供较为精确的边缘方向信息。因为Sobel算子结合了高斯平滑和微分求导(分化),因此结果会具有更多的抗噪性,当对精度要求不是很高时,Sobel 算子是一种较为常用的边缘 检测方法。

图像的每一个像素的横向及纵向梯度近似值可用以下的公式结合,来计算梯度的大小。

G = \sqrt{G_x^2+Gy^2}

但是实际工程中为简化计算常将上式,替换为下式来实现:

G=\left | G_x \right | + \left | G_y \right |

Isotropic Sobel算子

  Sobel算子另一种形式是(Isotropic Sobel)算子,加权平均算子,权值反比零点与中心店的距离,当沿不同方向检测边缘时梯度幅度一致,就是通常所说的各向同性Sobel(Isotropic Sobel)算子。模板也有两个,一个是检测水平边沿的 ,另一个是检测垂直平边沿的 。各向同性Sobel算子和普通Sobel算子相比,它的位置加权系数更为准确,在检测不同方向的边沿时梯度的幅度一致。

G_x = \begin{bmatrix} -1 & 0 & 1 \\ -\sqrt{2} & 0 & \sqrt{2}\\ -1 & 0 & 1\end{bmatrix}   G_y = \begin{bmatrix} -1 & -\sqrt{2} & -1 \\ 0 & 0 & 0 \\ 1 & \sqrt{2} & 1\end{bmatrix}

Scharr算子

Scharr算子与Sobel算子的不同点是在平滑部分,这里所用的平滑算子是 1/16 *[3, 10, 3],相比于 1/4*[1, 2, 1],中心元素占的权重更重,这可能是相对于图像这种随机性较强的信号,领域相关性不大,所以邻域平滑应该使用相对较小的标准差的高斯函数,也就是更瘦高的模板。

  由于Sobel算子在计算相对较小的核的时候,其近似计算导数的精度比较低,比如一个3*3的Sobel算子,当梯度角度接近水平或垂直方向时,其不精确性就越发明显。Scharr算子同Sobel算子的速度一样快,但是准确率更高,尤其是计算较小核的情景,所以利用3*3滤波器实现图像边缘提取更推荐使用Scharr算子。

  • Sobel算子是滤波算子的形式来提取边缘,X,Y方向各用一个模板,两个模板组合起来构成一个梯度算子。X方向模板对垂直边缘影响最大,Y方向模板对水平边缘影响最大。

  • Robert算子是一种梯度算子,它用交叉的查分表示梯度,是一种利用局部差分算子寻找边缘的算子,对具有陡峭的低噪声的图像效果最好。

  • prewitt算子是加权平均算子,对噪声有抑制作用,但是像素平均相当于对图像进行的同滤波,所以prewitt算子对边缘的定位不如robert算子。

二阶算子

Laplace

拉普拉斯(Laplacian)算子是n维欧几里德空间中的一个二阶微分算子,常用于图像增强领域和边缘提取。它通过灰度差分计算邻域内的像素,基本流程是:判断图像中心像素灰度值与它周围其他像素的灰度值,如果中心像素的灰度更高,则提升中心像素的灰度;反之降低中心像素的灰度,从而实现图像锐化操作。在算法实现过程中,Laplacian算子通过对邻域中心像素的四方向或八方向求梯度,再将梯度相加起来判断中心像素灰度与邻域内其他像素灰度的关系,最后通过梯度运算的结果对像素灰度进行调整。

G_4 = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0\end{bmatrix}   G_8= \begin{bmatrix} 1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1\end{bmatrix}

Laplacian算子的基本流程是:判断图像中心像素灰度值与它周围其他像素的灰度值,如果中心像素的灰度更高,则提升中心像素的灰度;反之降低中心像素的灰度,从而实现图像锐化操作。在算法实现过程中,Laplacian算子通过对邻域中心像素的四方向或八方向求梯度,再将梯度相加起来判断中心像素灰度与邻域内其他像素灰度的关系,最后通过梯度运算的结果对像素灰度进行调整。

         Laplace算子是一种各向同性算子,二阶微分算子,具有旋转不变性。在只关心边缘的位置而不考虑其周围的象素灰度差值时比较合适。Laplace算子对孤立象素的响应要比对边缘或线的响应要更强烈,因此只适用于无噪声图象。存在噪声情况下,使用Laplacian算子检测边缘之前需要先进行低通滤波。所以,通常的分割算法都是把Laplacian算子和平滑算子结合起来生成一个新的模板。

LOG(Laplacian of Gaussian)

LOG算子首先对图像做高斯滤波,然后再求其拉普拉斯(Laplacian)二阶导数,根据二阶导数的锅零点来检测图像的边界,即通过检测滤波结果的零交叉(Zero  crossings)来获得图像或物体的边缘。

  LOG算子综合考虑了对噪声的抑制和对边缘的检测两个方向,并且把Gauss平滑滤波器和Laplacian锐化滤波器结合了起来,先平滑掉噪声,再进行边缘检测,所以效果会更好。该算子与视觉生理中的数学模型相似,因此在图像处理领域中得到了广泛的应用。它具有抗干扰能力强,边界定位精度高,边缘连续性好,能有效提取对比度弱的边界等特点。

API介绍

填充边缘

copyMakeBorder(src,dst,top,bottom,left,right,borderType,color)
// src 原图   dst 目标图
// top,bottom,left,right 上下左右填充宽度
//borderType 填充类型 
//color 当使用BORDERTPYPE_CONSTANTP时,才用

Sobel(Scharr)算子

Sobel(  //Scharr相同
InputArray Src,   //输入图像
QutputArray dst,  //输出图像
int depth,        //输出位深度 设为"-1"会自适应 选择合适的
int dx,    //x方向,几阶导数
int dy,    //y方向,几阶导数      
int ksize,        //kernel大小,必须是奇数
double scale = 1, //输出是否缩放,即乘因子
double delta = 0, //输出是否加偏差,即加因子
int borderType = BORDER_DEFAULT  //
)

Laplacian算子

Laplacian( 
src_gray,     //src_gray: 输入图像
dst,          //dst: 输出图像
ddepth,       //ddepth: 输出图像的深度因为输入图像的深度是 CV_8U ,这里我们必须定义 ddepth = CV_16S 以避免外溢。
kernel_size,  //kernel_size: 内部调用的 Sobel算子的内核大小,此例中设置为3。
scale, 
delta, 
BORDER_DEFAULT 
);

代码实践

边缘补充

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

using namespace cv;
using namespace std;

int main(int argc, char* argv[])
{
	//src = imread("src.jpg");
	Mat src = imread("cat.png"),dst;
	if (!src.data)
	{
		cout << "cannot open image" << endl;
		return -1;
	}
	namedWindow("input image", WINDOW_AUTOSIZE);
	imshow("input image", src);
	Mat d1, d2, d3, d4,d5,d6,d7,d8;
	int top = (int)(0.10 * src.rows);
	int bottom = (int)(0.10 * src.rows);
	int left = (int)(0.10 * src.cols);
	int right = (int)(0.10 * src.cols);

	copyMakeBorder(src, d1, top, bottom, left, right, BORDER_CONSTANT, Scalar(255, 255, 255));
	copyMakeBorder(src, d2, top, bottom, left, right, BORDER_DEFAULT);
	copyMakeBorder(src, d3, top, bottom, left, right, BORDER_REFLECT);
	copyMakeBorder(src, d4, top, bottom, left, right, BORDER_REPLICATE);
	copyMakeBorder(src, d5, top, bottom, left, right, BORDER_WRAP);
	copyMakeBorder(src, d6, top, bottom, left, right, BORDER_ISOLATED);
	copyMakeBorder(src, d7, top, bottom, left, right, BORDER_REFLECT101);

	imshow("BORDER_CONSTANT", d1);
	imshow("BORDER_DEFAULT", d2);
	imshow("BORDER_REFLECT", d3);
	imshow("BORDER_REPLICATE", d4);
	imshow("BORDER_WRAP", d5);
	imshow("BORDER_ISOLATED", d6);
	imshow("BORDER_REFLECT101", d7);

	waitKey(0);
	return 0;
}

算子提取边缘

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

using namespace cv;
using namespace std;

int main(int argc, char* argv[])
{
	//src = imread("src.jpg");
	Mat src = imread("src.jpg"),gray_src;
	if (!src.data)
	{
		cout << "cannot open image" << endl;
		return -1;
	}
	namedWindow("input image", WINDOW_AUTOSIZE);
	imshow("input image", src);
	GaussianBlur(src, src, Size(3, 3), 0, 0);
	cvtColor(src,src,COLOR_BGR2GRAY);

	//Robert算子
	//X
	Mat dst_rx, dst_ry;
	Mat kernel_x1 = (Mat_<int>(2, 2) << 1, 0, 0, -1);
	filter2D(src, dst_rx, -1, kernel_x1, Point(-1, -1), 0.0);
	
	//Y
	Mat kernel_y1 = (Mat_<int>(2, 2) << 0 ,1, -1, 0);
	filter2D(src, dst_ry, -1, kernel_y1, Point(-1, -1), 0.0);

	//拼接两个方向的梯度
	Mat xygrad = Mat(src.size(), src.type());
	int width = xygrad.cols;
	int height = xygrad.rows;
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_rx.at<uchar>(row, col);
			int yg = dst_ry.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Rebort", xygrad);


	// prewitt算子
	Mat dst_sx, dst_sy;
	//X
	Mat kernel_x2 = (Mat_<int>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);
	filter2D(src, dst_sx, -1, kernel_x2, Point(-1, -1), 0.0);
	
	//Y
	Mat kernel_y2 = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
	filter2D(src, dst_sy, -1, kernel_y2, Point(-1, -1), 0.0);
	
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_sx.at<uchar>(row, col);
			int yg = dst_sy.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Prewitt", xygrad);

	//Sobel 算子
	Sobel(src, dst_rx, CV_16S, 1, 0, 3);
	convertScaleAbs(dst_rx, dst_rx);
	Sobel(src, dst_ry, CV_16S, 0, 1, 3);
	convertScaleAbs(dst_ry, dst_ry);
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_rx.at<uchar>(row, col);
			int yg = dst_ry.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Sobel", xygrad);

	//Scharr 算子
	Scharr(src, dst_rx, CV_16S, 1, 0);
	convertScaleAbs(dst_rx, dst_rx);
	Scharr(src, dst_ry, CV_16S, 0, 1);
	convertScaleAbs(dst_ry, dst_ry);
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_rx.at<uchar>(row, col);
			int yg = dst_ry.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Scharr", xygrad);

	//Laplacian 算子
	Mat edge_image;
	Laplacian(src, edge_image, CV_16S, 3);
	convertScaleAbs(edge_image, edge_image);
	imshow("Laplacian", edge_image);
	

	waitKey(0);
	return 0;
}

 

猜你喜欢

转载自blog.csdn.net/fan1102958151/article/details/107343870