I have the task to find the center, the side length and the rotation angle of a square in a binary image. In addition i have the following information given:
Given a binary image with white (=True) background containing a single black (=False) rotated square, return the center of mass (x, y) of said square, its edge length (l) and its rotation angle (alpha). The square will always be fully contained inside the image, i.e. none of its edges intersects with the image border.Return the angle alpha in degrees, where 0° is upright and equivalent to ..., -180°, -90°, 90°, 180°, ...and the positive direction of rotation is counter-clockwise. Please note the shape of img is (height, width) with y-axis first (row-major order). This task should be solvable with only standard numpy functions.
Im not really familiar with numpy but I think I was able to get the center of the square in x and y coordinates and side length with following code:
import numpy
def find_rect(img):
x, y, l, alpha = 0, 0, 0, 0
black_pixels = np.argwhere(img==0)
y, x = np.mean(black_pixels, axis=0)
l = np.sqrt((img==0).sum())
y_max1 = black_pixels[black_pixels[:,1]>= x, 0].max()
y_max2 = black_pixels[black_pixels[:,1]<= x, 0].max()
y_max = max(y_max1, y_max2)
x_max = black_pixels[black_pixels[:, 0] == y_max, 1][0]
alpha = np.degrees(np.arctan(((y_max - y)/(x_max - x))))
return x, y, l, alpha
#the code which generates the square
from scipy.ndimage import rotate
def generate_rot_square(w, h, x, y, l, alpha):
rect = np.ones((l, l))
rect = rotate(rect, alpha)
r_h, r_w = rect.shape
r_h_1 = r_h // 2
r_h_2 = r_h - r_h_1
r_w_1 = r_w // 2
r_w_2 = r_w - r_w_1
result = np.zeros((h, w), dtype=bool)
result[y - r_h_1:y + r_h_2, x - r_w_1:x + r_w_2] = rect > 0.9
return 1 - result
But as soon as the square is rotated the code to get the side length if the square won't work anymore. Also I dont really have an idea on how to get the rotation angle of the square. Maybe someone can give me tips on how to solve this task.
This is quite scholar, so unrealistic (in real life) solutions are probably expected. Not computer vision solutions (that would use opencv rather than just numpy).
For example, since you know for sure that this is a square, you know that the number of black pixels is l². So count the number of black pixels, and take the square root of that
To find the angle, ask yourself, what should be ymax for angle α? Find ymax, as you did, and deduce angle from that.
You have to be able to find angle to distinguish in reality the case where ymax occurs in first or second quadrand (or third/fourth, depending on y-axis being upward or downward. But there are only 2 cases to consider)
So, compute ymax1 for the first half of image (x-wise), and ymax2 for the other
Only the bigger one is relevant. Since the smaller one means that the max is not a corner, but the intersection of a side with y-axis (well, you could compute from that intersection too, but that is more complicated).
So, the bigger one tells you were is a corner. From the position of a corner and
lyou can deduce the angle.I've tried it. It is not very accurate (error is around 5 degs for angle near 90; better for smaller angles). Even l, is 149 when I try with 150. That is because of the
>0.9that filters out more pixels than it should.So, if that
>0.9is a constraint, it would probably be more accurate to avoid relying onlto compute the angle. For that, also compute the x matching you ymax (warning: it is not a xmax. Just draw a square and see that it is not the same thing. It should be the only black pixel in the row ymax:img[ymax].argmin(). Or, the mean of black pixels in that row, if there are more than one (but, except for angle 0, that should be just a few). And then the ratio of ymax and that x should be related to thetanof the angle.I don't say more, since you asked only for tips.
But the kind of computation you are already doing (mean, max, min, ...) of black pixels, in addition to the separation I've mentioned (max before x, max after x), or the corner coordinates (x that match ymax, or likewise, redundantly, y that match xmax) are all what you need. Just think backward, of what would be those values for a square centered at
x₀,y₀, of sideWand rotated by angle α. And then reverse the equation.Try with different strategies :
l.tan(angle)from thatlythat matchxmax(thus a corner) and deduce (witharctanalso) angle from thatMaybe you'll see that depending on the estimated angle, one strategy is more accurate, and you'll end up with a way to select the better of those 4 strategies.
And there are others: ymax-ymin, xmax-xmin also depends from angle. So if you can tell what they should be for a given angle, then you can reverse the formula and find what the angle is from their measured value
Edit
Some precision about corners and angle.
In a square, you have 4 corners. Which are at coordinates
(x+W×√2/2×cos(β), y+W×√2/2×sin(β))
(x+W×√2/2×cos(β+90°), y+W×√2/2×sin(β+90°))
(x+W×√2/2×cos(β+180°), y+W×√2/2×sin(β+180°))
(x+W×√2/2×cos(β+270°), y+W×√2/2×sin(β+270°))
β begin α+45°, α being the angle you search for. It is more convenient for me to reason from the angle, β, where we find corners. So if square is not rotated, α=0, then β=45°. So, coordinates of corners are (x+W√2×cos(45°)=x+W×√2/2×√2/2 = x+W/2, y+W/2
x-W/2, y+W/2
x-W/2, y-W/2
x+W/2, y-W/2
As you can see, that is quite expected.
If α=45°, then β=90°, and we get x, y+W√2/2
x-W√2/2, y
x, y-W√2/2
x+W√2/2, y
Again, quite as expected.
It doesn't really matter which corner we use: angle, as you said, is valid modulo 90°, and, obviously, corners are separated from each other by angles mutiples of 90°
So, we just need to find a corner, whichever.
So, if you look at ymax, that is the
yof one corner. Unless angle is α=0, in which case, it is theyof a whole side.What is the matching
x: it is thexwhere you find a 0 in row ymax.You can find it that way for example (using your
black_pixelsarray)Note that
black_pixels[black_pixels[:,0]==ymax,1]returns an array ofx. So I just take the first (see later about that).Likewise, working with image directly, I could also have computed
Which returns the index (in x, since we already indexed first axis with
ymax) of the first 0 inymaxrow.Both method would fail if there is no 0 in that row. But there is one. Otherwise,
ymaxwould have been different.So, no, you know one corner, whose coordinate is (xc, ymax).
That point is,relative to the center of the square (and center of rotation) with vector (xc-x, ymax-y).
The angle of that vector is
arctan((ymax-y)/(xc-x)). And since that is the angle where you find a corner, that is what I've called β, we need to remove 45°. So angle isarctan((ymax-y)/(xc-x))-45. At least if the corner we are working on is on the right side.Or
90-thatsince y axis is downward.Just play with different angles, and see how
arctan((ymax-y)/(xc-x))evolves...Full code
"Proof" that it works
Real and estimated angles overlap pretty well.