C# OpenCV实现形状匹配的方法详解

1. 多角度模板匹配测试效果如下图:

图1-1

图1-2

图1-3

正负角度均可正常识别,识别角度偏差<1

2. 下面分享一下开发过程:

a). ROI区域的生成,基于GDI+完成图形绘制,如图

绘制模板设置区域,用来生成需要的模板特征。

ROI区域绘制代码如下:

        /// <summary>
      /// 区域绘制
      /// </summary>
      /// <param name="graphics"></param>
      /// <param name="regionEx"></param>
      /// <param name="sizeratio"></param>
     public  static void drawRegion(this Graphics graphics, RegionEx regionEx,float sizeratio=1)
      {       
          if (regionEx?.Region is RectangleF)
          {
              RectangleF rect = (RectangleF)regionEx.Region;
              graphics.DrawRectangle(new Pen(regionEx.Color, regionEx.Size), rect.X / sizeratio, rect.Y / sizeratio,
                                                  rect.Width / sizeratio, rect.Height / sizeratio);
          }
          else if(regionEx?.Region is RotatedRectF)
          {
              RotatedRectF rrect = (RotatedRectF)regionEx.Region;
             
              using (var graph = new GraphicsPath())
              {
                  PointF Center = new PointF(rrect.cx / sizeratio, rrect.cy / sizeratio);
               
                  graph.AddRectangle(new RectangleF( rrect.getrectofangleEqualZero().X / sizeratio,
                      rrect.getrectofangleEqualZero().Y / sizeratio,
                      rrect.getrectofangleEqualZero().Width / sizeratio,
                      rrect.getrectofangleEqualZero().Height / sizeratio));
                  graph.AddLine(new PointF((rrect.cx - rrect.Width / 2) / sizeratio, rrect.cy / sizeratio),
                               new PointF((rrect.cx + rrect.Width/2) / sizeratio, rrect.cy / sizeratio));
                  /
                  RotatedRectF rotatedRectF = new RotatedRectF((rrect.cx + rrect.Width / 2) / sizeratio,
                      rrect.cy / sizeratio,20 / sizeratio, 10 / sizeratio, 0);
                  PointF[] point2Fs = rotatedRectF.getPointF();
                  graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
                      rrect.cy / sizeratio), new PointF(point2Fs[0].X, point2Fs[0].Y));
                  graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
                     rrect.cy / sizeratio), new PointF(point2Fs[3].X, point2Fs[3].Y));
                  /
                  var a = rrect.angle * (Math.PI / 180);
                  var n1 = (float)Math.Cos(a);
                  var n2 = (float)Math.Sin(a);
                  var n3 = -(float)Math.Sin(a);
                  var n4 = (float)Math.Cos(a);
                  var n5 = (float)(Center.X * (1 - Math.Cos(a)) + Center.Y * Math.Sin(a));
                  var n6 = (float)(Center.Y * (1 - Math.Cos(a)) - Center.X * Math.Sin(a));
                  graph.Transform(new Matrix(n1, n2, n3, n4, n5, n6));
                  graphics.DrawPath(new Pen(regionEx.Color, regionEx.Size), graph);
                
              }
          }           
          else if (regionEx?.Region is RotatedCaliperRectF)
          {
              RotatedCaliperRectF rrect = (RotatedCaliperRectF)regionEx.Region;

              using (var graph = new GraphicsPath())
              {
                  PointF Center = new PointF(rrect.cx / sizeratio, rrect.cy / sizeratio);

                  graph.AddRectangle(new RectangleF(rrect.getrectofangleEqualZero().X / sizeratio,
                      rrect.getrectofangleEqualZero().Y / sizeratio,
                      rrect.getrectofangleEqualZero().Width / sizeratio,
                      rrect.getrectofangleEqualZero().Height / sizeratio));
                  graph.AddLine(new PointF((rrect.cx - rrect.Width / 2) / sizeratio, rrect.cy / sizeratio),
                               new PointF((rrect.cx + rrect.Width / 2) / sizeratio, rrect.cy / sizeratio));
                  /
                  RotatedCaliperRectF rotatedRectF = new RotatedCaliperRectF((rrect.cx + rrect.Width / 2) / sizeratio,
                      rrect.cy / sizeratio, 20 / sizeratio, 10 / sizeratio, 0);
                  PointF[] point2Fs = rotatedRectF.getPointF();
                  graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
                      rrect.cy / sizeratio), new PointF(point2Fs[0].X, point2Fs[0].Y));
                  graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
                     rrect.cy / sizeratio), new PointF(point2Fs[3].X, point2Fs[3].Y));
                  /
                  var a = rrect.angle * (Math.PI / 180);
                  var n1 = (float)Math.Cos(a);
                  var n2 = (float)Math.Sin(a);
                  var n3 = -(float)Math.Sin(a);
                  var n4 = (float)Math.Cos(a);
                  var n5 = (float)(Center.X * (1 - Math.Cos(a)) + Center.Y * Math.Sin(a));
                  var n6 = (float)(Center.Y * (1 - Math.Cos(a)) - Center.X * Math.Sin(a));
                  graph.Transform(new Matrix(n1, n2, n3, n4, n5, n6));
                  graphics.DrawPath(new Pen(regionEx.Color, regionEx.Size), graph);

              }
          }
          else if (regionEx?.Region is CircleF)
          {
              CircleF circle = (CircleF)regionEx.Region;
              graphics.DrawEllipse(new Pen(regionEx.Color, regionEx.Size), (circle.Centerx - circle.Radius) / sizeratio,
                    (circle.Centery - circle.Radius) / sizeratio, 2 * circle.Radius / sizeratio, 2 * circle.Radius / sizeratio);

          }
          else if (regionEx?.Region is PointF)
          {
              PointF point = (PointF)regionEx.Region;
              graphics.DrawPolygon(new Pen(regionEx.Color, regionEx.Size), new PointF[] { new PointF (
                  point.X/sizeratio,point.Y/sizeratio
                  )});
          }
          else if (regionEx?.Region is PolygonF)
          {
              PolygonF polygon = (PolygonF)regionEx.Region;
              List<PointF> temlist = new List<PointF>();
              foreach (var s in polygon.Points)
                  temlist.Add(new PointF(s.X / sizeratio, s.Y / sizeratio));
              graphics.DrawPolygon(new Pen(regionEx.Color, regionEx.Size), temlist.ToArray());
          }
          else if (regionEx?.Region is LineF)
          {
              LineF line = (LineF)regionEx.Region;            
              graphics.DrawLine(new Pen(regionEx.Color, regionEx.Size), line.x1/ sizeratio, line.y1/ sizeratio,
                 line.x2/ sizeratio, line.y2/ sizeratio);
          }
          else if (regionEx?.Region is CrossF)
          {
              CrossF cross = (CrossF)regionEx.Region;
              graphics.DrawLine(new Pen(regionEx.Color, regionEx.Size), (cross.x1- cross.width/2) / sizeratio, cross.y1 / sizeratio,
                (cross.x1 + cross.width / 2) / sizeratio, cross.y1 / sizeratio);
              graphics.DrawLine(new Pen(regionEx.Color, regionEx.Size), cross.x1 / sizeratio, (cross.y1- cross.height/2) / sizeratio,
                cross.x1 / sizeratio, (cross.y1 + cross.height / 2) / sizeratio);
              graphics.DrawEllipse(new Pen(regionEx.Color, regionEx.Size), (cross.x1 - cross.radius) / sizeratio,
                     (cross.y1 - cross.radius) / sizeratio, 2 * cross.radius / sizeratio, 2 * cross.radius / sizeratio);
          }
          else if(regionEx?.Region is SectorF)
          {
              SectorF sectorF=(SectorF)regionEx.Region;

              //graphics.DrawEllipse(MyPens.assist, sectorF.x / sizeratio, sectorF.y / sizeratio,
              //  sectorF.width / sizeratio, sectorF.height / sizeratio);

              graphics.DrawPie(new Pen(regionEx.Color, regionEx.Size),
                  sectorF.x / sizeratio, sectorF.y / sizeratio, 
                  sectorF.width / sizeratio, sectorF.height / sizeratio, 
                  sectorF.startAngle, sectorF.sweepAngle);
          }
          else if (regionEx?.Region is Region)
          {
              Region unionRegion = (Region)regionEx?.Region;
              //RectangleF rectangleF = unionRegion.GetBounds(graphics);
             
              //Matrix matrix = new Matrix();
              //matrix.Scale(1/sizeratio, 1/sizeratio);
              //unionRegion.Transform(matrix);

              //RectangleF rectangleF2= unionRegion.GetBounds(graphics);

              graphics.FillRegion(Brushes.Orange, unionRegion);
           
          }
          else
              ;
      }

b). 模板创建

模板如图:

选择稳定唯一的形状特征,设置合适的参数,用来生成模板,此基础版生成的特征为闭合的轮廓,后期版本会推出非闭合的多轮廓形状匹配算法。

模板创建代码如下:

         //创建模板
      private void btncreateModel_Click(object sender, EventArgs e)
      {
          if (GrabImg == null || GrabImg.Width <= 0)
          {
              MessageBox.Show("未获取图像");
              return;
          }

          List<RectangleF> roiList = currvisiontool.getRoiList<RectangleF>();
          if (roiList.Count <= 0)
          {
              MessageBox.Show("请设置模板创建区域{矩形}");
              return;

          }
          if (MessageBox.Show("确认创建新模板?", "Info", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                                == DialogResult.Yes)
          {
              CVRect cVRect = new CVRect((int)roiList[0].X, (int)roiList[0].Y, (int)roiList[0].Width, (int)roiList[0].Height);
              Mat tp = MatExtension.Crop_Mask_Mat(GrabImg, cVRect);

              templateContour = null;
              coutourLen = 100;
              NumMincoutourLen.Value=100;
              contourArea = 100;
              NumMinContourArea.Value=100;
              double modelx = 0, modely = 0;


              runTool = new ShapeMatchTool();
              parmaData = new ShapeMatchData();
              (parmaData as ShapeMatchData).Segthreshold = (double)NumSegthreshold.Value;

              modeltp = (runTool as ShapeMatchTool).CreateTemplateContours(tp,
                   (parmaData as ShapeMatchData).Segthreshold, cVRect,
                  ref templateContour,
                  ref coutourLen, ref contourArea, ref modelx, ref modely, ref modelangle);

              picTemplate.Image = BitmapConverter.ToBitmap(modeltp);
              if (templateContour == null)
              {
                  MessageBox.Show("模板创建失败!");
                  return;
              }
              modelx += cVRect.X;
              modely += cVRect.Y;
              lIstModelInfo.Items.Clear();
              lIstModelInfo.Items.Add(new ListViewItem(
                  new string[] { "BaseX", modelx.ToString("f3") }));
              lIstModelInfo.Items.Add(new ListViewItem(
                new string[] { "BaseY", modely.ToString("f3") }));
              lIstModelInfo.Items.Add(new ListViewItem(
                new string[] { "BaseAngle", modelangle.ToString("f3") }));
              lIstModelInfo.Items.Add(new ListViewItem(
               new string[] { "ContourLength", coutourLen.ToString("f3") }));
              lIstModelInfo.Items.Add(new ListViewItem(
               new string[] { "ContourArea", contourArea.ToString("f3") }));

              modelOrigion = string.Format("{0},{1},{2}",
                    modelx.ToString("f3"),
                        modely.ToString("f3"),
                            modelangle.ToString("f3"));

            if(coutourLen * 0.8> (double)NumMincoutourLen.Maximum||
                  contourArea * 0.8> (double)NumMinContourArea.Maximum)
              {
                  MessageBox.Show("模板创建完成失败,模板区域过大!");
                  return;
              }
              NumMincoutourLen.Value = (decimal)(coutourLen *0.8);
              NumMaxcoutourLen.Value = (decimal)(coutourLen *1.2);
          
              NumMinContourArea.Value = (decimal)(contourArea * 0.8);
              NumMaxContourArea.Value = (decimal)(contourArea * 1.2);

              NumMatchValue.Value = (decimal)0.5;
              MessageBox.Show("模板创建完成!");
          }

      }

c). 模板匹配

多角度轮廓匹配算法,同时通过钜来获取中心,和角度

  //模板匹配
      void TestModelMatch()
      {
          if (GrabImg == null || GrabImg.Width <= 0)
          {
              stuModelMatchData.runFlag = false;
              MessageBox.Show("未获取图像");
              return;
          }

          if (templateContour == null)
          {
              stuModelMatchData.runFlag = false;
              MessageBox.Show("模板不存在,请先创建模板!");
              return;
          }
          runTool = new ShapeMatchTool();
          parmaData = new ShapeMatchData();
          (parmaData as ShapeMatchData).tpContour = templateContour;
          (parmaData as ShapeMatchData).Segthreshold = (double)NumSegthreshold.Value;
          (parmaData as ShapeMatchData).MatchValue = (double)NumMatchValue.Value;
          (parmaData as ShapeMatchData).MincoutourLen = (int)NumMincoutourLen.Value;
          (parmaData as ShapeMatchData).MaxcoutourLen = (int)NumMaxcoutourLen.Value;
          (parmaData as ShapeMatchData).MinContourArea = (int)NumMinContourArea.Value;
          (parmaData as ShapeMatchData).MaxContourArea = (int)NumMaxContourArea.Value;
          (parmaData as ShapeMatchData).baseAngle = modelangle;


          ResultOfToolRun = runTool.Run<ShapeMatchData>(GrabImg, parmaData as ShapeMatchData);

          currvisiontool.clearAll();
          currvisiontool.dispImage(ResultOfToolRun.resultToShow);

          ShapeMatchResult shapeMatchResult = ResultOfToolRun as ShapeMatchResult;

          if (shapeMatchResult.scores.Count <= 0)
          {
              currvisiontool.DrawText(new TextEx("模板匹配失败!") {x=1000,y=10, brush = new SolidBrush(Color.Red) });

              currvisiontool.AddTextBuffer(new TextEx("模板匹配失败!") { x = 1000, y = 10, brush = new SolidBrush(Color.Red) });

              stuModelMatchData.runFlag = false;
              return;
          }
          currvisiontool.DrawText(new TextEx("得分:" + shapeMatchResult.scores[0].ToString("f3")));
          currvisiontool.AddTextBuffer(new TextEx("得分:" + shapeMatchResult.scores[0].ToString("f3")));

          currvisiontool.DrawText(new TextEx("偏转角度:" + shapeMatchResult.rotations[0].ToString("f3")) { x = 10, y = 100 });
          currvisiontool.AddTextBuffer(new TextEx("偏转角度:" + shapeMatchResult.rotations[0].ToString("f3")) { x = 10, y = 100 });

          currvisiontool.DrawText(new TextEx(string.Format("匹配点位X:{0},Y:{1}", shapeMatchResult.positions[0].X.ToString("f3"),
              shapeMatchResult.positions[0].Y.ToString("f3")))
          { x = 10, y = 200 });
          currvisiontool.AddTextBuffer(new TextEx(string.Format("匹配点位X:{0},Y:{1}", shapeMatchResult.positions[0].X.ToString("f3"),
              shapeMatchResult.positions[0].Y.ToString("f3")))
          { x = 10, y = 200 });

          stuModelMatchData.matchPoint = shapeMatchResult.positions[0];
          stuModelMatchData.matchOffsetAngle = shapeMatchResult.rotations[0];
          stuModelMatchData.matchScore = shapeMatchResult.scores[0];
          stuModelMatchData.runFlag = true;


      }

3. 关键部位代码如下,包含模板创建,模板多角度匹配等

a)创建形状轮廓模板核心代码如下:

        /// <summary>
      /// 创建形状轮廓模板
      /// </summary>
      /// <param name="img_template">模板图像</param>
      ///  <param name="Segthreshold">分割阈值</param>
      /// <param name="templateContour">模板轮廓</param>
      /// <param name="coutourLen">模板轮廓长度</param>
      /// <param name="contourArea">模板轮廓面积</param>
      ///  <param name="modelx">模板轮廓X</param>
      ///   <param name="modely">模板轮廓Y</param>
      ///    <param name="modelangle">模板轮廓角度</param>
      /// <returns>返回绘制图</returns>
      public Mat CreateTemplateContours(Mat img_template,double Segthreshold, CVRect boundingRect,
          ref CVPoint[] templateContour, ref double coutourLen, ref double contourArea,
          ref double modelx,ref double modely,ref double modelangle)
      {
          //灰度化
          //Mat gray_img_template = new Mat();
          //Cv2.CvtColor(img_template, gray_img_template, ColorConversionCodes.BGR2GRAY);

          //阈值分割
          Mat thresh_img_template = new Mat();
          Cv2.Threshold(img_template, thresh_img_template, Segthreshold, 255, ThresholdTypes.Binary);
          //开运算处理,提出白色噪点
          Mat ellipse = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3));   
          Cv2.MorphologyEx(thresh_img_template, thresh_img_template, MorphTypes.Open, ellipse);

          //Mat cannyMat = new Mat();
          //Cv2.Canny(thresh_img_template, cannyMat, Segthreshold, 255);

          //寻找边界
          CVPoint[][] contours_template;
          //Vector<Vector<CVPoint>> contours_template=new Vector<Vector<CVPoint>>();
          //Vector<Vec4i> hierarchy=new Vector<Vec4i>();
      //    HierarchyIndex[] hierarchy;
          Cv2.FindContours(thresh_img_template, out contours_template, out _, RetrievalModes.Tree,
              ContourApproximationModes.ApproxNone);

          CVPoint[][] ExceptContours = ContourOperate.ExceptBoundPoints(img_template.BoundingRect(), contours_template);
          
          int count = ExceptContours.ToList<CVPoint[]>().Count;
          List<CVPoint[]> ModelContours=new List<CVPoint[]>();
      
          for (int i=0;i< count; i++)
          {
              if(Cv2.ContourArea(ExceptContours[i])>= contourArea&&
                  Cv2.ArcLength(ExceptContours[i],false)>= coutourLen)
              //if (ExceptContours[i].Length > 30)//至少30点有效
                  ModelContours.Add(ExceptContours[i]);
          }
          ModelContours = ModelContours.OrderByDescending(s => s.Length).ToList();
          //绘制边界
          Mat dst = new Mat();
          Cv2.CvtColor(img_template, dst, ColorConversionCodes.GRAY2BGR);
          if(ModelContours.Count>0)
          {
              Cv2.DrawContours(dst, ModelContours, 0, new Scalar(0, 0, 255));
              //获取重心点
              Moments M;
              M = Cv2.Moments(ModelContours[0]);
              double cX = (M.M10 / M.M00);
              double cY = (M.M01 / M.M00);
              
              float a = (float)(M.M20 / M.M00 - cX * cX);
              float b = (float)(M.M11 / M.M00 - cX * cY);
              float c = (float)(M.M02 / M.M00 - cY * cY);
              //计算角度(0~180)
            //  double tanAngle = Cv2.FastAtan2(2 * b, (a - c)) / 2;

              //计算角度2(-90~90)
           //   double ang = (Math.Atan2(2 * b, (a - c)) * 180 / Math.PI) / 2;

              //double ang2=  Cv2.MinAreaRect(ModelContours[0]).Angle;

              //if (tanAngle > 90)
              //    tanAngle -= 180;
              //当前轮廓旋转矩
              RotatedRect currrect = Cv2.MinAreaRect(ModelContours[0]);
              //绘制旋转矩形
                 dst.DrawRotatedRect(currrect, Scalar.Lime);

              //绘制目标边界
              Cv2.DrawContours(dst, ModelContours, 0, new Scalar(0, 0, 255));
              //显示目标中心
              dst.drawCross(new CVPoint((int)cX, (int)cY),
                     new Scalar(0, 255, 0), 10, 2);
              //


              //CVPoint[] HullP = Cv2.ConvexHull(ModelContours[0], true);//顺时针方向

              //List<CVPoint[]> HullPList = new List<CVPoint[]>();

              //HullPList.Add(HullP);

              Cv2.Polylines(dst, HullPList, true, Scalar.Red);

              //Point2f cVPoint = CalBestDisP(new Point2d(cX, cY), HullP);

              //double ang3 = ang;

              //if(!(cVPoint.X==0&& cVPoint.Y == 0))
  //            {
              //    //计算角度2(-180~180)
              //    ang3 = calAngleOfLx(cX, cY, cVPoint.X, cVPoint.Y);
              //    Cv2.Line(dst, (int)cX, (int)cY, (int)cVPoint.X, (int)cVPoint.Y, Scalar.DarkOrange);
              //}
                          
              //轮廓点位
              modelx = cX;
              modely = cY;
              modelangle = currrect.Angle;

              //轮廓长度
              coutourLen = Cv2.ArcLength(ModelContours[0],false);
              contourArea = Cv2.ContourArea(ModelContours[0]);
              templateContour = ModelContours[0];
          }    
          else
          {
              //轮廓点位
              modelx = 0;
              modely = 0;
              modelangle = 0;

              //轮廓长度
              coutourLen = 0;
              contourArea = 0;
              templateContour =null;
          }
          return dst;
      }

b)形状多角度匹配核心算法如下:

    /// <summary>
      /// 形状匹配
      /// </summary>
      /// <param name="image">输入图像</param>
      /// <param name="imgTemplatecontours">模板轮廓</param>
      ///  <param name="Segthreshold">分割阈值</param>
      /// <param name="MatchValue">匹配值</param>
      /// <param name="MincoutourLen">轮廓最小长度</param>
      /// <param name="MaxcoutourLen">轮廓最大长度</param>
      /// <param name="MinContourArea">轮廓最小面积</param>
      /// <param name="MaxContourArea">轮廓最大面积</param>
      /// <param name="shapeMatchResult">匹配结果</param>
      /// <param name="isMultipleTemplates">是否使用多模板</param>
      /// <returns>返回绘制图</returns>
      bool ShapeTemplateMatch(Mat image, CVPoint[] imgTemplatecontours, double Segthreshold,
          double MatchValue, int MincoutourLen, int MaxcoutourLen,
           double MinContourArea, double MaxContourArea,  double baseAngle,
           ref ShapeMatchResult shapeMatchResult,
           bool isMultipleTemplates=false)
      {
      
          //List<Point2d> image_coordinates = new List<Point2d>();
          //灰度化
          //Mat gray_img=new Mat();
          //Cv2.CvtColor(image, gray_img, ColorConversionCodes.BGR2GRAY);
          Mat dst = new Mat();
          Cv2.CvtColor(image, dst, ColorConversionCodes.GRAY2BGR);
          //阈值分割
          Mat thresh_img = new Mat();
          Cv2.Threshold(image, thresh_img, Segthreshold, 255, ThresholdTypes.Binary);


          #region------此处增加与模板创建时候同样的图像处理--------
          //开运算处理,提出白色噪点
          Mat ellipse = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3));
  
          Cv2.MorphologyEx(thresh_img, thresh_img, MorphTypes.Open, ellipse);
          #endregion


          //Mat cannyMat = new Mat();
          //Cv2.Canny(thresh_img, cannyMat, Segthreshold, 255);

          //寻找边界
          CVPoint[][] contours_img;
          //HierarchyIndex[] hierarchy;
          Cv2.FindContours(thresh_img, out contours_img, out _, RetrievalModes.Tree,
               ContourApproximationModes.ApproxNone);
          //根据形状模板进行匹配
          int min_pos = -1;
          double min_value = MatchValue;//匹配分值
          List<CVPoint[]> points = contours_img.ToList<CVPoint[]>();

          //List<double> angleList = new List<double>();
          for (int i = 0; i < points.Count; i++)
          {
              //计算轮廓面积,筛选掉一些没必要的小轮廓
              if (Cv2.ContourArea(contours_img[i]) < MinContourArea ||
                            Cv2.ContourArea(contours_img[i]) > MaxContourArea)
                  continue;
              //轮廓长度不达标            
              if (Cv2.ArcLength(contours_img[i], false) < MincoutourLen ||
                            Cv2.ArcLength(contours_img[i], false) > MaxcoutourLen)
                  continue;

              //得到匹配分值 ,值越小相似度越高
              double value = Cv2.MatchShapes(contours_img[i], imgTemplatecontours,
                                                         ShapeMatchModes.I3, 0.0);
              value = 1 - value;

              //将匹配分值与设定分值进行比较 
              if (value >= min_value)
              {
                  min_pos = i;

                  //将目标的得分都存在数组中 
                  shapeMatchResult.scores.Add(value);
                  //匹配到的轮廓
                  shapeMatchResult.contours.Add(contours_img[min_pos]);
                  /*----------------*/
              }
                              
          }
          /*----------------*/
          int count = shapeMatchResult.scores.Count;
          if(count<=0)
          {
              shapeMatchResult.resultToShow = dst;
              shapeMatchResult.exceptionInfo = "模板匹配失败!";
              return false;
          }

          if (isMultipleTemplates)
          {
              for (int j = 0; j < count; j++)
              {
                  //绘制目标边界
                  Cv2.DrawContours(dst, shapeMatchResult.contours, j, new Scalar(0, 0, 255));
                  //得分绘制
                  Cv2.PutText(dst,
                      string.Format("Score:{0};Angle:{1}", shapeMatchResult.scores[j].ToString("F3"),
                      shapeMatchResult.rotations[j].ToString("F3")),
                           //anglebuf[j].ToString("F3")),
                           new CVPoint(shapeMatchResult.contours[j][0].X + 10, shapeMatchResult.contours[j][0].Y - 10),
                                      HersheyFonts.HersheyDuplex, 1, Scalar.Yellow);
                  //显示目标中心并提取坐标点
                  dst.drawCross(new CVPoint((int)shapeMatchResult.positions[j].X, (int)shapeMatchResult.positions[j].Y),
                         new Scalar(0, 255, 0), 10, 2);
                  //当前轮廓旋转矩
                  RotatedRect currrect = Cv2.MinAreaRect(shapeMatchResult.contours[j]);

                  dst.DrawRotatedRect(currrect, Scalar.Lime);
              }
          }
          else
          {
              double bestScore=  shapeMatchResult.scores.Max();    //最佳得分
              int index = shapeMatchResult.scores.FindIndex(s=>s== bestScore);
            //  double bestangle = shapeMatchResult.rotations[index]; //最佳角度                
          //    Point2d bestpos = shapeMatchResult.positions[index]; //最佳点位
              CVPoint[] bestcontour= shapeMatchResult.contours[index]; //最佳轮廓            
          
              //绘制目标边界
              Cv2.DrawContours(dst, shapeMatchResult.contours, index, new Scalar(0, 0, 255));
          
              //获取重心点                
              Moments M = Cv2.Moments(bestcontour);
              double cX = (M.M10 / M.M00);
              double cY = (M.M01 / M.M00);

              float a = (float)(M.M20 / M.M00 - cX * cX);
              float b = (float)(M.M11 / M.M00 - cX * cY);
              float c = (float)(M.M02 / M.M00 - cY * cY);
              //计算角度(0~180)
             // double tanAngle = Cv2.FastAtan2(2 * b, (a - c)) / 2;
              //angleList.Add(tanAngle);

              //计算角度2(-90~90)
              //double ang = (Math.Atan2(2 * b, (a - c)) * 180 / Math.PI) / 2;

              #region----角度计算方式2---
              //-90~90度
              //由于先验目标最小包围矩形是长方形   
              //因此最小包围矩形的中心和重心的向量夹角为旋转
              RotatedRect rect_template = Cv2.MinAreaRect(imgTemplatecontours);
              RotatedRect rect_search = Cv2.MinAreaRect(bestcontour);
              //两个旋转矩阵是否同向
              float sign = (rect_template.Size.Width - rect_template.Size.Height) * 
                                (rect_search.Size.Width - rect_search.Size.Height);
              float angle=0;
              if (sign > 0)
                  // 可以直接相减
                  angle = rect_search.Angle - rect_template.Angle;
              else
                  angle = (90 + rect_search.Angle) - rect_template.Angle;

              if (angle > 90)
                  angle -= 180;
              #endregion


                  //显示目标中心并提取坐标点
              dst.drawCross(new CVPoint((int)cX, (int)cY),
                          new Scalar(0, 255, 0), 10, 2);
              //当前轮廓旋转矩
              RotatedRect currrect = Cv2.MinAreaRect(bestcontour);
              //绘制旋转矩形
              dst.DrawRotatedRect(currrect, Scalar.Lime);
         
              //CVPoint[] HullP = Cv2.ConvexHull(bestcontour, true);//顺时针方向

              //List<CVPoint[]> HullPList = new List<CVPoint[]>();

              //HullPList.Add(HullP);

              //Cv2.Polylines(dst, HullPList, true, Scalar.Red);

              //Point2f cVPoint = CalBestDisP(new Point2d(cX, cY), HullP);

              //double ang3 = ang;

              //if (!(cVPoint.X == 0 && cVPoint.Y == 0))
              //{
              //    //计算角度2(-180~180)
              //    ang3 = calAngleOfLx(cX, cY, cVPoint.X, cVPoint.Y);
              //    Cv2.Line(dst, (int)cX, (int)cY, (int)cVPoint.X, (int)cVPoint.Y, Scalar.DarkOrange);
              //}
          
              //double offsetA = ang3 - baseAngle;//偏转角
              //if (offsetA < -180)
              //    offsetA += 360;
              //else if (offsetA > 180)
              //    offsetA -= 360;

                  //得分绘制
              //Cv2.PutText(dst,
              //    string.Format("Score:{0};Angle:{1}", bestScore.ToString("F3"),
              //              ang3.ToString("F3")),
              //         new CVPoint(shapeMatchResult.contours[index][0].X + 10, shapeMatchResult.contours[index][0].Y - 10),
              //                    HersheyFonts.HersheyDuplex, 1, Scalar.Yellow);

              
              shapeMatchResult.positions.Clear();
              shapeMatchResult.rotations.Clear();
              shapeMatchResult.scores.Clear();
              shapeMatchResult.contours.Clear();
              //将目标的重心坐标都存在数组中 
              shapeMatchResult.positions.Add(new Point2d(cX, cY));//向数组中存放点的坐标
                                                                  
              shapeMatchResult.rotations.Add(angle);//将偏转角度都存在数组中 
                                                       
              shapeMatchResult.scores.Add(bestScore);//将目标的得分都存在数组中 
                                                    
              shapeMatchResult.contours.Add(bestcontour); //匹配到的轮廓
              /*----------------*/
          }

          shapeMatchResult.resultToShow = dst;
          return true;
      }

关于C# OpenCV实现形状匹配的方法详解的文章就介绍至此,更多相关C# OpenCV形状匹配内容请搜索编程宝库以前的文章,希望以后支持编程宝库

在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。有没有那么一种场景,后台数据明明已经发生变 ...