仅自学做笔记用,后续有错误会更改
理论
- 卷积的应用 - 图像边缘提取:
- 边缘是什么:是像素值发生跃迁的地方, 是图像的显著特征之一, 再图像特征提取丶对象检测丶模式识别等方面都有重要作用
- 如何捕捉/提取边缘:对图像求它的一阶导数,delta = f(x) - f(x-1), delta值越大, 说明像素在x方向变化越大,边缘信号越强
- 如果你已经忘记了数学求导什么的概念, 也不用担心, 直接用Sobel算子进行卷积操作就可以了!
- Sobel算子
- 是离散微分算子(discrete differentiation operator), 用来计算图像灰度的近似梯度
- Sobel算子功能集合了 高斯模糊和微分求导
- 又被称为一阶微分算子,求导算子,在水平和垂直两个方向求导,得到图像X方向与Y方向的梯度图像
- 求取导数的近似值(上图的实际应用公式), kernel=3时不是很准确,容易受到干扰, Opencv使用改进版本Scharr函数, 算子如下:
- Sobel算子边缘提取步骤:
- 先把原图像进行高斯模糊操作
- 再把图像转灰度图
- 再通过Sobel算子求X梯度和Y梯度
- 线性混合X与Y的梯度图(可以不使用addweighted接口,而是手动去写, 去掉权重α与1-α的影响,可以使最后的图更明显),得到最终的振幅图像
相关API
cv::Sobel(
InputArray src, //输入图像
OutputArray dst, //输出图像
int depth, //输出图像深度,填-1表示跟输入图像一致, 由于灰度图是CV_8U,所以Sobel一般使用CV_16S/CV_32F, 需要比输入的灰度图的深度更高, 结果才会更明显
int dx, //x方向,几阶导数, sobel取1
int dy, //y方向,几阶导数, sobel取1
int ksize, //算子(kernel)大小,Sobel算子必须是奇数, 常见的是3
double scale = 1, //输出图像放大或缩小倍数
double delta = 0, //偏移量
int borderType = BORDER_DEFAULT
)
cv::Scharr(
InputArray src, //输入图像
OutputArray dst, //输出图像
int depth, //输出图像深度,填-1表示跟输入图像一致, 由于灰度图是CV_8U,所以Sobel一般使用CV_16S/CV_32F, 需要比输入的灰度图的深度更高, 结果才会更明显
int dx, //x方向,几阶导数, sobel取1
int dy, //y方向,几阶导数, sobel取1
int ksize, //算子(kernel)大小,Sobel算子必须是奇数, 常见的是3
double scale = 1, //输出图像放大或缩小倍数
double delta = 0, //偏移量
int borderType = BORDER_DEFAULT
)
代码示例
using namespace cv;
int main(int argc, char** argv){
Mat src,dst;
int ksize = 0;
src = imread(...);
if( !src.data ){
return -1;
}
//原图
char INPUT_WIN[] = "input image";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
//先高斯模糊, 再转灰度图
Mat gray_src;
GaussianBlur(src, dst, Size(3,3), 0, 0);
cvtColor(dst, gray_src, CV_BGR2GRAY);
imshow("gray image",gray_src);
/*//cv::Scharr操作, 可以看最后的效果截图, 它的提取效果非常强烈, 根本不怕干扰
Mat xgrad, ygrad;
Scharr(gray_src, xgrad, CV_16S, 1, 0);
Scharr(gray_src, ygrad, CV_16S, 0, 1);*/
//cv::Sobel操作(输出深度为CV_16S,且有convertScaleAbs转换)
Mat xgrad, ygrad;
Scharr(gray_src, xgrad, CV_16S, 1, 0);
Scharr(gray_src, ygrad, CV_16S, 0, 1);
Sobel(gray_src, xgrad, CV_16S, 1, 0, 3); //这里为什么用CV_16S, 因为灰度图是CV_8U,我们设置输出图像的深度比灰度图更大, 就可以容纳更大的特征值, 提取效果也就更明显, 你也可以填-1等同于输入图像的深度,但是反正不能小于输入图像的深度。
Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);
convertScaleAbs(xgrad, xgrad); //这个函数的作用是保证Sobel算子操作过后有些负数结果值不被置为0, 也就是不被截取掉
convertScaleAbs(ygrad, ygrad);
imshow("xgrad", xgrad);
imshow("ygrad", ygrad);
/*//cv::Sobel操作(输出深度填-1,且没有convertScaleAbs转换)
Mat xgrad, ygrad;
Sobel(gray_src, xgrad, -1, 1, 0, 3);
Sobel(gray_src, ygrad, -1, 0, 1, 3);
imshow("xgrad", xgrad);
imshow("ygrad", ygrad);*/
//最终线性混合图:手动写线性混合,去掉权重影响, 可以看最后的效果截图,更明显
Mat xygrad = Mat(xgrad.size(), xgrad.type());
int width = xgrad.cols;
int height = ygrad.rows;
for(int row = 0; row < weight; row++){
for(int col = 0;col < width;col++){
int xg = xgrad.at<uchar>(row, col);
int yg = ygrad.at<uchar>(row, col); //因为灰度图是CV_8U,所以用uchar
int xy = xg + yg; //直接相加, 没有权重影响, 更亮更明显
xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy); //saturate_cast这个东西的作用是保证最终值不超过0~255
}
}
imshow("Final Result", xygrad);
/*//最终线性混合图:直接调用addWeighted接口
Mat xygrad;
addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad);
imshow("Final Result", xygrad);*/
waitKey(0);
return 0;
}
效果截图:
使用cv::Sobel(两个方向的输出深度都填CV_16S且有convertScaleAbs转换) x梯度图 与 y梯度图:
使用cv::Sobel(两个方向的输出深度都填-1且没有convertScaleAbs转换) x梯度图 与 y梯度图:
使用cv::Sobel(两个方向的输出深度都填CV_16S且有convertScaleAbs转换, 直接调用addWeighted接口) 最终线性混合图:
使用cv::Sobel(两个方向的输出深度都填CV_16S且有convertScaleAbs转换, 手动写线性混合) 最终线性混合图:
使用cv::Scharr(两个方向的输出深度都填CV_16S且有convertScaleAbs转换, 且手动写线性混合) 最终线性混合图: