/// <summary> /// returns the length of the longest path from this blob feature /// </summary> /// <param name="mark_path">mark the longest path as being selected</param> /// <param name="best_end_point">blob at the longest distance</param> /// <returns></returns> public int getLongestPath(bool mark_path, ref blob end_point) { int longest = 0; float best_ang = 0; float tollerance_degrees = 5; for (int i = 0; i < neighbours.Count; i++) { float ang = (float)angle[i]; blob curr_end_point = null; int length = getDirectionalLength(ang, false, true, 0, 1000, tollerance_degrees, ref curr_end_point); if (length > longest) { longest = length; best_ang = ang; end_point = curr_end_point; } } if ((longest > 0) && (mark_path)) { blob curr_end_point = null; getDirectionalLength(best_ang, true, true, 0, 1000, tollerance_degrees, ref curr_end_point); } return(longest); }
/// <summary> /// returns the distance from this blob to another /// </summary> /// <param name="other">the other blob object</param> /// <returns>distance to the other blob</returns> public float getSeparation(blob other) { float dx = other.interpolated_x - interpolated_x; float dy = other.interpolated_y - interpolated_y; float dist = (float)Math.Sqrt((dx * dx) + (dy * dy)); return(dist); }
/// <summary> /// returns a copy of this blob /// </summary> public blob Copy() { blob new_blob = new blob(x, y); new_blob.interpolated_x = interpolated_x; new_blob.interpolated_y = interpolated_y; new_blob.average_radius = average_radius; new_blob.ovality = ovality; new_blob.average_intensity = average_intensity; return (new_blob); }
/// <summary> /// returns a copy of this blob /// </summary> public blob Copy() { blob new_blob = new blob(x, y); new_blob.interpolated_x = interpolated_x; new_blob.interpolated_y = interpolated_y; new_blob.average_radius = average_radius; new_blob.ovality = ovality; new_blob.average_intensity = average_intensity; return(new_blob); }
/// <summary> /// add a neighbour if it's in da hood /// </summary> /// <param name="neighbour">a possibly neighbouring blob</param> /// <param name="neighbourhood_radius">the neighbourhood event horizon</param> public bool AddNeighbour(blob neighbour, float neighbourhood_radius) { float dx = neighbour.interpolated_x - interpolated_x; float dy = neighbour.interpolated_y - interpolated_y; float dist = (float)Math.Sqrt((dx * dx) + (dy * dy)); if (dist < neighbourhood_radius) { AddNeighbour(neighbour); return(true); } else { return(false); } }
/// <summary> /// everybody needs good neighbours... /// </summary> /// <param name="neighbour">a neighbouring blob</param> public void AddNeighbour(blob neighbour) { // update the neighbours list neighbours.Add(neighbour); // calculate the separation float dist = getSeparation(neighbour); separation.Add(dist); // calculate the angle to this neighbour float ang = 0; if (dist > 0.1f) { float dx = neighbour.interpolated_x - interpolated_x; float dy = neighbour.interpolated_y - interpolated_y; ang = (float)Math.Asin(dx / dist); if (dy < 0) { ang = (float)(Math.PI * 2) - ang; } if (ang < 0) { ang += (float)(Math.PI); } if (ang == float.NaN) { ang = 0; } } angle.Add(ang); // populate the direction lookup table int ang_index = (int)Math.Round((ang * 180 / Math.PI) / direction_increment_degrees); if (ang_index >= neighbourDirections.Length) { ang_index = neighbourDirections.Length - 1; } if (neighbourDirections[ang_index] == null) { neighbourDirections[ang_index] = new ArrayList(); } neighbourDirections[ang_index].Add(neighbours.Count - 1); }
/// <summary> /// add a neighbour if it's in da hood /// </summary> /// <param name="neighbour">a possibly neighbouring blob</param> /// <param name="neighbourhood_radius">the neighbourhood event horizon</param> public bool AddNeighbour(blob neighbour, float neighbourhood_radius) { float dx = neighbour.interpolated_x - interpolated_x; float dy = neighbour.interpolated_y - interpolated_y; float dist = (float)Math.Sqrt((dx * dx) + (dy * dy)); if (dist < neighbourhood_radius) { AddNeighbour(neighbour); return (true); } else return (false); }
/// <summary> /// returns the distance from this blob to another /// </summary> /// <param name="other">the other blob object</param> /// <returns>distance to the other blob</returns> public float getSeparation(blob other) { float dx = other.interpolated_x - interpolated_x; float dy = other.interpolated_y - interpolated_y; float dist = (float)Math.Sqrt((dx * dx) + (dy * dy)); return (dist); }
/// <summary> /// returns the length of the longest path from this blob feature /// </summary> /// <param name="mark_path">mark the longest path as being selected</param> /// <param name="best_end_point">blob at the longest distance</param> /// <returns></returns> public int getLongestPath(bool mark_path, ref blob end_point) { int longest = 0; float best_ang = 0; float tollerance_degrees = 5; for (int i = 0; i < neighbours.Count; i++) { float ang = (float)angle[i]; blob curr_end_point = null; int length = getDirectionalLength(ang, false, true,0,1000, tollerance_degrees, ref curr_end_point); if (length > longest) { longest = length; best_ang = ang; end_point = curr_end_point; } } if ((longest > 0) && (mark_path)) { blob curr_end_point = null; getDirectionalLength(best_ang, true, true,0,1000, tollerance_degrees, ref curr_end_point); } return (longest); }
public int getDirectionalLength(float direction_radians, bool mark_path, bool ignore_selected, int depth, int max_depth, float tollerance_degrees, ref blob end_point) { int length = 0; // mark the path if necessary if (mark_path) selected = true; if (depth < max_depth) { // get the index of the lookup table for this direction int ang_index = (int)Math.Round((direction_radians * 180 / Math.PI) / direction_increment_degrees); if (ang_index >= neighbourDirections.Length) ang_index -= neighbourDirections.Length; // search for neighbours in this direction float max_ang_diff = tollerance_degrees * (float)Math.PI / 180.0f; //float closest_ang = 0; blob winner = null; for (int j = -1; j <= 1; j++) { if ((ang_index + j > -1) && (ang_index + j < neighbourDirections.Length)) { // are there any neighbours in this direction? if (neighbourDirections[ang_index + j] != null) { // search through all neighbours to find the one which // has the most similar direction for (int i = 0; i < neighbourDirections[ang_index + j].Count; i++) { int neighbour_index = (int)neighbourDirections[ang_index + j][i]; if ((!ignore_selected) || ((ignore_selected) && (!((blob)neighbours[neighbour_index]).selected))) { float neighbour_ang = (float)angle[neighbour_index]; float ang_diff = Math.Abs(neighbour_ang - direction_radians); if (ang_diff < max_ang_diff) { // this is the most similar direction yet found //closest_ang = neighbour_ang; max_ang_diff = ang_diff; winner = (blob)neighbours[neighbour_index]; } } } } } } if ((winner != null) && (winner != this)) { // alter the search direction slighly // note: this might not be needed //direction_radians = (direction_radians * 0.8f) + (closest_ang * 0.2f); // keep note of the end point end_point = winner; // update the length, then carry on searching length = 1 + winner.getDirectionalLength(direction_radians, mark_path, ignore_selected, depth + 1, max_depth, tollerance_degrees, ref end_point); } } return (length); }
/// <summary> /// everybody needs good neighbours... /// </summary> /// <param name="neighbour">a neighbouring blob</param> public void AddNeighbour(blob neighbour) { // update the neighbours list neighbours.Add(neighbour); // calculate the separation float dist = getSeparation(neighbour); separation.Add(dist); // calculate the angle to this neighbour float ang = 0; if (dist > 0.1f) { float dx = neighbour.interpolated_x - interpolated_x; float dy = neighbour.interpolated_y - interpolated_y; ang = (float)Math.Asin(dx / dist); if (dy < 0) ang = (float)(Math.PI * 2) - ang; if (ang < 0) ang += (float)(Math.PI); if (ang == float.NaN) ang = 0; } angle.Add(ang); // populate the direction lookup table int ang_index = (int)Math.Round((ang * 180 / Math.PI) / direction_increment_degrees); if (ang_index >= neighbourDirections.Length) ang_index = neighbourDirections.Length - 1; if (neighbourDirections[ang_index] == null) neighbourDirections[ang_index] = new ArrayList(); neighbourDirections[ang_index].Add(neighbours.Count-1); }
/// <summary> /// detect spots within the region /// </summary> /// <param name="black_on_white">whether this image contains darker features on a lighter background</param> /// <param name="spot_detection_threshold">threshold used to remove low magnitude spot responses</param> /// <param name="max_radius_variance">maximum allowable variation in spot radius</param> /// <param name="minimum_spot_diameter_percent">minimum spot diameter as a percentage of the region height, which allows the system to ignore noise</param> public void detectSpots(bool black_on_white, float spot_detection_threshold, float max_radius_variance, float minimum_spot_diameter_percent) { // create a map of spottyness spot_map = new float[width, height]; // this map will contain the positions of spot centres // after non-maximal supression float[,] spot_centres_map = new float[width, height]; float maximal_response = 0; // estimate the spot radius if necessary if (spot_radius == 0) spot_radius = getSpotAverageRadius(black_on_white, minimum_spot_diameter_percent); // spot radius as an integer //int spot_radius_rounded = (int)Math.Round(spot_radius); int spot_radius_rounded = (int)spot_radius; // make an integral image from the binary image to speed things up int[,] integral_image = binaryIntegralImage(black_on_white); // apply the mask to the region of interest for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { int txx = x - spot_radius_rounded; int tyy = y - spot_radius_rounded; int bxx = x + spot_radius_rounded; int byy = y + spot_radius_rounded; if (txx < 0) txx = 0; if (tyy < 0) tyy = 0; if (bxx >= width) bxx = width - 1; if (byy >= height) byy = height - 1; // get the magnitude of response at this point int spot_response_magnitude = binaryGetIntegral(integral_image, txx, tyy, bxx, byy); // update the map using squared magnitude int squared_magnitude = spot_response_magnitude * spot_response_magnitude; spot_map[x, y] = squared_magnitude; // record the maximal response if (squared_magnitude > maximal_response) maximal_response = squared_magnitude; } } if (maximal_response > 0) { // errode the contours of the map (like mountains becoming weathered) // this helps to eliminate plateau regions where the // response is locally maximum // erode horizontally for (int y = 1; y < height; y++) { float prev_spot_value = 0; for (int x = 1; x < width; x++) { float spot_value = spot_map[x, y]; float new_spot_value = prev_spot_value + ((spot_value - prev_spot_value) / 2); spot_map[x, y] = new_spot_value; prev_spot_value = new_spot_value; } } // erode vertically maximal_response = 0; for (int x = 1; x < width; x++) { float prev_spot_value = 0; for (int y = 1; y < height; y++) { float spot_value = spot_map[x, y]; float new_spot_value = prev_spot_value + ((spot_value - prev_spot_value) / 2); spot_map[x, y] = new_spot_value; prev_spot_value = new_spot_value; // find the maximal response, which will allow // subsequent normalisation if (new_spot_value > maximal_response) maximal_response = new_spot_value; } } // normalise spot responses, and apply threshold to spot centres map for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { float spot_value = spot_map[x, y] / maximal_response; spot_centres_map[x, y] = spot_value; spot_map[x, y] = spot_value; if (spot_value < spot_detection_threshold) spot_centres_map[x, y] = 0; } // perform non-maximal supression int supression_radius = (int)(spot_radius * 19 / 10); for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { if (spot_centres_map[x, y] > 0) { bool killed = false; int xx = x - supression_radius; while ((xx <= x + supression_radius) && (!killed)) { if ((xx > -1) && (xx < width)) { int yy = y - supression_radius; while ((yy <= y + supression_radius) && (!killed)) { if ((yy > -1) && (yy < height)) { if (!((xx == x) && (yy == y))) { if (spot_centres_map[x, y] >= spot_centres_map[xx, yy]) spot_centres_map[xx, yy] = 0; else { spot_centres_map[x, y] = 0; killed = true; } } } yy++; } } xx++; } } } // store the spot centres in a list spots = new ArrayList(); for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) if (spot_centres_map[x, y] > 0) { blob new_spot = new blob(x, y); spots.Add(new_spot); } // sub-pixel interpolate spot positions for (int i = 0; i < spots.Count; i++) { blob spot = (blob)spots[i]; float interpolated_x = 0; float interpolated_y = 0; float interpolated_total = 0; for (int x = (int)spot.x - 1; x <= (int)spot.x + 1; x++) { if ((x > -1) && (x < width)) { for (int y = (int)spot.y - 1; y <= (int)spot.y + 1; y++) { if ((y > -1) && (y < height)) { float map_value = spot_map[x, y]; map_value *= map_value; interpolated_total += map_value; interpolated_x += (map_value * x); interpolated_y += (map_value * y); } } } } if (interpolated_total > 0) { interpolated_x /= interpolated_total; interpolated_y /= interpolated_total; spot.interpolated_x = interpolated_x; spot.interpolated_y = interpolated_y; } } // detect radii detectSpotRadii(max_radius_variance); } }
/// <summary> /// returns a list of spots arranged in a line between the two given spots /// </summary> /// <param name="spot1">the first spot</param> /// <param name="spot2">the second spot</param> /// <param name="max_distance">the maximum perpendicular distance below which the spot is considered to touch the line, in the range, typically in the range 0.0-1.0 as a fraction of the spot radius</param> /// <returns></returns> private ArrayList SpotsConnected(blob spot1, blob spot2, float max_distance, ref float average_separation) { return (SpotsIntersectWithLine(spot1.interpolated_x, spot1.interpolated_y, spot2.interpolated_x, spot2.interpolated_y, max_distance, ref average_separation)); }
public int getDirectionalLength(float direction_radians, bool mark_path, bool ignore_selected, int depth, int max_depth, float tollerance_degrees, ref blob end_point) { int length = 0; // mark the path if necessary if (mark_path) { selected = true; } if (depth < max_depth) { // get the index of the lookup table for this direction int ang_index = (int)Math.Round((direction_radians * 180 / Math.PI) / direction_increment_degrees); if (ang_index >= neighbourDirections.Length) { ang_index -= neighbourDirections.Length; } // search for neighbours in this direction float max_ang_diff = tollerance_degrees * (float)Math.PI / 180.0f; //float closest_ang = 0; blob winner = null; for (int j = -1; j <= 1; j++) { if ((ang_index + j > -1) && (ang_index + j < neighbourDirections.Length)) { // are there any neighbours in this direction? if (neighbourDirections[ang_index + j] != null) { // search through all neighbours to find the one which // has the most similar direction for (int i = 0; i < neighbourDirections[ang_index + j].Count; i++) { int neighbour_index = (int)neighbourDirections[ang_index + j][i]; if ((!ignore_selected) || ((ignore_selected) && (!((blob)neighbours[neighbour_index]).selected))) { float neighbour_ang = (float)angle[neighbour_index]; float ang_diff = Math.Abs(neighbour_ang - direction_radians); if (ang_diff < max_ang_diff) { // this is the most similar direction yet found //closest_ang = neighbour_ang; max_ang_diff = ang_diff; winner = (blob)neighbours[neighbour_index]; } } } } } } if ((winner != null) && (winner != this)) { // alter the search direction slighly // note: this might not be needed //direction_radians = (direction_radians * 0.8f) + (closest_ang * 0.2f); // keep note of the end point end_point = winner; // update the length, then carry on searching length = 1 + winner.getDirectionalLength(direction_radians, mark_path, ignore_selected, depth + 1, max_depth, tollerance_degrees, ref end_point); } } return(length); }