You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C#版OpenCV确定两条曲线中心点并在WinForm PictureBox显示

问题描述

我正在尝试用C#版OpenCV(Emgu CV)确定两条曲线在不同位置的中心点,之前在Roborealm里用edge_probe实现过这个功能,现在要处理目标图片复现该功能。因为曲线不是直线,需要取多个中心点再计算平均值。作为OpenCV新手,需要解决该问题的指导,同时希望在Windows窗体的PictureBox里显示边缘点和中心点,达到类似Roborealm的效果。

目前已有的处理代码:

Mat image = CvInvoke.Imread("C:\Image_2.png");
Mat kernel1 = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Rectangle, new Size(3, 3), new Point(1, 1));

CvInvoke.CvtColor(image, image, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);
CvInvoke.MedianBlur(image, image, 9);
CvInvoke.Threshold(image, image, 100, 255, Emgu.CV.CvEnum.ThresholdType.Binary);
CvInvoke.MorphologyEx(image, image, Emgu.CV.CvEnum.MorphOp.Close, kernel1, new Point(0, 0), 6, Emgu.CV.CvEnum.BorderType.Default, new MCvScalar());
CvInvoke.Canny(image, image, 50, 200);

pictureBox1.Image = new System.Drawing.Bitmap(image.ToBitmap(), image.Width, image.Height);
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;

一、提取两条曲线中心点的实现步骤

1. 修正预处理流程

当前代码直接修改原始image变量会丢失彩色信息,后续绘制点需要用到原图,建议保留原始彩色图,用副本做预处理:

// 读取原始彩色图,保留用于后续绘制
Mat originalImage = CvInvoke.Imread(@"C:\Image_2.png");
// 创建预处理用的副本
Mat grayImage = originalImage.Clone();

Mat kernel1 = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Rectangle, new Size(3, 3), new Point(1, 1));

CvInvoke.CvtColor(grayImage, grayImage, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);
CvInvoke.MedianBlur(grayImage, grayImage, 9);
CvInvoke.Threshold(grayImage, grayImage, 100, 255, Emgu.CV.CvEnum.ThresholdType.Binary);
CvInvoke.MorphologyEx(grayImage, grayImage, Emgu.CV.CvEnum.MorphOp.Close, kernel1, new Point(0, 0), 6, Emgu.CV.CvEnum.BorderType.Default, new MCvScalar());
CvInvoke.Canny(grayImage, grayImage, 50, 200);

2. 扫描边缘点并计算局部中心点

通过逐行(或逐列)扫描Canny边缘图,提取每行的左右(或上下)边缘点,计算对应中心点:

List<PointF> centerPoints = new List<PointF>();
int scanStep = 10; // 每隔10行扫描一次,可根据图像大小调整

for (int y = 0; y < grayImage.Rows; y += scanStep)
{
    List<int> edgeXs = new List<int>();
    for (int x = 0; x < grayImage.Cols; x++)
    {
        // Canny后边缘像素值为255
        byte pixel = grayImage.GetData<byte>(y, x)[0];
        if (pixel == 255)
        {
            edgeXs.Add(x);
        }
    }

    if (edgeXs.Count >= 2)
    {
        // 排序后取最左、最右的边缘点
        edgeXs.Sort();
        int leftX = edgeXs[0];
        int rightX = edgeXs[edgeXs.Count - 1];
        // 计算该行的中心点
        float centerX = (leftX + rightX) / 2f;
        centerPoints.Add(new PointF(centerX, y));

        // 实时绘制边缘点和中心点到原图
        CvInvoke.Circle(originalImage, new Point(leftX, y), 2, new MCvScalar(0, 255, 0), -1); // 绿色左边缘点
        CvInvoke.Circle(originalImage, new Point(rightX, y), 2, new MCvScalar(0, 0, 255), -1); // 红色右边缘点
        CvInvoke.Circle(originalImage, new Point((int)centerX, y), 3, new MCvScalar(255, 0, 0), -1); // 蓝色中心点
    }
}

3. 计算中心点平均值

如果需要整体的平均中心点,对记录的所有中心点求平均即可:

if (centerPoints.Count > 0)
{
    float avgX = centerPoints.Average(p => p.X);
    float avgY = centerPoints.Average(p => p.Y);
    // 绘制平均中心点,用更大的黄色圆区分
    CvInvoke.Circle(originalImage, new Point((int)avgX, (int)avgY), 5, new MCvScalar(255, 255, 0), -1);
}

二、在PictureBox中显示处理后的图像

直接将绘制好的原图转成Bitmap显示,注意释放旧资源避免内存泄漏:

// 先释放PictureBox中已有的Image资源
if (pictureBox1.Image != null)
{
    pictureBox1.Image.Dispose();
}

// 转换为Bitmap并设置显示模式
pictureBox1.Image = originalImage.ToBitmap();
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; // 用Zoom模式更适合查看细节

额外优化建议

  • 如果曲线是上下分布,将扫描逻辑改为按列遍历,提取每行的上下边缘点
  • 若边缘存在噪声,可在Canny后对图像做一次小的腐蚀操作,去除孤立点
  • 扫描步长scanStep可根据图像分辨率调整,步长越小,中心点越密集,平均值精度越高

内容的提问来源于stack exchange,提问作者GRANDMAST3R5

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:44:54