简体   繁体   中英

How to find the corner pixels of a rectangle?

Given a simple monochrome bitmap that contains a single, randomly rotated rectangle. How can I find the left/top, left/bottom, right/bottom and right/top corner positions of the rectangle inside the bitmap?

For example, this is how the bitmap could look like, where the X marks the pixels in question:

......... ......... ......... .........
.X11111X. ....X.... ..X11.... ....11X..
.1111111. ...111... ..11111X. X111111..
.1111111. ..X111X.. ..111111. .111111..
.X11111X. ...111... .1111111. .1111111.
......... ....X.... .111111.. ..111111.
......... ......... .X11111.. ..11111X.
......... ......... ....11X.. ..X11....
......... ......... ......... .........

Please excuse the bad ascii art. For the second example, the corner pixel at the top could either be the rectangles left/top or right/top corner. Either is fine.

What steps are required to determine the corner pixels/positions in the above examples?

The corner pixels are the pixels the furthest apart. Find the top most row and the bottom most row. There will always be a corner pixel in those.

The corner pixel can only be the first or last pixel in this the topmost row row (or both if there's just the one).

So compare the distances between the first pixel in the topmost row and the last pixel in the bottom most row. And last pixel in topmost with the first in bottom most. The corners there are the the ones that are the furthest apart.

Since they are all the same distance in the Y you need the pixels with the greatest difference with regard to their x location. The corners are the pixels for which abs(x0-x1) is the greatest, where x0 is in the topmost row and x1 is in the bottom most.

Repeat this for the rightmost and leftmost rows.

If the topmost corner is on the left then the leftmost corner is on the bottom, the bottom most corner is on the right and the rightmost corner is on the top. Once you have the top, bottom, left, and right rows there's really just the two possibilities that can be solved in an if statement. But, due to the edge condition of having one pixel on the topmost row and two on the rightmost row, you're better off just running the algorithm again with transposed x and ys to solve for the other two corners rather than trying to spare yourself an if statement.

Not every monochrome bitmap is going to give you an answer. A complete algorithm needs an output that says "Unique corners not present". The following figures give an example of the problem:

......... ......... .......... ...XX.... ....X.... ....XX.... ..X11X... ...111... ...1111... ..X11X... ..X111X.. ..X1111X.. ...XX.... ...111... ..X1111X.. ......... ....X.... ...X11X... ......... ......... ....XX.... ......... ......... ..........

The degeneracy illustrated happens when the slopes of the rectangle are +1 and -1 and the position of the center is half-integral . It can also occur with other combinations of slopes and positions. The general answer will need to contain pixel-pairs as the best approximation of a vertex.

  1. Start with the bounding box of the rectangle.
  2. For each corner, move it clockwise until there is a black square.

     public class Test { String[][] squares = { { ".........", ".X11111X.", ".1111111.", ".1111111.", ".X11111X.", ".........", ".........", ".........", ".........",}, { ".........", "....X....", "...111...", "..X111X..", "...111...", "....X....", ".........", ".........", ".........",}, { ".........", "..X11....", "..11111X.", "..111111.", ".1111111.", ".111111..", ".X11111..", "....11X..", ".........",}, { ".........", "....11X..", "X111111..", ".111111..", ".1111111.", "..111111.", "..11111X.", "..X11....", ".........",}}; private static final int WHITE = 0; private static final int BLACK = 1; class Point { private final int x; private final int y; public Point(Point p) { this.x = px; this.y = py; } public Point(int x, int y) { this.x = x; this.y = y; } @Override public String toString() { return "{" + x + "," + y + '}'; } // What colour is there? public int colour(int[][] bmp) { // Make everything off-bmp black. if (x < 0 || y < 0 || y >= bmp.length || x >= bmp[y].length) { return BLACK; } return bmp[y][x]; } private Point step(Point d) { return new Point(x + dx, y + dy); } } class Rectangle { private final Point[] corners = new Point[4]; public Rectangle(Point[] corners) { // Points are immutable but corners are not. System.arraycopy(corners, 0, this.corners, 0, corners.length); } public Rectangle(Rectangle r) { this(r.corners()); } public Rectangle(Point a, Point b, Point c, Point d) { corners[0] = a; corners[1] = b; corners[2] = c; corners[3] = d; } private Rectangle(Point tl, Point br) { this(tl, new Point(br.x, tl.y), br, new Point(tl.x, br.y)); } public Point[] corners() { return Arrays.copyOf(corners, corners.length); } @Override public String toString() { return Arrays.toString(corners); } } private Rectangle getBoundingBox(int[][] bmp) { int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, maxX = 0, maxY = 0; for (int r = 0; r < bmp.length; r++) { for (int c = 0; c < bmp[r].length; c++) { if (bmp[r][c] != WHITE) { if (minX > c) { minX = c; } if (minY > r) { minY = r; } if (maxX < c) { maxX = c; } if (maxY < r) { maxY = r; } } } } return new Rectangle(new Point(minX, minY), new Point(maxX, maxY)); } Point[] clockwise = new Point[]{ new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1)}; private void test(int[][] bmp) { // Find the bounding box. Rectangle bBox = getBoundingBox(bmp); System.out.println("bbox = " + bBox); Point[] corners = bBox.corners(); // Move each corner clockwise until it is black. for (int p = 0; p < corners.length; p++) { while (corners[p].colour(bmp) == WHITE) { corners[p] = corners[p].step(clockwise[p]); } } System.out.println("rect = " + new Rectangle(corners)); } private void test(String[] square) { // Build the int[][]. // . -> White // X/1 -> Black int[][] bmp = new int[square.length][]; for (int r = 0; r < square.length; r++) { bmp[r] = new int[square[r].length()]; for (int c = 0; c < bmp[r].length; c++) { switch (square[r].charAt(c)) { case '.': bmp[r][c] = WHITE; break; case 'X': case '1': bmp[r][c] = BLACK; break; } } } test(bmp); } public void test() { for (String[] square : squares) { test(square); } } public static void main(String args[]) { try { new Test().test(); } catch (Throwable t) { t.printStackTrace(System.err); } } } 

prints

    bbox = [{1,1}, {7,1}, {7,4}, {1,4}]
    rect = [{1,1}, {7,1}, {7,4}, {1,4}]
    bbox = [{2,1}, {6,1}, {6,5}, {2,5}]
    rect = [{4,1}, {6,3}, {4,5}, {2,3}]
    bbox = [{1,1}, {7,1}, {7,7}, {1,7}]
    rect = [{2,1}, {7,2}, {6,7}, {1,6}]
    bbox = [{0,1}, {7,1}, {7,7}, {0,7}]
    rect = [{4,1}, {7,4}, {4,7}, {0,2}]

Could be improved by looking for a run of black and choosing the middle of the run.

Scan the image from the top, row by row until you find a black run.

Repeat four ways, from the bottom up, left, right, giving you eight corner candidates.

Take the run endpoints the farthest apart in the top and bottom rows. This tells you which endpoints to take vertically.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM