How to find the corner pixels of a rectangle?
Asked Answered
A

4

6

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?

Ange answered 17/12, 2015 at 12:45 Comment(3)
There is no exact solution when the shapes are not symmetrical or skewed. To you want an exact rectangle or will you accept a quadrilateral ?Flavopurpurin
I'm trying to find a quadrilateral in an image.Fumigator
You should retitle the post then.Flavopurpurin
K
3

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.

Knitting answered 17/12, 2015 at 13:14 Comment(6)
Finding the top, bottom, right and left rows can be done in log(n) time. But, I wouldn't recommend it. This problem isn't something you'd need to play Battleship™ with. YOU SANK MY RECTANGLE!Knitting
"Finding the top, bottom, right and left rows can be done in log(n) time": how ??Flavopurpurin
Play Battleship to find the rectangle blob. Then do a binary search to find the edges. The shape must have a black pixel to to the right,left, or above until such time as it is in the topmost row.Knitting
I'm not at all convinced the problem is worth anything more than checking about half the bitmap, logging the 8 candidates when you run through, then applying the basic integer math and outputting the solution. Clearly my solution reduces it enough that finding the first black pixel is the biggest time suck.Knitting
But, yeah, if it's mission critical, check the center pixel then check each quadrant, I, II, III, IV, then repeat that for each of those points and recurse until you find a black pixel or have looked through the entire bitmap. When you find a black pixel, You can do a kind of flood fill to find the relevant rows. Because even a transformed rectangle cannot have a sub-optimal solution. You head in that direction, then try every point in that row or column for anything further in that direction, and you're done.Knitting
You still didn't say how to perform binary search.Flavopurpurin
T
1

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.

Thant answered 21/12, 2015 at 15:42 Comment(0)
K
0
  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 = p.x;
                this.y = p.y;
            }
    
            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 + d.x, y + d.y);
            }
    
        }
    
        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.

Kolkhoz answered 17/12, 2015 at 15:3 Comment(0)
F
0

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.

Flavopurpurin answered 18/12, 2015 at 17:17 Comment(4)
Once you find the black pixels run, from the top, if you find a row of pixels without a black pixel below that mark, it must be the case that the bottom most row is above that row. At a minimum, we are better off going from the top until we hit a row, then keep going down until we hit a pure white row. Then we have the bottom row. Assuming the rectangle isn't more than half the bitmap.Knitting
Properly you should just loop through the bitmap from the top until such time after you hit the first black pixel until you hit a row of all white pixels and just compare them to a running list of right most, left most, top most bottom most in a quick series of if statements. o(n*m/2)Knitting
@Tatarize: my solution is O(N-M) where N is the area of the image and M is the area of the bounding box of the rectangle.Flavopurpurin
You didn't specify that you don't recheck the upper left quadrant by the search for the topmost and search for the leftmost. Yeah, if once you find the bottom and the top, you only search the middle section below the top row and above the bottom row, then you don't duplicate the pixels in the N. My way should actually be O(n/2 + m*log(n)) or so, but most of that isn't needed anyway.Knitting

© 2022 - 2024 — McMap. All rights reserved.