AoiMarkMethod.cs 12.2 KB
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenCvSharp;
using OpenCvSharp.XFeatures2D;

namespace AOI
{
    /// <summary>
    /// 从搜索区域中查找Mark区域,对图片进行校准
    /// </summary>
    public class AoiMarkMethod : AoiMethod
    {
        /// <summary>
        /// 放大RoiPath作为SearchPath
        /// </summary>
        public float SearchPathZoom = 2.0f;
        /// <summary>
        /// 相似度百分比
        /// </summary>
        public float SamePercent = 50;

        /// <summary>
        /// 放大RoiPath作为SearchPath
        /// </summary>
        /// <param name="zoom">放大倍率</param>
        /// <returns></returns>
        public GraphicsPath GetSearchPath()
        {
            try
            {
                if (RoiPath != null && SearchPathZoom > 0)
                {
                    GraphicsPath SearchPath = new GraphicsPath(RoiPath.PathPoints, RoiPath.PathTypes);
                    Matrix matrix = new Matrix();
                    matrix.Scale(SearchPathZoom, SearchPathZoom);
                    SearchPath.Transform(matrix);

                    var oldBounds = this.RoiPath.GetBounds();
                    var newBounds = SearchPath.GetBounds();
                    var oldCenterX = oldBounds.X + oldBounds.Width / 2;
                    var oldCenterY = oldBounds.Y + oldBounds.Height / 2;

                    var newCenterX = newBounds.X + newBounds.Width / 2;
                    var newCenterY = newBounds.Y + newBounds.Height / 2;

                    matrix.Reset();
                    matrix.Translate(oldCenterX - newCenterX, oldCenterY - newCenterY);
                    SearchPath.Transform(matrix);

                    return SearchPath;
                }
                return RoiPath;
            }catch (Exception ex)
            {
                Console.WriteLine(ex.ToString()); 
            }return null;
        }

        /// <summary>
        /// 根据Mark点校正相机获取的图片
        /// </summary>
        /// <param name="standardImage">标准图片</param>
        /// <param name="imageToCheck">相机获取的图片</param>
        /// <returns></returns>
        public override ResultBean Check(Image standardImage, Image imageToCheck)
        {
            ResultBean resultBean = new ResultBean(MethodName,1,SamePercent,SamePercent);
            resultBean.standardRoiImage = standardImage;
            double sameValue = 0;
            Image resultImage = FixImage(standardImage, imageToCheck,out sameValue);
            if(resultImage != null)
            {
                resultBean.result = true;
                resultBean.currentRoiImage = resultImage;
                resultBean.percentValue = sameValue;
            }
            return resultBean;
        }

        public Mat Fix(Image standardImage, Image imageToCheck,out double sameValue)
        {
            sameValue = 0;
            bool needCut = true;
            //标准图中的Mart区域
            Image markImage = GetRoiImage(standardImage, RoiPath, needCut);
            //搜索区域
            var SearchPath = GetSearchPath();
            Image searchImage = GetRoiImage(imageToCheck, SearchPath, needCut);
            //searchImage = imageToCheck;
            if (markImage != null && searchImage != null)
            {
                Mat searchMat = ImageUtil.ToMat(new Bitmap(searchImage));
                Mat markMat = ImageUtil.ToMat(new Bitmap(markImage));

                //Mat graySearchMat = new Mat();
                //Mat grayMarkMat = new Mat();
                //Cv2.CvtColor(searchMat, graySearchMat, ColorConversionCodes.RGB2GRAY);
                //Cv2.CvtColor(markMat, grayMarkMat, ColorConversionCodes.RGB2GRAY);
                //double same = Cv2.MatchShapes(grayMarkMat, graySearchMat, ShapeMatchModes.I1);
                //Console.WriteLine("===============" + same);

                Mat result = new Mat(searchMat.Cols - markMat.Cols + 1, searchMat.Rows - markMat.Rows + 1, MatType.CV_32FC1);
                
                //进行匹配(1母图,2模版子图,3返回的result,4匹配模式_这里的算法比opencv少,具体可以看opencv的相关资料说明)
                Cv2.MatchTemplate(searchMat, markMat, result, TemplateMatchModes.CCoeffNormed);

                //对结果进行归一化(这里我测试的时候没有发现有什么用,但在opencv的书里有这个操作,应该有什么神秘加成,这里也加上)
                //Cv2.Normalize(result, result, 1, 0, NormTypes.MinMax, -1);
                /// 通过函数 minMaxLoc 定位最匹配的位置
                /// (这个方法在opencv里有5个参数,这里我写的时候发现在有3个重载,看了下可以直接写成拿到起始坐标就不取最大值和最小值了)
                /// minLocation和maxLocation根据匹配调用的模式取不同的点
                Cv2.MinMaxLoc(result, out double minVal, out double maxVal, out OpenCvSharp.Point minLocation, out OpenCvSharp.Point maxLocation);
                //画出匹配的矩,
                //  Cv2.Rectangle(mat1, maxLocation, new Point (maxLocation.X+mat2.Cols, maxLocation.Y+mat2.Rows), Scalar.Red, 2);
                Cv2.Rectangle(searchMat, maxLocation, new OpenCvSharp.Point(maxLocation.X + markMat.Cols, maxLocation.Y + markMat.Rows), Scalar.Red, 2);
                
                Console.WriteLine(maxLocation + "=" + maxVal);
                sameValue =Math.Round( maxVal * 100,3);
                if (maxVal * 100 > SamePercent)
                {
                    //大于相似度,开始平移图像
                    var searchBounds = SearchPath.GetBounds();
                    var srcPoints = new Point2f[] {
                        new Point2f(searchBounds.X + maxLocation.X, searchBounds.Y + maxLocation.Y),
                        new Point2f(searchBounds.X + maxLocation.X+markMat.Cols, searchBounds.Y + maxLocation.Y),
                        new Point2f(searchBounds.X + maxLocation.X, searchBounds.Y + maxLocation.Y + markMat.Rows),
                        new Point2f(searchBounds.X + maxLocation.X+markMat.Cols, searchBounds.Y + maxLocation.Y + markMat.Rows),
                    };
                    //变换后的四点
                    var markBounds = RoiPath.GetBounds();
                    var dstPoints = new Point2f[] {

                        new Point2f(markBounds.X , markBounds.Y),
                        new Point2f(markBounds.X + markMat.Cols , markBounds.Y),
                        new Point2f(markBounds.X , markBounds.Y + markMat.Rows),
                        new Point2f(markBounds.X + markMat.Cols , markBounds.Y + markMat.Rows),
                    };
                    //根据变换前后四个点坐标,获取变换矩阵
                    Mat mm = Cv2.GetAffineTransform(srcPoints, dstPoints);
                    return mm;

                }
            }

            return null;
            
        }

        public Image FixImage(Image standardImage, Image imageToCheck,out double sameValue)
        {
            
            //Fix(standardImage, imageToCheck);
            var affine = Fix(standardImage, imageToCheck,out sameValue);
            if (affine != null)
            {
                var matToCheck = ImageUtil.ToMat(imageToCheck);
                Mat fixedMat = new Mat();
                Cv2.WarpAffine(matToCheck, fixedMat, affine,new OpenCvSharp.Size(standardImage.Width, standardImage.Height));
                //var fixedMat = FixImage(affine, matToCheck);
                //Image markImage = GetRoiImage(ImageUtil.ToImage(fixedMat), RoiPath, true);
               // return markImage;
               // Cv2.ImShow("Fixed", ImageUtil.ToMat(markImage));
                return ImageUtil.ToImage(fixedMat);
            }
            //bool needCut = false;
            ////标准图中的Mart区域
            //Image markImage = GetRoiImage(standardImage, RoiPath, needCut);
            ////搜索区域
            //var SearchPath = GetSearchPath();
            //Image searchImage = GetRoiImage(imageToCheck, SearchPath, needCut);
            //if (markImage != null && searchImage != null)
            //{
            //    var affine = GetAffineMat(markImage, searchImage);
            //    if (affine != null)
            //    {
            //        var matToCheck = ImageUtil.ToMat(imageToCheck);
            //        var fixedMat = FixImage(affine, matToCheck);
            //        return ImageUtil.ToImage(fixedMat);
            //    }
            //}
            return null;
        }

        /// <summary>
        /// 校准图片
        /// </summary>
        /// <param name="affineMat"></param>
        /// <param name="srcMat"></param>
        /// <returns></returns>
        private Mat FixImage(Mat affineMat, Mat srcMat)
        {
            Mat resultMat = new Mat();
            Cv2.WarpAffine(srcMat, resultMat, affineMat, srcMat.Size());
            return resultMat;
        }
        /// <summary>
        /// 获取映射距阵
        /// </summary>
        /// <param name="markImage"></param>
        /// <param name="srcImage"></param>
        /// <returns></returns>
        private Mat GetAffineMat(Image markImage, Image srcImage)
        {
            Mat markMat = ImageUtil.ToMat(new Bitmap(markImage));
            Mat originalMat = ImageUtil.ToMat(new Bitmap(srcImage));
            Mat srcMat = new Mat();
            //灰度图转换
            Cv2.CvtColor(markMat, markMat, ColorConversionCodes.RGB2GRAY);
            Cv2.CvtColor(originalMat, srcMat, ColorConversionCodes.RGB2GRAY);

            //提取特征点
            SIFT sift = SIFT.Create(200);
            KeyPoint[] markKeyPoints, srcKeyPoints;
            MatOfFloat roiDescriptors = new MatOfFloat();
            MatOfFloat srcDescriptors = new MatOfFloat();
            sift.DetectAndCompute(markMat, null, out markKeyPoints, roiDescriptors);
            sift.DetectAndCompute(srcMat, null, out srcKeyPoints, srcDescriptors);

            var flannMatcher = new FlannBasedMatcher();
            DMatch[] matchePoints = flannMatcher.Match(srcDescriptors, roiDescriptors);
            //提取强特征点
            double minMatch = 1;
            double maxMatch = 0;
            for (int i = 0; i < matchePoints.Length; i++)
            {
                double distance = matchePoints[i].Distance;
                //匹配值最大最小值获取
                if (distance < minMatch)
                {
                    minMatch = distance;
                }
                if (distance > maxMatch)
                {
                    maxMatch = distance;
                }
            }

            List<DMatch> goodMatchePoints = new List<DMatch>();
            for (int i = 0; i < matchePoints.Length; i++)
            {
                if (matchePoints[i].Distance < minMatch + (maxMatch - minMatch) / 4)
                {
                    goodMatchePoints.Add(matchePoints[i]);
                }
            }
            //获取排在前N个的最优匹配特征点
            int num = goodMatchePoints.Count;
            if (num >= 3)
            {
                num = 3;
            }
            else
            {
                //不匹配
                return null;
            }
            List<Point2f> markPoints = new List<Point2f>();
            List<Point2f> srcPoints = new List<Point2f>();
            goodMatchePoints.Sort((left, right) =>
            {
                if (left.Distance > right.Distance)
                    return 1;
                else if (left.Distance == right.Distance)
                    return 0;
                else
                    return -1;
            });

            //Mat matchMat = new Mat();
            //Cv2.DrawMatches(srcMat, srcKeyPoints, markMat, roiKeyPoints, goodMatchePoints.Take(num), matchMat);
            //Cv2.ImShow("Match", matchMat);

            for (int i = 0; i < num; i++)
            {
                srcPoints.Add(srcKeyPoints[goodMatchePoints[i].QueryIdx].Pt);
                markPoints.Add(markKeyPoints[goodMatchePoints[i].TrainIdx].Pt);
            }

            //获取图像1到图像2的投影映射矩阵 尺寸为3*3
            Mat affineMat = Cv2.GetAffineTransform(srcPoints, markPoints);
            return affineMat;
        }
    }
}