public ShapeContour(ContourNode node) { Node = node; Shape = GetShape(); if (Shape == Shape.Other) { throw new ArgumentException("The contour does not match a shape"); } }
public ContourNode(Contour<Point> node) { this.Contour = node; Children = new List<ContourNode>(); List<Contour<Point>> kids = new List<Contour<Point>>(ContourNode.GetChildren(this.Contour)); foreach(Contour<Point> kid in kids) { ContourNode kidnode = new ContourNode(kid); kidnode.Parent = this; Children.Add(kidnode); } //if (kids.Count > 3) //{ // throw new SetGameException("Found a card with more than 3 shapes on it."); //} }
public static KeyValuePair<Card, ContourNode> AnalyzeNode(ContourNode cardNode) { if (cardNode.Shape != Shape.Card) { throw new ArgumentException("The node is not labeled as a Card", "node.Shape"); } else { int count = cardNode.Children.Count; CardColor color = cardNode.Children[0].Color; Shape shape = cardNode.Children[0].Shape; Card card = new Card(color, shape, cardNode.Fill, count); cardNode.Color = color; //Set this, the card's background is actually white, but this color matters for the game and in debugging return new KeyValuePair<Card, ContourNode>(card, cardNode); } }
public static IEnumerable<KeyValuePair<Card, CardContour>> GiveCards(ContourNode tree) { if (ContourAnalyzer.IsCard(tree)) { CardContour cardcont = new CardContour(tree); yield return new KeyValuePair<Card, CardContour>(cardcont.GetCard(), cardcont); } else { foreach (ContourNode child in tree.Children) { foreach (KeyValuePair<Card, CardContour> pair in GiveCards(child)) { yield return pair; } } } }
public Dictionary<Card, Point> LocateCards(Image<Bgr, Byte> table, Settings settings) { #region process image //Convert the image to grayscale and filter out the noise Image<Gray, Byte> gray = table.Convert<Gray, Byte>(); Gray cannyThreshold = new Gray(180); Gray cannyThresholdLinking = new Gray(120); Gray circleAccumulatorThreshold = new Gray(120); Image<Gray, Byte> cannyEdges = gray.Canny(cannyThreshold, cannyThresholdLinking); StructuringElementEx el = new StructuringElementEx(3, 3, 1, 1, CV_ELEMENT_SHAPE.CV_SHAPE_RECT); cannyEdges = cannyEdges.MorphologyEx(el, CV_MORPH_OP.CV_MOP_CLOSE, 1); #endregion Contour<Point> contours = cannyEdges.FindContours( CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, //was CV_CHAIN_APPROX_SIMPLE RETR_TYPE.CV_RETR_TREE); ContourNode tree = new ContourNode(contours); Dictionary<Card, Point> cardlocs = new Dictionary<Card, Point>(); foreach (KeyValuePair<Card, CardContour> pair in GiveCards(tree)) { ContourNode node = pair.Value.Node; Card card = pair.Key; PointF fcenter = node.Contour.GetMinAreaRect().center; Point center = new Point((int)fcenter.X, (int)fcenter.Y); cardlocs.Add(card, center); } #region draw #if DEBUG TreeViz.VizualizeTree(tree); ContourAnalyzer.DrawContours(tree, table); //ImageViewer.Show(table); #endif #endregion return cardlocs; }
public static void DrawContours(ContourNode node, Image<Bgr, Byte> canvas, System.Drawing.Color color) { Bgr _color = new Bgr(System.Drawing.Color.Red); foreach (ContourNode child in node.Children) { canvas.DrawPolyline(child.Contour.ToArray(), true, _color, 1); if (node.Shape != null) { MCvFont font = new MCvFont(FONT.CV_FONT_HERSHEY_PLAIN, 1, 1); canvas.Draw(child.Shape + child.Color.ToString(), ref font, child.Contour[0], new Bgr(System.Drawing.Color.Red) ); } DrawContours(child, canvas, color); } }
public static void DrawContours(ContourNode node, Image<Bgr, Byte> canvas) { #region color Random rnd = new Random(node.Contour.Ptr.ToInt32()); Bgr _color = new Bgr(rnd.Next(255), rnd.Next(255), rnd.Next(255)); #endregion foreach (ContourNode child in node.Children) { canvas.DrawPolyline(child.Contour.ToArray(), true, _color, 1); if (node.Shape != null) { MCvFont font = new MCvFont(FONT.CV_FONT_HERSHEY_PLAIN, 1, 1); canvas.Draw(child.Shape + child.Color.ToString(), ref font, child.Contour[0], new Bgr(System.Drawing.Color.Red) ); } DrawContours(child, canvas); } }
private static CardColor RecognizeColor(ContourNode node, Settings settings) { Image<Bgr, Byte> image = node.Image; image.ROI = node.Contour.BoundingRectangle; Image<Bgr, Byte> debugBest = null; Point bestpos = FindBestColoredPixel(image, out debugBest); Bgr bgr = image[bestpos]; #region classification CardColor classification = classifier.Classify(bgr); CardColor verdict = classification; if (verdict == CardColor.Other) { if (!isGray(bgr, 10)) { verdict = ClassifyBgr2(bgr); } else { verdict = CardColor.White; } } #endregion #if DEBUG //save for training: writer.Write((int)bgr.Blue, (int)bgr.Green, (int)bgr.Red); #endif #region debug if (settings.debuglevel >= 4) { ImageViewer.Show(debugBest, verdict.ToString()); } else if (settings.debuglevel >= 2 && verdict == CardColor.Other) { ImageViewer.Show(debugBest, verdict.ToString()); } #endregion return verdict; }
private static void AssignImage(ContourNode node, Image<Bgr, Byte> image, bool setROI) { node.Image = ExtractContourImage(image, node.Contour, out node.AttentionMask); if (setROI) { node.Image.ROI = node.Contour.BoundingRectangle; node.AttentionMask.ROI = node.Contour.BoundingRectangle; } }
private static void FilterTree(ContourNode tree) { FilterNode(tree); foreach (ContourNode child in tree.Children) { //FilterNode(child); FilterTree(child); } }
private static bool isDashed(ContourNode inner) { Image<Bgr, Byte> im = inner.Image.Clone(); Rectangle oldroi = inner.Image.ROI; #region calc ROI double scale = 0.33; Point center = new Point(oldroi.X + oldroi.Width / 2, oldroi.Y + oldroi.Height / 2); Size size = new Size((int)(oldroi.Size.Width * scale), (int)(oldroi.Size.Height * scale)); MCvBox2D box = new MCvBox2D(center, size, 0); Rectangle newroi = box.MinAreaRect(); #endregion im.ROI = newroi; Image<Bgr, float> laplace = im.Laplace(1); double[] mins, maxs; Point[] minlocs, maxlocs; laplace.MinMax(out mins, out maxs, out minlocs, out maxlocs); return (maxs[0] > 15); }
private static Fill DetermineFill(ContourNode cardNode) { Fill fill = Fill.Other; try { ContourNode outer = cardNode.Children[0]; try { ContourNode inner = outer.Children[0]; double bgrDist = ColorDistance(cardNode.averageBgr, inner.averageBgr); double hsvDist = ColorDistance(cardNode.averageHsv, inner.averageHsv); if (hsvDist < 20) { fill = Fill.Open; } else if (hsvDist > 100) { fill = Fill.Solid; } else if (isDashed(inner)) { fill = Fill.Dashed; } outer.Fill = fill; inner.Fill = fill; } catch (ArgumentOutOfRangeException) { //The inner-node has nu children, which indicates a solid node, as being solid doesnt make edges fill = Fill.Solid; } } catch (ArgumentOutOfRangeException e) { throw new VisionException("Could not distinguish shape from card", e, cardNode.Image); } return fill; }
private static void FilterNode(ContourNode node) { ContourNode card = node.FindParent(Shape.Card, null, null); int minArea = 1000; if (card != null) { minArea = (int)card.Contour.Area / 20; } for (int i = 0; i < node.Children.Count; i++) { ContourNode child = node.Children[i]; if (child.Contour.Area < minArea) { node.Children.Remove(child); } } }
/// <summary> /// LocateCards works by analyzing the contours in the image. /// For instance, the Diamond in Set is a polygon with exactly 4 vertices. /// The Oval has no such features yet. /// The Squiggle is not convex, but concave and has edges in a 'right bend', instead of only 'left bends' /// /// All these shapes are inside the contour of a (white) card, which is a rounded square. /// Cards may also be the (only) exterior boundaries /// </summary> /// <param name="table">An image displaying the table with the Set cards</param> /// <returns>A dict locating which cards are present where in the image</returns> public Dictionary<Card, Point> LocateCards(Image<Bgr, Byte> table, Settings settings) { classifier = new BgrHsvClassifier(); classifier.Train(); #region process image //Convert the image to grayscale and filter out the noise Image<Gray, Byte> gray = table.Convert<Gray, Byte>(); Gray cannyThreshold = new Gray(50); //180 Gray cannyThresholdLinking = new Gray(30); //120 Gray circleAccumulatorThreshold = new Gray(100); //120 #region old Image<Gray, Byte> cannyEdges = gray.Canny(cannyThreshold, cannyThresholdLinking); if (settings.debuglevel >= 3) { ImageViewer.Show(cannyEdges, "cannyEdges before Closing"); } #endregion //#region new //Image<Gray, Byte> thresholded = new Image<Gray, byte>(gray.Size); //CvInvoke.cvAdaptiveThreshold(gray.Ptr, thresholded.Ptr, // 255, // ADAPTIVE_THRESHOLD_TYPE.CV_ADAPTIVE_THRESH_GAUSSIAN_C, // THRESH.CV_THRESH_BINARY_INV, 9, 5); //StructuringElementEx el1 = new StructuringElementEx(3, 3, 1, 1, CV_ELEMENT_SHAPE.CV_SHAPE_RECT); //thresholded = thresholded.Erode(1);//thresholded.MorphologyEx(el1, CV_MORPH_OP.CV_MOP_CLOSE, 1);// //Image<Gray, Byte> cannyEdges = thresholded;//.Canny(new Gray(1), new Gray(10)); //#endregion //StructuringElementEx el = new StructuringElementEx(5, 5, 2, 2, CV_ELEMENT_SHAPE.CV_SHAPE_RECT); StructuringElementEx el = new StructuringElementEx(3, 3, 1, 1, CV_ELEMENT_SHAPE.CV_SHAPE_RECT); cannyEdges = cannyEdges.MorphologyEx(el, CV_MORPH_OP.CV_MOP_CLOSE, 1); if (settings.debuglevel >= 3) { ImageViewer.Show(cannyEdges, "cannyEdges after Closing"); } #endregion Contour<Point> contours = cannyEdges.FindContours( CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, //was CV_CHAIN_APPROX_SIMPLE RETR_TYPE.CV_RETR_TREE); ContourNode tree = new ContourNode(contours); FilterTree(tree); #region debug if (settings.debuglevel >= 3) { var debug = table.Clone(); DrawContours(tree, debug); ImageViewer.Show(debug, "Contours after filtering"); } #endregion AssignShapes(tree); AssignImages(tree, table, true); #region debug if (settings.debuglevel >= 3) { var debug1 = table.Clone(); DrawContours(tree, debug1); ImageViewer.Show(debug1); } #endregion FilterTree(tree); #region debug if (settings.debuglevel >= 3) { var debug2 = table.Clone(); DrawContours(tree, debug2); ImageViewer.Show(debug2); } #endregion AssignColors(tree, table, settings); #region debug if (settings.debuglevel >= 3) { TreeViz.VizualizeTree(tree); } #endregion AssignFills(tree); Dictionary<Card, Point> cardlocs = new Dictionary<Card, Point>(); foreach (KeyValuePair<Card, ContourNode> pair in GiveCards(tree)) { ContourNode node = pair.Value; Card card = pair.Key; PointF fcenter = node.Contour.GetMinAreaRect().center; Point center = new Point((int)fcenter.X, (int)fcenter.Y); cardlocs.Add(card, center); } #region debug if (settings.debuglevel >= 1) { TreeViz.VizualizeTree(tree); } if (settings.debuglevel >= 2) { DrawContours(tree, table); ImageViewer.Show(table); } #endregion return cardlocs; }
public static IEnumerable<KeyValuePair<Card, ContourNode>> GiveCards(ContourNode tree) { if (tree.Shape == Shape.Card) { yield return AnalyzeNode(tree); } else { foreach (ContourNode child in tree.Children) { foreach (KeyValuePair<Card, ContourNode> pair in GiveCards(child)) { yield return pair; } } } }
private bool IsCard(ContourNode node) { bool area = node.Contour.Area > 5000; MCvBox2D bounds = node.Contour.GetMinAreaRect(); float ratio = ((bounds.size.Width / bounds.size.Height) + (bounds.size.Height / bounds.size.Width)); //ratio is supposed to be 2.16667 bool ratioOK = (ratio > 2.0 && ratio < 2.3); //#if DEBUG //if (area) //{ // ShowContour(node.Contour, new Image<Bgr, Byte>(900, 900), "CARD"); //} //else //{ // ShowContour(node.Contour, new Image<Bgr, Byte>(900, 900), ""); //} //#endif return ratioOK && area; }
private bool IsOval(ContourNode node) { bool inCard = node.Parent != null && (node.Parent.Shape == Shape.Card || node.Parent.Shape == Shape.Oval); if (inCard) { #region sharp //An oval doesn't have sharp angles bool sharp = false; bool vertices = false; using (MemStorage storage = new MemStorage()) { Contour<Point> currentContour = node.Contour.ApproxPoly(node.Contour.Perimeter * 0.01, storage); vertices = currentContour.Total > 4; Point[] points = currentContour.ToArray(); List<LineSegment2D> edges = new List<LineSegment2D>(PointCollection.PolyLine(points, true)); #if DEBUG #region draw //Image<Bgr, Byte> debug = new Image<Bgr, Byte>(800, 800); //debug.DrawPolyline(points, true, new Bgr(0, 0, 255), 1); #endregion draw #endif for (int i = 0; i < edges.Count; i++) { double angle = edges[(i + 1) % edges.Count].GetExteriorAngleDegree(edges[i]); if (Math.Abs(angle) > 60) { sharp = true; } //MCvFont font = new MCvFont(FONT.CV_FONT_HERSHEY_PLAIN, 1, 1); //debug.Draw(((int)angle).ToString(), ref font, edges[i].P2, new Bgr(0, 0, 255)); } //ImageViewer.Show(debug, "Sharp:" + sharp.ToString()); } #endregion return !sharp && vertices; } else { return false; } }
private static void AssignAverageColors(ContourNode node, Settings settings) { //Then get the average color of the card (should be white or gray) Bgr avgBgr = new Bgr(); MCvScalar scr1 = new MCvScalar(); node.Image.AvgSdv(out avgBgr, out scr1, node.AttentionMask); node.averageBgr = avgBgr; Hsv avgHsv = new Hsv(); MCvScalar scr2 = new MCvScalar(); node.Image.Convert<Hsv, Byte>().AvgSdv(out avgHsv, out scr2, node.AttentionMask); node.averageBgr = avgBgr; node.averageHsv = avgHsv; }
private static void AssignFills(ContourNode tree) { AssignFill(tree); foreach (ContourNode child in tree.Children) { AssignFills(child); } }
private static void AssignFill(ContourNode node) { if (node.Shape == Shape.Card) { node.Fill = DetermineFill(node); } }
private static void AssignColors(ContourNode tree, Image<Bgr, Byte> image, Settings settings) { AssignColor(tree, image, settings); foreach (ContourNode child in tree.Children) { //AssignColor(child, image, settings); AssignColors(child, image, settings); } }
private static void AssignColor(ContourNode node, Image<Bgr, Byte> image, Settings settings) { if (node.Contour.Area > 100 && ((node.Shape == Shape.Squiggle) || (node.Shape == Shape.Diamond) || (node.Shape == Shape.Oval))) { //CardColor color = RecognizeColor(image, node); //CardColor color = RecognizeColor(node); CardColor color = RecognizeColor(node, settings); node.Color = color; AssignAverageColors(node, settings); } else if (node.Shape == Shape.Card) { AssignAverageColors(node, settings); } }
public CardContour(ContourNode node) { Node = node; }
private static void AssignImages(ContourNode tree, Image<Bgr, Byte> image, bool setROI) { AssignImage(tree, image, setROI); foreach (ContourNode child in tree.Children) { AssignImages(child, image, setROI); } }
private bool IsDiamond(ContourNode node) { bool inCard = node.Parent != null && (node.Parent.Shape == Shape.Card || node.Parent.Shape == Shape.Diamond); if (inCard) { using (MemStorage storage = new MemStorage()) { Contour<Point> currentContour = node.Contour.ApproxPoly(node.Contour.Perimeter * 0.02, storage); Point[] points = currentContour.ToArray(); List<LineSegment2D> contourEdges = new List<LineSegment2D>(PointCollection.PolyLine(points, true)); bool sides4 = currentContour.Total == 4; //Image<Bgr, Byte> debug = new Image<Bgr, byte>(700, 700); //debug.DrawPolyline(points, true, new Bgr(0, 255, 0), 1); //ImageViewer.Show(debug, "Sides:" + currentContour.Total.ToString()); return sides4; } } else { return false; } }
private static void AssignShape(ContourNode node) { if (IsCard(node)) { node.Shape = Shape.Card; } else if (IsSquiggle(node)) { node.Shape = Shape.Squiggle; } else if (IsOval(node)) { node.Shape = Shape.Oval; } else if (IsDiamond(node)) { node.Shape = Shape.Diamond; } else { node.Shape = Shape.Other; } }
private bool IsSquiggle(ContourNode node) { bool inCard = node.Parent != null && (node.Parent.Shape == Shape.Card || node.Parent.Shape == Shape.Squiggle); bool convex = true; if (inCard) { #if DEBUG //ShowContour(node.Contour, new Image<Bgr, Byte>(900, 900), "Squiggle"); #endif using (MemStorage storage = new MemStorage()) { Contour<Point> currentContour = node.Contour.ApproxPoly(node.Contour.Perimeter * 0.01, storage); Point[] points = currentContour.ToArray(); List<LineSegment2D> edges = new List<LineSegment2D>(PointCollection.PolyLine(points, true)); #if DEBUG #region draw //Image<Bgr, Byte> debug = new Image<Bgr, Byte>(800, 800); //debug.DrawPolyline(points, true, new Bgr(0, 0, 255), 1); //ImageViewer.Show(debug); #endregion draw #endif //List<double> angles = new List<double>(edges.Count); for (int i = 0; i < edges.Count; i++) { double angle = edges[(i + 1) % edges.Count].GetExteriorAngleDegree(edges[i]); //angles.Add(angle); if (angle < 0) { convex = false; break; } //MCvFont font = new MCvFont(FONT.CV_FONT_HERSHEY_PLAIN, 1, 1); //debug.Draw(((int)angle).ToString(), ref font, edges[i].P2 ,new Bgr(0, 0, 255)); } //angles.Sort(); //if (angles[0] < 45) //{ // convex = false; //} //ImageViewer.Show(debug); } } return !convex && inCard; }
private static void AssignShapes(ContourNode tree) { AssignShape(tree); foreach (ContourNode child in tree.Children) { //AssignShape(child); AssignShapes(child); //TODO: should be enabled } }
private Image<Bgr, Byte> RemoveCardColor(ContourNode node, Image<Bgr, Byte> removeFrom) { ContourNode parentCard = node.FindParent(Shape.Card, null, null); if (parentCard != null) { //Image<Bgr, Byte> eroded = removeFrom.Erode(1); //Image<Bgr, Byte> thresholded = eroded.ThresholdBinary(parentCard.averageBgr, new Bgr(255,255,255)); //return thresholded; Image<Gray, Byte> gray = removeFrom.Convert<Gray, Byte>(); Image<Bgr, Byte> multiplier = new Image<Bgr, byte>(new Image<Gray, byte>[] { gray, gray, gray }); Image<Bgr, Byte> mul = removeFrom.Mul(multiplier, 0.015); Image<Bgr, Byte> threshed = mul.ThresholdToZeroInv(new Bgr(254, 254, 254)); Image<Gray, Byte> mask = threshed.Convert<Gray, Byte>(); Image<Bgr, Byte> result = mul.And(new Bgr(255, 255, 255), mask); result = result.Mul(multiplier, 0.005); return result; } else { return removeFrom; } }
public void FillTree(ContourNode tree) { List<ContourNode> _children = new List<ContourNode>(); Children = _children; }