I am working on an application which first needs to rotate an image slightly in order to deskew it.
I have detected a vertical line down the image between two points (x1, y1) and (x2,y2). The vertical line is not entirely vertical - the second x coordinate is slightly less or slightly more than the top x and therefore the line has a slope.
I am trying to calculate the angle of the slope of the line so that I can rotate the image and then re-detect the line so that it is perfectly vertical. I am using OpenCV in Python for this.
Unfortunately I am having problems calculating the slope of the line and the rotation of the image is therefore inaccurate.
My function to detect the slope of the vertical line in the image is as follows:
def find_vert_angles(vertical_line_candidates, vertical_line_candidates2, roi_x_coordinates, roi_x_coordinates2):
line_angles_radians = []
for line_x, line_x2 in itertools.izip(vertical_line_candidates, vertical_line_candidates2):
x_diff = line_x2 - line_x
y_diff = roi_x_coordinates[1][1] - roi_x_coordinates[0][1]
if x_diff != 0:
slope = y_diff / x_diff
angle_in_radians = atan(slope)
line_angles_radians.append(angle_in_radians)
else:
line_angles_radians.append(0)
return line_angles_radians
My code to rotate the image so that the line can be vertical is as follows:
skew_angle = degrees(vert_angle[1])
print "Detected skew angle is " + str(skew_angle) + " degrees"
warp = cv2.getRotationMatrix2D((img_width/2,img_height/2),skew_angle,1)
image = cv2.warpAffine(image,warp,(img_width,img_height))
But the angles of rotation are coming out as 249 degrees, 89 degrees etc. when they should just be a few degrees each way.
If anyone can help me find a solution to this problem so that I can correct the skew of the image correctly, that would be much appreciated.
I believe you're finding the angle that's complementary to the one you want.
A nearly vertical line will have a very small angle with respect to the y-axis, but a near 90-degree angle with respect to the x-axis.
Consider your line segment to be the hypotenuse of a right triangle. The angle it forms with respect to the x-axis is atan((y2-y1)/(x2-x1)). The angle with respect to the y-axis, which I think is what you want to rotate should be atan((x2-x1)/(y2-y1))
Related
I am building a video game overlay that sends data back to the player to create a custom HUD, just for fun.
I am trying to read an image of a video game compass and determine the exact orientation of the compass to be a part of my HUD.
Example photo which shows the compass at the top of the screen:
(The circle currently facing ~170°, NOTE: The position of the compass is also fixed)
Example photo which shows the compass at the top of the screen:
Obviously, when I image process on the compass I will only be looking at the compass and not the whole screen.
This has been more challenging for me compared to previous computer vision aspects of my HUD. I have been trying to process the image using cv2 and from there use some object detection to find the "needle" of the compass.
I am struggling to get a triangle shape detection on either needle that will help me know my orientation.
The solution could be lower-tech and hackier, perhaps just searching for the pixel on the edge of the compass and determining that is the end of the needle.
One solution I do not think is viable is using object detection to find a picture of a compass facing true north and then calculating the rotation of the current compass. This is due to the fact that the background of the compass does not rotate only the needle does.
So far I have applied Hough Circle Transform as seen here:
https://opencv24-python-tutorials.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghcircles/py_houghcircles.html#hough-circles
Which has helped me get a circle around my compass as well as the middle of my compass. However, I cannot find a good solution for finding the facing of the needle compared to the middle of the compass.
I understand this is a pretty open-ended question but I am looking for any theoretical solutions that would help me implement a solution. Anything would help as this is a strange problem for me and I am struggling to think how to go about solving it.
In general I would suggest to look at a thin ring just beneath the border or your compass (This will give you lowest error). Either you could work on an image which is a polar transform of this ring or directly on that ring, looking for the center of gravity of the color red. This center of gravity with respect to the center of your compass should give you the angle. Most likely you don't even need the polar transform.
im = cv.imread("RPc9Q.png")
(x,y,w,h) = (406, 14, 29, 29)
warped = cv.warpPolar(
src=im,
dsize=(512, 512),
center=(x + (w-1)/2, y + (h-1)/2),
maxRadius=(w-1)/2,
flags=cv.WARP_POLAR_LINEAR | cv.INTER_LINEAR
)
Here's some more elaboration on the polar warp approach.
polar warp
take a column of pixels, being a circle in the source picture
plot to see what's there
argmax to find the red bits of the arrow
im = cv.imread("RPc9Q.png") * np.float32(1/255)
(x,y,w,h) = (406, 14, 29, 29)
# polar warp...
steps_angle = 360 * 2
steps_radius = 512
warped = cv.warpPolar(
src=im,
dsize=(steps_radius, steps_angle),
center=(x + (w-1)/2, y + (h-1)/2),
maxRadius=(w-1)/2,
flags=cv.WARP_POLAR_LINEAR | cv.INTER_LANCZOS4
)
# goes 360 degrees, starting from 90 degrees (east) clockwise
# sample at 85% of "full radius", picked manually
col = int(0.85 * steps_radius)
# for illustration
imshow(cv.rotate(cv.line(warped.copy(), (col, 0), (col, warped.shape[0]), (0, 0, 255), 1), rotateCode=cv.ROTATE_90_COUNTERCLOCKWISE))
signal = warped[:,col,2] # red channel, that column
# polar warp coordinate system:
# first row of pixels is sampled at exactly 90 degrees (east)
samplepoints = np.arange(steps_angle) / steps_angle * 360 + 90
imax = np.argmax(signal) # peak
def vertex_parabola(y1, y2, y3):
return 0.5 * (y1 - y3) / (y3 - 2*y2 + y1)
# print("samples around maximum:", signal[imax-1:imax+2] * 255)
imax += vertex_parabola(*signal[imax-1:imax+2].astype(np.float32))
# that slice will blow up in your face if the index gets close to the edges
# either use np.roll() or drop the correction entirely
angle = imax / steps_angle * 360 + 90 # ~= samplepoints[imax]
print("angle:", angle) # 176.2
plt.figure(figsize=(16,4))
plt.xlim(90, 360+90)
plt.xticks(np.arange(90, 360+90, 45))
plt.plot(
samplepoints, signal, 'k-',
samplepoints, signal, 'k.')
plt.axvline(x=angle, color='r', linestyle='-')
plt.show()
I have been able to solve my question with the feedback provided.
First I grab the image of the compass:
step_1
After I process the image crop out the middle and edges of the compass as seen here:
step_2
Now I have a cropped compass with only a little bit of red showing where the compass needle points. I masked out the red part of the image.
step_3
From there it is a simple operation to find the center of the blob which roughly outputs where the needle is pointing. Although this is not perfectly accurate I believe it will work for my purposes.
step_4
Now that I know where the needle end is it should be easy to calculate the direction based on that.
Some references:
Finding red color in image using Python & OpenCV
https://www.geeksforgeeks.org/python-opencv-find-center-of-contour/
Using PyCairo, I want to be able to have a method that can put, resize & rotate a given ImageSurface on a context, but rotating by the center of the image (not the top-left)
Okay I've tried the examples I've found here but without any success.
Let's introduce the "context" in details.
I've got a "finale" ImageSurface (say A) on which some other images & texts are written.
I want to put on it another ImageSurface (say B), at a specified position where this position is the top-left where to put B on A. Then I need to resize B (reduce its size) and to rotate it by its center instead of by its top-left corner.
Here is an illustration of the wanted result :
I've tried the following but without success:
def draw_rotated_image(ctx, image_surface, left, top, width, height, angle):
ctx.save()
w = image_surface.get_width()
h = image_surface.get_height()
cl = left / (width/w)
ct = top / (height/h)
ctx.rotate(angle*3.1415927/180)
ctx.scale(width/w, height/h)
ctx.translate(cl + (-0.5*w),ct + (-0.5*h) )
ctx.set_source_surface(image_surface, 0, 0)
ctx.paint()
ctx.restore()
return
Thanks a lot for your help :)
Well, I finally made it ! (thanks to my 14 year old son who made me revise my trigonometry)
I'm trying to explain here my solution.
First, I AM NOT A MATHEMATICIAN. So there is probably a best way, and surely my explanation have errors, but I'm just explaining the logical way I have used to get the good result.
The best idea for that is to first draw a circle around the rectangle, because we need to move the top-left corner of this rectangle, around its own circle, according to the desired angle.
So to get the radius of the rectangle circle, we need to compute its hypothenuse, then to divide by 2 :
hypothenuse = math.hypot(layerWidth,layerHeight)
radius = hypothenuse / 2
Then we will be able to draw a circle around the rectangle.
Second, we need to know at which angle, on this circle, is the actual top-left corner of the rectangle.
So for that, we need compute the invert tangent of the rectangle, which is arc-tan(height/width).
But because we want to know how many degrees are we far from 0°, we need to compute the opposite so arc-tan(width/height).
Finally, another singularity is that Cairo 0° is in fact at 90°, so we will have to rotate again.
This can be shown by this simple graphic :
So finally, what is necessary to understand ?
If you want to draw a layer, with an angle, rotated by its center, the top-left point will move around the circle according to the desired angle.
The top-left position with a given angle of 0 needs to be "the reference".
So we need to get the new X-Y position where to start putting the layer to be able to rotate it :
Now, we can write a function that will return the X-Y pos of the top left rectangle where to draw it with a given angle :
def getTopLeftForRectangleAtAngle(layerLeft,layerTop,layerWidth,layerHeight,angleInDegrees):
# now we need to know the angle of the top-left corner
# for that, we need to compute the arc tangent of the triangle-rectangle:
layerAngleRad = math.atan((layerWidth / layerHeight))
layerAngle = math.degrees(layerAngleRad)
# 0° is 3 o'clock. So we need to rotate left to 90° first
# Then we want that 0° will be the top left corner which is "layerAngle" far from 0
if (angleInDegrees >= (90 + layerAngle)):
angleInDegrees -= (90 + layerAngle)
else:
angleInDegrees = 360 - ((90 + layerAngle) - angleInDegrees)
angle = (angleInDegrees * math.pi / 180.0)
centerLeft = layerLeft + (layerWidth / 2)
centerTop = layerTop + (layerHeight / 2)
# hypothenuse will help us knowing the circle radius
hypothenuse = math.hypot(layerWidth,layerHeight)
radius = hypothenuse / 2
pointX = centerLeft + radius * math.cos(angle)
pointY = centerTop + radius * math.sin(angle)
return (pointX,pointY)
And finally, here is how to use it with an image we want to resize, rotate and write on a context:
def draw_rotated_image(ctx, image_surface, left, top, width, height, angle=0.0, alpha=1.0):
ctx.save()
w = image_surface.get_width()
h = image_surface.get_height()
# get the new top-left position according to the given angle
newTopLeft = getTopLeftForRectangleAtAngle(left, top, width, height, angle)
# translate
ctx.translate(newTopLeft[0], newTopLeft[1])
# rotate
ctx.rotate(angle * math.pi / 180)
# scale & write
ctx.scale(width/w, height/h)
ctx.set_source_surface(image_surface, 0, 0)
ctx.paint_with_alpha(alpha)
ctx.restore()
return
I have have a function to generate pixel coordinates of a line of a given angle and pixel length.
# Start position of line
lineStartX = 100
lineStartY = 100
# Length of line in pixels
lineLength = 100
# Set angle of line
angle = 0
for pixel in range(lineLength):
# Next pixel with angle adjustment
endy = (pixel + 1) * math.sin(math.radians(angle))
endx = (pixel + 1) * math.cos(math.radians(angle))
# Add next pixel with angle adjustment to line start coordinates
Xcoordinate = lineStartX + (endx)
Ycoordinate = lineStartY + (endy)
How can i adjust the angle of each concurrent pixel coordinate to form a circle?
I've tried adjusting the angle incrementally for each pixel but it only partially completes the circle, I'm not sure what to do next.
angle += 0.10
Update: When i increment the angle as suggested:
angle += 2 * np.arcsin(math.pi / lineLength)
The circle still only partially completes
Should the line be a circle, the length equal would its circumference.
Since drawing a perfect circle on a pixel matrix is not possible, some approximation rules will be in order. You could try incrementally adjusting the angle not by 0.10 but by
2 * arcsin(pi / length)
to make the spiral's ends meet :)
I've extracted a Circle shaped mask from an image in OpenCV. I used the following code for the same:
H, W = img.shape
x, y = np.meshgrid(np.arange(W), np.arange(H))**
d2 = (x - xc)**2 + (y - yc)**2**
mask = d2 < r **2**
And, used the mask value to find the average color outside the circle.
outside = np.ma.masked_where(mask, img)**
average_color = outside.mean()**
I want to extract an Ellipse from an image in the same above process in OpenCV Python.
Thank You.
Drawing Ellipse
To draw the ellipse, we need to pass several arguments. One argument is the center location (x,y). Next argument is axes lengths (major axis length, minor axis length). angle is the angle of rotation of ellipse in anti-clockwise direction. startAngle and endAngle denotes the starting and ending of ellipse arc measured in clockwise direction from major axis. i.e. giving values 0 and 360 gives the full ellipse. For more details, check the documentation of cv2.ellipse(). Below example draws a half ellipse at the center of the image.
cv2.ellipse(img,(256,256),(100,50),0,0,180,255,-1)
Taken from Miki's Link in the Question Comments
I'm trying to do draw widgets on the circle, for this I need to paint the widgets as a arc. I know the number of widgets (let's say), then each widget is at 36 degrees from the origin to the circumference. The information I have is the center of circle, radius and I know the starting and end point on the circumference for each such widget.
This is computed by doing
dx = int(round(400 + 300 * np.cos(angle)))
dy = int(round(400 + 300 * np.sin(angle)))
where angle = 2 * np.pi / 15
I go over a for loop computing the new value for angle which is basically angle * i where i = (1, 10)
I don't understand the start angle and span angle for the arcs function in QPainter.QPainter Arc. I googled and not many terms came up. Maybe there is a different term for them.
So the problem is I have a starting point and ending point on the circumference and center and radius, how do I use them to draw Arcs such that I get something that looks like :
circos
What I have tried is, I can compute the center point (cx) of the two end points, if I draw a line from the center of the circle to this point cx, then I can compute how far this point circumference which essentially is my width, but how to get the orientation correct to represent them as circles.
Instead of circular I do have a layout with just lines like this, but would like to be like the circos one.
My image
I don't understand the start angle and span angle for the arcs function in QPainter.QPainter Arc.
Why? The documentation is IMHO very clear:
The startAngle and spanAngle must be specified in 1/16th of a degree, i.e. a full circle equals 5760 (16 * 360).
This means that your units are 1/16º. E.g. 45º is 45*16 units.
Positive values for the angles mean counter-clockwise while negative values mean the clockwise direction. Zero degrees is at the 3 o’clock position."
This means that 90*16 points at 12 o'clock (goes 90º counter-clockwise from 3 o'clock), and -90*16 points at 6 o'clock.
Of course the "zero" degrees only has sense for the start angle. The span angle states how much further does the arc go, and in which direction.
For example, to draw an arc from 3 o'clock to 12 o'clock, you'd do
painter.drawArc(rect, 0, 90*16)
*or*
painter.drawArc(rect, 90*16, -90*16)
But to draw an arc from 3 o'clock to 6 o'clock, you'd do
painter.drawArc(rect, 0, -90*16)
*or*
painter.drawArc(rect, -90*16, 90*16)
The arcs are not specified using center and radius, but rather using a bounding rectangle. If the arc was a full ellipse, it would be inscribed in the rectangle - the arcs are implicitly elliptical arcs.
So, given x and y centerpoint, and r for circular radius, the bounding rectangle is
rect = QRect(x-r, y-r, 2*r, 2*r)