Esempio n. 1
0
        /// <summary>
        /// Search the Card Image For The Title And Result It As a Wrapped Class (CardTitleImage)
        /// </summary>
        internal static CardTitleImage FindBestTitleImage(Guid cardId, Image cardImage, float X, float Y, IEnumerable <CannyParam> cannyParameters, IEnumerable <double> angles)
        {
            List <CardTitleImage> potentialCardTitleImages = new List <CardTitleImage>();

            ContourCache contourCache = new ContourCache();

            using (var contours = new VectorOfVectorOfPoint())
            {
                foreach (var angle in angles)
                {
                    foreach (var cannyParameter in cannyParameters)
                    {
                        Debug.WriteLine(string.Format("Find Title Image Canny {0} Angle {1}...", cannyParameter, angle));

                        using (Mat cannyImage = cardImage.GetCannyImage(() => { return(cardImage.GetGreyImage(angle)); }, angle, cannyParameter))
                        {
                            CvInvoke.FindContours(cannyImage, contours, hierarchy: null, mode: RetrType.List, method: ChainApproxMethod.ChainApproxNone);

                            var sortedContours = new List <VectorOfPoint>();
                            for (int idx = 0; idx < contours.Size; idx++)
                            {
                                sortedContours.Add(contours[idx]);
                            }

                            double optimalAspectRatio = ((MaxTitleAspectRatio - MinTitleAspectRatio) / 2.0) + MinTitleAspectRatio;

                            // Sort Each Contour By Delta From the Optiomal Aspect Ratio, Smallest First
                            sortedContours.Sort((c1, c2) =>
                            {
                                RotatedRect rotatedRect1 = CvInvoke.MinAreaRect(c1);
                                double aspectRatio1      = (double)(rotatedRect1.Size.Height / rotatedRect1.Size.Width);

                                RotatedRect rotatedRect2 = CvInvoke.MinAreaRect(c2);
                                double aspectRatio2      = (double)(rotatedRect2.Size.Height / rotatedRect2.Size.Width);

                                double diff1 = Math.Abs(aspectRatio1 - optimalAspectRatio);
                                double diff2 = Math.Abs(aspectRatio2 - optimalAspectRatio);

                                return(diff1.CompareTo(diff2));
                            });

                            using (Mat rotatedImage = cardImage.GetRotatedImage(angle))
                            {
                                cardImage.FireImageEvent(null, cardId, cardId, ImageType.CardContoured, angle: angle, X: X, Y: Y,
                                                         contours: contours, cannyParameter: cannyParameter);

                                // Find The Contour That Matchs What A Title Should Look Like
                                foreach (VectorOfPoint countour in sortedContours)
                                {
                                    // Keep a list of contours that have been tested and don't test those again
                                    // that allows us to go through a bunch of test thresholds to determine
                                    // if they will yield new contours.
                                    if (contourCache.Contains(countour, angle))
                                    {
                                        continue;
                                    }

                                    // Store all the rotated rects that have been examined
                                    contourCache.Add(countour, angle);

                                    Mat          result = null;
                                    MTGCardFrame cardTitleType;

                                    try
                                    {
                                        Guid cardTitleId = Guid.NewGuid();

                                        if (TryFindTitle(cardId, cardTitleId, rotatedImage, angle, cannyParameter, countour, result: out result, cardTitleType: out cardTitleType))
                                        {
                                            // Wrap the Mat Found
                                            Image image = new Image(result);

                                            // Get the MTG card with the minimum Levenshtein distance for the text parsed
                                            // from the image, this will be the closet MTG card to the image
                                            var levenshteinDistanceResults = CardTitleImage.GetLowLevenshteinDistanceCards(cardId, cardTitleId, X, Y, cardTitleType, image);

                                            // Create a class from the card title image, the card title type, and the minimum levenshtien distance cards
                                            CardTitleImage cardTitleImage = new CardTitleImage(cardId, cardTitleId, result, cardTitleType, levenshteinDistanceResults, angle);

                                            // Direct Match Short Circuit
                                            if (levenshteinDistanceResults.Any(ldr => ldr.Distance < LevenshteinDistanceShortCircuit))
                                            {
                                                return(cardTitleImage);
                                            }

                                            // Add it to the potential card titles
                                            potentialCardTitleImages.Add(cardTitleImage);
                                        }
                                    }
                                    finally
                                    {
                                        if (result != null)
                                        {
                                            result.Dispose();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (potentialCardTitleImages.Count == 0)
            {
                return(null);
            }

            // Find The minimum number of character changes (the result of the Levenshtein Distance) alogorithm
            double minLevenshteinResults = potentialCardTitleImages.Min(cti => cti.LevenshteinResults.Min(lr => lr.PercentDistance));

            // Find all unique cards that have this distance
            var potentialCardNames = potentialCardTitleImages.SelectMany(cti => cti.LevenshteinResults)
                                     .Where(lr => lr.PercentDistance == minLevenshteinResults)
                                     .Select(lr => lr.Card.Name)
                                     .Distinct()
                                     .ToArray();

            if (potentialCardNames.Length == 0)
            {
                // There are no cards that match the image
                return(null);
            }
            else if (potentialCardNames.Length > 1)
            {
                // Inconclusive Results
                // There is more than one card with a low Levenshtein score that matches the images presented.
                return(null);
            }
            else
            {
                // Winner Winner Chicken Dinner
                string name = potentialCardNames[0];
                return(potentialCardTitleImages.FirstOrDefault(cti => cti.LevenshteinResults.Any(lr => lr.Card.Name.Equals(name))));
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Find All Cards In Field
        /// </summary>
        /// <returns>List of Cards</returns>
        public IEnumerable <CardImage> FindCards(Guid cardId)
        {
            ContourCache contourCache = new ContourCache();

            // Before contouring field image for cards, try using the whole field image
            // assuming it is a card
            using (Mat image = GetImage())
            {
                float aspectRatio = (float)image.Width / (float)image.Height;

                // Find the Card Aspect Raito
                if ((aspectRatio >= MinCardAspectRatio) && (aspectRatio <= MaxCardAspectRatio))
                {
                    Image.FireImageEvent(null, cardId, cardId, ImageType.CardCropped, image, angle: 0,
                                         X: image.Size.Width / 2.0F, Y: image.Size.Height / 2.0F);

                    var cardTitleImage = CardImage.FindBestTitleImage(cardId, this,
                                                                      X: image.Size.Width / 2.0F, Y: image.Size.Height / 2.0F,
                                                                      cannyParameters: CardImage.CannyParameters(), angles: CardImage.Angles());

                    if (cardTitleImage != null)
                    {
                        var cardImage = new CardImage(cardId, image, cardTitleImage);
                        yield return(cardImage);
                    }
                }
            }

            // Iterate though canny parameters looking for contours that will yield cards
            foreach (var cannyParameter in GetCannyParameters())
            {
                FireImageEvent(this, cardId, _fieldId, ImageType.FieldContoured, angle: 0, X: 0, Y: 0, cannyParameter: cannyParameter);

                Debug.WriteLine("Find Card Image: {0}...", cannyParameter);

                using (Mat cannyImage = GetCannyImage(() => { return(GetGreyImage(angle: 0)); }, angle: 0, cannyParameter: cannyParameter))
                {
                    using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
                    {
                        CvInvoke.FindContours(cannyImage, contours, hierarchy: null, mode: retrType, method: chainApproxMethod);

                        var sortedContours = new List <VectorOfPoint>();
                        for (int idx = 0; idx < contours.Size; idx++)
                        {
                            sortedContours.Add(contours[idx]);
                        }

                        // Sort Each Contour By Area, Largest First
                        sortedContours.Sort((v1, v2) =>
                        {
                            RotatedRect rotatedRect1 = CvInvoke.MinAreaRect(v1);
                            float area1 = rotatedRect1.Size.Width * rotatedRect1.Size.Height;

                            RotatedRect rotatedRect2 = CvInvoke.MinAreaRect(v2);
                            float area2 = rotatedRect2.Size.Width * rotatedRect2.Size.Height;

                            return(area2.CompareTo(area1));
                        });

                        // Iterate Each Contour Trying To Determine If It Is a Card
                        foreach (VectorOfPoint countour in sortedContours)
                        {
                            // Keep a list of contours that have been tested and don't test those again
                            // that allows us to quickly go through a bunch of test thresholds to determine
                            // if they will yield new contours.
                            if (contourCache.Contains(countour, angle: 0))
                            {
                                continue;
                            }

                            // Store all the rotated rects that have been examined
                            contourCache.Add(countour, angle: 0);

                            Mat imageResult = null;

                            try
                            {
                                if (TryFindCard(cardId, this.Size, countour, result: out imageResult))
                                {
                                    RotatedRect rotatedRect1 = CvInvoke.MinAreaRect(countour);

                                    Image.FireImageEvent(null, cardId, cardId, ImageType.CardCropped, imageResult, angle: 0,
                                                         X: rotatedRect1.Center.X, Y: rotatedRect1.Center.Y, cannyParameter: cannyParameter);

                                    // The card image (result) is in portrait mode, however it could be upsidedown
                                    using (Image image = new Image(imageResult))
                                    {
                                        var cardTitleImage = CardImage.FindBestTitleImage(cardId, image,
                                                                                          X: rotatedRect1.Center.X, Y: rotatedRect1.Center.Y,
                                                                                          cannyParameters: CardImage.CannyParameters(), angles: CardImage.Angles());

                                        if (cardTitleImage != null)
                                        {
                                            var cardImage = new CardImage(cardId, imageResult, cardTitleImage);
                                            yield return(cardImage);
                                        }
                                    }
                                }
                            }
                            finally
                            {
                                if (imageResult != null)
                                {
                                    imageResult.Dispose();
                                }
                            }
                        }
                    }
                }
            }
        }