OpenCv案例(十三):基于OpenCVSharp-模板匹之旋转角度匹配

11 篇文章 5 订阅
10 篇文章 6 订阅

原模板匹配方法中,是无法进行任意角度和旋转匹配的,但我们在实际的使用中,模板图像的位置和方向都是不确定的,因此,就需要我们对模板图像进行预处理,处理后在进行模板匹配。

基本处理方法如下:

  • 模板图像读取
  • 进行模糊
  • 绘制轮廓
  • 形态学处理
  • 提取轮廓
  • 获取当前位置角度
  • 图像旋转。
  • 原图读取
  • 从4个角度进行模板匹配(0°,90°,180°,270°)
  • 记录每个角度的匹配值
  • 进行后续操作。

1:原图如下所示:

2:模板图像如下所示:

3:思路:在模板图像中,抠出图像,并且找到当前的旋转角度,将其恢复为正向0°,在进行对其模板匹配,给出匹配结果。处理过程图像如下:

分别是将其调整为正向后,0°,90°,180°,270°的旋转匹配,同时记录匹配度的值。

 

源码图下:

 public List<Point2d> GetResult(Mat tempImage, string srcImgPath, out double robotRotateAngel, out double arcVal)
 {
     List<Point2d> point2Ds = new List<Point2d>();
     robotRotateAngel = 999;
     arcVal = 0;
     try
     {
         double rotAngel;
         Point2f centerPoint;
         Rect rect;
         Mat tempMat = SetTemplateImage1(tempImage, out rotAngel, out rect, out centerPoint);
         bool matchRet = GetMatchRet1(srcImgPath, tempMat, rotAngel, out robotRotateAngel, out arcVal);

         if (!matchRet)
         {
             //图像识别失败
             return null;
         }
     }
     catch (Exception ex)
     {
         
     }
     return point2Ds;
 }

public Mat SetTemplateImage1(Mat image, out double rotAngel, out Rect rect, out Point2f centerPoint)
{
    rotAngel = -1;
    rect = new Rect();
    centerPoint = new Point2f();
    Mat blurImg = GaussBlurImage(image);//ip.MedBlurImage(image);// 
    Mat cannyImg = new Mat();
    Cv2.Canny(blurImg, cannyImg, 30, 90);
    Mat morphImg = MorphImage(cannyImg);
    //Cv2.ImShow("morphImg1", morphImg);
    Mat roiImage = GetRioOutline(morphImg, image, out rotAngel, out rect, out centerPoint);
    //Cv2.ImShow("roiImage", roiImage);
    Mat rotImg = ImageRotate(roiImage, rotAngel, rect);
    //Cv2.ImShow("rotImg", rotImg);
    Mat mat = GetRoi(rotImg, rect);
    Cv2.ImShow("mat", mat);
    return mat;
}

        /// <summary>
        /// 高斯 模糊
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public Mat GaussBlurImage(Mat image)
        {
            Mat gaussianimage = new Mat();
            Cv2.GaussianBlur(image, gaussianimage, new OpenCvSharp.Size(3, 3), 3, 3);//尽可能多的去除杂质
            return gaussianimage;
        }

        /// <summary>
        /// 图像形态学操作
        /// </summary>
        /// <param name="image"></param>
        /// <returns></returns>
        public Mat MorphImage(Mat image)
        {
            Mat morphImage = new Mat();
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(3, 3), new Point(-1, -1));
            Cv2.MorphologyEx(image, morphImage, MorphTypes.Gradient, kernel, new Point(-1, -1), 2);
            return morphImage;
        }

        /// <summary>
        /// 提取图像轮廓
        /// </summary>
        /// <param name="morphImage">形态学图片</param>
        /// <param name="srcImage">原图</param>
        /// <param name="rotateAngel">返回旋转角度</param>
        /// <param name="rect">返回获取的矩形边框</param>
        /// <returns></returns>
        public Mat GetRioOutline(Mat morphImage, Mat srcImage, out double rotateAngel, out Rect rect, out Point2f minRectcenterPoint)
        {
            OpenCvSharp.Point[][] contours;
            HierarchyIndex[] hierarchies;
            //Cv2.ImShow(",mmmm", morphImage);
            Cv2.FindContours(morphImage, out contours, out hierarchies, RetrievalModes.External, ContourApproximationModes.ApproxTC89KCOS, new Point());
            Mat connImg = Mat.Zeros(morphImage.Size(), MatType.CV_8UC3);
            rotateAngel = 0;
            //Point2f[] vertices = new Point2f[4];
            minRectcenterPoint = new Point2f();
            rect = new Rect();
            int index = 0;
            int count = -1;
            //寻找最大轮廓
            for (int i = 0; i < contours.Length; i++)
            {
                for (int j = 0; j < contours[i].Length; j++)
                {
                    if (count < contours[i].Length)
                    {
                        count = contours[i].Length;
                        index = i;
                    }
                }
            }
            //获取轮廓点的矩形区域
            rect = Cv2.BoundingRect(contours[index]);
            //minAreaRect():以X轴正方形为起点,顺时针为正,逆时针为负
            //绘制Rio区域最小矩形
            #region 绘制Rio区域最小矩形
            RotatedRect minRect = Cv2.MinAreaRect(contours[index]);
            rotateAngel = minRect.Angle;
            minRectcenterPoint = minRect.Center;
            //vertices = minRect.Points(); 
            #endregion
            //绘制最小矩形
            #region 绘制最小矩形
            //Cv2.Line(srcImage, Convert.ToInt32(vertices[0].X), Convert.ToInt32(vertices[0].Y), Convert.ToInt32(vertices[1].X), Convert.ToInt32(vertices[1].Y), new Scalar(0, 255, 255));
            //Cv2.Line(srcImage, Convert.ToInt32(vertices[0].X), Convert.ToInt32(vertices[0].Y), Convert.ToInt32(vertices[3].X), Convert.ToInt32(vertices[3].Y), new Scalar(0, 255, 255));
            //Cv2.Line(srcImage, Convert.ToInt32(vertices[1].X), Convert.ToInt32(vertices[1].Y), Convert.ToInt32(vertices[2].X), Convert.ToInt32(vertices[2].Y), new Scalar(0, 255, 255));
            //Cv2.Line(srcImage, Convert.ToInt32(vertices[2].X), Convert.ToInt32(vertices[2].Y), Convert.ToInt32(vertices[3].X), Convert.ToInt32(vertices[3].Y), new Scalar(0, 255, 255));
            //Console.WriteLine("AngleRect_angle: {0}", minRect.Angle); 
            #endregion

            //Cv2.ImShow("srcImage", srcImage);

            return srcImage;
        }

        /// <summary>
        /// 图像旋转
        /// </summary>
        /// <param name="image">旋转图像</param>
        /// <param name="rotateAngel">旋转角度</param>
        /// <returns></returns>
        public Mat ImageRotate(Mat image, double rotateAngel, Rect rect)
        {
            if (rotateAngel > -90 && rotateAngel <= -45)
            {
                rotateAngel += 90;
            }
            else if (!(rotateAngel > -45 && rotateAngel < 0))
            {
                //degree = degree;
                rotateAngel = 0;
            }
            //OpenCvSharp.Point center = new OpenCvSharp.Point(image.Cols / 2, image.Rows / 2);
            //以被截取的区域中心位置旋转
            //边缘放大截取
            OpenCvSharp.Point center = new OpenCvSharp.Point(rect.TopLeft.X + (rect.Width / 2), rect.TopLeft.Y + (rect.Height / 2));
            //截取最小矩形
            //OpenCvSharp.Point center = new OpenCvSharp.Point(image.Cols / 2, image.Rows / 2);
            double angle = rotateAngel;
            double scale = 1;
            Mat rotate = new Mat(2, 3, MatType.CV_32FC1);
            rotate = Cv2.GetRotationMatrix2D(center, angle, scale);//getRotationMatrix2D():以X轴正方形为起点,顺时针为负,逆时针为正
            Mat rotImage = new Mat();
            Cv2.WarpAffine(image, rotImage, rotate, image.Size(), InterpolationFlags.Linear, BorderTypes.Wrap);
            int winSize = 0;
            if (image.Cols > image.Rows)
            {
                winSize = image.Cols;
            }
            else
            {
                winSize = image.Rows;
            }
            return rotImage;
        }


        /// <summary>
        /// 获取感兴趣区域
        /// </summary>
        /// <param name="image">图像</param>
        /// <param name="rect">面积</param>
        /// <returns></returns>
        public Mat GetRoi(Mat image, Rect rect)
        {
            //Mat imageRoi = new Mat(image, new Rect(new Point(rect.TopLeft.X - rect.Width / 2, rect.TopLeft.Y), new Size(rect.Height, rect.Height)));
            Mat imageRoi = new Mat(image, new Rect(new Point(rect.TopLeft.X, rect.TopLeft.Y), new Size(rect.Width, rect.Height)));
            return imageRoi;
        }

        Dictionary<int, double> angelDic1;
        /// <summary>
        /// 模板匹配是否成功
        /// </summary>
        /// <param name="srcBmp">原图</param>
        /// <param name="templateMat">模板图像</param>
        /// <param name="rotAngel">初始旋转角度</param>
        /// <param name="robotRotateAngel">机器人需要旋转的角度</param>
        /// <returns></returns>
        public bool GetMatchRet1(string srcImgFilePath, Mat templateMat, double rotAngel, out double robotRotateAngel, out double arcValue)
        {
            double matchVal = 0;
            //最大旋转角度360
            robotRotateAngel = 999;
            arcValue = 0;
            try
            {
                Mat src = Cv2.ImRead(@"E:\SoftwarePackage\CodePackage\ImgPro\Img\T1.bmp");
                //src.ConvertTo(src, MatType.CV_8UC4);
                if (src.Empty())
                {
                    //Console.WriteLine("src could not load image...\n");
                    return false;
                }
                Cv2.ImShow("src", src);
                //Mat templateMat = Cv2.ImRead(@"E:\SoftwarePackage\CodePackage\ImgPro\Img\T21.bmp");
                if (templateMat.Empty())
                {
                    //Console.WriteLine("src could not load image...\n");
                    return false;
                }

                angelDic1 = new Dictionary<int, double>(); ;
                //记录角度
                int rotateAngel = 0;
                //第一张调正之后的图像
                //Cv2.ImShow(0.ToString(), mat);
                Point p;
                Bitmap rotateBmp;
                //旋转图像4个角度
                for (int i = 0; i < 4; i++)
                {
                    if (templateMat.Rows > src.Rows || templateMat.Cols > src.Cols)
                    {
                        continue;
                    }
                    Bitmap bmp = MatchTemplate(templateMat, src, out p, out matchVal);
                    if (matchVal > TemplateMatchThreshold)
                    {
                        angelDic1.Add(rotateAngel, matchVal);
                    }
                    //Mat rotImg1 = ip.ImageRotate(mat, 90);
                    rotateBmp = BitmapConverter.ToBitmap(templateMat);
                    rotateBmp.RotateFlip(RotateFlipType.Rotate90FlipXY);
                    rotateAngel += 90;
                    templateMat = BitmapConverter.ToMat(rotateBmp);
                    if (1 + i > 3)
                    {
                        break;
                    }
                    Cv2.ImShow((i + 1).ToString(), templateMat);
                }
                //若匹配值没有符合条件
                if (angelDic1.Count < 1)
                {
                    return false;
                }

                List<double> matchRet = new List<double>();
                foreach (KeyValuePair<int, double> kvp in angelDic1)
                {
                    matchRet.Add(kvp.Value);
                }
                //判断匹配最大值是否大于设定阈值 否则 匹配失败
                //if (matchRet.Max() < TemplateMatchThreshold)
                //{
                //    return false;
                //}
                //抠出模板图像 与原图进行模板匹配,并且记住最大匹配度的角度
                var ag = angelDic1.Where(q => q.Value == matchRet.Max()).Select(q => q.Key);
                robotRotateAngel = CalcRotateAngel(rotAngel, ag.Max());
                //角度转弧度
                arcValue = robotRotateAngel * arc;
            }
            catch (Exception ex)
            {
                //LogHelper.WriteLog(ex.Message, ex);
            }
            return true;
        }

        private double arc1 = Math.PI / 180;

        /// <summary>
        /// 模板匹配
        /// </summary>
        /// <param name="tempalte">模板图片像</param>
        /// <param name="srcPic">被匹配图像</param>
        /// <returns>标注匹配区域的图像</returns>
        public Bitmap MatchTemplate(Mat tempalte, Mat srcPic, out OpenCvSharp.Point tempPoint, out double matchValue)
        {

            using (Mat result = new Mat()) //匹配结果
            {                             //模板匹配
                Double minVul;
                Double maxVul;
                OpenCvSharp.Point minLoc = new OpenCvSharp.Point(0, 0);
                OpenCvSharp.Point maxLoc = new OpenCvSharp.Point(0, 0);
                OpenCvSharp.Point matchLoc = new OpenCvSharp.Point(0, 0);
                Cv2.MatchTemplate(srcPic, tempalte, result, TemplateMatchModes.CCoeffNormed);//CCoeffNormed  最好匹配为1,值越小匹配越差 xxxNormed的算法无需归一化
                                                                                             //Cv2.Normalize(result, result, 0, 1, NormTypes.MinMax, -1);//归一化
                Cv2.MinMaxLoc(result, out minVul, out maxVul, out minLoc, out maxLoc);//查找极值
                                                                                      //Cv2.MinMaxLoc(result, out minVul, out maxVul, out minLoc, out maxLoc, null);//查找极值
                matchLoc = maxLoc;//最大值坐标
                                  //result.Set(matchLoc.Y, matchLoc.X, 0);//改变最大值为最小值  
                Mat mask = srcPic.Clone();//复制整个矩阵
                                          //Console.WriteLine("最大值:{0},X:{1},Y:{2}", maxVul, matchLoc.Y, matchLoc.X);
                matchValue = maxVul;
                tempPoint.X = matchLoc.X;
                tempPoint.Y = matchLoc.Y;
                //画框显示 :对角线画框,起点和终点,都用Point,线宽
                if (matchValue > TemplateMatchThreshold)
                {
                    Cv2.Rectangle(mask, matchLoc, new OpenCvSharp.Point(matchLoc.X + tempalte.Cols, matchLoc.Y + tempalte.Rows), Scalar.Green, 2);//2代表画的线条的宽细程度
                }
                return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mask);
            }
        }

        /// <summary>
        /// 计算角度
        /// </summary>
        /// <param name="srcAngel"></param>
        /// <param name="rotateAngle"></param>
        /// <returns></returns>
        public double CalcRotateAngel(double srcAngel, int rotateAngle)
        {
            //逆时针旋转为负 顺时针旋转为正
            double tempSrcAngel = Math.Abs(srcAngel);
            double resultAngel = -1;
            if (tempSrcAngel >= 45)
            {
                switch (rotateAngle)
                {
                    case 0:
                        resultAngel = 90 - tempSrcAngel;
                        break;
                    case 90:
                        resultAngel = 180 - tempSrcAngel;
                        break;
                    case 180:
                        //逆时针
                        resultAngel = -(90 + tempSrcAngel);
                        break;
                    case 270:
                        //逆时针
                        resultAngel = -tempSrcAngel;
                        break;
                }
            }
            else if (tempSrcAngel < 45)
            {
                switch (rotateAngle)
                {
                    case 0:
                        //逆时针
                        resultAngel = -tempSrcAngel;
                        break;
                    case 90:
                        resultAngel = 90 - tempSrcAngel;
                        break;
                    case 180:
                        resultAngel = 90 + tempSrcAngel;
                        break;
                    case 270:
                        //逆时针
                        resultAngel = -(90 + tempSrcAngel);
                        break;
                }
            }
            return resultAngel;
        }

完整代码都在上面了,感兴趣的小伙伴可以在优化优化。下个案例见。

  • 11
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、主要内容:OpenCV能够实现强大丰富的图像处理,但是它缺少一个能够支持它运行的界面。Csharp经过多年的发展,得益于它的“所见及所得”能力,非常方便编写界面。这两者如果能够“双剑合璧”,将有效帮助实际工作产出。本课着重推荐GOCW采用“Csharp基于CLR直接调用Opencv编写的算法库”方法,能够将最新的OpenCV技术引入进来,同时保证生成程序的最小化。    为了进一步说明Csharp和OpenCV的结合使用,首先一个较为完整的基于winform实现答题卡识别的例子,相比较之前的实现,本次进一步贴近生产实际、内涵丰富,对算法也进行了进一步提炼。同时我们对WPF下对OpenCV函数的调用、OpenCV.js的调用进行相关教授。       二、课程结构1、 EmguCV、OpenCVSharp和GOCW之间进行比较(方便代码编写、能够融入最新的算法、速度有保障、方便调试找错、拒绝黑箱化);2、视频采集模块的构建,视频采集和图像处理之间的关系;3、视频采集专用的SDK和“陪练”系统的介绍;4、在视频增强类项目中和图像处理项目中,算法的选择;5、Csharp界面设计、图片的存储和其他构建设计;6、较为完整的答题卡识别例子,兼顾界面设计和算法分析;8、WPF基于GOCW也同样可以基于GOCW实现算法调用;webForm虽然也可以通过类似方法调用,但是OpenCV.JS的方法更现代高效。9、关于软件部署的相关要点和窍门。       三、知识要点:1、基本环境构建和程序框架;2、CLR基本原理和应用方法;3、接入、采集、模拟输入;4、图像处理,通过构建循环采集图片;5、增强和实时处理;6、基于投影等技术的答题卡识别算法;7、存储、转换;8、部署交付。        课程能够帮助你掌握Csharp调用Opencv的基本方法,获得相应框架代码和指导;从而进一步提升实现“基于图像处理”的解决方案能力。  

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值