OpenCvSharp函数:Canny边缘检测

Canny边缘检测

函数说明

使用Canny算法检测边缘,该算法满足边缘检测的三个条件

  • 低错误率:只检测到存在的边缘。
  • 良好的定位:检测到的边缘在真实边缘的中心。
  • 最小响应:每个边缘只有一个检测器响应,尽可能排除噪声。

算法步骤:

  1. 使用高斯模糊去噪声
  2. 计算图像每个像素点的梯度强度和方向。默认使用Sobel算子,也可以自定义
  3. 应用非极大值(Non-maximum)抑制。这样可以保留细边缘。
  4. 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
  • a. 如果像素的梯度大于上限阈值,则认为是边缘
  • b. 如果像素的梯度小于下限阈值,则认为是非边缘
  • c. 如果像素的梯度在两阈值之间,只有该像素于边缘像素(对应梯度大于大限阈值)时,才认为是边缘。
    Canny推荐上限阈值与下限阈值的比在2:1 到 3:1 之间(为什么?还有这两个阈值如何取,是难点,网上有自适应阈值的算法,待研究)
  1. 边缘连接

函数原型

//函数原型1
void Canny(InputArray src,
	OutputArray edges,
	double threshold1,
	double threshold2,
	int apertureSize = 3,
	bool L2gradient = false)
//函数原型2
void Canny(InputArray dx,
	InputArray dy,
	OutputArray edges,
	double threshold1,
	double threshold2,
	bool L2gradient = false)

参数说明

参数 说明                            
InputArray src 输入图像, 单通道8位图像
InputArray dx 对水平方向(x轴)求导的图像(CV_16SC1 或 CV_16SC3),可使用其它算子求导
InputArray dy 对垂直方向(y轴)求导的图像(CV_16SC1 或 CV_16SC3),可使用其它算子求导
OutputArray edges 输出边缘图像。大小与通道数与输入图像一致
double threshold1 最小阈值。像素梯度比该值小,不认为是边缘
double threshold2 最大阈值(比threshold1小时,会自动交换值)。像素梯度比该值大,认为是边缘
int apertureSize Sobel算子的核大小,必须为3、5、7
L2gradient 是否使用L2梯度计算,为true时更准确、但更耗时,按L2norm = ( d I / d x ) 2 + ( d I / d y ) 2 =\sqrt{(dI/dx)^2 + (dI/dy)^2} =(dI/dx)2+(dI/dy)2 计算,
默认为false已足够,按L1norm==丨dI/dx丨+丨dI/dy丨

图像示例

OpenCvSharp函数:Canny边缘检测_第1张图片

代码示例

Mat srcColor;
Mat srcGray = new Mat();

string winName = "Canny Demo,按 T 切换L2,按Esc退出";                

bool L2gradient = false;

string tbMinThresholdName = "Low";//最小阈值
string tbThresholdRatioName = "Ratio";//最大、最小阈值比 2~3之间
string tbApertureSizeName = "Size";//核

public void Run()
{
    if (!Utils.SelectFile(out string fileName)) fileName = ImagePath.Fruits;
    srcColor = Cv2.ImRead(fileName, ImreadModes.Color);
    if (srcColor.Empty()) throw new Exception($"图像打开有误:{fileName}");
    Cv2.CvtColor(srcColor, srcGray, ColorConversionCodes.BGR2GRAY);
    //均值平滑
    Cv2.Blur(srcGray, srcGray,new Size(3, 3));

    Cv2.NamedWindow(winName, WindowFlags.AutoSize);
    //下限阈值调整
    Cv2.CreateTrackbar(tbMinThresholdName, winName, 255, LowThresholdOnChanged);
    Cv2.SetTrackbarPos(tbMinThresholdName, winName, 100);

    //上、下限阈值比例调整,建议最小阈值与最大阈值比,1:2至1:3之间
    Cv2.CreateTrackbar(tbThresholdRatioName, winName, 30, ThresholdRatioOnChanged);
    Cv2.SetTrackbarMin(tbThresholdRatioName, winName, 20);
    Cv2.SetTrackbarPos(tbThresholdRatioName, winName, 20);

    //Sobel算子核大小
    Cv2.CreateTrackbar(tbApertureSizeName, winName, 3, SizeOnChanged);
    Cv2.SetTrackbarMin(tbApertureSizeName, winName, 1);
    Init = false;//初始化结束
    Cv2.SetTrackbarPos(tbApertureSizeName, winName, 1);

    bool loop = true;
    while (loop)
    {
        var c = (Char)Cv2.WaitKey(50);
        switch(c)
        {
            case 't':
            case 'T':
                L2gradient = !L2gradient;
                OnChanged();
                break;
            case 'q':
            case 'Q':
            case (Char)27:
                loop = false;
                break;
        }
    }
    Cv2.DestroyAllWindows();
}

bool Init = true;
private void OnChanged()
{
    if (Init) return;//初始化中,避免初始化未完成和多次调用
    using var dstEdgeSobel = new Mat();
    //最大阈值,原文建议是最小阈值的2至3倍
    var HighThreshold = LowThreshold * ThresholdRatio;
    Cv2.Canny(srcGray, dstEdgeSobel, LowThreshold, HighThreshold, apertureSize, L2gradient);
    
    using Mat dst = Mat.Zeros(srcColor.Size(), srcColor.Type());
    //使用边缘掩膜复制
    srcColor.CopyTo(dst, dstEdgeSobel);


    //自定义对图像求导,下面的结果与上面一样
    double scale = 1.0D;
    if (apertureSize == 7)
    {
        scale = 1 / 16.0D;
        LowThreshold = LowThreshold / 16.0D;
        HighThreshold = HighThreshold / 16.0D;
    }
    using var dx = new Mat();
    Cv2.Sobel(srcGray, dx, MatType.CV_16S, 1, 0, apertureSize, scale, 0, BorderTypes.Replicate);
    using var dy = new Mat();
    Cv2.Sobel(srcGray, dy, MatType.CV_16S, 0, 1, apertureSize, scale, 0, BorderTypes.Replicate);

    using var dstEdgeCustom=new Mat();
    //dx、dy可使用其它算子求导
    Cv2.Canny(dx, dy, dstEdgeCustom, LowThreshold, HighThreshold, L2gradient);

    //dstEdgeSobel与dstEdgeCustom一样
    //Utils.CompareMat(dstEdgeSobel, dstEdgeCustom);

    Utils.PutText(dst, $"t1={LowThreshold},t2={LowThreshold * ThresholdRatio},size={apertureSize},L2={L2gradient}");
    Cv2.ImShow(winName, dst);
}


double LowThreshold = 0;
/// 
/// 调整Canny最小阈值
/// 
/// 
/// 
private void LowThresholdOnChanged(int pos, IntPtr userData)
{
    LowThreshold = pos;
    OnChanged();
}
double ThresholdRatio = 3;
/// 
/// 调用Canny最大阈值
/// 
/// 
/// 
private void ThresholdRatioOnChanged(int pos, IntPtr userData)
{
    ThresholdRatio = pos / 10.0D;
    OnChanged();
}

int apertureSize = 3;//3到7
private void SizeOnChanged(int pos, IntPtr userData)
{
    apertureSize = pos * 2 + 1;
    OnChanged();
}

OpenCvSharp函数示例(目录)
参考
https://docs.opencv.org/4.7.0/da/d5c/tutorial_canny_detector.html

你可能感兴趣的:(OpenCVSharp学习,OpenCvSharp函数,opencv,c#,图像处理)