OpenCvSharp function: Canny edge detection

Canny edge detection

Function description

Detect edges using the Canny algorithm, which satisfies the three conditions for edge detection

  • Low error rate: only existing edges are detected.
  • Good localization: the detected edge is in the center of the real edge.
  • Minimal response: Only one detector response per edge, eliminating as much noise as possible.

Algorithm steps:

  1. Use Gaussian blur to remove noise
  2. Calculate the gradient intensity and direction of each pixel in the image. The Sobel operator is used by default, but can also be customized.
  3. Apply non-maximum suppression. This preserves the thin edges.
  4. Apply Double-Threshold detection to determine real and potential edges.
  • a. If the gradient of a pixel is greater than the upper threshold, it is considered an edge.
  • b. If the gradient of a pixel is less than the lower threshold, it is considered a non-edge.
  • c. If the gradient of a pixel is between two thresholds, it is considered an edge only if the pixel is on an edge pixel (the corresponding gradient is greater than the maximum threshold).
    Canny recommends that the ratio of the upper threshold to the lower threshold be between 2:1 and 3:1 (why? How to choose these two thresholds is difficult. There are adaptive threshold algorithms on the Internet, to be studied)
  1. edge connection

function prototype

//函数原型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)

Parameter Description

parameter illustrate                            
InputArray src Input image, single channel 8-bit image
InputArray dx For images that are differentiated in the horizontal direction (x-axis) (CV_16SC1 or CV_16SC3), other operators can be used to derive the derivative
InputArray dy For images that are differentiated in the vertical direction (y-axis) (CV_16SC1 or CV_16SC3), other operators can be used to derive the derivative
OutputArray edges Output edge image. The size and number of channels are the same as the input image
double threshold1 Minimum threshold. Pixel gradients smaller than this value are not considered edges
double threshold2 Maximum threshold (less than threshold1, the value will be automatically exchanged). Pixel gradients larger than this value are considered edges
int apertureSize The kernel size of the Sobel operator must be 3, 5, or 7
L2gradient Whether to use L2 gradient calculation. When true, it is more accurate but more time-consuming. According to L 2 norm = ( d I / dx ) 2 + ( d I / dy ) 2 =\sqrt{(dI/dx)^2 + ( dI/dy)^2}=( d I / d x )2+( d I / d y )2 Calculation,
the default is false is enough, press L 1 norm==丨dI/dx丨+丨dI/dy丨

Image example

Canny example

code example

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;
/// <summary>
/// 调整Canny最小阈值
/// </summary>
/// <param name="pos"></param>
/// <param name="userData"></param>
private void LowThresholdOnChanged(int pos, IntPtr userData)
{
    
    
    LowThreshold = pos;
    OnChanged();
}
double ThresholdRatio = 3;
/// <summary>
/// 调用Canny最大阈值
/// </summary>
/// <param name="pos"></param>
/// <param name="userData"></param>
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 function examples (directory)
refer to
https://docs.opencv.org/4.7.0/da/d5c/tutorial_canny_detector.html

Guess you like

Origin blog.csdn.net/TyroneKing/article/details/130209107