OpenCV学习笔记基础篇(十二):基于OpenCV的边缘检测、源码分析

前言:

笔者目前在校本科大二,有志于进行计算机视觉、计算机图形学方向的研究,准备系统性地、扎实的学习一遍OpenCV的内容,故记录学习笔记,同时,由于笔者同时学习数据结构、机器学习等知识,会尽量根据自己的理解,指出OpenCV的应用,并在加上自己理解的前提下进行叙述。
若有不当之处,希望各位批评、指正。


本篇学习内容:

1.基于OpenCV的边缘检测
2.源码分析


1.基于OpenCV的边缘检测

1.1 边缘检测一般步骤

摘自《OpenCV3编程入门》:
1.滤波
边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声敏感,因此需要采用滤波器来改善和噪声有关的边缘检测器的性能。
2.增强
增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来。在具体编程实现时,可通过计算梯度幅值来确定。
3.检测
经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定的应用中,这些点并不是要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际工程中,常用的方法是通过阈值化方法来检测。

1.2 Sobel算子

我之所以要先介绍Sobel算子,是因为在后文的Canny边缘检测函数中调用了Sobel()函数。

*Sobel算子可以计算图像灰度函数的近似梯度。
*Sobel算子结合了高斯平滑和微分,因此结果对噪声有一定的抵抗能力。

下面介绍Sobel()函数:

void cv::Sobel	(	
InputArray 	src,	//输入图像
OutputArray dst,	//输出图像
int 	ddepth,		//输出图像的深度。如果设置为-1,输出深度和输入深入一致
int 	dx,			//对x偏导的阶
int 	dy,			//对y偏导的阶
int 	ksize = 3,	//卷积核尺寸。只能为1/3/5/7!
double 	scale = 1,	//可选的计算导数值的比例因子,一般用不到
double 	delta = 0,	//可选的一个增量值,用于改变输出结果
int 	borderType = BORDER_DEFAULT //边界类型。一般不管它
)	

Sobel()用一个卷积核对每个像素的邻域卷积,来得到近似的梯度。一般用Sobel()分别计算出x和y的梯度,然后合成得到最终结果。
官方文档中的举例:
在这里插入图片描述
如果设置ksize = -1,对应的是Scharr。

在这里插入图片描述
注1:关于Scharr,这个函数的核大小只能是3,Scharr()函数的运算与Sobel()一样快,但结果更加精确。在调用Scharr()函数时,等同于在调用Sobel()时设置ksize = -1。
注2:一般在进行Sobel()计算梯度后,用addWeighted()函数合成,得到最终结果。

1.3 Canny边缘检测

用Canny()进行Canny边缘检测。

void cv::Canny	(	
InputArray 	image,//输入图像
OutputArray edges,//输出图像
double 	threshold1,//阈值1
double 	threshold2,//阈值2
int 	apertureSize = 3,//Sobel操作的卷积核大小
bool 	L2gradient = false //是否使用更精确的L2范数
)	

对部分参数进行进一步解释:
image:8位输入图像。可以多通道,但是在多通道时不支持就地操作。单通道时支持就地操作。
edges:8位单通道。Size和输入图像相同。
threshold1:第一个滞后性阈值
threshold2:第二个滞后性阈值。这两个阈值大小顺序可以任意。OpenCV会帮忙排好大小。
L2gradient:这是计算导数的两种方式。L2是计算x梯度和y梯度平方和的二次开方。而L1是计算x梯度和y梯度的绝对值之和。

注1:阈值1和阈值2中较小的用于边缘连接,较大的用于寻找强边缘的初始段。
注2:Canny()还有一个重载,可以直接输入梯度dx、dy,输出edges。

稍微举个例子:

Mat img = imread("E:/program/image/1.jpg");
Mat src,dst;
cvtColor(img, src, COLOR_BGR2GRAY);
GaussianBlur(src, src, Size(3, 3),0,0);
Canny(src, dst, 180, 120, 3);
imshow("src", img);
imshow("dst", dst);
waitKey();
return 0;

在这里插入图片描述
也可以输出彩色边缘:

Mat img = imread("E:/program/image/1.jpg");
Mat gray,mask,dst;
cvtColor(img, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, mask, Size(3, 3), 0, 0);
Canny(mask, mask, 100, 150);
dst = Mat::zeros(img.size(), img.type());
img.copyTo(dst, mask);
imshow("src", img);
imshow("dst", dst);
waitKey();
return 0;

在这里插入图片描述

2. 源码分析

Canny()函数文件路径:opencv\sources\modules\imgproc\src\canny.cpp 第823行

准备工作:

    CV_INSTRUMENT_REGION();

    CV_Assert( _src.depth() == CV_8U );

    const Size size = _src.size();

    // we don't support inplace parameters in case with RGB/BGR src
    CV_Assert((_dst.getObj() != _src.getObj() || _src.type() == CV_8UC1) && "Inplace parameters are not supported");

    _dst.create(size, CV_8U);

    if (!L2gradient && (aperture_size & CV_CANNY_L2_GRADIENT) == CV_CANNY_L2_GRADIENT)
    {
        // backward compatibility
        aperture_size &= ~CV_CANNY_L2_GRADIENT;
        L2gradient = true;
    }

    if ((aperture_size & 1) == 0 || (aperture_size != -1 && (aperture_size < 3 || aperture_size > 7)))
        CV_Error(CV_StsBadFlag, "Aperture size should be odd between 3 and 7");

    if (aperture_size == 7)
    {
        low_thresh = low_thresh / 16.0;
        high_thresh = high_thresh / 16.0;
    }

    if (low_thresh > high_thresh)
        std::swap(low_thresh, high_thresh);

从这里可以看到一些准备工作:包括不支持多通道的就地操作、调整low_thresh和high_thresh的顺序等。
随后调用了ocl_Canny函数:

CV_OCL_RUN(_dst.isUMat() && (_src.channels() == 1 || _src.channels() == 3),
               ocl_Canny<false>(_src, UMat(), UMat(), _dst, (float)low_thresh, (float)high_thresh, aperture_size, L2gradient, _src.channels(), size))

这个函数在同文件的135行:

static bool ocl_Canny(
InputArray _src, 
const UMat& dx_, 
const UMat& dy_, 
OutputArray _dst, 
float low_thresh, 
float high_thresh,
int aperture_size, 
bool L2gradient, 
int cn, 
const Size & size
)

在226行,调用了Sobel()函数:

      if (!useCustomDeriv)
       {
           Sobel(_src, dx, CV_16S, 1, 0, aperture_size, scale, 0, BORDER_REPLICATE);
           Sobel(_src, dy, CV_16S, 0, 1, aperture_size, scale, 0, BORDER_REPLICATE);
       }
       else
       {
           dx = dx_;
           dy = dy_;
       }

可以看到,Canny()中通过调用2次Sobel()来分别计算x和y的梯度。

Canny主要做的事情就是利用计算好的梯度来进行一些阈值操作。所以,我们有必要看一下Sobel()的源码:
文件路径:opencv\sources\modules\imgproc\src\deriv.cpp 第414行
在进行了一些准备工作后,出现了:

getDerivKernels( kx, ky, dx, dy, ksize, false, ktype );

这行代码用于生成卷积核。
然后对图像进行卷积:

sepFilter2D(src, dst, ddepth, kx, ky, Point(-1, -1), delta, borderType );

所以,Sobel()的本质上还是一种滤波(广义的滤波)。只是得到的结果可以很好地进行边缘检测(由于边缘检测算法基于图像的一阶、二阶梯度)。

参考文献:

  1. OpenCV官方文档:https://docs.opencv.org/4.x/
  2. 《OpenCV3编程入门》毛星云、冷雪飞等编著
  3. 《OpenCV4快速入门》冯振、郭延宁、吕跃勇著
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Yolov5和OpenCV的课堂学习状态识别检测码可以通过以下步骤实现: 1. 安装Yolov5和OpenCV库:首先,需要在计算机上安装Yolov5和OpenCV库。可以通过pip命令来安装这些库,如'pip install yolov5'和'pip install opencv-python'。 2. 下载Yolov5模型权重:从Yolov5的GitHub页面上可以下载预训练的Yolov5模型权重文件,如'yolov5s.pt'。 3. 设置输入:可以使用OpenCV来设置输入,如摄像头、视频文件或图像。例如,可以使用以下代码来设置使用摄像头作为输入: ```python import cv2 cap = cv2.VideoCapture(0) ``` 4. 加载Yolov5模型:使用Yolov5的load_model()方法来加载预训练的Yolov5模型权重文件。 ```python from models.experimental import attempt_load model = attempt_load('yolov5s.pt') ``` 5. 进行目标检测:使用Yolov5模型对输入中的图像进行目标检测。可以使用以下代码来实现: ```python ret, frame = cap.read() results = model(frame) ``` 6. 进行状态识别:根据目标检测的结果,使用OpenCV的图像处理和分析方法来进行状态识别。例如,可以检测人脸表情、姿势、动作等。 7. 输出结果:根据状态识别的结果,使用OpenCV的图像绘制函数来标注人脸或其他识别到的物体,并显示在屏幕上。 以上就是基于Yolov5和OpenCV的课堂学习状态识别检测码的基本步骤。可以根据实际需求进行相应的代码实现和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值