Exemple #1
0
 /// <summary>
 /// Initilize a new instance of the CardImage class
 /// </summary>
 /// <param name="image">Card Image</param>
 /// <param name="cardTitleImage">Card Title Image (Sub Image of image)</param>
 public CardImage(Guid cardId, Mat image, CardTitleImage cardTitleImage) : base(image)
 {
     _cardId         = cardId;
     _cardTitleImage = cardTitleImage;
     _angle          = cardTitleImage.Angle;
     _cardFrame      = cardTitleImage.CardFrame;
 }
Exemple #2
0
 /// <summary>
 /// Initilize a new instance of the CardTitleImage class
 /// </summary>
 /// <param name="image">Image</param>
 internal CardTitleImage(Guid cardId, Guid cardTitleId, Mat image, MTGCardFrame cardTitleType, IEnumerable <LevenshteinResults> levenshteinResults, double angle) : base(image)
 {
     _cardId             = cardId;
     _cardTitleId        = cardTitleId;
     _cardFrame          = cardTitleType;
     _angle              = angle;
     _levenshteinResults = levenshteinResults.ToArray();
 }
Exemple #3
0
 internal static Rectangle BoundingRectangle(MTGCardFrame mcf, Size cardSize)
 {
     return(new Rectangle(
                (int)(LeftMargin[mcf] * cardSize.Width),
                (int)(TopMargin[mcf] * cardSize.Height),
                (int)((RightMargin[mcf] - LeftMargin[mcf]) * cardSize.Width),
                (int)((BottomMargin[mcf] - TopMargin[mcf]) * cardSize.Height)));
 }
Exemple #4
0
        /// <summary>
        /// Get a filtered image that is good for OCR
        /// </summary>
        public Mat GetFilteredImage(MTGCardFrame cardTitleType, double angle, ThresholdParm thresoldParameter, CannyParam cannyParameter)
        {
            if (_filteredImage.image == null || _filteredImage.cannyParam != cannyParameter || _filteredImage.angle != angle)
            {
                using (Mat titleImage = GetImage())
                {
                    Size titleSize = titleImage.Size;

                    int minX = titleImage.Width;
                    int maxX = titleImage.Height;
                    int minY = 0;
                    int maxY = 0;

                    // Get the Canny Image To Make A Contour Set For the Letters
                    using (Mat titleCannyImage = GetCannyImage(() => { return(GetThresholdImage(angle, thresoldParameter)); }, angle, cannyParameter))
                    {
                        using (Mat maskedImage = new Mat(titleSize, DepthType.Cv8U, 1))
                        {
                            // White/black out the mask.  Legacy cards are white letters on color, the new style is black letters in a colored title box.
                            maskedImage.SetTo(cardTitleType == MTGCardFrame.M15 ? black : white);

                            using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
                            {
                                CvInvoke.FindContours(titleCannyImage, contours, hierarchy: null, mode: retrType, method: chainApproxMethod);

                                for (int i = 1; i < contours.Size; i++)
                                {
                                    using (VectorOfPoint contour = contours[i])
                                    {
                                        Rectangle rect = CvInvoke.BoundingRectangle(contour);

                                        double relativeY      = (double)rect.Y / (double)titleSize.Height;
                                        double relativeX      = (double)rect.X / (double)titleSize.Width;
                                        double relativeBottom = ((double)rect.Y + (double)rect.Height) / (double)titleSize.Height;
                                        double relativeRight  = ((double)rect.X + (double)rect.Width) / (double)titleSize.Width;
                                        double relativeWidth  = (double)rect.Width / (double)titleSize.Width;
                                        double relativeHeight = (double)rect.Height / (double)titleSize.Height;

                                        // The Characters Start In the 10 % of the Title Image, and Complete Within the
                                        if (((relativeY > .10) && (relativeY < .80) &&
                                             (relativeBottom > .30) && (relativeBottom < .95) &&
                                             (relativeRight > .025) && (relativeX < .95)) &&
                                            (relativeWidth < .09) && (relativeHeight > .07))
                                        {
                                            // Make a mask of rectangles that represent the title.
                                            CvInvoke.Rectangle(maskedImage, rect, new MCvScalar(255, 255, 255), -1);

                                            // Keep Track Of the Border Area Which Comprises The Letters
                                            minX = Math.Min(rect.X, minX);
                                            minY = Math.Min(rect.Y, minY);
                                            maxX = Math.Max(rect.X + rect.Width, maxX);
                                            maxY = Math.Max(rect.Y + rect.Height, maxY);
                                        }
                                    }
                                }
                            }

                            // The result is the same depth/channels as the thresold
                            using (Mat result = GetThresholdImage(angle, thresoldParameter))
                            {
                                // Clear the result
                                result.SetTo(cardTitleType == MTGCardFrame.M15 ? black : white);

                                // Use the Mask To Take Just The Title From the thresoldImage
                                GetThresholdImage(angle, thresoldParameter).CopyTo(result, maskedImage);

                                _filteredImage.image      = result.Clone();
                                _filteredImage.cannyParam = cannyParameter;
                                _filteredImage.angle      = angle;
                            }
                        }
                    }
                }
            }

            return(_filteredImage.image.Clone());
        }
Exemple #5
0
        //public IEnumerable<Mat> FindSetIcons()
        //{
        //    List<Mat> results = new List<Mat>();

        //    using (var contours = new VectorOfVectorOfPoint())
        //    {
        //        foreach (var angle in CardImage.IconAngles())
        //        {
        //            foreach (var cannyParameter in CardImage.CannyIconParameters())
        //            {
        //                Debug.WriteLine("Find Set Icon Image Angle: {0} Canny: {1} ...", angle, cannyParameter);

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

        //                    List<Rectangle> rectangleList = new List<Rectangle>();

        //                    // Create a List of Rectangles From the Contours
        //                    for (int idx = 0; idx < contours.Size; idx++)
        //                    {
        //                        Rectangle rectangle = CvInvoke.MinAreaRect(contours[idx]).MinAreaRect();
        //                        rectangleList.Add(rectangle);
        //                    }

        //                    // Only add those which fall in the Icon "Zone"
        //                    rectangleList = rectangleList.Where(r => CardExpansionSymbolFilter.ExpansionSymbolFilterPass1(_cardFrame, cannyImage.Size, r)).ToList();

        //                    // Output Contoured Image For Debugging
        //                    this.FireImageEvent(null, _cardId, _cardId, ImageType.CardContoured, angle: angle, X: 0, Y: 0,
        //                        rectangles: rectangleList, color: blue, thickness: 1, cannyParameter: cannyParameter, postFunc: new Action<Mat>((image) =>
        //                        {
        //                            // After Drawing the Rectangles, Add The Margin Lines
        //                            CvInvoke.Line(image, new Point((int)(image.Width * CardExpansionSymbolFilter.LeftMargin[_cardFrame]), 0),
        //                                new Point((int)(image.Width * CardExpansionSymbolFilter.LeftMargin[_cardFrame]), image.Height), white, thickness: 1);

        //                            CvInvoke.Line(image, new Point((int)(image.Width * CardExpansionSymbolFilter.RightMargin[_cardFrame]), 0),
        //                                new Point((int)(image.Width * CardExpansionSymbolFilter.RightMargin[_cardFrame]), image.Height), white, thickness: 1);

        //                            CvInvoke.Line(image, new Point(0, (int)(image.Height * CardExpansionSymbolFilter.TopMargin[_cardFrame])),
        //                                new Point(image.Width, (int)(image.Height * CardExpansionSymbolFilter.TopMargin[_cardFrame])), white, thickness: 1);

        //                            CvInvoke.Line(image, new Point(0, (int)(image.Height * CardExpansionSymbolFilter.BottomMargin[_cardFrame])),
        //                                new Point(image.Width, (int)(image.Height * CardExpansionSymbolFilter.BottomMargin[_cardFrame])), white, thickness: 1);

        //                        }));

        //                    // Merge all possible rectangles that could be icons together
        //                    var intersectedRectangles = MergedRectangles(rectangleList.ToArray())
        //                        .Distinct()
        //                        .Except(rectangleList);

        //                    rectangleList.AddRange(intersectedRectangles);

        //                    // Make a second pass at filtering to reduce the rectangles into only those
        //                    // that could possibly be icons based on aspect ratio, size, etc...
        //                    rectangleList = rectangleList.Where(r => CardExpansionSymbolFilter.ExpansionSymbolFilterPass2(_cardFrame, cannyImage.Size, r)).ToList();

        //                    // Sort Each Rectangle From Biggest To Smallest
        //                    rectangleList.Sort((r1, r2) =>
        //                    {
        //                        float area1 = r1.Width * r1.Height;
        //                        float area2 = r2.Width * r2.Height;

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

        //                    // Skip mat creation if there are no rectangles
        //                    if (rectangleList.Count > 0)
        //                    {
        //                        using (Mat image = GetGreyImage(angle))
        //                        {
        //                            foreach (Rectangle rectangle in rectangleList)
        //                            {
        //                                using (Mat cropped = new Mat(image, rectangle))
        //                                {
        //                                    // Output cropped image for debugging.
        //                                    Image.FireImageEvent(this, _cardId, _cardId, ImageType.SetIcon,
        //                                        cropped, angle: angle, X: rectangle.X + (rectangle.Width / 2), Y: rectangle.Y + (rectangle.Height / 2),
        //                                        cannyParameter: cannyParameter);

        //                                    results.Add(cropped.Clone());
        //                                }
        //                            }
        //                        }
        //                    }
        //                }
        //            }
        //        }
        //    }

        //    // Sort to biggest first, this make sure FirstDefault() take the most likely one
        //    results.Sort((m1, m2) =>
        //    {
        //        float area1 = m1.Size.Height * m1.Size.Width;
        //        float area2 = m2.Size.Height * m2.Size.Width;

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

        //    return results;
        //}

        /// <summary>
        /// Try To Find The Title In the Image
        /// </summary>
        /// <param name="cardImage">Image</param>
        /// <param name="countor">Contour To Test</param>
        /// <param name="result">Resulting Title Image</param>
        /// <returns>True If Title Is Found</returns>
        private static bool TryFindTitle(Guid cardId, Guid cardTitleId,
                                         Mat cardImage, double angle, CannyParam cannyParameter,
                                         VectorOfPoint countor, out Mat result, out MTGCardFrame cardTitleType)
        {
            result        = null;
            cardTitleType = MTGCardFrame.M15;

            RotatedRect rotatedRect = CvInvoke.MinAreaRect(countor);

            // Prevent Divide By Zero
            if (rotatedRect.Size.Height == 0)
            {
                return(false);
            }

            float width           = rotatedRect.Size.Width;
            float height          = rotatedRect.Size.Height;
            float heightRatio     = rotatedRect.Size.Height / (float)cardImage.Size.Height;
            float relativeCenterY = rotatedRect.Center.Y / (float)cardImage.Size.Height;

            Rectangle box = rotatedRect.MinAreaRect();

            // Prevent Divide By Zero
            if (box.Size.Width == 0)
            {
                return(false);
            }

            float widthRatio   = (float)box.Size.Width / (float)cardImage.Size.Width;
            float aspectRatio  = (float)box.Size.Height / (float)box.Size.Width;
            float area         = (float)box.Size.Height * (float)box.Size.Width;
            float imageArea    = (float)cardImage.Size.Height * (float)cardImage.Size.Width;
            float relativeArea = area / imageArea;

            // Title bar should have a height
            if (height < 1.0F)
            {
                return(false);
            }

            // Box Should Be Inside the Image
            if ((box.Y < 0) || (box.X < 0) || ((box.X + box.Width) > cardImage.Size.Width) || ((box.Y + box.Height) > cardImage.Size.Height))
            {
                return(false);
            }


            // Name bar should center in the top 15% of the image, this is the new style cards with the name "boxed" in the image
            if (relativeCenterY < .15F)
            {
                // Title Bar Should Be Wider Than 80% of the Image Width
                if (widthRatio < .80F)
                {
                    return(false);
                }

                using (Mat cropped = new Mat(cardImage, box))
                {
                    Image.FireImageEvent(null, cardId, cardId, ImageType.TitleCropped, cropped, angle: angle, X: box.X, Y: box.Y, cannyParameter: cannyParameter);
                }

                // Title Bar Should Be 6% of the Card Height
                if (heightRatio < .048 || heightRatio > .077)
                {
                    return(false);
                }

                Debug.WriteLine("Title Contour ({14}) - Center: {0}/{1} Relative Center: ({9}%)/({10}%) Width: {2} ({11}%) Height: {3} ({12}%) Area: {4} ({13}%) : AspectRatio: {5}, Angle: {6} Image Size: {7}/{8}",
                                rotatedRect.Center.X, rotatedRect.Center.Y,
                                rotatedRect.Size.Width, rotatedRect.Size.Height,
                                area, aspectRatio, rotatedRect.Angle,
                                cardImage.Size.Width, cardImage.Size.Height,
                                (rotatedRect.Center.X / cardImage.Size.Width) * 100.0,
                                relativeCenterY * 100.0,
                                widthRatio * 100.0,
                                heightRatio * 100.0,
                                relativeArea * 100.0,
                                cardTitleId);

                using (Mat cropped = new Mat(cardImage, box))
                {
                    Image.FireImageEvent(null, cardId, cardTitleId, ImageType.TitleCropped, cropped, angle: angle, X: box.X, Y: box.Y, cannyParameter: cannyParameter);

                    result = cropped.Clone();
                    return(true);
                }
            }
            else if (relativeCenterY < .50F)
            {
                // Assume that this card is the older style card with the name above the photo, but not "boxed"
                //
                cardTitleType = MTGCardFrame.Original;

                // Using the Aspect Ratio Find the Art Box
                if (aspectRatio < .75F || aspectRatio > .85F)
                {
                    return(false);
                }

                // The art relative area to the card should be above 35%
                if (relativeArea < .35F)
                {
                    return(false);
                }

                Debug.WriteLine("Title Contour ({14}) - Center: {0}/{1} Relative Center: ({9}%)/({10}%) Width: {2} ({11}%) Height: {3} ({12}%) Area: {4} ({13}%) : AspectRatio: {5}, Angle: {6} Image Size: {7}/{8}",
                                rotatedRect.Center.X, rotatedRect.Center.Y,
                                rotatedRect.Size.Width, rotatedRect.Size.Height,
                                area, aspectRatio, rotatedRect.Angle,
                                cardImage.Size.Width, cardImage.Size.Height,
                                (rotatedRect.Center.X / cardImage.Size.Width) * 100.0,
                                relativeCenterY * 100.0,
                                widthRatio * 100.0,
                                heightRatio * 100.0,
                                relativeArea * 100.0,
                                cardTitleId);

                int borderHeight = (int)((double)box.Y * .45);

                // Create a box that is as wide as the art, and directly above the art to
                // the top of the card
                Rectangle titleBox = new Rectangle(box.X, borderHeight, box.Width, box.Y - borderHeight);

                heightRatio = titleBox.Size.Height / (float)cardImage.Size.Height;

                // Title Bar Should Be 6% of the Card Height
                if (heightRatio < .050 || heightRatio > .065)
                {
                    return(false);
                }

                using (Mat cropped = new Mat(cardImage, titleBox))
                {
                    Image.FireImageEvent(null, cardId, cardId, ImageType.TitleCropped, cropped, angle: angle, X: titleBox.X, Y: titleBox.Y, cannyParameter: cannyParameter);

                    result = cropped.Clone();
                    return(true);
                }
            }

            return(false);
        }
Exemple #6
0
 /// <summary>
 /// Initilize a new instance of the CardImage class
 /// </summary>
 public CardImage(Guid cardId, Mat image, double angle, MTGCardFrame cardFrame) : base(image)
 {
     _cardId    = cardId;
     _angle     = angle;
     _cardFrame = cardFrame;
 }
Exemple #7
0
        internal static IEnumerable <LevenshteinResults> GetLowLevenshteinDistanceCards(Guid cardId, Guid cardTitleId, float X, float Y, MTGCardFrame cardTitleType, Image image)
        {
            List <LevenshteinResults> results = new List <LevenshteinResults>();

            foreach (var thresholdParameter in GetThresholdParamters())
            {
                foreach (var cannyParameter in GetCannyParameters())
                {
                    using (var filteredImage = image.GetFilteredImage(cardTitleType, angle: 0, thresoldParameter: thresholdParameter, cannyParameter: cannyParameter))
                    {
                        Image.FireImageEvent(null, cardId, cardTitleId, ImageType.TitleFiltered,
                                             filteredImage, angle: 0, X: X, Y: Y,
                                             cannyParameter: cannyParameter,
                                             thresholdParmeter: thresholdParameter);
                        {
                            var lldc = GetLowLevenshteinDistanceCards(filteredImage);

                            // Short Circuit
                            if (lldc.Any(lr => lr.Distance < LevenshteinDistanceShortCircuit))
                            {
                                return(lldc.Where(lr => lr.Distance < LevenshteinDistanceShortCircuit));
                            }

                            results.AddRange(lldc);
                        }

                        using (Mat erodeImage = new Mat())
                        {
                            CvInvoke.Erode(filteredImage, erodeImage, null, new Point(-1, -1), 1, BorderType.Constant,
                                           cardTitleType == MTGCardFrame.M15 ? white : black);

                            Image.FireImageEvent(null, cardId, cardTitleId, ImageType.TitleErode, erodeImage, angle: 0, X: 0, Y: 0,
                                                 cannyParameter: cannyParameter, thresholdParmeter: thresholdParameter);

                            var lldc = GetLowLevenshteinDistanceCards(erodeImage);

                            // Short Circuit
                            if (lldc.Any(lr => lr.Distance < LevenshteinDistanceShortCircuit))
                            {
                                return(lldc.Where(lr => lr.Distance < LevenshteinDistanceShortCircuit));
                            }

                            results.AddRange(lldc);
                        }

                        using (Mat dilateImage = new Mat())
                        {
                            CvInvoke.Dilate(filteredImage, dilateImage, null, new Point(-1, -1), 1, BorderType.Constant,
                                            cardTitleType == MTGCardFrame.M15 ? white : black);

                            Image.FireImageEvent(null, cardId, cardTitleId, ImageType.TitleDilate, dilateImage, angle: 0, X: 0, Y: 0,
                                                 cannyParameter: cannyParameter, thresholdParmeter: thresholdParameter);

                            var lldc = GetLowLevenshteinDistanceCards(dilateImage);

                            // Short Circuit
                            if (lldc.Any(lr => lr.Distance < LevenshteinDistanceShortCircuit))
                            {
                                return(lldc.Where(lr => lr.Distance < LevenshteinDistanceShortCircuit));
                            }

                            results.AddRange(lldc);
                        }
                    }
                }
            }

            // Find all cards with the minimum number of character changes (the result of the Levenshtein Distance)
            // alogorithm and return them
            double min = results.Min(result => result.PercentDistance);

            return(results.Where(result => result.PercentDistance == min).Distinct());
        }