如何用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
相关产品推荐
相关产品推荐

