예제 #1
0
        //**********************************************************************************************************************************************************************************************

        /// <summary>
        /// Classify the type of piece
        /// </summary>
        private void classify()
        {
            int count = 0;
            for (int i = 0; i < 4; i++)
            {
                if(Edges[i] == null) { return; }
                if (Edges[i].EdgeType == EdgeTypes.LINE) count++;
            }
            if (count == 0)
            {
                PieceType = PieceTypes.INNER;
            }
            else if (count == 1)
            {
                PieceType = PieceTypes.BORDER;
            }
            else if (count == 2)
            {
                PieceType = PieceTypes.CORNER;
            }
            else
            {
                _logHandle.Report(new LogEventError(PieceID + " Found too many edges for this piece. " + count.ToString() + " found."));
            }

            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                _logHandle.Report(new LogEventInfo(PieceID + " Type " + PieceType.ToString()));
            }
        }
예제 #2
0
        //##############################################################################################################################################################################################

        public Edge(string pieceID, int edgeNumber, LocalDriveBitmap pieceImgColor, VectorOfPoint edgeContour, IProgress <LogEvent> logHandle, CancellationToken cancelToken)
        {
            _logHandle   = logHandle;
            _cancelToken = cancelToken;
            PieceID      = pieceID;
            EdgeNumber   = edgeNumber;
            contour      = edgeContour;
            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                PieceImgColor = pieceImgColor;
                ContourImg    = new LocalDriveBitmap(System.IO.Path.GetDirectoryName(PieceImgColor.LocalFilePath) + @"\Edges\" + PieceID + "_Edge#" + edgeNumber.ToString() + ".png", null);
            }

            NormalizedContour = normalize(contour);    //Normalized contours are used for comparisons

            VectorOfPoint contourCopy = new VectorOfPoint(contour.ToArray().Reverse().ToArray());

            ReverseNormalizedContour = normalize(contourCopy);   //same as normalized contour, but flipped 180 degrees
            contourCopy.Dispose();

            classify();
        }
예제 #3
0
        //##############################################################################################################################################################################################

        public Piece(Image<Rgba, byte> color, Image<Gray, byte> bw, string pieceSourceFileName, Point pieceSourceFileLocation, IProgress<LogEvent> logHandle, CancellationToken cancelToken)
        {
            _logHandle = logHandle;
            _cancelToken = cancelToken;
            PieceID = "Piece#" + NextPieceID.ToString();
            PieceIndex = NextPieceID;
            NextPieceID++;
            
            PieceImgColor = new LocalDriveBitmap(System.IO.Path.GetDirectoryName(pieceSourceFileName) + @"\Results\" + PieceID + "_Color.png", (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults ? color.Bitmap : color.LimitImageSize(200, 200).Bitmap));
            PieceImgBw = new LocalDriveBitmap(System.IO.Path.GetDirectoryName(pieceSourceFileName) + @"\Results\" + PieceID + "_Bw.png", bw.Bitmap);
            PieceSourceFileName = pieceSourceFileName;
            PieceSourceFileLocation = pieceSourceFileLocation;
            PieceSize = bw.Bitmap.Size;

            
            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                _logHandle.Report(new LogEventImage(PieceID + " Color", color.Bitmap));
                _logHandle.Report(new LogEventImage(PieceID + " Bw", bw.Bitmap));
            }

            process();
        }
예제 #4
0
        /// <summary>
        /// Load the solved puzzle result from the given XML file
        /// </summary>
        /// <param name="puzzleFileName">Path to an XML file containing the solved puzzle result</param>
        private async Task LoadPuzzle(string puzzleFileName)
        {
            try
            {
                PuzzleSavingState = PuzzleSavingStates.LOADING;
                logHandle.Report(new LogEventInfo("Loading puzzle from \"" + puzzleFileName + "\""));
                PuzzleHandle = new Puzzle()
                {
                    PuzzleXMLOutputPath = puzzleFileName
                };                                                                         // Neccessary to show the path while loading (when PuzzleHandle is null, no path is displayed)
                PuzzleHandle = await Task.Run(() => { return(Puzzle.Load(puzzleFileName, PluginFactory.GetGeneralSettingsPlugin().CompressPuzzleOutputFile)); });

                PuzzleHandle.PuzzleXMLOutputPath = puzzleFileName;
                PuzzleSavingState = PuzzleSavingStates.LOADED;
                logHandle.Report(new LogEventInfo("Loading puzzle ready."));
                CommandManager.InvalidateRequerySuggested();
            }
            catch (Exception ex)
            {
                PuzzleSavingState = PuzzleSavingStates.ERROR;
                logHandle.Report(new LogEventError("Error while loading: " + ex.Message));
            }
        }
예제 #5
0
        //**********************************************************************************************************************************************************************************************

        private async Task SavePuzzle()
        {
            try
            {
                if (PuzzleHandle == null)
                {
                    return;
                }
                System.Windows.Forms.SaveFileDialog saveFileDialog = new System.Windows.Forms.SaveFileDialog();
                saveFileDialog.Title            = "Please enter the output file name";
                saveFileDialog.Filter           = "XML file|*.xml";
                saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(PuzzleHandle.PuzzleXMLOutputPath);
                saveFileDialog.RestoreDirectory = true;
                saveFileDialog.FileName         = System.IO.Path.GetFileName(PuzzleHandle.PuzzleXMLOutputPath);
                if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    PuzzleSavingState = PuzzleSavingStates.SAVING;
                    logHandle.Report(new LogEventInfo("Saving puzzle to \"" + PuzzleHandle.PuzzleXMLOutputPath + "\""));
                    PuzzleHandle.PuzzleXMLOutputPath = saveFileDialog.FileName;
                    await Task.Run(() => { PuzzleHandle.Save(PuzzleHandle.PuzzleXMLOutputPath, PluginFactory.GetGeneralSettingsPlugin().CompressPuzzleOutputFile); });

                    PuzzleSavingState = PuzzleSavingStates.SAVED;
                    logHandle.Report(new LogEventInfo("Saving puzzle ready."));
                    CommandManager.InvalidateRequerySuggested();
                }
            }
            catch (Exception ex)
            {
                PuzzleSavingState = PuzzleSavingStates.ERROR;
                logHandle.Report(new LogEventError("Error while saving: " + ex.Message));
            }
        }
        /// <summary>
        /// This comparison iterates over every point in "edge1" contour, finds the closest point in "edge2" contour and sums up those distances.
        /// The end result is the sum divided by length of the both contours.
        /// It also takes the difference of the distances of the contour endpoints into account.
        /// </summary>
        /// <param name="edge1">First Edge to compare to edge2</param>
        /// <param name="edge2">Second Edge to compare to edge1</param>
        /// <returns>Similarity factor of edges. Special values are:
        /// 300000000: Same piece
        /// 200000000: At least one edge is a line edge
        /// 150000000: The pieces have the same edge type
        /// 100000000: One of the contour sizes is 0</returns>
        public override double CompareEdges(Edge edge1, Edge edge2)
        {
            try
            {
                //Return large numbers if we know that these shapes simply wont match...
                if (edge1.PieceID == edge2.PieceID)
                {
                    return(300000000);
                }
                if (edge1.EdgeType == EdgeTypes.LINE || edge2.EdgeType == EdgeTypes.LINE)
                {
                    return(200000000);
                }
                if (edge1.EdgeType == edge2.EdgeType)
                {
                    return(150000000);
                }
                if (edge1.NormalizedContour.Size == 0 || edge2.ReverseNormalizedContour.Size == 0)
                {
                    return(100000000);
                }
                double cost         = 0;
                double total_length = CvInvoke.ArcLength(edge1.NormalizedContour, false) + CvInvoke.ArcLength(edge2.ReverseNormalizedContour, false);

                int windowSizePoints = (int)(Math.Max(edge1.NormalizedContour.Size, edge2.ReverseNormalizedContour.Size) * EdgeCompareWindowSizePercent);
                if (windowSizePoints < 1)
                {
                    windowSizePoints = 1;
                }

                double distEndpointsContour1    = Utils.Distance(edge1.NormalizedContour[0], edge1.NormalizedContour[edge1.NormalizedContour.Size - 1]);
                double distEndpointsContour2    = Utils.Distance(edge2.ReverseNormalizedContour[0], edge2.ReverseNormalizedContour[edge2.ReverseNormalizedContour.Size - 1]);
                double distEndpointContoursDiff = Math.Abs(distEndpointsContour1 - distEndpointsContour2);
                if (distEndpointContoursDiff <= EdgeCompareEndpointDiffIgnoreThreshold)
                {
                    distEndpointContoursDiff = 0;
                }

                for (int i = 0; i < Math.Min(edge1.NormalizedContour.Size, edge2.ReverseNormalizedContour.Size); i++)
                {
                    double min = 10000000;
                    for (int j = Math.Max(0, i - windowSizePoints); j < Math.Min(edge2.ReverseNormalizedContour.Size, i + windowSizePoints); j++)
                    {
                        if (PluginFactory.CancelToken.IsCancellationRequested)
                        {
                            PluginFactory.CancelToken.ThrowIfCancellationRequested();
                        }

                        double dist = Utils.Distance(edge1.NormalizedContour[i], edge2.ReverseNormalizedContour[j]);
                        if (dist < min)
                        {
                            min = dist;
                        }
                    }
                    cost += min;
                }
                double matchResult = cost / total_length;

                if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                {
                    Image <Rgb, byte> contourOverlay = new Image <Rgb, byte>(500, 500);
                    VectorOfPoint     contour1       = Utils.TranslateContour(edge1.NormalizedContour, 100, 0);
                    VectorOfPoint     contour2       = Utils.TranslateContour(edge2.ReverseNormalizedContour, 100, 0);
                    CvInvoke.DrawContours(contourOverlay, new VectorOfVectorOfPoint(contour1), -1, new MCvScalar(0, 255, 0), 2);
                    CvInvoke.DrawContours(contourOverlay, new VectorOfVectorOfPoint(contour2), -1, new MCvScalar(0, 0, 255), 2);

                    PluginFactory.LogHandle.Report(new LogEventImage("Compare " + edge1.PieceID + "_Edge" + edge1.EdgeNumber + " <-->" + edge2.PieceID + "_Edge" + edge2.EdgeNumber + " ==> distEndpoint = " + distEndpointContoursDiff.ToString() + ", MatchResult = " + matchResult, contourOverlay.Bitmap));
                }

                return(distEndpointContoursDiff + matchResult);
            }
            catch (OperationCanceledException)
            {
                throw;
            }
        }
예제 #7
0
        //##############################################################################################################################################################################################

        /// <summary>
        /// Calculate the edge type (LINE, BULB, HOLE)
        /// </summary>
        private void classify()
        {
            try
            {
                EdgeType = EdgeTypes.UNKNOWN;
                if (NormalizedContour.Size <= 1)
                {
                    return;
                }

                //See if it is an outer edge comparing the distance between beginning and end with the arc length.
                double contour_length = CvInvoke.ArcLength(NormalizedContour, false);

                double begin_end_distance = Utils.Distance(NormalizedContour.ToArray().First(), NormalizedContour.ToArray().Last());
                if (contour_length < begin_end_distance * 1.3)
                {
                    EdgeType = EdgeTypes.LINE;
                }

                if (EdgeType == EdgeTypes.UNKNOWN)
                {
                    //Find the minimum or maximum value for x in the normalized contour and base the classification on that
                    int minx = 100000000;
                    int maxx = -100000000;
                    for (int i = 0; i < NormalizedContour.Size; i++)
                    {
                        if (minx > NormalizedContour[i].X)
                        {
                            minx = (int)NormalizedContour[i].X;
                        }
                        if (maxx < NormalizedContour[i].X)
                        {
                            maxx = (int)NormalizedContour[i].X;
                        }
                    }

                    if (Math.Abs(minx) > Math.Abs(maxx))
                    {
                        EdgeType = EdgeTypes.BULB;
                    }
                    else
                    {
                        EdgeType = EdgeTypes.HOLE;
                    }
                }

                if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                {
                    Bitmap contourImg = PieceImgColor.Bmp;
                    for (int i = 0; i < contour.Size; i++)
                    {
                        Graphics g = Graphics.FromImage(contourImg);
                        g.DrawEllipse(new Pen(Color.Red), new RectangleF(PointF.Subtract(contour[i], new Size(1, 1)), new SizeF(2, 2)));
                    }
                    _logHandle.Report(new LogEventImage(PieceID + " Edge " + EdgeNumber.ToString() + " " + EdgeType.ToString(), contourImg));
                    ContourImg.Bmp = contourImg;
                    contourImg.Dispose();
                }
            }
            catch (Exception ex)
            {
                _logHandle.Report(new LogBox.LogEvents.LogEventError(ex.Message));
            }
        }
예제 #8
0
        //**********************************************************************************************************************************************************************************************

        public async Task Solve()
        {
            await Task.Run(() =>
            {
                try
                {
                    PuzzleSolutionImages.Clear();

                    compareAllEdges();

                    CurrentSolverState = PuzzleSolverState.SOLVE_PUZZLE;
                    CurrentSolverStepPercentageFinished = 0;
                    PuzzleDisjointSet p = new PuzzleDisjointSet(Pieces.Count);
                    p.JoinValidation    = JoinValidationFunction;

                    _logHandle.Report(new LogEventInfo("Join Pieces"));

                    for (int i = 0; i < matches.Count; i++)
                    {
                        if (_cancelToken.IsCancellationRequested)
                        {
                            _cancelToken.ThrowIfCancellationRequested();
                        }

                        CurrentSolverStepPercentageFinished = (i / (double)matches.Count) * 100;
                        if (p.InOneSet())
                        {
                            break;
                        }

                        int p1 = matches[i].PieceIndex1;
                        int e1 = matches[i].EdgeIndex1;
                        int p2 = matches[i].PieceIndex2;
                        int e2 = matches[i].EdgeIndex2;

                        p.JoinSets(p1, p2, e1, e2);
                    }

                    _logHandle.Report(new LogEventInfo("Possible solution found (" + p.SetCount.ToString() + " solutions)"));
                    CurrentSolverStepPercentageFinished = 100;
                    CurrentSolverState        = PuzzleSolverState.SOLVED;
                    CurrentSolutionNumber     = 0;
                    CurrentSolutionPieceIndex = 0;
                    int setNo = 0;
                    foreach (Forest jointSet in p.GetJointSets())
                    {
                        Matrix <int> solution           = jointSet.locations;
                        Matrix <int> solution_rotations = jointSet.rotations;

                        for (int i = 0; i < solution.Size.Width; i++)
                        {
                            for (int j = 0; j < solution.Size.Height; j++)
                            {
                                int piece_number = solution[j, i];
                                if (piece_number == -1)
                                {
                                    continue;
                                }
                                Pieces[piece_number].Rotate(4 - solution_rotations[j, i]);

                                Pieces[piece_number].SolutionRotation = solution_rotations[j, i] * 90;
                                Pieces[piece_number].SolutionLocation = new Point(i, j);
                                Pieces[piece_number].SolutionID       = setNo;
                            }
                        }
                        Solutions.Add(solution);
                        SolutionsRotations.Add(solution_rotations);

                        // Get the enabled Plugins for solution image generation
                        List <PluginGroupGenerateSolutionImage> pluginsGenerateSolutionImage = PluginFactory.GetEnabledPluginsOfGroupType <PluginGroupGenerateSolutionImage>();

                        foreach (PluginGroupGenerateSolutionImage plugin in pluginsGenerateSolutionImage)
                        {
                            PluginNameAttribute nameAttribute = plugin.GetType().GetCustomAttributes(false).Where(a => a.GetType() == typeof(PluginNameAttribute)).FirstOrDefault() as PluginNameAttribute;

                            Bitmap solutionImg = plugin.GenerateSolutionImage(solution, setNo, Pieces.ToList());
                            PuzzleSolutionImages.Add(new ImageDescribedLight("Solution #" + setNo.ToString() + " (" + nameAttribute.Name + ")", PuzzlePiecesFolderPath + @"\Results\Solutions\Solution#" + setNo.ToString() + ".png", solutionImg));

                            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                            {
                                _logHandle.Report(new LogEventImage("Solution #" + setNo.ToString() + " (" + nameAttribute.Name + ")", solutionImg));
                            }
                            solutionImg.Dispose();
                        }
                        setNo++;
                    }
                }
                catch (OperationCanceledException)
                {
                    _logHandle.Report(new LogEventWarning("The operation was canceled. Step: " + CurrentSolverState.ToString()));
                    CurrentSolverState = PuzzleSolverState.UNSOLVED;
                }
                catch (Exception ex)
                {
                    _logHandle.Report(new LogEventError("The following error occured in step " + CurrentSolverState.ToString() + ":\n" + ex.Message));
                    CurrentSolverState = PuzzleSolverState.ERROR;
                    CurrentSolverStepPercentageFinished = 100;
                }
            }, _cancelToken);
        }
예제 #9
0
        //**********************************************************************************************************************************************************************************************

        /// <summary>
        /// Fill the list with all match scores for all piece edge combinations
        /// </summary>
        private void compareAllEdges()
        {
            try
            {
                CurrentSolverState = PuzzleSolverState.COMPARE_EDGES;
                CurrentSolverStepPercentageFinished = 0;
                _logHandle.Report(new LogEventInfo("Comparing all edges"));

                matches.Clear();
                int no_edges    = (int)Pieces.Count * 4;
                int no_compares = (no_edges * (no_edges + 1)) / 2;      // Number of loop runs of the following nested loops
                int loop_count  = 0;

                ConcurrentDictionary <int, MatchScore> matchesDict = new ConcurrentDictionary <int, MatchScore>();        // Using ConcurrentDictionary because ConcurrentList doesn't exist

                // Get the (first) enabled Plugin for edge comparison
                PluginGroupCompareEdges pluginCompareEdges = PluginFactory.GetEnabledPluginsOfGroupType <PluginGroupCompareEdges>().FirstOrDefault();

                ParallelOptions parallelOptions = new ParallelOptions
                {
                    CancellationToken      = _cancelToken,
                    MaxDegreeOfParallelism = (PluginFactory.GetGeneralSettingsPlugin().UseParallelLoops ? Environment.ProcessorCount : 1)
                };
                Parallel.For(0, no_edges, parallelOptions, (i) =>
                {
                    Parallel.For(i, no_edges, parallelOptions, (j) =>
                    {
                        if (_cancelToken.IsCancellationRequested)
                        {
                            _cancelToken.ThrowIfCancellationRequested();
                        }

                        loop_count++;
                        if (loop_count > no_compares)
                        {
                            loop_count = no_compares;
                        }
                        CurrentSolverStepPercentageFinished = (loop_count / (double)no_compares) * 100;

                        MatchScore matchScore = new MatchScore
                        {
                            PieceIndex1 = i / 4,
                            PieceIndex2 = j / 4,
                            EdgeIndex1  = i % 4,
                            EdgeIndex2  = j % 4
                        };

                        Edge edge1 = Pieces[matchScore.PieceIndex1].Edges[matchScore.EdgeIndex1];
                        Edge edge2 = Pieces[matchScore.PieceIndex2].Edges[matchScore.EdgeIndex2];
                        if (edge1 == null || edge2 == null)
                        {
                            matchScore.score = 400000000;
                        }
                        else
                        {
                            matchScore.score = pluginCompareEdges.CompareEdges(edge1, edge2);
                        }

                        if (matchScore.score <= PluginFactory.GetGeneralSettingsPlugin().PuzzleSolverKeepMatchesThreshold)  // Keep only the best matches (all scores above or equal 100000000 mean that the edges won't match)
                        {
                            matchesDict.TryAdd(matchesDict.Count, matchScore);
                        }
                    });
                });

                matches = matchesDict.Select(m => m.Value).ToList();

                matches.Sort(new MatchScoreComparer(ScoreOrders.LOWEST_FIRST)); // Sort the matches to get the best scores first. The puzzle is solved by the order of the MatchScores

                if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                {
                    foreach (MatchScore matchScore in matches)
                    {
                        Bitmap contourImg1 = Pieces[matchScore.PieceIndex1].Edges[matchScore.EdgeIndex1].ContourImg.Bmp;
                        Bitmap contourImg2 = Pieces[matchScore.PieceIndex2].Edges[matchScore.EdgeIndex2].ContourImg.Bmp;
                        _logHandle.Report(new LogEventImage("MatchScore " + Pieces[matchScore.PieceIndex1].PieceID + "_Edge" + (matchScore.EdgeIndex1).ToString() + " <-->" + Pieces[matchScore.PieceIndex2].PieceID + "_Edge" + (matchScore.EdgeIndex2).ToString() + " = " + matchScore.score.ToString(), Utils.Combine2ImagesHorizontal(contourImg1, contourImg2, 20)));

                        contourImg1.Dispose();
                        contourImg2.Dispose();
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }
예제 #10
0
        //##############################################################################################################################################################################################

        /// <summary>
        /// Extract all pieces from the source image.
        /// </summary>
        private void extract_pieces()
        {
            try
            {
                CurrentSolverState = PuzzleSolverState.INIT_PIECES;
                Piece.NextPieceID  = 0;
                CurrentSolverStepPercentageFinished = 0;
                _logHandle.Report(new LogEventInfo("Extracting Pieces"));
                NumberPuzzlePieces = 0;

                Pieces.Clear();
                InputImages.Clear();

                List <string> imageExtensions = new List <string>()
                {
                    ".jpg", ".png", ".bmp", ".tiff"
                };
                FileAttributes  attr           = File.GetAttributes(PuzzlePiecesFolderPath);
                List <FileInfo> imageFilesInfo = new List <FileInfo>();
                if (attr.HasFlag(FileAttributes.Directory))      //detect whether its a directory or file
                {
                    DirectoryInfo folderInfo = new DirectoryInfo(PuzzlePiecesFolderPath);
                    imageFilesInfo = folderInfo.GetFiles().ToList();
                }
                else
                {
                    FileInfo fileInfo = new FileInfo(PuzzlePiecesFolderPath);
                    imageFilesInfo.Add(fileInfo);
                }

                imageFilesInfo = imageFilesInfo.Where(f => imageExtensions.Contains(f.Extension)).ToList();

                int loopCount = 0;

                ParallelOptions parallelOptions = new ParallelOptions
                {
                    CancellationToken      = _cancelToken,
                    MaxDegreeOfParallelism = (PluginFactory.GetGeneralSettingsPlugin().UseParallelLoops ? Environment.ProcessorCount : 1)
                };
                //For each input image
                Parallel.For(0, imageFilesInfo.Count, parallelOptions, (i) =>
                {
                    using (Image <Rgba, byte> sourceImg = new Image <Rgba, byte>(imageFilesInfo[i].FullName)) //.LimitImageSize(1000, 1000))
                    {
                        CvInvoke.MedianBlur(sourceImg, sourceImg, 5);

                        // Get the (first) enabled Plugin for input image mask generation
                        PluginGroupInputImageMask pluginInputImageMask = PluginFactory.GetEnabledPluginsOfGroupType <PluginGroupInputImageMask>().FirstOrDefault();

                        using (Image <Gray, byte> mask = pluginInputImageMask.GetMask(sourceImg))
                        {
                            _logHandle.Report(new LogEventInfo("Extracting Pieces from source image " + i.ToString()));
                            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                            {
                                _logHandle.Report(new LogEventImage("Source image " + i.ToString(), sourceImg.Bitmap));
                                _logHandle.Report(new LogEventImage("Mask " + i.ToString(), mask.Bitmap));
                            }

                            CvBlobDetector blobDetector = new CvBlobDetector();                 // Find all blobs in the mask image, extract them and add them to the list of pieces
                            CvBlobs blobs = new CvBlobs();
                            blobDetector.Detect(mask, blobs);

                            foreach (CvBlob blob in blobs.Values.Where(b => b.BoundingBox.Width >= PluginFactory.GetGeneralSettingsPlugin().PuzzleMinPieceSize&& b.BoundingBox.Height >= PluginFactory.GetGeneralSettingsPlugin().PuzzleMinPieceSize))
                            {
                                if (_cancelToken.IsCancellationRequested)
                                {
                                    _cancelToken.ThrowIfCancellationRequested();
                                }

                                Rectangle roi = blob.BoundingBox;

                                Image <Rgba, byte> pieceSourceImg;
                                Image <Gray, byte> pieceMask;

                                try
                                {
                                    if (sourceImg.Height > roi.Height + 4 && sourceImg.Width > roi.Width + 4)
                                    {
                                        roi.Inflate(2, 2);
                                    }
                                    pieceSourceImg = sourceImg.Copy(roi);
                                    pieceMask      = mask.Copy(roi);
                                }
                                catch (Exception)
                                {
                                    roi            = blob.BoundingBox;
                                    pieceSourceImg = sourceImg.Copy(roi);
                                    pieceMask      = mask.Copy(roi);
                                }

                                // Mask out background of piece
                                Image <Rgba, byte> pieceSourceImageForeground = new Image <Rgba, byte>(pieceSourceImg.Size);
                                CvInvoke.BitwiseOr(pieceSourceImg, pieceSourceImg, pieceSourceImageForeground, pieceMask);

                                Image <Gray, byte> pieceMaskInverted = pieceMask.Copy(pieceMask);
                                pieceMaskInverted._Not();
                                Image <Rgba, byte> background = new Image <Rgba, byte>(pieceSourceImg.Size);
                                background.SetValue(new Rgba(255, 255, 255, 0));
                                Image <Rgba, byte> pieceSourceImageBackground = new Image <Rgba, byte>(pieceSourceImg.Size);
                                CvInvoke.BitwiseOr(background, background, pieceSourceImageBackground, pieceMaskInverted);

                                Image <Rgba, byte> pieceSourceImgMasked = new Image <Rgba, byte>(pieceSourceImg.Size);
                                CvInvoke.BitwiseOr(pieceSourceImageForeground, pieceSourceImageBackground, pieceSourceImgMasked);

                                Piece p = new Piece(pieceSourceImgMasked, pieceMask, imageFilesInfo[i].FullName, roi.Location, _logHandle, _cancelToken);
                                lock (_piecesLock) { Pieces.Add(p); }

                                sourceImg.Draw(roi, new Rgba(255, 0, 0, 1), 2);
                                int baseLine  = 0;
                                Size textSize = CvInvoke.GetTextSize(p.PieceID.Replace("Piece", ""), FontFace.HersheyDuplex, 3, 2, ref baseLine);
                                CvInvoke.PutText(sourceImg, p.PieceID.Replace("Piece", ""), Point.Add(roi.Location, new Size(0, textSize.Height + 10)), FontFace.HersheyDuplex, 3, new MCvScalar(255, 0, 0), 2);

                                NumberPuzzlePieces++;

                                pieceSourceImg.Dispose();
                                pieceMask.Dispose();
                                pieceSourceImageForeground.Dispose();
                                pieceMaskInverted.Dispose();
                                background.Dispose();
                                pieceSourceImageBackground.Dispose();
                                pieceSourceImgMasked.Dispose();

                                GC.Collect();
                            }

                            Interlocked.Add(ref loopCount, 1);
                            CurrentSolverStepPercentageFinished = (loopCount / (double)imageFilesInfo.Count) * 100;

                            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                            {
                                _logHandle.Report(new LogEventImage("Source Img " + i.ToString() + " Pieces", sourceImg.Bitmap));
                            }
                            InputImages.Add(new ImageDescribedLight(Path.GetFileName(imageFilesInfo[i].FullName), PuzzlePiecesFolderPath + @"\Results\InputImagesMarked\" + Path.GetFileName(imageFilesInfo[i].FullName), sourceImg.Bitmap)); //sourceImg.LimitImageSize(1000, 1000).Bitmap));
                            blobs.Dispose();
                            blobDetector.Dispose();
                            GC.Collect();
                        }
                    }

                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                    GC.Collect();
                });

                Pieces.Sort(p => ((Piece)p).PieceIndex, null);
            }
            catch (OperationCanceledException)
            {
                _logHandle.Report(new LogEventWarning("The operation was canceled. Step: " + CurrentSolverState.ToString()));
                CurrentSolverState = PuzzleSolverState.UNSOLVED;
            }
            catch (Exception ex)
            {
                _logHandle.Report(new LogEventError("The following error occured in step " + CurrentSolverState.ToString() + ":\n" + ex.Message));
                CurrentSolverState = PuzzleSolverState.ERROR;
                CurrentSolverStepPercentageFinished = 100;
            }
        }
예제 #11
0
        /// <summary>
        /// Find the 4 strongest corners using the GFTT algorithm.
        /// </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://docs.opencv.org/doc/tutorials/features2d/trackingmotion/corner_subpixeles/corner_subpixeles.html
        public override List <Point> FindCorners(string pieceID, Bitmap pieceImgBw, Bitmap pieceImgColor)
        {
            PluginFactory.LogHandle.Report(new LogEventInfo(pieceID + " Finding corners with GFTT algorithm"));

            double minDistance = PluginFactory.GetGeneralSettingsPlugin().PuzzleMinPieceSize;    //How close can 2 corners be?

            double min = 0;
            double max = 1;
            bool   found_all_corners = false;

            Image <Gray, byte> bw_clone = new Image <Gray, byte>(pieceImgBw);

            List <Point> corners = new List <Point>();

            //Binary search, altering quality until exactly 4 corners are found. Usually done in 1 or 2 iterations
            while (0 < MaxIterations--)
            {
                if (PluginFactory.CancelToken.IsCancellationRequested)
                {
                    PluginFactory.CancelToken.ThrowIfCancellationRequested();
                }

                double qualityLevel = (min + max) / 2;

                VectorOfKeyPoint keyPoints       = new VectorOfKeyPoint();
                GFTTDetector     featureDetector = new GFTTDetector(100, qualityLevel, minDistance, BlockSize, true, HarrisDetectorParameterK);

                featureDetector.DetectRaw(bw_clone, keyPoints);

                if (keyPoints.Size > 4)
                {
                    min = qualityLevel;     //Found too many corners increase quality
                }
                else if (keyPoints.Size < 4)
                {
                    max = qualityLevel;
                }
                else
                {
                    for (int i = 0; i < keyPoints.Size; i++)
                    {
                        corners.Add(Point.Round(keyPoints[i].Point));
                    }

                    found_all_corners = true;       //found all corners
                    break;
                }
            }

            //Find the sub-pixel locations of the corners.
            //Size winSize = new Size(blockSize, blockSize);
            //Size zeroZone = new Size(-1, -1);
            //MCvTermCriteria criteria = new MCvTermCriteria(40, 0.001);

            // Calculate the refined corner locations
            //CvInvoke.CornerSubPix(bw_clone, corners, winSize, zeroZone, criteria);

            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                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();
            }

            if (!found_all_corners)
            {
                PluginFactory.LogHandle.Report(new LogEventError(pieceID + " Failed to find correct number of corners. " + corners.Count + " found."));
            }
            return(corners);
        }
예제 #12
0
        //**********************************************************************************************************************************************************************************************
        //**********************************************************************************************************************************************************************************************
        //**********************************************************************************************************************************************************************************************

        /// <summary>
        /// Extract the contour.
        /// TODO: probably should have this passed in from the puzzle, since it already does this. It was done this way because the contours don't correspond to the correct pixel locations in this cropped version of the image.
        /// </summary>
        private void extract_edges()
        {
            Bitmap bwImg = PieceImgBw.Bmp;
            _logHandle.Report(new LogEventInfo(PieceID + " Extracting edges"));

            if (corners.Size != 4) { return; }

            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
            CvInvoke.FindContours(new Image<Gray, byte>(bwImg), contours, null, RetrType.List, ChainApproxMethod.ChainApproxNone);
            bwImg.Dispose();
            if (contours.Size == 0)
            {
                _logHandle.Report(new LogEventError(PieceID + " No contours found."));
                return;
            }

            int indexLargestContour = 0;                // Find the largest contour
            double largestContourArea = 0;
            for(int i = 0; i < contours.Size; i++)
            {
                double contourAreaTmp = CvInvoke.ContourArea(contours[i]);
                if(contourAreaTmp > largestContourArea) { largestContourArea = contourAreaTmp; indexLargestContour = i; }
            }

            VectorOfPoint contour = contours[indexLargestContour];

            VectorOfPoint new_corners = new VectorOfPoint();
            for (int i = 0; i < corners.Size; i++)      //out of all of the found corners, find the closest points in the contour, these will become the endpoints of the edges
            {
                double best = 10000000000;
                Point closest_point = contour[0];
                for (int j = 0; j < contour.Size; j++)
                {
                    double d = Utils.Distance(corners[i], contour[j]);
                    if (d < best)
                    {
                        best = d;
                        closest_point = contour[j];
                    }
                }
                new_corners.Push(closest_point);
            }
            corners = new_corners;

            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                Bitmap colorImg = PieceImgColor.Bmp;
                Image<Rgb, byte> edge_img = new Image<Rgb, byte>(colorImg);
                for (int i = 0; i < corners.Size; i++) { CvInvoke.Circle(edge_img, Point.Round(corners[i]), 2, new MCvScalar(255, 0, 0), 1); }
                _logHandle.Report(new LogEventImage(PieceID + " New corners", edge_img.Bitmap));
                colorImg.Dispose();
                edge_img.Dispose();
            }

            List<int> sections = find_all_in(contour, corners);

            //Make corners go in the correct order
            Point[] new_corners2 = new Point[4];
            int cornerIndexUpperLeft = -1;
            double cornerDistUpperLeft = double.MaxValue;
            for (int i = 0; i < 4; i++)
            {
                new_corners2[i] = contour[sections[i]];
                double cornerDist = Utils.DistanceToOrigin(contour[sections[i]]);
                if(cornerDist < cornerDistUpperLeft)
                {
                    cornerDistUpperLeft = cornerDist;
                    cornerIndexUpperLeft = i;
                }
            }
            new_corners2.Rotate(-cornerIndexUpperLeft);
            corners.Push(new_corners2);
            sections.Rotate(-cornerIndexUpperLeft);

            Edges[0] = new Edge(PieceID, 0, PieceImgColor, contour.GetSubsetOfVector(sections[0], sections[1]), _logHandle, _cancelToken);
            Edges[1] = new Edge(PieceID, 1, PieceImgColor, contour.GetSubsetOfVector(sections[1], sections[2]), _logHandle, _cancelToken);
            Edges[2] = new Edge(PieceID, 2, PieceImgColor, contour.GetSubsetOfVector(sections[2], sections[3]), _logHandle, _cancelToken);
            Edges[3] = new Edge(PieceID, 3, PieceImgColor, contour.GetSubsetOfVector(sections[3], sections[0]), _logHandle, _cancelToken);
        }
        /// <summary>
        /// Calculate a mask for the pieces. The function calculates a histogram to find the piece background color.
        /// Everything within a specific HSV range around the piece background color is regarded as foreground. The rest is regarded as background.
        /// </summary>
        /// <param name="inputImg">Color input image</param>
        /// <returns>Mask image</returns>
        /// see: https://docs.opencv.org/2.4/modules/imgproc/doc/histograms.html?highlight=calchist
        public override Image <Gray, byte> GetMask(Image <Rgba, byte> inputImg)
        {
            Image <Gray, byte> mask;

            using (Image <Hsv, byte> hsvSourceImg = inputImg.Convert <Hsv, byte>())       //Convert input image to HSV color space
            {
                Mat hsvImgMat = new Mat();
                hsvSourceImg.Mat.ConvertTo(hsvImgMat, DepthType.Cv32F);
                VectorOfMat vm = new VectorOfMat(hsvImgMat);

                // Calculate histograms for each channel of the HSV image (H, S, V)
                Mat histOutH = new Mat(), histOutS = new Mat(), histOutV = new Mat();
                int hbins = 32, sbins = 32, vbins = 32;
                CvInvoke.CalcHist(vm, new int[] { 0 }, new Mat(), histOutH, new int[] { hbins }, new float[] { 0, 179 }, false);
                CvInvoke.CalcHist(vm, new int[] { 1 }, new Mat(), histOutS, new int[] { sbins }, new float[] { 0, 255 }, false);
                CvInvoke.CalcHist(vm, new int[] { 2 }, new Mat(), histOutV, new int[] { vbins }, new float[] { 0, 255 }, false);

                hsvImgMat.Dispose();
                vm.Dispose();

                // Draw the histograms for debugging purposes
                if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                {
                    PluginFactory.LogHandle?.Report(new LogEventImage("Hist H", Utils.DrawHist(histOutH, hbins, 30, 1024, new MCvScalar(255, 0, 0)).Bitmap));
                    PluginFactory.LogHandle?.Report(new LogEventImage("Hist S", Utils.DrawHist(histOutS, sbins, 30, 1024, new MCvScalar(0, 255, 0)).Bitmap));
                    PluginFactory.LogHandle?.Report(new LogEventImage("Hist V", Utils.DrawHist(histOutV, vbins, 30, 1024, new MCvScalar(0, 0, 255)).Bitmap));
                }

                //#warning Use border color
                //int borderHeight = 10;
                //Image<Hsv, byte> borderImg = hsvSourceImg.Copy(new Rectangle(0, hsvSourceImg.Height - borderHeight, hsvSourceImg.Width, borderHeight));
                //MCvScalar meanBorderColorScalar = CvInvoke.Mean(borderImg);
                //Hsv meanBorderColor = new Hsv(meanBorderColorScalar.V0, meanBorderColorScalar.V1, meanBorderColorScalar.V2);
                //if (PuzzleSolverParameters.Instance.SolverShowDebugResults)
                //{
                //    Image<Hsv, byte> borderColorImg = new Image<Hsv, byte>(12, 12);
                //    borderColorImg.SetValue(meanBorderColor);
                //    _logHandle.Report(new LogBox.LogEventImage("HSV Border Color (" + meanBorderColor.Hue + " ; " + meanBorderColor.Satuation + "; " + meanBorderColor.Value + ")", borderColorImg.Bitmap));
                //}


                // Find the peaks in the histograms and use them as piece background color. Black and white areas are ignored.
                Hsv pieceBackgroundColor = new Hsv
                {
                    Hue       = Utils.HighestBinValInRange(histOutH, MainHueSegment - HueDiffHist, MainHueSegment + HueDiffHist, 179), //25, 179, 179);
                    Satuation = Utils.HighestBinValInRange(histOutS, 50, 205, 255),                                                    //50, 255, 255);
                    Value     = Utils.HighestBinValInRange(histOutV, 75, 205, 255)                                                     //75, 255, 255);
                };

                histOutH.Dispose();
                histOutS.Dispose();
                histOutV.Dispose();

                // Show the found piece background color
                if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
                {
                    Image <Hsv, byte> pieceBgColorImg     = new Image <Hsv, byte>(4, 12);
                    Image <Hsv, byte> lowPieceBgColorImg  = new Image <Hsv, byte>(4, 12);
                    Image <Hsv, byte> highPieceBgColorImg = new Image <Hsv, byte>(4, 12);
                    pieceBgColorImg.SetValue(pieceBackgroundColor);
                    lowPieceBgColorImg.SetValue(new Hsv(pieceBackgroundColor.Hue - HueDiff, pieceBackgroundColor.Satuation - SaturationDiff, pieceBackgroundColor.Value - ValueDiff));
                    highPieceBgColorImg.SetValue(new Hsv(pieceBackgroundColor.Hue + HueDiff, pieceBackgroundColor.Satuation + SaturationDiff, pieceBackgroundColor.Value + ValueDiff));

                    PluginFactory.LogHandle?.Report(new LogEventImage("HSV Piece Bg Color (" + pieceBackgroundColor.Hue + " ; " + pieceBackgroundColor.Satuation + "; " + pieceBackgroundColor.Value + ")", Utils.Combine2ImagesHorizontal(Utils.Combine2ImagesHorizontal(lowPieceBgColorImg.Convert <Rgb, byte>(), pieceBgColorImg.Convert <Rgb, byte>(), 0), highPieceBgColorImg.Convert <Rgb, byte>(), 0).Bitmap));

                    pieceBgColorImg.Dispose();
                    lowPieceBgColorImg.Dispose();
                    highPieceBgColorImg.Dispose();
                }

                // do HSV segmentation and keep only the meanColor areas with some hysteresis as pieces
                mask = hsvSourceImg.InRange(new Hsv(pieceBackgroundColor.Hue - HueDiff, pieceBackgroundColor.Satuation - SaturationDiff, pieceBackgroundColor.Value - ValueDiff), new Hsv(pieceBackgroundColor.Hue + HueDiff, pieceBackgroundColor.Satuation + SaturationDiff, pieceBackgroundColor.Value + ValueDiff));

                // close small black gaps with morphological closing operation
                Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(5, 5), new Point(-1, -1));
                CvInvoke.MorphologyEx(mask, mask, MorphOp.Close, kernel, new Point(-1, -1), 5, BorderType.Default, new MCvScalar(0));
            }
            return(mask);
        }
        /// <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);
        }
예제 #15
0
        /// <summary>
        /// Get the piece corners by finding the biggest rectangle 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>
        public override List <Point> FindCorners(string pieceID, Bitmap pieceImgBw, Bitmap pieceImgColor)
        {
            PluginFactory.LogHandle.Report(new LogEventInfo(pieceID + " Finding corners by finding the maximum rectangle within candidate points"));

            List <Point> corners = new List <Point>();

            // Find all dominant corner points using the GFTTDetector (this uses the Harris corner detector)
            GFTTDetector detector = new GFTTDetector(500, 0.01, 5, 2, true, 0.04);

            MKeyPoint[]  keyPoints       = detector.Detect(new Image <Gray, byte>(pieceImgBw));
            List <Point> possibleCorners = keyPoints.Select(k => Point.Round(k.Point)).ToList();

            if (possibleCorners.Count > 0)
            {
                // Sort the dominant corners by the distance to upper left corner of the bounding rectangle (0, 0) and keep only the corners that are near enough to this point
                List <Point> possibleCornersSortedUpperLeft = new List <Point>(possibleCorners);
                possibleCornersSortedUpperLeft.Sort(new DistanceToPointComparer(new Point(0, 0), DistanceOrders.NEAREST_FIRST));
                double minCornerDistUpperLeft = Utils.Distance(possibleCornersSortedUpperLeft[0], new PointF(0, 0));
                possibleCornersSortedUpperLeft = possibleCornersSortedUpperLeft.Where(c => Utils.Distance(c, new PointF(0, 0)) < minCornerDistUpperLeft * PieceFindCornersMaxCornerDistRatio).ToList();

                // Sort the dominant corners by the distance to upper right corner of the bounding rectangle (ImageWidth, 0) and keep only the corners that are near enough to this point
                List <Point> possibleCornersSortedUpperRight = new List <Point>(possibleCorners);
                possibleCornersSortedUpperRight.Sort(new DistanceToPointComparer(new Point(pieceImgBw.Width, 0), DistanceOrders.NEAREST_FIRST));
                double minCornerDistUpperRight = Utils.Distance(possibleCornersSortedUpperRight[0], new PointF(pieceImgBw.Width, 0));
                possibleCornersSortedUpperRight = possibleCornersSortedUpperRight.Where(c => Utils.Distance(c, new PointF(pieceImgBw.Width, 0)) < minCornerDistUpperRight * PieceFindCornersMaxCornerDistRatio).ToList();

                // Sort the dominant corners by the distance to lower right corner of the bounding rectangle (ImageWidth, ImageHeight) and keep only the corners that are near enough to this point
                List <Point> possibleCornersSortedLowerRight = new List <Point>(possibleCorners);
                possibleCornersSortedLowerRight.Sort(new DistanceToPointComparer(new Point(pieceImgBw.Width, pieceImgBw.Height), DistanceOrders.NEAREST_FIRST));
                double minCornerDistLowerRight = Utils.Distance(possibleCornersSortedLowerRight[0], new PointF(pieceImgBw.Width, pieceImgBw.Height));
                possibleCornersSortedLowerRight = possibleCornersSortedLowerRight.Where(c => Utils.Distance(c, new PointF(pieceImgBw.Width, pieceImgBw.Height)) < minCornerDistLowerRight * PieceFindCornersMaxCornerDistRatio).ToList();

                // Sort the dominant corners by the distance to lower left corner of the bounding rectangle (0, ImageHeight) and keep only the corners that are near enough to this point
                List <Point> possibleCornersSortedLowerLeft = new List <Point>(possibleCorners);
                possibleCornersSortedLowerLeft.Sort(new DistanceToPointComparer(new Point(0, pieceImgBw.Height), DistanceOrders.NEAREST_FIRST));
                double minCornerDistLowerLeft = Utils.Distance(possibleCornersSortedLowerLeft[0], new PointF(0, pieceImgBw.Height));
                possibleCornersSortedLowerLeft = possibleCornersSortedLowerLeft.Where(c => Utils.Distance(c, new PointF(0, pieceImgBw.Height)) < minCornerDistLowerLeft * PieceFindCornersMaxCornerDistRatio).ToList();

                // Combine all possibleCorners from the four lists and discard all combination with too bad angle differences
                List <FindCornerRectangleScore> scores = new List <FindCornerRectangleScore>();
                for (int indexUpperLeft = 0; indexUpperLeft < possibleCornersSortedUpperLeft.Count; indexUpperLeft++)
                {
                    for (int indexUpperRight = 0; indexUpperRight < possibleCornersSortedUpperRight.Count; indexUpperRight++)
                    {
                        for (int indexLowerRight = 0; indexLowerRight < possibleCornersSortedLowerRight.Count; indexLowerRight++)
                        {
                            for (int indexLowerLeft = 0; indexLowerLeft < possibleCornersSortedLowerLeft.Count; indexLowerLeft++)
                            {
                                if (PluginFactory.CancelToken.IsCancellationRequested)
                                {
                                    PluginFactory.CancelToken.ThrowIfCancellationRequested();
                                }

                                // Possible corner combination
                                Point[] tmpCorners = new Point[]
                                {
                                    possibleCornersSortedUpperLeft[indexUpperLeft],         // the corners are ordered beginning in the upper left corner and going counter clock wise
                                    possibleCornersSortedLowerLeft[indexLowerLeft],
                                    possibleCornersSortedLowerRight[indexLowerRight],
                                    possibleCornersSortedUpperRight[indexUpperRight]
                                };
                                double angleDiff = RectangleDifferenceAngle(tmpCorners);
                                if (angleDiff > PieceFindCornersMaxAngleDiff)
                                {
                                    continue;
                                }

                                double area = CvInvoke.ContourArea(new VectorOfPoint(tmpCorners));
                                FindCornerRectangleScore score = new FindCornerRectangleScore()
                                {
                                    AngleDiff = angleDiff, RectangleArea = area, PossibleCorners = tmpCorners
                                };
                                scores.Add(score);
                            }
                        }
                    }
                }

                // Order the scores by rectangle area (biggest first) and take the PossibleCorners of the biggest rectangle as corners
                scores = scores.OrderByDescending(s => s.RectangleArea).ToList();
                if (scores.Count > 0)
                {
                    corners.AddRange(scores[0].PossibleCorners);
                }
            }

            if (corners.Count != 4)
            {
                PluginFactory.LogHandle.Report(new LogEventError(pieceID + " Failed to find correct number of corners. " + corners.Count + " found."));
            }

            if (PluginFactory.GetGeneralSettingsPlugin().SolverShowDebugResults)
            {
                using (Image <Rgb, byte> imgCorners = new Image <Rgb, byte>(pieceImgColor))
                {
                    Features2DToolbox.DrawKeypoints(imgCorners, new VectorOfKeyPoint(keyPoints), imgCorners, new Bgr(0, 0, 255));       // Draw the dominant key points

                    for (int i = 0; i < corners.Count; i++)
                    {
                        CvInvoke.Circle(imgCorners, Point.Round(corners[i]), 4, new MCvScalar(0, Math.Max(255 - i * 50, 50), 0), 3);
                    }
                    PluginFactory.LogHandle.Report(new LogEventImage(pieceID + " Corners", imgCorners.Bitmap));
                    imgCorners.Dispose();
                }
            }
            return(corners);
        }