OpenCvSharp手绘ROI区域+模板匹配+霍夫变换检测圆的边界

it2025-05-25  10

最终效果如下:

左侧为检测图片、右侧为模板,右下角textbox为轮毂中心的像素坐标

操作步骤:

1、点击打开图像选择一张比较不错的图片,用于画模板;

2、在picturebox中画取ROI区域生成模板(有拖拽线不显示这点小问题);

3、重新选取一张待检测图片,点击模板匹配,即可找到ROI区域并把圆的边界和圆心找到;

完整代码如下(部分代码还有待改善):

using OpenCvSharp; using OpenCvSharp.Extensions; using System; using System.Linq; using System.Windows.Forms; using Point = OpenCvSharp.Point; using Size = OpenCvSharp.Size; namespace DrawROI { public partial class Form1 : Form { private System.Drawing.Point RectStartPoint, tempEndPoint; bool blnDraw; Mat ImageROI; Mat OrgMat; private string FilePath; public Form1() { InitializeComponent(); } private void pictureBox1_MouseDown(object sender, MouseEventArgs e) { RectStartPoint = e.Location; //获得鼠标按下的pictureBox上坐标 Invalidate(); blnDraw = true;//判断标志 } private void pictureBox1_MouseMove(object sender, MouseEventArgs e) { if (blnDraw) { if (e.Button != MouseButtons.Left)//判断是否按下左键 { return; } tempEndPoint = e.Location; //记录框的位置和大小 //pictureBox上开始点坐标 //Rect.Location = new System.Drawing.Point( //Math.Min(RectStartPoint.X, tempEndPoint.X), //Math.Min(RectStartPoint.Y, tempEndPoint.Y)); pictureBox上矩形大小 //Rect.Size = new System.Drawing.Size( //Math.Abs(RectStartPoint.X - tempEndPoint.X), //Math.Abs(RectStartPoint.Y - tempEndPoint.Y)); pictureBox1.Invalidate(); // 最后点位置 int X0, Y0; Utilities.ConvertCoordinates(pictureBox1, out X0, out Y0, e.X, e.Y); //在控件中 //textBox1.Text = Convert.ToString("pictureBox最后点坐标" + e.X + " ," + e.Y); //pictureBox 上终点坐标 //textBox2.Text = Convert.ToString("pictureBox开始点坐标" + Rect.X + " ," + Rect.Y); //开始点坐标 //textBox3.Text = Convert.ToString("pictureBox的Width" + Rect.Width + " ," + Rect.Height);//大小 //Create ROI 感兴趣区域 Utilities.ConvertCoordinates(pictureBox1, out X0, out Y0, RectStartPoint.X, RectStartPoint.Y); int X1, Y1; Utilities.ConvertCoordinates(pictureBox1, out X1, out Y1, tempEndPoint.X, tempEndPoint.Y); //感兴趣区域 左上点坐标-宽-高 //RealImageRect.Location = new System.Drawing.Point( // Math.Min(X0, X1), // Math.Min(Y0, Y1)); //RealImageRect.Size = new System.Drawing.Size( // Math.Abs(X0 - X1), // Math.Abs(Y0 - Y1)); //textBox4.Text = "原图像上最后点坐标: X:" + X0 + " Y:" + Y0; //textBox5.Text = "原图像上RealImageRect: X:" + RealImageRect.X + " Y:" + RealImageRect.Y; // 原图像-左上点坐标 //textBox6.Text = "原图像上RealImageRectSize: X:" + RealImageRect.Width + " Y:" + RealImageRect.Height; // 原图像-大小 Rect tmp_Rect = new Rect(Math.Min(X0, X1), Math.Min(Y0, Y1), Math.Abs(X0 - X1), Math.Abs(Y0 - Y1)); ImageROI = new Mat(OrgMat, tmp_Rect);//新建一个mat,把roi内的图像加载到里面去。 //Cv2.ImWrite("4.jpg",ImageROI); //保存 } } private void pictureBox1_MouseUp(object sender, MouseEventArgs e) { // mouseUp 结束以后 将图像显示在pictureBox2控件中 pictureBox2.Image = ImageROI.ToBitmap(); //***************************************// blnDraw = false; //结束绘制 } private void button1_Click(object sender, EventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.ShowDialog(); FilePath = openFileDialog.FileName; OrgMat = new Mat(FilePath, ImreadModes.Grayscale); OrgMat.MedianBlur(3); pictureBox1.Image = BitmapConverter.ToBitmap(OrgMat); } private void button3_Click(object sender, EventArgs e) { if (ImageROI == null) { MessageBox.Show("请先绘制模板"); return; } Mat RoiClone = ImageROI.Clone(); Mat mat3 = new Mat(); //创建result的模板,就是MatchTemplate里的第三个参数 //mat3.Create(mat1.Cols - mat2.Cols + 1, mat1.Rows - mat2.Rows + 1, MatType.CV_32FC1); //进行匹配(1母图,2模版子图,3返回的result,4匹配模式) Cv2.MatchTemplate(OrgMat, RoiClone, mat3, TemplateMatchModes.SqDiff); //对结果进行归一化(这里我测试的时候没有发现有什么用,但在opencv的书里有这个操作,应该有什么神秘加成,这里也加上) Cv2.Normalize(mat3, mat3, 1, 0, NormTypes.MinMax, -1); //double minValue, maxValue; Point minLocation, maxLocation; /// 通过函数 minMaxLoc 定位最匹配的位置 /// (这个方法在opencv里有5个参数,这里我写的时候发现在有3个重载,看了下可以直接写成拿到起始坐标就不取最大值和最小值了) /// minLocation和maxLocation根据匹配调用的模式取不同的点 Cv2.MinMaxLoc(mat3, out minLocation, out maxLocation); Mat OrgMatClone = OrgMat.Clone(); //画出匹配的矩, //Cv2.Rectangle(mask, maxLocation, new Point(maxLocation.X + mat2.Cols, maxLocation.Y + mat2.Rows), Scalar.Red, 2); Cv2.Rectangle(OrgMatClone, minLocation, new Point(minLocation.X + RoiClone.Cols, minLocation.Y + RoiClone.Rows), Scalar.Red, 2); //Cv2.ImShow("mat1", mat1); //Cv2.ImShow("mat2", mat2); //霍夫圆检测:使用霍夫变换查找灰度图像中的圆。 /* * 参数: * 1:输入参数: 8位、单通道、灰度输入图像 * 2:实现方法:目前,唯一的实现方法是HoughCirclesMethod.Gradient * 3: dp :累加器分辨率与图像分辨率的反比。默认=1 * 4:minDist: 检测到的圆的中心之间的最小距离。(最短距离-可以分辨是两个圆的,否则认为是同心圆- src_gray.rows/8) * 5:param1: 第一个方法特定的参数。[默认值是100] canny边缘检测阈值低 * 6:param2: 第二个方法特定于参数。[默认值是100] 中心点累加器阈值 – 候选圆心 * 7:minRadius: 最小半径 * 8:maxRadius: 最大半径 * */ CircleSegment[] cs = Cv2.HoughCircles(RoiClone, HoughMethods.Gradient, 1, 80, 70, 100, 100, 200); for (int i = 0; i < cs.Count(); i++) { //画圆 Cv2.Circle(OrgMatClone, (int)(cs[i].Center.X + minLocation.X), (int)(cs[i].Center.Y + minLocation.Y), (int)cs[i].Radius, new Scalar(0, 0, 255), 2, LineTypes.AntiAlias); //加强圆心显示 Cv2.Circle(OrgMatClone, (int)cs[i].Center.X, (int)cs[i].Center.Y, 3, new Scalar(0, 0, 255), 2, LineTypes.AntiAlias); textBox1.Text = cs[i].Center.X.ToString(); textBox2.Text = cs[i].Center.Y.ToString(); } //pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage; pictureBox1.Image = BitmapConverter.ToBitmap(OrgMatClone); } //private void pictureBox1_Paint(object sender, PaintEventArgs e) //{ // if (blnDraw) // { // if (pictureBox1.Image != null) // { // if (Rect != null && Rect.Width > 0 && Rect.Height > 0) // { // e.Graphics.DrawRectangle(new Pen(Color.Red, 1), Rect);//重新绘制颜色为红色 // } // } // } //} public class Utilities { //坐标转换 //************************************** //* 图片左边转换, //* Input输入: pictureBox 坐标X,Y //* Output输出: Image 图像上对应的坐标 //**************************************// public static void ConvertCoordinates(PictureBox pic, out int X0, out int Y0, int x, int y) { int pic_hgt = pic.ClientSize.Height; int pic_wid = pic.ClientSize.Width; int img_hgt = pic.Image.Height; int img_wid = pic.Image.Width; X0 = x; Y0 = y; switch (pic.SizeMode) { case PictureBoxSizeMode.AutoSize: case PictureBoxSizeMode.StretchImage: X0 = (int)(img_wid * x / (float)pic_wid); Y0 = (int)(img_hgt * y / (float)pic_hgt); break; } } }

 

最新回复(0)