OpenCvSharp手繪ROI區域+模板匹配+霍夫變換檢測圓的邊界

x1angzzz發表於2020-10-21

最終效果如下:

左側為檢測圖片、右側為模板,右下角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;
                }
            }

        }

 

相關文章