C# 自定义控件 opencvsharp 找圆

最近在做个工业视觉检测项目时,发现WinForm自带的PictureBox控件完全不够用。客户要求实时显示摄像头画面还要标出圆形瑕疵,这逼得我不得不撸起袖子造轮子——用C#自定义控件整合OpenCvSharp实现找圆功能。

先搞个基础画布控件,继承UserControl后往里塞个双缓冲的PictureBox:

public class VisionCanvas : UserControl
{
    private PictureBox picBox = new PictureBox();
    private float zoomScale = 1.0f;

    public VisionCanvas()
    {
        picBox.SizeMode = PictureBoxSizeMode.Zoom;
        picBox.Dock = DockStyle.Fill;
        this.Controls.Add(picBox);

        this.Resize += (s, e) => {
            if (picBox.Image != null)
            {
                // 保持图像比例缩放
                zoomScale = Math.Min(
                    (float)this.Width / picBox.Image.Width,
                    (float)this.Height / picBox.Image.Height);
            }
        };
    }
}

这里有个坑要注意:直接修改PictureBox的尺寸会导致图像变形,所以我在Resize事件里动态计算缩放比例。zoomScale这个参数后面画标记时会派上大用场。

重点来了!OpenCvSharp的找圆方法比传统Hough变换更高效。在控件类里加个FindCircles方法:

public CircleSegment[] FindCircles(Mat src, int minRadius=10, int maxRadius=100)
{
    using (var gray = new Mat())
    {
        Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
        Cv2.MedianBlur(gray, gray, 5);
        
        return Cv2.HoughCircles(gray, HoughMethods.Gradient, 
            dp: 1.2, 
            minDist: gray.Height / 8,
            param1: 100,
            param2: 30,
            minRadius: minRadius,
            maxRadius: maxRadius);
    }
}

参数解释时间:

  • dp=1.2:图像分辨率缩小比例,实测1.2能平衡精度和速度
  • minDist:防止圆圈检测重叠,按图像高度动态调整更智能
  • param2:这个值越小检测越敏感,但误检率会上升

检测到圆之后得在控件上画出来。重写控件的OnPaint方法:

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    
    if (_circles != null)
    {
        using (var g = e.Graphics)
        {
            foreach (var circle in _circles)
            {
                // 坐标转换(原始图像坐标→缩放后控件坐标)
                var center = new Point(
                    (int)(circle.Center.X * zoomScale),
                    (int)(circle.Center.Y * zoomScale));
                var radius = (int)(circle.Radius * zoomScale);

                // 画圆
                g.DrawEllipse(Pens.Red, 
                    center.X - radius, center.Y - radius,
                    radius * 2, radius * 2);
                
                // 画圆心十字线
                g.DrawLine(Pens.Blue, center.X-10, center.Y, center.X+10, center.Y);
                g.DrawLine(Pens.Blue, center.X, center.Y-10, center.X, center.Y+10);
            }
        }
    }
}

这里有个细节:直接拿原始图像的坐标画会错位,必须乘以之前算好的zoomScale系数。画十字线时用固定10像素长度,这样无论怎么缩放标记都清晰可见。

实际使用时只需要三行代码:

var canvas = new VisionCanvas { Dock = DockStyle.Fill };
var image = Cv2.ImRead("test.jpg");
canvas.SetImage(image.ToBitmap());
canvas.FindCircles(image);

调试时发现个有趣现象——当图像中有多个同心圆时,适当调高param2参数能过滤掉内层小圆。这比写复杂的逻辑判断省事多了,果然参数调优才是OpenCV的正确打开方式。

完整项目里还可以给控件添加右键菜单,保存当前参数配置到本地。毕竟产线上的工人大哥可没耐心每次都手动调参,咱们得把复杂度封装在底层。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐