卷积应用-图像边缘提取
边缘是什么 – 是像素值发生跃迁的地方,是图像的显著特征之一,在图像特征提取、对象检测、模式识别等方面都有重要的作用。
如何捕捉/提取边缘 – 对图像求它的一阶导数
delta = f(x) – f(x-1), delta越大,说明像素在X方向变化越大,边缘信号越强,
怎么求一阶导数? 我已经忘记啦,不要担心,用Sobel算子就好!卷积操作!
Sobel算子
是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度
Soble算子功能集合高斯平滑和微分求导
又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方法与Y方向梯度图像。 拉普拉斯算子是二阶求导算子
最终图像梯度:理论上 G = sqrt(Gx^2 + Gy^2) , 实际为了减少计算速度 G = |Gx| + |Gy|
求取导数的近似值,kernel=3时不是很准确,OpenCV使用改进版本 Scharr 函数,算子为: ┌ -3 0 3 ┐ ┌ -3 -10 -3 ┐ 相比Sobel算子,得到的差异更大
│-10 0 10 │ │ 0 0 0 │
└ -3 0 3 ┘ └ 3 10 3 ┘
Scharr 将梯度更加的放大,相比于 sobel ,非常的不怕干扰
cv::Sobel ( // Scharr 函数与 Sobel 函数参数几乎一致,没有 ksize 参数,固定是3 ? Laplacian 函数也是如此
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度. input depth output depth 输入图像深度与输出图像深度关系(注意颜色数据的数据类型与深度的数据类型)
CV_8U -1/CV_16S/CV_32F/CV_64F -1表示跟输入深度一样,输出深度最好大于输入深度。 S 表示有符号
CV_16U/CV_16S -1/CV_32F/CV_64F
CV_32F -1/CV_32F/CV_64F
CV_64F -1/CV_64F
Int dx. // X方向,几阶导数
int dy // Y方向,几阶导数.
int ksize, SOBEL算子kernel大小,必须是1、3、5、7、
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
代码
#include "../common/common.hpp"
static void run(Mat , Mat , Mat , Mat , char * , char * , char * , char * ); //图像梯度处理
void main(int argc, char** argv)
{
Mat src, dst1, gray, sobelX, sobelY, add_sobel, sum_sobel, scharrX, scharrY, add_scharr, sum_scharr;
src = imread(getCVImagesPath("images/test1_3.png"), IMREAD_COLOR);
GaussianBlur(src, dst1, Size(3, 3), 0, 0);//先模糊,为了去噪声
cvtColor(dst1, gray, CV_BGR2GRAY);//再转灰度图
int ddepth = CV_16S;
// 输出图像使用 CV_16S 相比输出深度为原图的 CV_8U ,输出的图像梯度更明显
Sobel(gray, sobelX, ddepth, 1, 0, 3);//Sobel X 方向求导,求梯度,输出的图像是二值图
Sobel(gray, sobelY, ddepth, 0, 1, 3);//Sobel Y 方向求导,求梯度
run(sobelX, sobelY, add_sobel, sum_sobel, "sobelX", "sobelY", "add_sobel", "sum_sobel");//图像梯度处理
Scharr(gray, scharrX, ddepth, 1, 0);//Scharr 将梯度更加的放大,相比于 sobel ,非常的不怕干扰
Scharr(gray, scharrY, ddepth, 0, 1);
run(scharrX, scharrY, add_scharr, sum_scharr, "scharrX", "scharrY", "add_scharr", "sum_scharr");
waitKey(0);
}
void run(Mat gx, Mat gy, Mat addWeight, Mat sum, char * gxName, char * gyName, char * addName, char * sumName)
{
convertScaleAbs(gx, gx);//convertScaleAbs函数将颜色数据取绝对值,否则图像可能无法显示
convertScaleAbs(gy, gy);
imshow(gxName, gx);
imshow(gyName, gy);
addWeighted(gx, 0.5, gy, 0.5, 0.0, addWeight);//振幅图像,图像合成
imshow(addName, addWeight);
sum.create(gx.size(), gx.type());
int width = gx.cols;
int height = gy.rows;
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
int xg = gx.at<uchar>(row, col);
int yg = gy.at<uchar>(row, col);
sum.at<uchar>(row, col) = saturate_cast<uchar>(xg + yg);//此方式比addWeighted,合成之后的梯度更明显
}
}
imshow(sumName, sum);
}
效果图