Transform points between two screens - geometry

I have a large collection of points that I want to transform from one screen/monitor to another in a script.
Screen A - 2880x1800 resolution
Screen B - 1280x1024 resolution
• Black box: screen/monitor.
• Green box: portion of the screen where all points will be transformed from C(a) to C(b).
The green box is a video feed with an aspect ratio of 16:9 on both screens. I'm not actually sure this green box matters but I included it to show what I'm trying to do.
So far I've tried to follow this post, which seems to work fine for transforming x values:
Rectangle Old
NW coordinate - (X0_Old, Y0_Old)
SE coordinate - (X1_Old, Y1_Old)
Rectangle New
NW coordinate - (X0_New, Y0_New)
SE coordinate - (X1_New, Y1_New)
X_New = X0_New + (X_Old - X0_Old) * X_Coeff
X_New = 0 + (X_Old - 0 ) * X_Coeff
where
X_Coeff = (X1_New - X0_New) / (X1_Old - X0_Old)
X_Coeff = (1280 - 0 ) / (2880 - 0 )
X_New = (X_Old)*(1280/2880)
That X_New wasn't perfect, but for some reason this one was:
X_New = 2*(X_Old)*(1280/2880)
Maybe this was due to some kind of scaling? The x coordinates were working so I didn't question it and moved on.
My problem came when I tried to transform the Y coordinates:
Y_New = Y0_New + (Y_Old - Y0_Old) * Y_Coeff
Y_Coeff = (Y1_New - Y0_New) / (Y1_Old - Y0_Old)
Y_New = Y0_New + (Y_Old - Y0_Old) * Y_Coeff
Y_New = 0 + (Y_Old - 136 ) * Y_Coeff
where
Y_Coeff = (Y1_New - Y0_New) / (Y1_Old - Y0_Old)
Y_Coeff = ( 720 - 174 ) / (1620 - 136)
Y_Coeff = 546/1484
Y_New = (Y_Old - 136) * (546/1484)
Y values calculated with Y_New are definitely much too low to be correct. What's the right way to calculate Y_New?

Definitely, formula X_New = (X_Old)*(1280/2880) is correct - it transforms 0 to 0, 2880 to 1280, 1440 to 640. Perhaps you really have some kind of scaling?
About Y - in this case (limited by X-axis) you should use the same coefficient X_Coeff for Y too.
But I can see that 16x9 rectangle at the second screen has size 1280x720 with empty stripes of height 152, and for the first screen size is 2880x1620 with empty stripes of height 90
Y_New = 152 + (Y_Old - 90) * X_Coeff
In general case one have to choose either X_Coeff or Y_Coeff depending on ratio between screen and video size rectangle.

Related

YoloV1 : how to turn predicted bounding boxe relative coordinates into absolute ones

I'm struggling to create a light YoloV1 (with only one bounding box) on MNIST dataset (I randomly paste 28x28 digit into a 75x75 black background).
I can't figure out how to turn relative-to-cell coordinates into absolute coordinates.
Since now, I'm using the groundtruth bounding boxes to retrieve the cell which should contain an object, then I save the i,j positions, then I use those positions to get back to absolute coordinates with my predictions.
This method works but when it's time to detect a real image, I won't have the groundtruth coordinates and so, the i,j object position, and so the absolute position of the predicted bounding box.
I provide some line of code :
Encoding absolute coordinates of shape (N,4) to (N,S,S,5)
def encode(self, box):
"""
box : torch.Tensor of shape (N,4)
Absolute coordinates [xmin, ymin, w_bbox, h_bbox]
"""
### Absolute box infos
xmin, ymin, w_bbox, h_bbox = box
### Relative box infos
rw = w_bbox / 75
rh = h_bbox / 75
rx_min = xmin / 75
ry_min = ymin / 75
### x and y box center coords
rxc = (rx_min + rw/2)
ryc = (ry_min + rh/2)
### Object grid location
i = (rxc / self.cell_size).ceil() - 1.0
j = (ryc / self.cell_size).ceil() - 1.0
i, j = int(i), int(j)
### x & y of the cell left-top corner
x0 = i * self.cell_size
y0 = j * self.cell_size
### x & y of the box on the cell, normalized from 0.0 to 1.0.
x_norm = (rxc - x0) / self.cell_size
y_norm = (ryc - y0) / self.cell_size
box_target = torch.zeros(self.S, self.S, 4+1)
box_target[j, i, :5] = torch.Tensor([x_norm, y_norm, rw, rh, 1.])
return box_target
Convert relative-to-cell coordinates into absolute ones
def relative2absolute(box_true:torch.Tensor, box_pred:torch.Tensor)->tuple:
"""
Turns bounding box relative to cell coordinates into absolute coordinates
(pixels). Used to calculate IoU and to plot boxes.
Args:
box_true : torch.Tensor of shape (N, S, S, 5)
Groundtruth bounding box coordinates to convert.
box_pred : torch.Tensor of shape (N, S, S, 5)
Predicted bounding box coordinates to convert.
Return:
box_true_absolute : torch.Tensor of shape (N, 4)
box_pred_absolute : torch.Tensor of shape (N, 4)
"""
assert len(box_true.shape)==4 and len(box_pred.shape)==4, "Bbox should be of size (N,S,S,5)."
SIZEHW = 75
S = 6
CELL_SIZE = 1/S
### Get non-zero coordinates
cells_with_obj = box_true.nonzero()[::5]
N, cells_i, cells_j, _ = cells_with_obj.permute(1,0)
### Retrieving box coordinates. TBM if nb_obj > 1
xrcell_true, yrcell_true, rw_true, rh_true = box_true[N, cells_i, cells_j, 0:4].permute(1,0)
xrcell_pred, yrcell_pred, rw_pred, rh_pred = box_pred[N, cells_i, cells_j, 0:4].permute(1,0)
### Compute relative-to-image center coordinates
xc_rimg_true = xrcell_true * CELL_SIZE + cells_j * CELL_SIZE
xc_rimg_pred = xrcell_pred * CELL_SIZE + cells_j * CELL_SIZE
yc_rimg_true = yrcell_true * CELL_SIZE + cells_i * CELL_SIZE
yc_rimg_pred = yrcell_pred * CELL_SIZE + cells_i * CELL_SIZE
### Compute absolute top left coordinates
xmin_true = (xc_rimg_true - rw_true/2) * SIZEHW
xmin_pred = (xc_rimg_pred - rw_pred/2) * SIZEHW
ymin_true = (yc_rimg_true - rh_true/2) * SIZEHW
ymin_pred = (yc_rimg_pred - rh_pred/2) * SIZEHW
### Compute absolute bottom right coordinates
xmax_true = xmin_true + rw_true*SIZEHW
xmax_pred = xmin_pred + rw_pred*SIZEHW
ymax_true = ymin_true + rh_true*SIZEHW
ymax_pred = ymin_pred + rh_pred*SIZEHW
### Stacking
box_true_absolute = torch.stack((xmin_true, ymin_true, xmax_true, ymax_true), dim=-1)
box_pred_absolute = torch.stack((xmin_pred, ymin_pred, xmax_pred, ymax_pred), dim=-1)
return box_true_absolute, box_pred_absolute

How to translate points on image after cropping it and resizing it?

I am creating a program which allows a user to annotate images with points.
This program allows user to zoom in an image so user can annotate more precisely.
Program zooms in an image doing the following:
Find the center of image
Find minimum and maximum coordinates of new cropped image relative to center
Crop image
Resize the image to original size
For this I have written the following Python code:
import cv2
def zoom_image(original_image, cut_off_percentage, list_of_points):
height, width = original_image.shape[:2]
center_x, center_y = int(width/2), int(height/2)
half_new_width = center_x - int(center_x * cut_off_percentage)
half_new_height = center_y - int(center_y * cut_off_percentage)
min_x, max_x = center_x - half_new_width, center_x + half_new_width
min_y, max_y = center_y - half_new_height, center_y + half_new_height
#I want to include max coordinates in new image, hence +1
cropped = original_image[min_y:max_y+1, min_x:max_x+1]
new_height, new_width = cropped.shape[:2]
resized = cv2.resize(cropped, (width, height))
translate_points(list_of_points, height, width, new_height, new_width, min_x, min_y)
I want to resize the image to original width and height so user always works on same "surface"
regardless of how zoomed image is.
The problem I encounter is how to correctly scale points (annotations) when doing this. My algorithm to do so was following:
Translate points on original image by subtracting min_x from x coordinate and min_y from y coordinate
Calculate constants for scaling x and y coordinates of points
Multiply coordinates by constants
For this I use the following Python code:
import cv2
def translate_points(list_of_points, height, width, new_height, new_width, min_x, min_y):
#Calculate constants for scaling points
scale_x, scale_y = width / new_width, height / new_height
#Translate and scale points
for point in list_of_points:
point.x = (point.x - min_x) * scale_x
point.y = (point.y - min_y) * scale_y
This code doesn't work. If I zoom in once, it is hard to detect the offset of pixels but it happens. If I keep zooming in, it will be much easier to detect the "drift" of points. Here are images to provide examples. On original image (1440x850) I places a point in the middle of blue crosshair. The more I zoom in the image it is easier to see that algorithm doesn't work with bigger cut-ofs.
Original image. Blue crosshair is middle point of an image. Red angles indicate what will be borders after image is zoomed once
Image after zooming in once.
Image after zooming in 5 times. Clearly, green point is no longer in the middle of image
The cut_off_percentage I used is 15% (meaning that I keep 85% of width and height of original image, calculated from the center).
I have also tried the following library: Augmentit python library
Library has functions for cropping images and resizing them together with points. Library also causes the points to drift. This is expected since the code I implemented and library's functions use the same algorithm.
Additionally, I have checked whether this is a rounding problem. It is not. Library rounds the points after multiplying coordinates with scales. Regardless on how they are rounded, points are still off by 4-5 px. This increases the more I zoom in the picture.
EDIT: A more detailed explanation is given here since I didn't understand a given answer.
The following is an image of right human hand.
Image of a hand in my program
Original dimension of this image is 1440 pixels in width and 850 pixels in height. As you can see in this image, I have annotated right wrist at location (756.0, 685.0). To check whether my program works correctly, I have opened this exact image in GIMP and placed a white point at location (756.0, 685.0). The result is following:
Image of a hand in GIMP
Coordinates in program work correctly. Now, if I were to calculate parameters given in first answer according to code given in first answer I get following:
vec = [756, 685]
hh = 425
hw = 720
cov = [720, 425]
These parameters make sense to me. Now I want to zoom the image to scale of 1.15. I crop the image by choosing center point and calculating low and high values which indicate what rectangle of image to keep and what to cut. On the following image you can see what is kept after cutting (everything inside red rectangle).
What is kept when cutting
Lows and highs when cutting are:
xb = [95,1349]
yb = [56,794]
Size of cropped image: 1254 x 738
This cropped image will be resized back to original image. However, when I do that my annotation gets completely wrong coordinates when using parameters described above.
After zoom
This is the code I used to crop, resize and rescale points, based on the first answer:
width, height = image.shape[:2]
center_x, center_y = int(width / 2), int(height / 2)
scale = 1.15
scaled_width = int(center_x / scale)
scaled_height = int(center_y / scale)
xlow = center_x - scaled_width
xhigh = center_x + scaled_width
ylow = center_y - scaled_height
yhigh = center_y + scaled_height
xb = [xlow, xhigh]
yb = [ylow, yhigh]
cropped = image[yb[0]:yb[1], xb[0]:xb[1]]
resized = cv2.resize(cropped, (width, height), cv2.INTER_CUBIC)
#Rescaling poitns
cov = (width / 2, height / 2)
width, height = resized.shape[:2]
hw = width / 2
hh = height / 2
for point in points:
x, y = point.scx, point.scy
x -= xlow
y -= ylow
x -= cov[0] - (hw / scale)
y -= cov[1] - (hh / scale)
x *= scale
y *= scale
x = int(x)
y = int(y)
point.set_coordinates(x, y)
So this really is an integer rounding issue. It's magnified at high zoom levels because being off by 1 pixel at 20x zoom throws you off much further. I tried out two versions of my crop-n-zoom gui. One with int rounding, another without.
You can see that the one with int rounding keeps approaching the correct position as the zoom grows, but as soon as the zoom takes another step, it rebounds back to being wrong. The non-rounded version sticks right up against the mid-lines (denoting the proper position) the whole time.
Note that the resized rectangle (the one drawn on the non-zoomed image) blurs past the midlines. This is because of the resize interpolation from OpenCV. The yellow rectangle that I'm using to check that my points are correctly scaling is redrawn on the zoomed frame so it stays crisp.
With Int Rounding
Without Int Rounding
I have the center-of-view locked to the bottom right corner of the rectangle for this demo.
import cv2
import numpy as np
# clamp value
def clamp(val, low, high):
if val < low:
return low;
if val > high:
return high;
return val;
# bound the center-of-view
def boundCenter(cov, scale, hh, hw):
# scale half res
scaled_hw = int(hw / scale);
scaled_hh = int(hh / scale);
# bound
xlow = scaled_hw;
xhigh = (2*hw) - scaled_hw;
ylow = scaled_hh;
yhigh = (2*hh) - scaled_hh;
cov[0] = clamp(cov[0], xlow, xhigh);
cov[1] = clamp(cov[1], ylow, yhigh);
# do a zoomed view
def zoomView(orig, cov, scale, hh, hw):
# calculate crop
scaled_hh = int(hh / scale);
scaled_hw = int(hw / scale);
xlow = cov[0] - scaled_hw;
xhigh = cov[0] + scaled_hw;
ylow = cov[1] - scaled_hh;
yhigh = cov[1] + scaled_hh;
xb = [xlow, xhigh];
yb = [ylow, yhigh];
# crop and resize
copy = np.copy(orig);
crop = copy[yb[0]:yb[1], xb[0]:xb[1]];
display = cv2.resize(crop, (width, height), cv2.INTER_CUBIC);
return display;
# draw vector shape
def drawVec(img, vec, pos, cov, hh, hw, scale):
con = [];
for point in vec:
# unpack point
x,y = point;
x += pos[0];
y += pos[1];
# here's the int version
# Note: this is the same as xlow and ylow from the above function
# x -= cov[0] - int(hw / scale);
# y -= cov[1] - int(hh / scale);
# rescale point
x -= cov[0] - (hw / scale);
y -= cov[1] - (hh / scale);
x *= scale;
y *= scale;
x = int(x);
y = int(y);
# add
con.append([x,y]);
con = np.array(con);
cv2.drawContours(img, [con], -1, (0,200,200), -1);
# font stuff
font = cv2.FONT_HERSHEY_SIMPLEX;
fontScale = 1;
fontColor = (255, 100, 0);
thickness = 2;
# draw blank
res = (800,1200,3);
blank = np.zeros(res, np.uint8);
print(blank.shape);
# draw a rectangle on the original
cv2.rectangle(blank, (100,100), (400,200), (200,150,0), -1);
# vectored shape
# comparison shape
bshape = [[100,100], [400,100], [400,200], [100,200]];
bpos = [0,0]; # offset
# random shape
vshape = [[148, 89], [245, 179], [299, 67], [326, 171], [385, 222], [291, 235], [291, 340], [229, 267], [89, 358], [151, 251], [57, 167], [167, 164]];
vpos = [100,100]; # offset
# get original image res
height, width = blank.shape[:2];
hh = int(height / 2);
hw = int(width / 2);
# center of view
cov = [600, 400];
camera_spd = 5;
# scale
scale = 1;
scale_step = 0.2;
# loop
done = False;
while not done:
# crop and show image
display = zoomView(blank, cov, scale, hh, hw);
# drawVec(display, vshape, vpos, cov, hh, hw, scale);
drawVec(display, bshape, bpos, cov, hh, hw, scale);
# draw a dot in the middle
cv2.circle(display, (hw, hh), 4, (0,0,255), -1);
# draw center lines
cv2.line(display, (hw,0), (hw,height), (0,0,255), 1);
cv2.line(display, (0,hh), (width,hh), (0,0,255), 1);
# draw zoom text
cv2.putText(display, "Zoom: " + str(scale), (15,40), font,
fontScale, fontColor, thickness, cv2.LINE_AA);
# show
cv2.imshow("Display", display);
key = cv2.waitKey(1);
# check keys
done = key == ord('q');
# Note: if you're actually gonna make a GUI
# use the keyboard module or something else for this
# wasd to move center-of-view
if key == ord('d'):
cov[0] += camera_spd;
if key == ord('a'):
cov[0] -= camera_spd;
if key == ord('w'):
cov[1] -= camera_spd;
if key == ord('s'):
cov[1] += camera_spd;
# z,x to decrease/increase zoom (lower bound is 1.0)
if key == ord('x'):
scale += scale_step;
if key == ord('z'):
scale -= scale_step;
scale = round(scale, 2);
# bound cov
boundCenter(cov, scale, hh, hw);
Edit: Explanation of the drawVec parameters
img: The OpenCV image to be drawn on
vec: A list of [x,y] points
pos: The offset to draw those points at
cov: Center-Of-View, where the middle of our zoomed display is pointed at
hh: Half-Height, the height of "img" divided by 2
hw: Half-Width, the width of "img" divided by 2
I have looked through my code and realized where I was making a mistake which caused points to be offset.
In my program, I have a canvas of specific size. The size of canvas is a constant and is always larger than images being drawn on canvas. When program draws an image on canvas it first resizes that image so it could fit on canvas. The size of resized image is somewhat smaller than size of canvas. Image is usually drawn starting from top left corner of canvas. Since I wanted to always draw image in the center of canvas, I shifted the location from top left corner of canvas to another point. This is what I didn't account when doing image zooming.
def zoom(image, ratio, points, canvas_off_x, canvas_off_y):
width, height = image.shape[:2]
new_width, new_height = int(ratio * width), int(ratio * height)
center_x, center_y = int(new_width / 2), int(new_height / 2)
radius_x, radius_y = int(width / 2), int(height / 2)
min_x, max_x = center_x - radius_x, center_x + radius_x
min_y, max_y = center_y - radius_y, center_y + radius_y
img_resized = cv2.resize(image, (new_width,new_height), interpolation=cv2.INTER_LINEAR)
img_cropped = img_resized[min_y:max_y+1, min_x:max_x+1]
for point in points:
x, y = point.get_original_coordinates()
x -= canvas_off_x
y -= canvas_off_y
x = int((x * ratio) - min_x + canvas_off_x)
y = int((y * ratio) - min_y + canvas_off_y)
point.set_scaled_coordinates(x, y)
In the code below canvas_off_x and canvas_off_y is the location of offset from top left corner of canvas

How to find orientation of an object in image?

I have bunch of images of gear and they all are in different orientation and I need them all in same orientation. I mean there is one reference image and rest of the images should be rotated so they look like same as reference image. I followed these steps, first segment the gear and then tried to find an angle using moments but its not working correctly. I've attached the 3 images considering the first image as reference image and here's the code so far
def adjust_gamma(image, gamma=1.0):
invGamma = 1.0 / gamma
table = np.array([((i / 255.0) ** invGamma) * 255
for i in np.arange(0, 256)]).astype("uint8")
return cv2.LUT(image, table)
def unsharp_mask(image, kernel_size=(13, 13), sigma=1.0, amount=2.5, threshold=10):
"""Return a sharpened version of the image, using an unsharp mask."""
blurred = cv2.GaussianBlur(image, kernel_size, sigma)
sharpened = float(amount + 1) * image - float(amount) * blurred
sharpened = np.maximum(sharpened, np.zeros(sharpened.shape))
sharpened = np.minimum(sharpened, 255 * np.ones(sharpened.shape))
sharpened = sharpened.round().astype(np.uint8)
if threshold > 0:
low_contrast_mask = np.absolute(image - blurred) < threshold
np.copyto(sharpened, image, where=low_contrast_mask)
return sharpened
def find_orientation(cont):
m = cv2.moments(cont, True)
cen_x = m['m10'] / m['m00']
cen_y = m['m01'] / m['m00']
m_11 = 2*m['m11'] - m['m00'] * (cen_x*cen_x+cen_y*cen_y)
m_02 = m['m02'] - m['m00'] * cen_y*cen_y
m_20 = m['m20'] - m['m00'] * cen_x*cen_x
theta = 0 if m_20==m_02 else atan2(m_11, m_20-m_02)/2.0
theta = theta * 180 / pi
return (cen_x, cen_y, theta)
def rotate_image(img, angles):
height, width = img.shape[:2]
rotation_matrix = cv2.getRotationMatrix2D((width/2, height/2), angles, 1)
rotated_image = cv2.warpAffine(img, rotation_matrix, (width,height))
return rotated_image
img = cv2.imread('gear1.jpg')
resized_img = imutils.resize(img, width=540)
height, width = resized_img.shape[:2]
gamma_adjusted = adjust_gamma(resized_img, 2.5)
sharp = unsharp_mask(gamma_adjusted)
gray = cv2.cvtColor(sharp, cv2.COLOR_BGR2GRAY)
gauss_blur = cv2.GaussianBlur(gray, (13,13), 2.5)
ret, thresh = cv2.threshold(gauss_blur, 250, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_DILATE, kernel, iterations=2)
kernel = np.ones((3,3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[0]
cen_x, cen_y, theta = find_orientation(contours[0])
reference_angle = -24.14141919602858
rot_angle = 0.0
if theta < reference_angle:
rot_angle = -(theta - reference_angle)
else:
rot_angle = (reference_angle - theta)
rot_img = rotate_image(resized_img, rot_angle)
Can anyone tell me where did i go wrong? Any help would be appreciated.
Binarization of the gear and the holes seems easy. You should be able to discriminate the holes from noise and extra small features.
First find the geometric center, and sort the holes by angle around the center. Also compute the areas of the holes. Then you can try to match the holes to the model in a cyclic way. There are 20 holes, and you just need to test 20 positions. You can rate a matching by some combination of the differences in the angles and the areas. The best match tells you the orientation.
This should be very reliable.
You can obtain a very accurate value of the angle by computing the average error per hole and correcting to cancel that value (this is equivalent to least-squares fitting).

Finding the characteristics of a hand written Arrows with opencv

I'm trying to retrieve the orientation of a hand written arrows:
after removing shadows and applying binarization and dilating the lines, here are the images:
Now I'd like to get the orientation of the arrow so I have tried using HoughLines,
lines = cv2.HoughLines(edges, rho=1, theta=np.pi / 180, threshold=20)
But is seems it generates too many lines (around 54 lines), I'd like it to generate only 3 lines so I would be able to find the intersection of those lines. I can group the lines into groups of similar angle (+/-20 degrees) and then average the angle. but I'm not sure what should be rho of an average line, can somebody please give a simple example?
Is there any other approach which may be more accurate?
I'll be glad to hear, thank you all
I suggest a different approach. In summary, the approach goes as follows (made it in a hurry, might need some tuning):
Find the center of the minimum area rectangle (rotated rectangle) that encloses the whole arrow. (The circle drawn in the third image)
Find the center of gravity for all white points. It will be shifted a bit towards the actual head of the arrow. (Drawn in 4th pic as the origin of the eigenvector)
Find eigenvectors for all white points.
Find the displacement vector (the center of gravity - the center of the rotated rectangle)
Now:
Arrow angle(unoriented): is the angle of the first eigenvector
Arrow direction: is the sign of the dot product of (the first eigenvector and the centers' displacement vector)
Code:
Parts related to PCA are inspired by and mostly copied from this. I only made a minor change to the "getOrientation" method, added the following lines before it returns
angle = (angle - math.pi) * 180 / math.pi
return angle, (mean[0,0]), (mean[0,1]), p1
Code implementing the logic above:
#threshold
_, img = cv2.threshold(img, 128, 255, cv2.THRESH_OTSU)
imshow(img)
#close the image to make sure the contour is connected)
st_el = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, st_el)
imshow(img)
#get white points
pnts = cv2.findNonZero(img)
#min area rect
rect_center = cv2.minAreaRect(pnts)[0]
#draw rect center
cv2.circle(img, (int(rect_center[0]), int(rect_center[1])), 3, 128, -1)
imshow(img)
angle, pca_center, eigen_vec = getOrientation(pnts, img)
cc_vec = (rect_center[0] - pca_center[0], rect_center[1] - pca_center[1])
dot_product = cc_vec[0] * eigen_vec[0] + cc_vec[1] * eigen_vec[1]
if dot_product > 0:
angle *= -1
print ("Angle = ", angle)
imshow(img)
Edit
I suggest a simpler method. This new method does not depend on PCA for finding the unoriented angle [0 - 180]. Instead, uses the min area rectangle angle immediately. And uses the contour momentum for finding the center of gravity.
Simpler Method Code:
#get white points
pnts = cv2.findNonZero(img)
#min area rect
rect_center, size, angle = cv2.minAreaRect(pnts)
#simple fix for angle to make it in [0, 180]
angle = abs(angle)
if size[0] < size[1]:
angle += 90
#find center of gravity
M = cv2.moments(img)
gravity_center = (M["m10"] / M["m00"], M["m01"] / M["m00"])
#rot rect vec based on angle
angle_unit_vec = (math.cos(angle * 180 / math.pi), math.sin(angle * 180 / math.pi))
#cc_vec = gravity center - rect center
cc_vec = (gravity_center[0] - rect_center[0], gravity_center[1] - rect_center[1])
#if dot product is negative add 180 -> angle between [0, 360]
dot_product = cc_vec[0] * angle_unit_vec[0] + cc_vec[1] * angle_unit_vec[1]
angle += (dot_product < 0) * 180
#draw rect center
cv2.circle(img, (int(rect_center[0]), int(rect_center[1])), 3, 128, -1)
cv2.circle(img, (int(gravity_center[0]), int(gravity_center[1])), 3, 20, -1)
imshow(img)
print ("Angle = ", angle)
Edit2:
This edit includes these changes:
Use cv2.fitLine() and use the fitted line angle for orientation.
Replace angle_unit_vec with a vector that has the gravity center as the origin and goes parallel to the fitted line.
Code
#get white points
pnts = cv2.findNonZero(img)
#min area rect
rect_center, size, angle = cv2.minAreaRect(pnts)
#fit line to get angle
[vx, vy, x, y] =cv2.fitLine(pnts, cv2.DIST_L12, 0, 0.01, 0.01)
angle = (math.atan2(vy, -vx)) * 180 / math.pi
M = cv2.moments(img)
gravity_center = (M["m10"] / M["m00"], M["m01"] / M["m00"])
angle_vec = (int(gravity_center[0] + 100 * vx), int(gravity_center[1] + 100 * vy))
#cc_vec = gravity center - rect center
cc_vec = (gravity_center[0] - rect_center[0], gravity_center[1] - rect_center[1])
#if dot product is positive add 180 -> angle between [0, 360]
dot_product = cc_vec[0] * angle_vec[0] + cc_vec[1] * angle_vec[1]
angle += (dot_product > 0) * 180
angle += (angle < 0) * 360
#draw rect center
cv2.circle(img, (int(rect_center[0]), int(rect_center[1])), 3, 128, -1)
cv2.circle(img, (int(gravity_center[0]), int(gravity_center[1])), 3, 20, -1)
imshow(img)
print ("Angle = ", angle)
Output:
Using code from edit2:
First image:
Second image:
Third image:

Drawing circles in python pixel by pixel

Hey guys so I have a question. I would like to draw a circle in python pixel by pixel but I can't seem to get it.
It was a question we talked about in my programming class today and my teacher said it was possible by using the distance formula to find the distance between the center of the window and the points on the outside of the circle and then just telling the program to fill it in if it fits that case.
So this is not homework, purely for my own curiosity. I have been working on it for hours and I feel like I am close but definitely missing a key element.
My goal is to put a red circle in the middle of the window, which has a width and height between 300 and 400.
I said that the center point is = to (w/2, h/2) and I want to fill in every point that is a fixed distance away from that.
I chose (100, 50) as a random point that would be on the circle and then set the variable distance = to the distance formula using those previous two points.
The two for-loops are there to iterate over every pixel in the window. I was able to create a box right where I want my circle... But I can't seem to get anything close to a circle to appear
here is my code, I named my function Japanese flag as it seemed appropriate:
import cImage
import math
def japanese_flag(w, h):
newimg = EmptyImage(w, h)
distance = int(math.sqrt((50 - h / 2) ** 2 + (100 - w / 2) ** 2))
for col in range(w):
for row in range(h):
if h/2 - distance < row < (h/2 + distance) and w / 2 - distance < col < (w / 2 + distance):
newpixel = Pixel(255, 0, 0) # this denotes the color red
newimg.setPixel(col, row, newpixel)
return newimg
def show_one_flag(func_name):
import random
width = random.randint(300, 400)
height = random.randint(200, 300)
win = ImageWin(str(func_name),width,height)
newimg = func_name(width, height)
newimg.draw(win)
show_one_flag(japanese_flag)
Use squared radius for simpler comparison
SqDist = (50 - h / 2) ** 2 + (100 - w / 2) ** 2
To draw only circumference, compare with some tolerance level
Eps = 1
...
if (Math.Abs((h/2 - row) ** 2 + (w/2 - col) ** 2 - SqDist) < Eps)
draw pixel
To draw filled circle:
if ((h/2 - row) ** 2 + (w/2 - col) ** 2 < SqDist)
draw pixel

Resources