卷积核,通常也叫算子。用一个设定数值模板去处理一张输入图片,进行卷积运算。目的是使目标与目标之间的差距变得更大。卷积在数字图像处理中最常见的应用为锐化和边缘提取。
边缘提取:
当前景目标像素值与周边背景目标的像素值有较大差异时,可以通过卷积核对原图矩阵中的这个位置进行卷积运算,得出的值和该像素点原来的灰度值会产生显著的差异。变化的值超过我们预设的范围后,就可以将图像进行阈值处理,将图像的差异最大化,可以得到了一黑色为背景,白色线条作为边缘或形状的边缘提取效果图。
锐化算子:
通过卷积运算,可以增大矩阵每一个元素与周边元素的反差,起到锐化作用。图像锐化是补偿图像的轮廓,增强图像的边缘及灰度跳变的部分,使图像变得清晰,分为空间域处理和频域处理两类。图像锐化是为了突出图像上地物的边缘、轮廓,或某些线性目标要素的特征,也被称为边缘增强。
对图像求它的一阶导数delta = f(x) – f(x-1)
, delta
越大,说明像素在X方向变化越大,边缘信号越强
=========================================================================
卷积核作为一个设定数值模板矩阵,输入的待处理的图像作为原型矩阵,二者进行图像卷积运算,主要是使模板矩阵的中心像素点(称之为锚点)覆盖在待计算原型矩阵元素上面,中心像素点逐一对齐原型矩阵上的像素点(边缘像素无法对齐锚点,也就无法进行卷积计算,只能通过边缘像素处理后才能进行卷积),然后计算元素值与被覆盖的卷积核中的值的乘积和。将这个和赋值给当前锚点,这就是卷积的运算过程。
--------------------------------------------------------------------------------------------------------------------------------
Sobel算子的概念
Sobel算子是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度。sobel算子由两个3X3的卷积核构成,分别用于计算中心像素邻域的灰度加权差。分为垂直方向和水平方向的索伯滤波器Gx 和Gy。Soble算子功能集合高斯平滑和微分求导,又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方法与Y方向梯度图像,如下图。
参数说明:
#参数说明:
I 代表输入图像产生的图像矩阵,
Gx及Gy 分别代表经横向及纵向边缘检测的图像灰度值
G 表示图像的每一个像素的横向及纵向灰度值,有两种计算方式,通常Gx和Gy开方运算比较复杂
Sobel算子的卷积计算过程
为计算图像x方向上的梯度图像,我们需要一个卷积核kernel(Gx)和3*3的像素图片矩阵P。
#卷积核kernel和图像 P
Mat kernel = (Mat_(3, 3) << -1,0,+1,-2,0,+2,-1,0,+1);
Mat P = (Mat_(3, 3) <
以卷积核模板的中心像素为锚点,将卷积核与图像上像素值一一对应进行像素遍历,卷积核上的数字相当于加权系数。利用如下公式即可计算出卷积核中心的x方向梯度。
卷积计算过程为:P5 = (P3-P1)+2*(P6-P4)+(P9-P7)
同样的原理,在Gy方向上的梯度计算也可以求取
卷积计算过程为:P5 = (P7-P1)+2*(P8-P2)+(P9-P3),这样就得到了垂直方向和水平方向的图像梯度Gx 和Gy,就可以求出总的图像梯度,通常取|Gx| + |Gy|的和作为总的图像梯度。
---------------------------------------------------------------------------------------------------------------------------------
#函数API接口:
cv::Sobel (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度.
Int dx. // X方向,几阶导数
int dy // Y方向,几阶导数.
int ksize, SOBEL算子kernel大小,必须是1、3、5、7、
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
参数说明:
第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,要求与源图片有一样的尺寸和类型。
第三个参数,int类型的ddepth,输出图像的深度,目标图像的深度必须大于原图像的深度,支持如下src.depth()和ddepth的组合:
若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_64F, 取ddepth = -1/CV_64F
第四个参数,int类型的dx,x 方向上的差分阶数。
第五个参数,int类型的dy,y方向上的差分阶数。
第六个参数,int类型ksize,有默认值3,表示Sobel核的大小; 必须取1,3,5或7奇数的核。
第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
第九个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。
enum BorderTypes {
BORDER_CONSTANT = 0, //使用指定像素值来填充边缘
BORDER_REPLICATE = 1, //用已知的边缘像素值来填充边缘
BORDER_REFLECT = 2, //使用已知的边缘像素值来反转填充边缘
BORDER_WRAP = 3, //用另外一边的像素来补偿填充
BORDER_REFLECT_101 = 4, //使用已知的边缘像素值来反转填充边缘
BORDER_TRANSPARENT = 5, //使用黑色进行填充,本质上就是填充0
BORDER_REFLECT101 = BORDER_REFLECT_101,
BORDER_DEFAULT = BORDER_REFLECT_101,
BORDER_ISOLATED = 16 //
--------------------------------------------------------------------------------------------------------------------------------
1.图像在经过处理后,需要用cv::函数将其转回原来的uint8形式,否则将无法显示图像,而只是一副灰色的窗口。
convertScaleAbs()函数原型:
void cv::convertScaleAbs(
2 cv::InputArray src, // 输入数组
3 cv::OutputArray dst, // 输出数组
4 double alpha = 1.0, // 乘数因子
5 double beta = 0.0 // 偏移量
6 );
//结果返回uint8类型的图片
功能:实现将原图片转换为uint8类型
2.由于Sobel算子是在X轴,Y轴两个方向计算的,还需要用cv2.addWeighted()函数将其组合起来
函数原型:
CV_EXPORTS_W void addWeighted(InputArray src1, double alpha, InputArray src2,
double beta, double gamma, OutputArray dst, int dtype=-1);
src1: 第一幅输入图像
alpha: 线性混合时第一幅图像的权重
src2: 第二幅输入图像
beta: 第二幅输入图像的权重
dst: 图像线性混合后的目标图像
gamma: 添加到每一个线性叠加总和的gamma值
dtype: 目标图像深度,当两幅图像深度相同时可以将dtype置为-1,这样目标图像的深度将与输入图像相同
其中beta - (1.0 - alpha);
对于每个像素点其计算公式如下:
dst = a×src1+b×src2+r
功能:实现以不同的权重将两幅图片叠加,对于不同的权重,叠加后的图像会有不同的透明度
=========================================================================
Scharr算子比Sobel算子的值更大,是在Sorbel算子基础上改进的,因此对于灰度变化更为敏感,会得到较强的边缘强度,但是也会损失一些细节。
cv::Scharr (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度.
Int dx. // X方向,几阶导数
int dy // Y方向,几阶导数.
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,要求与源图片有一样的尺寸和类型。
第三个参数,int类型的ddepth,输出图像的深度,目标图像的深度必须大于原图像的深度,支持如下src.depth()和ddepth的组合:
BORDER_CONSTANT = 0, //使用指定像素值来填充边缘
BORDER_REPLICATE = 1, //用已知的边缘像素值来填充边缘
BORDER_REFLECT = 2, //使用已知的边缘像素值来反转填充边缘
BORDER_WRAP = 3, //用另外一边的像素来补偿填充
BORDER_REFLECT_101 = 4, //使用已知的边缘像素值来反转填充边缘
BORDER_TRANSPARENT = 5, //使用黑色进行填充,本质上就是填充0
第四个参数,int类型的dx,x 方向上的差分阶数。
第五个参数,int类型的dy,y方向上的差分阶数。
第六个参数,int类型ksize,有默认值3,表示Sobel核的大小; 必须取1,3,5或7奇数的核。
第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
第九个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。
=========================================================================
拉普拉斯算子原理
拉普拉斯算子(Laplance operator) 边缘提取的数学依据:在二阶导数的时候,最大变化处的值为零即边缘是零值。计算图像二阶导数,可以用来梯度计算、提取边缘、检测边缘。
Laplacian提取边缘流程
1)高斯模糊 – 去噪声GaussianBlur()
GaussianBlur(src, dst, Size(3, 3), 0, 0); //高斯模糊
2)转灰度 - cvtColor()
cvtColor(dst, gray_src, CV_BGR2GRAY); //转灰度
3)拉普拉斯 – 二阶导数计算Laplacian()
Laplacian(gray_src, edge_image, CV_16S, 3); //Laplacian算子
4)取绝对值 - convertScaleAbs() - 此处即可得到边缘图像
convertScaleAbs(edge_image, edge_image); //取绝对值
5)再二值化阈值处理 - 增强边缘特征threshold() - 边缘图像更明显
threshold(edge_image, edge_image, 0, 255, THRESH_BINARY | THRESH_OTSU);//自动计算二值化otsu阈值,忽略输入的阈值
--------------------------------------------------------------------------------------------------------------------------------
Laplacian算子API函数接口
void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
参数说明:
前两个是必须的参数:
第一个参数是输入的原图像,Mat类型的图片;
第二个参数是图像的深度,-1表示与原图像相同深度。目标图像深度必须大于等于原图像深度;
其后是可选的参数:
第三个参数dst目标图像,Mat类型的图片;
第四个参数ksize是算子的大小,必须为1、3、5、7。默认为1。
第五个参数scale是缩放导数的比例常数,默认无伸缩系数;
第六个参数delta是可选增量,会加到最终的dst中,默认情况下无额外的值加dst;
第七个参数borderType是判断图像边界模式。缺省cv.BORDER_DEFAULT。
=========================================================================
#include"stdafx.h"
#include
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char**argv)
{
Mat src, gray_src, dst, dst2;
src = imread("F:/photo/qx.jpg");
if (!src.data) {
printf("can naot load the image ...\n");
return -1;
}
char input_title[] = "input_picture";
char output_title[] = "sobel demo2";
namedWindow(input_title, WINDOW_AUTOSIZE);
imshow(input_title, src);//输出原图像
GaussianBlur(src, dst, Size(3, 3), 0, 0);//高斯模糊
cvtColor(dst, gray_src, COLOR_BGR2GRAY);//输出灰度图
imshow("gray_picture", gray_src);
Mat xgrad, ygrad;
Sobel(gray_src, xgrad, CV_32F, 1, 0,3);//x方向
Sobel(gray_src, ygrad, CV_32F, 0, 1,3);//y方向
Mat xygrad_0;
convertScaleAbs(xgrad, xgrad);//取绝对值
convertScaleAbs(ygrad, ygrad);
imshow("xgrad", xgrad);
imshow("ygrad", ygrad); //叠加图像输出
addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad_0,-1);
imshow("sobel_addweight", xygrad_0);
imwrite("sobel_demo1.jpg", xygrad_0);
//手动实现融合
Mat xygrad_2 = Mat(xgrad.size(), xgrad.type());
int width = xgrad.cols;
int height = ygrad.rows;
for (int row = 0; row < height; row++) {
for (int col = 0; col (row, col);
int yg = ygrad.at(row, col);
int xy = xg + yg;
xygrad_2.at(row, col) = saturate_cast(xy);
}
}
imshow(output_title, xygrad_2);
imwrite("sobel_demo2.jpg", xygrad_2);
Mat xgrad_1, ygrad_1;
Scharr(gray_src, xgrad_1, CV_32F, 1, 0);
Scharr(gray_src, ygrad_1, CV_32F, 0, 1);
convertScaleAbs(xgrad_1, xgrad_1);//取绝对值
convertScaleAbs(ygrad_1, ygrad_1);
addWeighted(xgrad_1, 0.5, ygrad_1, 0.5, 0, dst2, -1);
imshow("scharr_demo", dst2);
imwrite("scharr_demo.jpg", dst2);
imshow("schaar_xaddweight", xgrad_1);
imshow("schaar_yaddweight", ygrad_1);
waitKey(0);
return 0;
}
输入原图和灰度图
Sorbel算子在X方向的梯度和Y方向的梯度以及addWeighted()整合后的图像
手动融合的Sorbel图像的图像梯度效果会比ddWeighted()整合后的图像强很多
=========================================================================
Scharr算子图像边缘提取部分