public bool TryGetAverageColor(Bitmap image, Rectangle bounds, int ColorThreshold, out ColorDouble averageColor) { averageColor = new ColorDouble(); var count = 0; for (var x = bounds.Left; x < bounds.Right; x++) { for (var y = bounds.Top; y < bounds.Bottom; y++) { var color = image.GetPixel(x, y); if (count > 0 && ColorDiff(averageColor / count, color) > ColorThreshold) { return(false); } averageColor += color; count++; } } averageColor = averageColor / count; return(true); }
public double GetColorPercentOnCircle(Bitmap image, ColorDouble color, Point center, double radius, int colorThreshold) { var totalCount = 0; var colorCount = 0; for (var angle = 0d; angle < 2 * Math.PI; angle += 1 / radius) { var p = new Point((int)(center.X + Math.Cos(angle) * radius), (int)(center.Y + Math.Sin(angle) * radius)); if (p.X < 0 || p.Y < 0 || p.X >= image.Width || p.Y >= image.Height) { continue; } var pixel = image.GetPixel(p.X, p.Y); if (ColorDiff(color, pixel) < colorThreshold) { colorCount++; } totalCount++; } return(colorCount * 1.0 / totalCount); }
public RecognizerResult Run(Bitmap image) { var colorThreshold = 25; var needDispose = false; var multiplier = 1; var smallImage = image; while (smallImage.Width > 600 || smallImage.Height > 600) { Log?.Debug("Downsample image to {0} x {1} for faster initial search", smallImage.Width / 2, smallImage.Height / 2); var newImage = new Bitmap(smallImage, new Size(smallImage.Width / 2, smallImage.Height / 2)); multiplier *= 2; if (needDispose) { smallImage.Dispose(); } smallImage = newImage; needDispose = true; } try { var corners = new Rectangle[] { new Rectangle(0, 0, 10, 10), new Rectangle(smallImage.Width - 10, 0, 10, 10), new Rectangle(0, smallImage.Height - 10, 10, 10), new Rectangle(smallImage.Width - 10, smallImage.Height - 10, 10, 10) }; var backColorFound = false; var backColor = new ColorDouble(); foreach (var corner in corners) { Log?.Debug("Selecting background color as average of {0} rectangle", corner); if (Tools.TryGetAverageColor(smallImage, corner, colorThreshold, out backColor)) { backColorFound = true; Log?.Info("Treating color {0} as background color", backColor); break; } else { Log?.Debug("Color at {0} are mixed - trying other corner", corner); } } if (!backColorFound) { Log?.Error("Failed to determing background color"); return(null); } var colors = Tools.GetImageColors(smallImage, colorThreshold); colors.Sort((x, y) => y.Count - x.Count); Log?.Debug("Retrieved all image colors: {0}", colors.Aggregate("", (s, c) => s + (s.Length > 0 ? ", " : "") + c)); Log?.Debug("Taking the most used color that is not a background"); var outerCircleColor = colors.First(c => Tools.ColorDiff(backColor, c.Color) >= colorThreshold).Color; Log?.Info("Outer circle color = {0}", outerCircleColor); Log?.Debug("Scanning for outer circle bounds a for lines, that have more than 5% of outer circle color"); int outerCircleLeft, outerCircleRight, outerCircleTop, outerCircleBottom; if (!Tools.TryLineScan <int>(smallImage, new Rectangle(Point.Empty, smallImage.Size), LineScanDirection.LeftToRight, (count, color) => count + (Tools.ColorDiff(outerCircleColor, color) < colorThreshold ? 1 : 0), (count) => count > smallImage.Height / 20, out outerCircleLeft)) { Log?.Error("Failed to find left border of the outer circle"); return(null); } if (!Tools.TryLineScan <int>(smallImage, new Rectangle(Point.Empty, smallImage.Size), LineScanDirection.RightToLeft, (count, color) => count + (Tools.ColorDiff(outerCircleColor, color) < colorThreshold ? 1 : 0), (count) => count > smallImage.Height / 20, out outerCircleRight)) { Log?.Error("Failed to find right border of the outer circle"); return(null); } if (!Tools.TryLineScan <int>(smallImage, new Rectangle(Point.Empty, smallImage.Size), LineScanDirection.TopToBottom, (count, color) => count + (Tools.ColorDiff(outerCircleColor, color) < colorThreshold ? 1 : 0), (count) => count > smallImage.Width / 20, out outerCircleTop)) { Log?.Error("Failed to find top border of the outer circle"); return(null); } if (!Tools.TryLineScan <int>(smallImage, new Rectangle(Point.Empty, smallImage.Size), LineScanDirection.BottomToTop, (count, color) => count + (Tools.ColorDiff(outerCircleColor, color) < colorThreshold ? 1 : 0), (count) => count > smallImage.Width / 20, out outerCircleBottom)) { Log?.Error("Failed to find bottom border of the outer circle"); return(null); } Log?.Info("Outer circle bounds: ({0}, {1}) - ({2}, {3})", outerCircleLeft, outerCircleTop, outerCircleRight, outerCircleBottom); var center = new Point((outerCircleLeft + outerCircleRight) / 2 * multiplier + multiplier / 2, (outerCircleTop + outerCircleBottom) / 2 * multiplier + multiplier / 2); var radius = (outerCircleRight - outerCircleLeft + outerCircleBottom - outerCircleTop) / 4.0 * multiplier + multiplier / 2; Log?.Info("Draftly taking {0} as a center and {1} as radius", center, radius); colorThreshold = (int)Tools.ColorDiff(backColor, outerCircleColor) / 2; Log?.Debug("Adjusting color threshold to {0} - half difference between back and outer circle color", colorThreshold); AdjustCenterAndRadius(image, outerCircleColor, colorThreshold, ref center, ref radius); Log?.Info("Adjusted center {0} and radius {1}", center, radius); ColorDouble innerCircleColor; Tools.TryGetAverageColor(image, new Rectangle(center.X - 5, center.Y - 5, 10, 10), 3 * 255, out innerCircleColor); Log?.Info("Taking color {0} from center of outer circle as color of inner circle", innerCircleColor); colorThreshold = (int)Math.Max(10, Tools.ColorDiff(outerCircleColor, innerCircleColor) / 2); Log?.Info("Taking {0} as color threshold for border of inner circle", colorThreshold); int innerCircleLeft = center.X - (int)radius, innerCircleRight = center.X + (int)radius, innerCircleTop = center.Y - (int)radius, innerCircleBottom = center.Y + (int)radius; for (var x = center.X; x > center.X - radius; x--) { if (Tools.ColorDiff(innerCircleColor, image.GetPixel(x, center.Y)) >= colorThreshold) { innerCircleLeft = x; break; } } for (var x = center.X; x < center.X + radius; x++) { if (Tools.ColorDiff(innerCircleColor, image.GetPixel(x, center.Y)) >= colorThreshold) { innerCircleRight = x; break; } } for (var y = center.Y; y > center.Y - radius; y--) { if (Tools.ColorDiff(innerCircleColor, image.GetPixel(center.X, y)) >= colorThreshold) { innerCircleTop = y; break; } } for (var y = center.Y; y < center.Y + radius; y++) { if (Tools.ColorDiff(innerCircleColor, image.GetPixel(center.X, y)) >= colorThreshold) { innerCircleBottom = y; break; } } Log?.Info("Inner circle bounds: ({0}, {1}) - ({2}, {3})", innerCircleLeft, innerCircleTop, innerCircleRight, innerCircleBottom); var innerCenter = new Point((innerCircleLeft + innerCircleRight) / 2, (innerCircleTop + innerCircleBottom) / 2); var innerRadius = (innerCircleRight - innerCircleLeft + innerCircleBottom - innerCircleTop) / 4.0; Log?.Info("Draftly taking {0} as a inner circle center and {1} as radius", innerCenter, innerRadius); AdjustCenterAndRadius(image, innerCircleColor, colorThreshold, ref innerCenter, ref innerRadius); Log?.Info("Adjusted inner circle center {0} and radius {1}", innerCenter, innerRadius); return(new RecognizerResult { OuterCircleCenter = center, OuterCircleRadius = radius, InnerCircleCenter = innerCenter, InnerCircleRadius = innerRadius }); } finally { if (needDispose) { smallImage.Dispose(); } } }
public double ColorDiff(ColorDouble x, ColorDouble y) { return(Math.Abs(x.R - y.R) + Math.Abs(x.G - y.G) + Math.Abs(x.B - y.B)); }