/// <summary> /// Draw All the Contours On An Image /// </summary> internal void FireImageEvent(object sender, Guid cardId, Guid imageId, ImageType imageType, double angle, float X, float Y, VectorOfVectorOfPoint contours, CannyParam cannyParameter = null, Func <Size, Rectangle, bool> contourFilter = null) { if (ImageEvent != null) { using (Mat rotatedImage = GetRotatedImage(angle)) { for (int i = 0; i < contours.Size; i++) { RotatedRect rotatedRect = CvInvoke.MinAreaRect(contours[i]); Rectangle box = rotatedRect.MinAreaRect(); if (contourFilter == null || (contourFilter != null && contourFilter(rotatedImage.Size, box))) { CvInvoke.DrawContours(rotatedImage, contours, i, yellow); CvInvoke.Rectangle(rotatedImage, box, green, thickness: 2); //if (tagged) //{ // CvInvoke.PutText(rotatedImage, string.Format("HR: {0:0.00} AR: {0:0.00}", heightRatio, aspectRatio), // new Point((int)box.X + (box.Width / 2), (int)box.Y + (box.Height / 2)), // FontFace.HersheyPlain, fontScale: 1, color: black, thickness: 2); //} } } Image.FireImageEvent(null, cardId, imageId, imageType, rotatedImage, angle, X, Y, cannyParameter: cannyParameter); } } }
/// <summary> /// Get the Canny Image Of the Gray Image /// </summary> public Mat GetCannyImage(Func <Mat> getCannyBase, double angle, CannyParam cannyParameter) { if (cannyParameter == null) { throw new ArgumentNullException("cannyParameter"); } if (_cannyImage.image == null || _cannyImage.angle != angle || !_cannyImage.cannyParam.Equals(cannyParameter)) { if (_cannyImage.image != null) { _cannyImage.image.Dispose(); _cannyImage.image = null; } using (Mat grayImage = getCannyBase()) { using (Mat cannyImage = new Mat()) { CvInvoke.Canny(grayImage, cannyImage, threshold1: cannyParameter.Threshold1, threshold2: cannyParameter.Threshold2, apertureSize: cannyParameter.Aperture, l2Gradient: cannyParameter.L2Graident); _cannyImage.image = cannyImage.Clone(); _cannyImage.angle = angle; _cannyImage.cannyParam = cannyParameter; } } } return(_cannyImage.image.Clone()); }
/// <summary> /// Fire the Image Event And Send a Contoured Image /// </summary> protected void FireImageEvent(object sender, Guid cardId, Guid imageId, ImageType imageType, double angle, float X, float Y, CannyParam cannyParameter = null, ThresholdParm thresholdParmeter = null) { if (ImageEvent != null) { using (var image = GetContouredImage(angle, cannyParameter)) { Image.FireImageEvent(null, cardId, imageId, imageType, image, angle, X, Y, cannyParameter); } } }
/// <summary> /// Fire An Image Event /// </summary> /// <param name="image">Image To Event</param> protected static void FireImageEvent(object sender, Guid cardId, Guid imageId, ImageType imageType, Mat image, double angle, float X, float Y, CannyParam cannyParameter = null, ThresholdParm thresholdParmeter = null) { ImageEventArgs eventArgs = new ImageEventArgs(cardId, imageId, imageType, image, angle, X, Y, cannyParameter: cannyParameter, thresholdParamter: thresholdParmeter); if (ImageEvent != null) { ImageEvent(sender: sender, data: eventArgs); } }
public ImageEventArgs(Guid cardId, Guid imageId, ImageType imageType, Mat image, double angle, float x, float y, CannyParam cannyParameter = null, ThresholdParm thresholdParamter = null) { CardId = cardId; ImageId = imageId; ImageType = imageType; Image = image.Clone(); Angle = angle; X = x; Y = y; CannyParameters = cannyParameter; ThresholdParamters = thresholdParamter; }
/// <summary> /// Get Original Title Image With Contour Lines Drawn /// </summary> public Mat GetContouredImage(double angle, CannyParam cannyParameter, float minArea = 5000.0F) { if (cannyParameter == null) { throw new ArgumentNullException("cannyParameter"); } if (_contouredImage.image == null || !_contouredImage.cannyParam.Equals(cannyParameter)) { if (_contouredImage.image != null) { _contouredImage.image.Dispose(); _contouredImage.image = null; } _contouredImage.image = GetGreyImage(angle); using (Mat titleCannyImage = GetCannyImage(() => { return(GetGreyImage(angle)); }, angle, cannyParameter)) { using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) { CvInvoke.FindContours(titleCannyImage, contours, hierarchy: null, mode: RetrType.List, method: ChainApproxMethod.ChainApproxNone); for (int i = 1; i < contours.Size; i++) { RotatedRect rotatedRect = CvInvoke.MinAreaRect(contours[i]); float area = rotatedRect.Size.Width * rotatedRect.Size.Height; if (area > minArea) { CvInvoke.DrawContours(_contouredImage.image, contours, i, yellow, thickness: 2); Rectangle box = rotatedRect.MinAreaRect(); CvInvoke.Rectangle(_contouredImage.image, box, green, thickness: 2); } } _contouredImage.cannyParam = cannyParameter; } } } return(_contouredImage.image.Clone()); }
/// <summary> /// Fire Image Event With Rectangles Drawn On Image /// </summary> internal void FireImageEvent(object sender, Guid cardId, Guid imageId, ImageType imageType, double angle, float X, float Y, IEnumerable <Rectangle> rectangles, MCvScalar color, int thickness = 1, CannyParam cannyParameter = null, Action <Mat> postFunc = null) { if (ImageEvent != null) { using (Mat rotatedImage = GetRotatedImage(angle)) { foreach (Rectangle rectangle in rectangles) { CvInvoke.Rectangle(rotatedImage, rectangle, color, thickness: thickness); } if (postFunc != null) { postFunc(rotatedImage); } Image.FireImageEvent(null, cardId, imageId, imageType, rotatedImage, angle, X, Y, cannyParameter: cannyParameter); } } }
/// <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()); }
//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); }