Ejemplo n.º 1
0
        /// <summary>
        /// Convert a polar point to cartesian
        /// </summary>
        /// <param name="p">polar point to convert</param>
        /// <returns>cartesian point</returns>
        /// see: https://de.wikipedia.org/wiki/Polarkoordinaten
        public static PointF PolarToCartesian(PolarCoordinate p)
        {
            double a = p.Angle * (Math.PI / 180);       // Convert angle from degree to radians

            return(new PointF((float)(p.Radius * Math.Cos(a)), (float)(p.Radius * Math.Sin(a))));
        }
        /// <summary>
        /// Get the piece corners by finding peaks in the polar representation of the contour points
        /// </summary>
        /// <param name="pieceID">ID of the piece</param>
        /// <param name="pieceImgBw">Black white image of piece</param>
        /// <param name="pieceImgColor">Color image of piece</param>
        /// <returns>List with corner points</returns>
        /// see: http://www.martijn-onderwater.nl/2016/10/13/puzzlemaker-extracting-the-four-sides-of-a-jigsaw-piece-from-the-boundary/
        /// see: https://web.stanford.edu/class/cs231a/prev_projects_2016/computer-vision-solve__1_.pdf
        public override List <Point> FindCorners(string pieceID, Bitmap pieceImgBw, Bitmap pieceImgColor)
        {
            List <Point> corners = new List <Point>();

            Size pieceMiddle = new Size(pieceImgBw.Width / 2, pieceImgBw.Height / 2);

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

            CvInvoke.FindContours(new Image <Gray, byte>(pieceImgBw), contours, null, RetrType.List, ChainApproxMethod.ChainApproxSimple);
            if (contours.Size == 0)
            {
                return(corners);
            }
            VectorOfPoint contour = contours[0];

            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                using (Image <Rgb, byte> polarContourImg = new Image <Rgb, byte>(pieceImgColor))
                {
                    for (int i = 0; i < contour.Size; i++)
                    {
                        CvInvoke.Circle(polarContourImg, Point.Round(contour[i]), 2, new MCvScalar(255 * ((double)i / contour.Size), 0, 255 - 255 * ((double)i / contour.Size)), 1);
                    }
                    PluginFactory.LogHandle.Report(new LogEventImage(pieceID + " Polar Contour", polarContourImg.Bitmap));
                }
            }

            List <PolarCoordinate> polarContour = new List <PolarCoordinate>();

            for (int i = 0; i < contour.Size; i++)
            {
                polarContour.Add(PolarCoordinate.CartesianToPolar(Point.Subtract(contour[i], pieceMiddle)));        // Shift the origin to the middle of the piece to get the full 360 degree range for angles instead of only using 90 degree
            }

            //List<PointF> polarContourPoints = polarContour.Select(p => new PointF((float)p.Angle, (float)p.Radius)).ToList();
            //polarContourPoints = DouglasPeuckerLineApprox.DouglasPeuckerReduction(polarContourPoints, 1);
            //polarContour = polarContourPoints.Select(p => new PolarCoordinate(p.X, p.Y)).ToList();

            //List<double> smoothedValues = SmoothingFilter.SmoothData(polarContour.Select(p => p.Radius).ToList(), 7, 0.4);
            //List<PolarCoordinate> polarContourSmoothed = new List<PolarCoordinate>();
            //for (int i = 0; i < polarContour.Count; i++) { polarContourSmoothed.Add(new PolarCoordinate(polarContour[i].Angle, smoothedValues[i])); }
            //polarContour = polarContourSmoothed;

            List <double>          contourRadius         = polarContour.Select(p => p.Radius).ToList();
            List <int>             peakPosOut            = DifferencePeakFinder.FindPeaksCyclic(contourRadius, 5, 0, 1); //2, 0.999);
            double                 contourRadiusRange    = contourRadius.Max() - contourRadius.Min();
            List <PolarCoordinate> cornerCandidatesPolar = polarContour.Where(p => peakPosOut != null && peakPosOut[polarContour.IndexOf(p)] == 1 && p.Radius > contourRadius.Min() + PieceFindCornersPeakDismissPercentage * contourRadiusRange).ToList();

            cornerCandidatesPolar = cornerCandidatesPolar.OrderBy(p => p.Angle).ToList();

            List <PolarCoordinate> cornersPolar = new List <PolarCoordinate>();

            if (cornerCandidatesPolar.Count < 4)
            {
                PluginFactory.LogHandle.Report(new LogEventWarning(pieceID + " not enough corners found (" + cornerCandidatesPolar.Count.ToString() + ")"));
            }
            else
            {
                //Rotate perfect square to find corners with minimum difference to it
                double minSum   = double.MaxValue;
                int    minAngle = 0;
                for (int i = 0; i < 360; i++)
                {
                    double angleDiffSum = 0;
                    for (int a = 0; a < 360; a += 90)
                    {
                        List <PolarCoordinate> rangePolarPoints = cornerCandidatesPolar.Where(p => Utils.IsAngleInRange(p.Angle, i + a - 45, i + a + 45)).ToList();
                        List <double>          rangeDiffs       = rangePolarPoints.Select(p => Math.Abs(Utils.GetPositiveAngle(i + a) - p.Angle)).ToList();
                        double angleDiff = rangeDiffs.Count <= 0 ? double.MaxValue : rangeDiffs.Sum();
                        angleDiffSum += angleDiff;
                    }
                    if (angleDiffSum < minSum)
                    {
                        minSum   = angleDiffSum;
                        minAngle = i;
                    }
                }

                for (int a = 270; a >= 0; a -= 90)
                {
                    PolarCoordinate polarCorner = cornerCandidatesPolar.OrderBy(p => Utils.AngleDiff(p.Angle, Utils.GetPositiveAngle(minAngle + a), true)).First();     // Get the corner candiate that has the minimum distance to the current ideal square point position
                    corners.Add(Point.Add(Point.Round(PolarCoordinate.PolarToCartesian(polarCorner)), pieceMiddle));
                    cornersPolar.Add(polarCorner);
                }
            }

            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                int maxRadius = (int)polarContour.Select(p => p.Radius).Max();
                using (Mat polarImg = new Mat(maxRadius, 360, DepthType.Cv8U, 3))
                {
                    for (int i = 0; i < polarContour.Count - 1; i++)
                    {
                        CvInvoke.Line(polarImg, new Point((int)polarContour[i].Angle, maxRadius - (int)polarContour[i].Radius), new Point((int)polarContour[i + 1].Angle, maxRadius - (int)polarContour[i + 1].Radius), new MCvScalar(255, 0, 0), 1, LineType.EightConnected);
                    }
                    for (int i = 0; i < cornerCandidatesPolar.Count; i++)
                    {
                        CvInvoke.Circle(polarImg, new Point((int)cornerCandidatesPolar[i].Angle, maxRadius - (int)cornerCandidatesPolar[i].Radius), 3, new MCvScalar(0, 0, 255), -1);
                    }
                    for (int i = 0; i < cornersPolar.Count; i++)
                    {
                        CvInvoke.Circle(polarImg, new Point((int)cornersPolar[i].Angle, maxRadius - (int)cornersPolar[i].Radius), 2, new MCvScalar(0, 255, 0), -1);
                    }
                    PluginFactory.LogHandle.Report(new LogEventImage(pieceID + " Polar", polarImg.ToImage <Rgb, byte>().Bitmap));
                    polarImg.Dispose();

                    Image <Rgb, byte> corner_img = new Image <Rgb, byte>(pieceImgColor);
                    for (int i = 0; i < corners.Count; i++)
                    {
                        CvInvoke.Circle(corner_img, Point.Round(corners[i]), 7, new MCvScalar(255, 0, 0), -1);
                    }
                    PluginFactory.LogHandle.Report(new LogEventImage(pieceID + " Found Corners (" + corners.Count.ToString() + ")", corner_img.Bitmap));
                    corner_img.Dispose();
                }
            }
            return(corners);
        }