图像梯度与边缘检测
是什么以及目的与意义
梯度是什么?梯度本身是个向量,表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模),这点我们在高数都学过。
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。
梯度计算需要求导数。但是图像梯度的计算,是通过计算像素值的差得到梯度的近似值。图像梯度表示的是图像变化的速度,反映了图像的边缘信息。
边缘是像素值快速变化的地方。所以对于图像的边缘部分,其灰度值变化较大,梯度值也较大;对于图像中较平滑的部分,其灰度值变化较小,梯度值也较小。
为了检测边缘,我们需要检测图像中的不连续性,可以使用图像梯度来检测不连续性。但是,图像梯度也会受到噪声的影响,因此建议先对图像进行平滑处理
Sobel算子
关于原理:
我感觉我解释也很难,建议直接看下面的链接,非常通俗易懂,超级推荐!!!一定要看
直接放函数,虽然可能这些情况并不会完全用上,但方便查阅,全放上
#include <opencv2/imgproc.hpp>
函数说明:void cv::Sobel( InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize = 3, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT );
输入参数:
src 输入图像。
dst 与src具有相同大小和相同通道数的输出图像。
ddepth 输出图像深度(使用src.depth()时为-1)。
dx x方向的求导阶数。一般为0,1,2。其中,0表示该方向没有求导。
dy y方向的求导阶数。一般为0,1,2。其中,0表示该方向没有求导。
ksize = 3 卷积核大小。一般为1、3、5或7。
scale = 1 计算导数值的可选比例因子;默认1,即不应用缩放。
delta = 0 偏移量,卷积结果要加上这个数字。
borderType = BORDER_DEFAULT 边界类型(即边界填充方式)。默认BORDER_DEFAULT。不支持BORDER_WRAP。
cv::BORDER_CONSTANT = 0 iiiiii|abcdefgh|iiiiiii 常量法。填充常数值
cv::BORDER_REPLICATE = 1 aaaaaa|abcdefgh|hhhhhhh 复制法。复制最边缘像素
cv::BORDER_REFLECT = 2 fedcba|abcdefgh|hgfedcb 反射法。以两边为轴
cv::BORDER_WRAP = 3 cdefgh|abcdefgh|abcdefg 外包装法。
cv::BORDER_REFLECT_101 = 4 gfedcb|abcdefgh|gfedcba 反射法。以最边缘像素为轴
cv::BORDER_TRANSPARENT = 5 uvwxyz|abcdefgh|ijklmno
cv::BORDER_REFLECT101 = 6 same as BORDER_REFLECT_101
cv::BORDER_DEFAULT = 7 same as BORDER_REFLECT_101
cv::BORDER_ISOLATED = 8 do not look outside of ROI
Scharr算子
和Sobel算子类似,懂了Sobel算子,这个算子就很容易理解了
区别在于:Sobel算子比较擅长检测轮廓比较明显的区域,而Scharr算子可以检测出轮廓并不是很明显的区域
照例链接:4.Scharr算子原理_哔哩哔哩_bilibili
(应该是同一个人讲的,很清楚,建议1.5-2倍速)
#include <opencv2/imgproc.hpp>
函数说明:void cv::Scharr( InputArray src, OutputArray dst, int ddepth, int dx, int dy, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT );
输入参数:
src 输入图像。
dst 与src具有相同大小和相同通道数的输出图像。
ddepth 输出图像深度(使用src.depth()时为-1)。
dx x方向的求导阶数。一般为0,1,2。其中,0表示该方向没有求导。
dy y方向的求导阶数。一般为0,1,2。其中,0表示该方向没有求导。
ksize = 3 卷积核大小。它必须是1、3、5或7。
scale = 1 计算导数值的可选比例因子;默认1,即不应用缩放。
delta = 0 偏移量,卷积结果要加上这个数字。
borderType = BORDER_DEFAULT 边界类型(即边界填充方式)。默认BORDER_DEFAULT。不支持BORDER_WRAP。
cv::BORDER_CONSTANT = 0 iiiiii|abcdefgh|iiiiiii 常量法。填充常数值
cv::BORDER_REPLICATE = 1 aaaaaa|abcdefgh|hhhhhhh 复制法。复制最边缘像素
cv::BORDER_REFLECT = 2 fedcba|abcdefgh|hgfedcb 反射法。以两边为轴
cv::BORDER_WRAP = 3 cdefgh|abcdefgh|abcdefg 外包装法。
cv::BORDER_REFLECT_101 = 4 gfedcb|abcdefgh|gfedcba 反射法。以最边缘像素为轴
cv::BORDER_TRANSPARENT = 5 uvwxyz|abcdefgh|ijklmno
cv::BORDER_REFLECT101 = 6 same as BORDER_REFLECT_101
cv::BORDER_DEFAULT = 7 same as BORDER_REFLECT_101
cv::BORDER_ISOLATED = 8 do not look outside of ROI
Laplacian算子
和前两个算子一样,但是他更精致,检测性能较好,常用于图像增强领域和边缘提取。
(看原理的话看完2分钟就ok了)
#include <opencv2/imgproc.hpp>
函数说明:void cv::Laplacian( InputArray src, OutputArray dst, int ddepth, int ksize = 1, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT );
输入参数:
src 输入图像。
dst 与src具有相同大小和相同通道数的输出图像。
ddepth 输出图像深度(使用src.depth()时为-1)。
ksize = 1 卷积核大小。它必须是1、3、5或7。
scale = 1 计算导数值的可选比例因子;默认1,即不应用缩放。
delta = 0 偏移量,卷积结果要加上这个数字。
borderType = BORDER_DEFAULT 边界类型(即边界填充方式)。默认BORDER_DEFAULT。不支持BORDER_WRAP。
cv::BORDER_CONSTANT = 0 iiiiii|abcdefgh|iiiiiii 常量法。填充常数值
cv::BORDER_REPLICATE = 1 aaaaaa|abcdefgh|hhhhhhh 复制法。复制最边缘像素
cv::BORDER_REFLECT = 2 fedcba|abcdefgh|hgfedcb 反射法。以两边为轴
cv::BORDER_WRAP = 3 cdefgh|abcdefgh|abcdefg 外包装法。
cv::BORDER_REFLECT_101 = 4 gfedcb|abcdefgh|gfedcba 反射法。以最边缘像素为轴
cv::BORDER_TRANSPARENT = 5 uvwxyz|abcdefgh|ijklmno
cv::BORDER_REFLECT101 = 6 same as BORDER_REFLECT_101
cv::BORDER_DEFAULT = 7 same as BORDER_REFLECT_101
cv::BORDER_ISOLATED = 8 do not look outside of ROI
Canny边缘检测算子
canny边缘检测算法步骤:
1、使用高斯滤波器对图像进行平滑处理。
2、利用一阶偏导算子找到灰度图像沿着水平方向Gx和垂直方向Gy的偏导数,并计算梯度的幅值和方向。
3、对梯度幅值进行NMS非极大值抑制,获取局部梯度的最大值。
在3X3窗口中,将给定像素P与沿着梯度线方向的两个像素进行比较,若P的梯度幅值小于该两个像素的梯度幅值,则令P=0;否则保留原幅值。
备注:将梯度方向分为4种来比较梯度幅值的强度:水平方向、垂直方向、正方向、-45°方向。
4、用双边阈值检测和边缘连接。
分三种情况:
(1)若像素值大于高阈值,则该像素一定是边缘像素(强边缘点),置为255;
(2)若小于低阈值,则一定不是,置为0;
(3)若像素值大于低阈值但小于高阈值,则观察该像素的(3X3)8邻域像素中是否有大于高阈值的像素点,若有则该像素是边缘像素,并将该点置为255,用以连接强边缘点;否则不是,则该点置为0。
老样子,不如直接看视频:
#include <opencv2/imgproc.hpp>
函数说明:void cv::Canny ( InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false );
输入参数:
image 8位输入图像。
edges 输出图像。其具有与输入图像相同大小和类型。
threshold1 第一个阈值(低阈值)。
threshold2 第二个阈值(高阈值)。
apertureSize = 3 Sobel算子孔径尺寸 。默认为3。可以是1、3、5、7
L2gradient = false 选择L1 or L2范数计算图像梯度大小。
(L2graduation=false)默认L1范数=|dI/dx|+|dI/dy|。
(L2gradient=true)L2范数= (dI/dx)^2 + (dI/dy)^2。
/*注释: 高阈值比较严格,求的边缘很少,一般认为高阈值的边缘都是有效。
低阈值比较宽松,求的边缘很多(一般包括高阈值求到的边缘),其中不少是无效的边缘。
Canny求得的边缘希望是连在一起的(通常是封闭的)
(1)先用高阈值将要提取轮廓的物体与背景区分开来,但可能边缘轮廓不连续或者不够平滑。
(2)然后低阈值平滑边缘的轮廓,将不连续的部分连接起来。*/
栗子
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
string img_path1 = "D:/系统默认/桌面/RM/sha.jpeg";
//string img_path2 = "D:/系统默认/桌面/RM/tou.jpeg";
Mat src = imread(img_path1, 1);
//Mat src2 = imread(img_path2, 1);
if (src.empty() ) {
cout << "can't read image!!" << endl;
return -1;
}
//预处理
Mat src_Gray, src_Gaus;
GaussianBlur(src, src_Gaus, Size(3, 3), 0, 0);
cvtColor(src, src_Gray, COLOR_BGR2GRAY);
//Sobel算子
Mat Sobel_X, Sobel_Y, Sobel_X_abs, Sobel_Y_abs, Sobel_XY, Sobel_XY1;
Sobel(src_Gray, Sobel_X, src_Gray.depth(), 1, 0); //计算 x 轴方向
Sobel(src_Gray, Sobel_Y, src_Gray.depth(), 0, 1); //计算 y 轴方向
convertScaleAbs(Sobel_X, Sobel_X_abs); //取绝对值
convertScaleAbs(Sobel_Y, Sobel_Y_abs); //取绝对值
addWeighted(Sobel_X_abs, 0.5, Sobel_Y_abs, 0.5, 0, Sobel_XY); //图像融合
//Scharr算子
Mat Scharr_X, Scharr_Y, Scharr_X_abs, Scharr_Y_abs, Scharr_XY, Scharr_XY1;
Scharr(src_Gray, Scharr_X, src_Gray.depth(), 1, 0); //计算 x 轴方向
Scharr(src_Gray, Scharr_Y, src_Gray.depth(), 0, 1); //计算 y 轴方向
convertScaleAbs(Scharr_X, Scharr_X_abs); //取绝对值
convertScaleAbs(Scharr_Y, Scharr_Y_abs); //取绝对值
addWeighted(Scharr_X_abs, 0.5, Scharr_Y_abs, 0.5, 0, Scharr_XY);
//拉普拉斯算子
Mat src_Laplacian;
Laplacian(src_Gray, src_Laplacian, src_Gray.depth());
//Canny
Mat src_Canny;
Canny(src_Gray, src_Canny, 10, 100);
imshow("src", src);
imshow("Sobel_XY", Sobel_XY);
imshow("Scharr_XY", Scharr_XY);
imshow("src_Laplacian", src_Laplacian);
imshow("src_Canny", src_Canny);
waitKey();
return 0;
}
学习参考
学习的主要参考依旧是:Opencv C++图像处理(全)_c++ opencv 图像处理-优快云博客
还有一些视频链接在文章里面
另外,今天中秋节,学的比较少,中秋节快乐!!!
//ps:案例图片用的是我莎莎的照片哈哈哈,今天看莎头混双半决赛4:0赢了哦
大家中秋节都要快乐哦!!