/// <summary> /// detects square shapes within the given mono image /// </summary> /// <param name="img_mono"> /// mono image data <see cref="System.Byte"/> /// </param> /// <param name="img_width"> /// image width <see cref="System.Int32"/> /// </param> /// <param name="img_height"> /// image height <see cref="System.Int32"/> /// </param> /// <param name="ignore_periphery"> /// whether to ignore edge features which stray into the border of the image <see cref="System.Boolean"/> /// </param> /// <param name="image_border_percent"> /// percentage of the image considred to be the surrounding border <see cref="System.Int32"/> /// </param> /// <param name="grouping_radius_percent"> /// radius used to group adjacent edge features <see cref="System.Int32"/> /// </param> /// <param name="erosion_dilation"> /// erosion dilation level <see cref="System.Int32"/> /// </param> /// <param name="black_on_white"> /// whether this image contains dark markings on a lighter background <see cref="System.Boolean"/> /// </param> /// <param name="minimum_volume_percent"> /// minimum volume of the square as a percentage of the image volume (prevents very small stuff being detected) <see cref="System.Int32"/> /// </param> /// <param name="use_original_image"> /// whether to allow img_mono to be altered /// </param> /// <param name="minimum_aspect_ratio"> /// minimum aspect ratio when searching for valid regions /// </param> /// <param name="maximum_aspect_ratio"> /// maximum aspect ratio when searching for valid regions /// </param> /// <returns> /// list of polygons representing the square regions <see cref="List`1"/> /// </returns> public static List<polygon2D> DetectSquaresMono(byte[] mono_img, int img_width, int img_height, bool ignore_periphery, int image_border_percent, int[] grouping_radius_percent, int[] erosion_dilation, bool black_on_white, int minimum_volume_percent, bool use_original_image, float minimum_aspect_ratio, float maximum_aspect_ratio, bool squares_only, bool debug, int circular_ROI_radius, ref List<int> edges) { #if SHOW_TIMINGS stopwatch timer_square_detection = new stopwatch(); timer_square_detection.Start(); #endif const int minimum_size_percent = 5; const int connect_edges_radius = 5; // maximum recursion depth when joining edges const int max_search_depth = 8000; // create a list to store the results List<polygon2D> square_shapes = new List<polygon2D>(); // create edge detection object CannyEdgeDetector edge_detector = new CannyEdgeDetector(); edge_detector.automatic_thresholds = true; edge_detector.enable_edge_orientations = false; #if SHOW_TIMINGS stopwatch timer_copy_invert = new stopwatch(); timer_copy_invert.Start(); #endif byte[] img_mono = mono_img; if (!use_original_image) { // make a copy of the original image (we don't want to alter it) img_mono = (byte[])mono_img.Clone(); } // if the image contains light markings on a darker background invert the pixels if (!black_on_white) { for (int i = img_mono.Length-1; i >= 0; i--) { img_mono[i] = (byte)(255 - img_mono[i]); } } #if SHOW_TIMINGS timer_copy_invert.Stop(); if (timer_copy_invert.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: copy/invert " + timer_copy_invert.time_elapsed_mS.ToString() ); #endif int image_border = img_width * image_border_percent / 100; byte[] img_mono2 = null; byte[] eroded = null; byte[] dilated = null; bool previous_eroded = false; bool previous_dilated = false; // try each erosion/dilation level for (int erosion_dilation_level = 0; erosion_dilation_level < erosion_dilation.Length; erosion_dilation_level++) { #if SHOW_TIMINGS stopwatch timer_erode_dilate = new stopwatch(); timer_erode_dilate.Start(); #endif // erode if (erosion_dilation[erosion_dilation_level] > 0) { if (previous_eroded) eroded = image.Erode(eroded, img_width, img_height, erosion_dilation[erosion_dilation_level] - erosion_dilation[erosion_dilation_level-1], eroded); else eroded = image.Erode(img_mono, img_width, img_height, erosion_dilation[erosion_dilation_level], null); img_mono2 = eroded; previous_eroded = true; } // dilate if (erosion_dilation[erosion_dilation_level] < 0) { if (previous_dilated) dilated = image.Dilate(dilated, img_width, img_height, -erosion_dilation[erosion_dilation_level] + erosion_dilation[erosion_dilation_level-1], dilated); else dilated = image.Dilate(img_mono, img_width, img_height, -erosion_dilation[erosion_dilation_level], null); img_mono2 = dilated; previous_dilated = true; } // just copy the original image if (erosion_dilation[erosion_dilation_level] == 0) { if (erosion_dilation_level > 0) img_mono2 = (byte[])img_mono.Clone(); else img_mono2 = img_mono; previous_eroded = false; previous_dilated = false; } #if SHOW_TIMINGS timer_erode_dilate.Stop(); if (timer_erode_dilate.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: erode/dilate " + timer_erode_dilate.time_elapsed_mS.ToString() ); stopwatch timer_edge_detection = new stopwatch(); timer_edge_detection.Start(); #endif // detect edges using canny algorithm edge_detector.Update(img_mono2, img_width, img_height); #if SHOW_TIMINGS timer_edge_detection.Stop(); if (timer_edge_detection.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: edge detect " + timer_edge_detection.time_elapsed_mS.ToString() ); #endif if (debug) { string filename = "canny_magnitudes_" + erosion_dilation_level.ToString() + ".bmp"; byte[] magnitudes = new byte[img_width * img_height * 3]; edge_detector.ShowMagnitudes(magnitudes, img_width, img_height); Bitmap magnitudes_bmp = new Bitmap(img_width, img_height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); BitmapArrayConversions.updatebitmap_unsafe(magnitudes, magnitudes_bmp); magnitudes_bmp.Save(filename, System.Drawing.Imaging.ImageFormat.Bmp); } #if SHOW_TIMINGS stopwatch timer_edge_joining = new stopwatch(); timer_edge_joining.Start(); #endif // connect edges which are a short distance apart edge_detector.ConnectBrokenEdges(connect_edges_radius); #if SHOW_TIMINGS timer_edge_joining.Stop(); if (timer_edge_joining.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: edge joining " + timer_edge_joining.time_elapsed_mS.ToString() ); #endif // remove edges which are outside a circular region of interest if (circular_ROI_radius > 0) { int circular_ROI_radius_sqr = circular_ROI_radius * circular_ROI_radius; int half_width = img_width / 2; int half_height = img_height / 2; for (int i = edge_detector.edges.Count - 2; i >= 0; i -= 2) { int x = edge_detector.edges[i]; int y = edge_detector.edges[i+1]; int dx = x - half_width; int dy = y - half_height; int dist_sqr = (dx*dx)+(dy*dy); if (dist_sqr > circular_ROI_radius_sqr) { edge_detector.edges.RemoveAt(i+1); edge_detector.edges.RemoveAt(i); } } } edges = edge_detector.edges; // try different groupings for (int group_radius_index = 0; group_radius_index < grouping_radius_percent.Length; group_radius_index++) { #if SHOW_TIMINGS stopwatch timer_grouping = new stopwatch(); timer_grouping.Start(); stopwatch timer_perim2 = new stopwatch(); timer_perim2.Start(); #endif // group edges together into objects List<List<int>> groups = GetGroups(edge_detector.edges, img_width, img_height, image_border, minimum_size_percent, false, max_search_depth, ignore_periphery, grouping_radius_percent[group_radius_index]); #if SHOW_TIMINGS timer_grouping.Stop(); if (timer_grouping.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: edge grouping " + timer_grouping.time_elapsed_mS.ToString() ); #endif if (groups != null) { #if SHOW_TIMINGS stopwatch timer_aspect = new stopwatch(); timer_aspect.Start(); #endif //List<List<int>> squares = groups; // get the set of edges with aspect ratio closest to square List<List<int>> squares = GetValidGroups(groups, img_width, img_height, //minimum_aspect_ratio, maximum_aspect_ratio, minimum_size_percent); #if SHOW_TIMINGS timer_grouping.Stop(); if (timer_aspect.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: aspect checking " + timer_aspect.time_elapsed_mS.ToString() ); #endif if (squares != null) { for (int i = 0; i < squares.Count; i++) { #if SHOW_TIMINGS stopwatch timer_periphery = new stopwatch(); timer_periphery.Start(); #endif List<int> square = squares[i]; polygon2D perim = null; List<List<int>> periphery = GetSquarePeriphery(square, erosion_dilation[erosion_dilation_level], ref perim); #if SHOW_TIMINGS timer_periphery.Stop(); //if (timer_periphery.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: periphery detect " + timer_periphery.time_elapsed_mS.ToString() ); #endif if (perim != null) { float longest_side = perim.getLongestSide(); float shortest_side = perim.getShortestSide(); // not too small if (shortest_side * 100 / img_width > minimum_volume_percent) { float aspect = shortest_side / longest_side; if ((aspect > minimum_aspect_ratio) && (aspect < maximum_aspect_ratio)) { // check the angles bool angle_out_of_range = false; int vertex = 0; while ((vertex < perim.x_points.Count) && (!angle_out_of_range) && (vertex < 4)) { float angle = perim.GetInteriorAngle(vertex); angle = angle / (float)Math.PI * 180; if ((angle < 70) || (angle > 110)) angle_out_of_range = true; vertex++; } if (!angle_out_of_range) { float aspect1 = perim.getSideLength(0) / perim.getSideLength(2); if ((aspect1 > 0.9f) && (aspect1 < 1.1f)) { float aspect2 = perim.getSideLength(1) / perim.getSideLength(3); if ((aspect2 > 0.9f) && (aspect2 < 1.1f)) square_shapes.Add(perim); } } } } } } } } #if SHOW_TIMINGS timer_perim2.Stop(); if (timer_perim2.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: grouping and periphery " + timer_perim2.time_elapsed_mS.ToString()); #endif } } // remove any overlapping squares square_shapes = SelectSquares(square_shapes, 0); #if SHOW_TIMINGS timer_square_detection.Stop(); if (timer_square_detection.time_elapsed_mS > 20) Console.WriteLine("DetectSquaresMono: " + timer_square_detection.time_elapsed_mS.ToString() ); #endif return(square_shapes); }
public static void ShowPeriphery(string filename, string periphery_filename, int grouping_radius_percent, int erosion_dilation, int minimum_size_percent, bool black_on_white, bool squares_only) { const int max_search_depth = 8000; const int connect_edges_radius = 5; if (File.Exists(filename)) { Bitmap bmp = (Bitmap)Bitmap.FromFile(filename); byte[] img = new byte[bmp.Width * bmp.Height * 3]; byte[] squares_img = null; //new byte[bmp.Width * bmp.Height * 3]; BitmapArrayConversions.updatebitmap(bmp, img); // convert to mono byte[] img_mono = image.monoImage(img, bmp.Width, bmp.Height); if (!black_on_white) { for (int i = 0; i < img_mono.Length; i++) img_mono[i] = (byte)(255 - img_mono[i]); } if (erosion_dilation > 0) { byte[] eroded = image.Erode(img_mono, bmp.Width, bmp.Height, erosion_dilation, null); img_mono = eroded; } if (erosion_dilation < 0) { byte[] dilated = image.Dilate(img_mono, bmp.Width, bmp.Height, -erosion_dilation, null); img_mono = dilated; } CannyEdgeDetector edge_detector = new CannyEdgeDetector(); edge_detector.automatic_thresholds = true; edge_detector.Update(img_mono, bmp.Width, bmp.Height); edge_detector.ConnectBrokenEdges(connect_edges_radius); // group edges together into objects List<List<int>> groups = GetGroups(edge_detector.edges, bmp.Width, bmp.Height, 0, minimum_size_percent, false, max_search_depth, false, grouping_radius_percent); if (groups != null) { float minimum_aspect_ratio = 0.7f; float maximum_aspect_ratio = 1.3f; if (!squares_only) { minimum_aspect_ratio = 0.3f; maximum_aspect_ratio = 20.0f; } // get the set of edges with aspect ratio closest to square List<List<int>> squares = GetAspectRange(groups, bmp.Width, bmp.Height, minimum_aspect_ratio, maximum_aspect_ratio, minimum_size_percent, squares_only); if (squares != null) { for (int i = 0; i < squares.Count; i++) { List<int> square = squares[i]; polygon2D perim = null; List<List<int>> periphery = GetSquarePeriphery(square, erosion_dilation, ref perim); if (perim != null) { squares_img = ShowEdges(ref squares_img, periphery, bmp.Width, bmp.Height); if (squares_img != null) perim.show(squares_img, bmp.Width, bmp.Height, 150, 150, 150, 0); } } } } Bitmap periphery_bmp = new Bitmap(bmp.Width, bmp.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); BitmapArrayConversions.updatebitmap_unsafe(squares_img, periphery_bmp); periphery_bmp.Save(periphery_filename, System.Drawing.Imaging.ImageFormat.Bmp); } }
public static void ShowRectangles(string filename, string square_filename, bool ignore_periphery, int image_border_percent, int grouping_radius_percent, int minimum_size_percent, bool show_centres, int erosion_dilation, bool black_on_white, int minimum_volume_percent) { const int max_search_depth = 8000; const int connect_edges_radius = 5; if (File.Exists(filename)) { Bitmap bmp = (Bitmap)Bitmap.FromFile(filename); byte[] img = new byte[bmp.Width * bmp.Height * 3]; BitmapArrayConversions.updatebitmap(bmp, img); if (!black_on_white) { for (int i = 0; i < img.Length; i++) { img[i] = (byte)(255 - img[i]); } } int image_border = bmp.Width * image_border_percent / 100; // convert to mono byte[] img_mono = image.monoImage(img, bmp.Width, bmp.Height); if (erosion_dilation > 0) { byte[] eroded = image.Erode(img_mono, bmp.Width, bmp.Height, erosion_dilation, null); img_mono = eroded; } if (erosion_dilation < 0) { byte[] dilated = image.Dilate(img_mono, bmp.Width, bmp.Height, -erosion_dilation, null); img_mono = dilated; } CannyEdgeDetector edge_detector = new CannyEdgeDetector(); edge_detector.automatic_thresholds = true; edge_detector.Update(img_mono, bmp.Width, bmp.Height); edge_detector.ConnectBrokenEdges(connect_edges_radius); List<List<int>> groups = GetGroups(edge_detector.edges, bmp.Width, bmp.Height, image_border, minimum_size_percent, false, max_search_depth, ignore_periphery, grouping_radius_percent); if (groups != null) { // get the set of edges with aspect ratio closest to square List<List<int>> squares = GetAspectRange(groups, bmp.Width, bmp.Height, 0.3f, 20.0f, minimum_size_percent, false); //Console.WriteLine("squares: " + squares.Count.ToString()); if (squares != null) { byte[] squareImage = null; squareImage = ShowEdges(ref squareImage, squares, bmp.Width, bmp.Height); Bitmap square_bmp = new Bitmap(bmp.Width, bmp.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); BitmapArrayConversions.updatebitmap_unsafe(squareImage, square_bmp); square_bmp.Save(square_filename, System.Drawing.Imaging.ImageFormat.Bmp); } } } }
public static void ShowPerimeters(string filename, string edges_filename, string magnitudes_filename, string perimeters_filename, bool ignore_periphery, int grouping_radius_percent, int minimum_size_percent, bool show_centres, int erosion_dilation, bool black_on_white) { const int max_search_depth = 8000; const int connect_edges_radius = 5; if (File.Exists(filename)) { Bitmap bmp = (Bitmap)Bitmap.FromFile(filename); byte[] img = new byte[bmp.Width * bmp.Height * 3]; BitmapArrayConversions.updatebitmap(bmp, img); if (!black_on_white) { for (int i = 0; i < img.Length; i++) { img[i] = (byte)(255 - img[i]); } } // convert to mono byte[] img_mono = image.monoImage(img, bmp.Width, bmp.Height); if (erosion_dilation > 0) { byte[] eroded = image.Erode(img_mono, bmp.Width, bmp.Height, erosion_dilation, null); img_mono = eroded; } if (erosion_dilation < 0) { byte[] dilated = image.Dilate(img_mono, bmp.Width, bmp.Height, -erosion_dilation, null); img_mono = dilated; } CannyEdgeDetector edge_detector = new CannyEdgeDetector(); edge_detector.automatic_thresholds = true; edge_detector.Update(img_mono, bmp.Width, bmp.Height); edge_detector.ConnectBrokenEdges(connect_edges_radius); byte[] magnitudes = new byte[bmp.Width * bmp.Height * 3]; edge_detector.ShowMagnitudes(magnitudes, bmp.Width, bmp.Height); Bitmap magnitudes_bmp = new Bitmap(bmp.Width, bmp.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); BitmapArrayConversions.updatebitmap_unsafe(magnitudes, magnitudes_bmp); magnitudes_bmp.Save(magnitudes_filename, System.Drawing.Imaging.ImageFormat.Bmp); /* byte[] perimetersImage = ShowLongestPerimeters(edge_detector.edges, bmp.Width, bmp.Height, 0, minimum_size_percent, false, max_search_depth, ignore_periphery, show_centres); */ List<List<int>> groups = null; byte[] perimetersImage = ShowGroups(edge_detector.edges, bmp.Width, bmp.Height, 0, minimum_size_percent, false, max_search_depth, ignore_periphery, grouping_radius_percent, ref groups); Bitmap edges_bmp = new Bitmap(bmp.Width, bmp.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); BitmapArrayConversions.updatebitmap_unsafe(edge_detector.getEdgesImage(), edges_bmp); edges_bmp.Save(edges_filename, System.Drawing.Imaging.ImageFormat.Bmp); Bitmap perimeters_bmp = new Bitmap(bmp.Width, bmp.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); BitmapArrayConversions.updatebitmap_unsafe(perimetersImage, perimeters_bmp); perimeters_bmp.Save(perimeters_filename, System.Drawing.Imaging.ImageFormat.Bmp); } }
public static List<calibrationDot> DetectDots(byte[] mono_img, byte[] img_colour, int img_width, int img_height, int grouping_radius_percent, int erosion_dilation, float minimum_width, float maximum_width, ref List<int> edges, ref List<List<int>> groups) { // create a list to store the results List<calibrationDot> dots = null; const int connect_edges_radius = 5; int minimum_width_pixels = (int)(minimum_width * img_width / 100); int maximum_width_pixels = (int)(maximum_width * img_width / 100); int minimum_size_percent = (int)((minimum_width_pixels*minimum_width_pixels) * 100 / (img_width*img_height)); // maximum recursion depth when joining edges const int max_search_depth = 8000; // create edge detection object CannyEdgeDetector edge_detector = new CannyEdgeDetector(); edge_detector.automatic_thresholds = true; edge_detector.enable_edge_orientations = false; byte[] img_mono = (byte[])mono_img.Clone(); // erode if (erosion_dilation > 0) { byte[] eroded = image.Erode(img_mono, img_width, img_height, erosion_dilation, null); img_mono = eroded; } // dilate if (erosion_dilation < 0) { byte[] dilated = image.Dilate(img_mono, img_width, img_height, -erosion_dilation, null); img_mono = dilated; } // detect edges using canny algorithm edge_detector.Update(img_mono, img_width, img_height); // connect edges which are a short distance apart edge_detector.ConnectBrokenEdges(connect_edges_radius); edges = edge_detector.edges; // group edges together into objects groups = GetGroups(edge_detector.edges, img_width, img_height, 0, minimum_size_percent, false, max_search_depth, false, grouping_radius_percent); if (groups != null) { dots = GetDots(groups, img_colour, img_width, img_height, 0.7f, 1.3f, minimum_width_pixels, maximum_width_pixels); } // remove any overlapping squares //dot_shapes = SelectSquares(dot_shapes, 0); return(dots); }