public static void FitContoursToSize(double drawingWidth, double drawingHeight, FloatContour unknownContour, FloatContour dbContour, out Contour displayUnknownContour, out Contour displayDBContour, out double xOffset, out double yOffset) { if (unknownContour == null && dbContour == null) { throw new ArgumentNullException("Both contours null!"); } float xMax, yMax, xMin, yMin; if (unknownContour == null) { xMax = dbContour.MaxX(); yMax = dbContour.MaxY(); xMin = dbContour.MinX(); yMin = dbContour.MinY(); } else if (dbContour == null) { xMax = unknownContour.MaxX(); yMax = unknownContour.MaxY(); xMin = unknownContour.MinX(); yMin = unknownContour.MinY(); } else { xMax = (dbContour.MaxX() > (float)unknownContour.MaxX()) ? dbContour.MaxX() : (float)unknownContour.MaxX(); yMax = (dbContour.MaxY() > (float)unknownContour.MaxY()) ? dbContour.MaxY() : (float)unknownContour.MaxY(); xMin = (dbContour.MinX() < (float)unknownContour.MinX()) ? dbContour.MinX() : (float)unknownContour.MinX(); yMin = (dbContour.MinY() < (float)unknownContour.MinY()) ? dbContour.MinY() : (float)unknownContour.MinY(); } float xRange = xMax - xMin + 8, //***1.5 - added POINT_SIZE yRange = yMax - yMin + 8; //***1.5 - added POINT_SIZE float heightRatio = (float)(drawingWidth / yRange), widthRatio = (float)(drawingHeight / xRange); float ratio; if (heightRatio < widthRatio) { ratio = heightRatio; xOffset = (drawingWidth - ratio * xRange) / 2 - xMin * ratio; yOffset = 0 - yMin * ratio; } else { ratio = widthRatio; xOffset = 0 - xMin * ratio; yOffset = (drawingHeight - ratio * yRange) / 2 - yMin * ratio; } float ratioInverse = 1 / ratio; if (unknownContour == null) { displayUnknownContour = null; } else { displayUnknownContour = new Contour(unknownContour, ratioInverse); } if (dbContour == null) { displayDBContour = null; } else { displayDBContour = new Contour(dbContour, ratioInverse); } }
/* TODO: References spots in old code * The main algorithm to extract a fin outline from an image. * This method is called from all of this class's constructors, which are called in * TraceWindow.cxx (on_traceButtonImageOK_clicked). * * There are 9 stages: * * 1) Construct, analyize histogram * 2) Threshold GrayImage to create BinaryImage * 3) Clean up / get a cleanner edge through morphological processes * a) open (erode, dialate) * b) erosion with high coefficient to clean up noise, but leave the fin intact * c) AND with the orginal BinaryImage (#3) to restore the fin shape * 4) Feature recognition to select the largest Feature / blob * 5) Get a one pixel outline through one erosion and XORing with #4 * 6) Feature recognition to slect the largest outline (feature /blob) (same code as #4) * 7) Find the start point * A valid start point (p1) must be in math quadrant III * and be followed by two points (p2,p3) * such that p1.row>p2.row>p3.row && p1.col<p2.col<p3.col * 8) Walk the outline from the starting point, recording a pixel in the contour every 3 pixels * 9) Walk ends when no more black pixels may be found. The outline is recouresed backwards until * a valid end point (p1) is found. A valid end point must be in math quadrant IV * and be prefaced by two points (p2,p3) such that p1.row>p2.row>p3.row && p1.col>p2.col>p3.col * * The Contour represented by this object has a length of 0 if at anytime the algorithm * cannot continue. This often occurs for images of poor contrast. * * The Contour represented by this object is adjusted with the snake code in TraceWindow.cxx * if its length is greater than 0. * */ public Contour GetPointsFromBitmap(ref DirectBitmap bmp, Contour ctour, int left, int top, int right, int bottom) { // Resample the image to lower resolution int width = right - left; int factor = 1; while (width / (float)factor > 1024) { //***1.96 - changed magic num JHS factor *= 2; } DirectBitmap workingBmp; if (factor > 1) { workingBmp = DirectBitmapHelper.ResizePercentageNearestNeighbor(bmp, 100.0f / factor); } else { workingBmp = new DirectBitmap(bmp.Bitmap); } int xoffset; int yoffset; workingBmp = DirectBitmapHelper.ApplyBounds(workingBmp, left, top, right, bottom, factor, out xoffset, out yoffset); IntensityHistogram histogram = new IntensityHistogram(workingBmp); Range lowestRange = histogram.FindNextValley(0); Range nextRange = histogram.FindNextValley(lowestRange.End); int totalPixels = workingBmp.Width * workingBmp.Height; // TODO: Magic numbers? if (nextRange.Tip - lowestRange.End < 25 && nextRange.HighestValue > totalPixels / 256) { lowestRange.End = nextRange.End; //TODO: update other values in strut as needed } // TODO: Magic numbers? // low contrast check if (lowestRange.PixelCount > totalPixels * .7 || lowestRange.End > 150 || lowestRange.PixelCount < totalPixels * .2) { //exceeds 90% of images //cout << "Low Contrast Warning..." << endl; //TODO: Do something } // use the range to threshold DirectBitmapHelper.ThresholdRange(ref workingBmp, lowestRange); DirectBitmap saveBinaryBmp = new DirectBitmap(workingBmp.Bitmap); /* ---------------------------------------------------- * Open the image (erode and dilate) * ---------------------------------------------------- */ int iterations = 4; // Default number of iterations int ecount = 0; int itcount = 0; int i; for (i = 0; i < iterations; i++) { ecount = MorphologicalOperators.Erode(ref workingBmp, i % 2); } // Shrink all other regions with less than 5 neighbor black pixles while (ecount != 0) { ecount = MorphologicalOperators.Erode(ref workingBmp, 5); itcount++; } for (i = 0; i < iterations; i++) { MorphologicalOperators.Dilate(ref workingBmp, i % 2); } ; // AND opended image with orginal MorphologicalOperators.And(ref workingBmp, saveBinaryBmp); //Trace.WriteLine("Starting feature recognition..."); var largestFeature = FeatureIdentification.FindLargestFeature(workingBmp); Trace.WriteLine("Feature recognition complete."); // now binImg can be reset to the mask of the NEW largestFeature DirectBitmap binaryBmp = largestFeature.Mask; DirectBitmap outline = new DirectBitmap(binaryBmp.Bitmap); for (i = 0; i < 1; i++) { MorphologicalOperators.Erode(ref outline, 0); } MorphologicalOperators.Xor(ref binaryBmp, outline); //***1.0LK - a bit of a mess - JHS // at this point binImg points to the largestFeature->mask (eroded and XORed). // We want to use this binImg to find a new largestFeature, but we cannot // delete the current largestFeature until after the call to binImg->getLargestFeature() // because binImg will be wiped out by the deletion of the current largestFeature // We must delete the current largestFeature at some point OTHEWISE we have a // memory leak. //get rid of outlines of inner features (e.g. glare spots) by selecting largest outline //cout << "Looking for 2nd Fin Candidate: "; //Feature oldLargest = largestFeature; //***1.0LK var finalLargestFeature = FeatureIdentification.FindLargestFeature(binaryBmp); if (finalLargestFeature == null) { // TODO Trace.WriteLine("largestFeature NULL, aborting. No fin outline determined"); return(null); } // now binImg can be reset to the mask of the NEW largestFeature DirectBitmap finalWorkingBmp = finalLargestFeature.Mask; int row = 0, col = 0; int rows = finalWorkingBmp.Height, cols = finalWorkingBmp.Width; bool done = false; width = 15; //cout << "Finding first point..." << endl; Darwin.Point pt = ctour[0]; //Contour::addPoint(pt.x,pt.y);//Add User start int stx = pt.X / factor - xoffset; int sty = pt.Y / factor - yoffset; pt = ctour[1]; int endy = pt.Y / factor - yoffset; int endx = pt.X / factor - xoffset; //find starting point (midx,midy) int midx; int midy = 0; int maxy = (sty > endy) ? sty : endy; // Math.max(sty,endy); midx = Convert.ToInt32((endx + stx) * .5); //find black pixel closest to bottom in column midx row = maxy; while (!done && row >= 0) { if (finalWorkingBmp.GetPixel(midx, row).GetIntensity() == 0) { midy = row; done = true; } else { row--; } } if (!done) { Trace.WriteLine("no starting point found."); Trace.WriteLine("\nNo outline intersected the bisector of the secant line formed by user supplied start and end points!\n"); // TODO return(null); } Trace.WriteLine(string.Format("Have starting point ({0}, {1})", midx, midy)); Trace.WriteLine(string.Format("FYI ending point ({0}, {1})", endy, endx)); row = midy; col = midx; int st2y = row; int st2x = col; // Walk from point (row,col) finalWorkingBmp.SetPixel(col, row, Color1); /* Prioritize direction of movement * * 4 | 3 | 2 * -- -- -- * 5 | * | 1 * -- -- -- * 4 | 3 | 2 */ i = 0; bool foundPoint = true; bool prepend = false; done = false; while (!done) { /* Prioritize direction of movement * * 4 | 3 | 2 * -- -- -- * 5 | * | 1 * -- -- -- * 4 | 3 | 2 */ if (col + 1 < cols && finalWorkingBmp.GetPixel(col + 1, row).GetIntensity() == 0) {//E foundPoint = true; col = col + 1; } else if (row - 1 >= 0 && finalWorkingBmp.GetPixel(col, row - 1).GetIntensity() == 0) {//N foundPoint = true; row = row - 1; } else if (row + 1 < rows && finalWorkingBmp.GetPixel(col, row + 1).GetIntensity() == 0) {//S foundPoint = true; row = row + 1; } else if (col - 1 >= 0 && finalWorkingBmp.GetPixel(col - 1, row).GetIntensity() == 0) {//W foundPoint = true; col = col - 1; } else { if (prepend) { done = true; break; } else { prepend = true; row = midy; col = midx; continue; } } if (foundPoint /*&& i%3==0*/) { if (prepend) { AddPoint(factor * (col + xoffset), factor * (row + yoffset), 0);//prepend } else { AddPoint(factor * (col + xoffset), factor * (row + yoffset)); } finalWorkingBmp.SetPixel(col, row, Color128); if (row == maxy) { //done with this direction if (prepend) { done = true; break; } else { prepend = true; row = midy; col = midx; continue; } } } i++; } // end loop until done TrimAndReorder(ctour[0], ctour[1]); Trace.WriteLine("IntensityContour::GetPointsFromBitmap COMPLETE"); return(null); }