Draw a rotated box in openCV in python - python

I would like to draw a rotated rectangle I've got the top left point and bottom right point, width and height of box. As well as the angle. But I can't seem work out how you draw the rotated rectangle using OpenCV in Python. Please note that I do not want to rotate the image.
Thanks

There are many ways to draw a rectangle in OpenCV.
From the OpenCV documentatation: Drawing Functions
rectangle
Draws a simple, thick, or filled up-right rectangle.
So this function doesn't help as you want to draw it rotated.
A rectangle is nothing but a special 4-sided polygon. So simply use the function for drawing polygons instead.
polylines
Draws several polygonal curves.
Python:
cv2.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]) → img
and insert the 4 vertices of your rotated rectangle
or draw the 4 sides separately using
line
Draws a line segment connecting two points.
or
drawContours
Draws contours outlines or filled contours.
The points can be obtained using simple math or for example using OpenCV's RotatedRect https://docs.opencv.org/2.4/modules/core/doc/basic_structures.html#rotatedrect

class Point:
def __init__(self, x, y):
self.x = int(x)
self.y = int(y)
class Rectangle:
def __init__(self, x, y, w, h, angle):
# Center Point
self.x = x
self.y = y
# Height and Width
self.w = w
self.h = h
self.angle = angle
def rotate_rectangle(self, theta):
pt0, pt1, pt2, pt3 = self.get_vertices_points()
# Point 0
rotated_x = math.cos(theta) * (pt0.x - self.x) - math.sin(theta) * (pt0.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt0.x - self.x) + math.cos(theta) * (pt0.y - self.y) + self.y
point_0 = Point(rotated_x, rotated_y)
# Point 1
rotated_x = math.cos(theta) * (pt1.x - self.x) - math.sin(theta) * (pt1.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt1.x - self.x) + math.cos(theta) * (pt1.y - self.y) + self.y
point_1 = Point(rotated_x, rotated_y)
# Point 2
rotated_x = math.cos(theta) * (pt2.x - self.x) - math.sin(theta) * (pt2.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt2.x - self.x) + math.cos(theta) * (pt2.y - self.y) + self.y
point_2 = Point(rotated_x, rotated_y)
# Point 3
rotated_x = math.cos(theta) * (pt3.x - self.x) - math.sin(theta) * (pt3.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt3.x - self.x) + math.cos(theta) * (pt3.y - self.y) + self.y
point_3 = Point(rotated_x, rotated_y)
return point_0, point_1, point_2, point_3
Returns four new points that have been translated by theta
https://github.com/rij12/YOPO/blob/yopo/darkflow/net/yopo/calulating_IOU.py

Related

Formula for rotating a 4 point (rectangle) polygon?

I am using Zelle graphics, but I suppose it is the same for any graphics program. From what I can tell, Zelle graphics does not have a "setheading" or "rotate" command for the rectangle (this is probably because the bounding box is created with two points). In turtle graphics it is easy with setheading(). So presently, I have a class that creates a polygon. I have a draw method and a rotate method. In the rotate method I am changing the Point(x1,y1) for each corner in a FOR LOOP. I broke out some graph paper to get the initial first few points to rotate, there is complex math I believe that would make this more palatable.
Presently this is my init method with these attributes, but I am sure there is a better way:
class create():
def __init__(self, p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y):
self.p1x = p1x
self.p2x = p2x
self.p3x = p3x
self.p4x = p4x
self.p1y = p1y
self.p2y = p2y
self.p3y = p3y
self.p4y = p4y
NOTE : this is not the entirety of my code. This is just the INIT method. I don't want anyone to think I have not tried this. I have with working code to rotate a polygon to the left about 5 points, but this is just using self.p1x=self.p1x+1 - I know this is not the correct way to rotate a rectangle. But at present I have a working model for rotating it slightly.
EDIT :
Here is the new work I have tried. I am still trying to understand this :
I need the midpoint, because the equation is derived from it. That is after I get the rotation correct. I am going to assume a midpoint in a 10x10 starting from the origin is going to be 5,5 for this example.
Does this code look ok so far? I am going to assume a rotation angle of 10 degrees for this example.
import math
#assume a 10 x 10 rectangle top left corner 0,0
#top left coords
x=0
y=0
#midpoints
midx = 5
midy = 5
#degrees to radians???????
angle=math.radians(10)
#new top left x
newx1 = x - midx * math.cos(angle) - (y-midy)*math.sin(angle)+ midx
#new top left y
newy1 = y - midy * math.sin(angle) + (x-midx)*math.cos(angle)+ midy
print(newx1, newy1)
The output is : 0.9442021232736115 -0.7922796533956911
This is version which works for me - it rotates Point around midx, midy with angle.
First it has to move middle to (0,0) and get current angle.
def rotate_point(point, angle, midx, midy):
old_x = point.x
old_y = point.y
old_cx = old_x - midx # move middle to (0,0)
old_cy = old_y - midy # move middle to (0,0)
angle_radians = math.radians(angle)
angle_sin = math.sin(angle_radians)
angle_cos = math.cos(angle_radians)
new_cx = old_cx * angle_cos - old_cy * angle_sin
new_cy = old_cx * angle_sin + old_cy * angle_cos
new_x = new_cx + midx # move back
new_y = new_cy + midy # move back
point = Point(new_x, new_y)
return point
And next I can use it to rotate Polygon
def rotate_polygon(polygon, angle, midx, midy):
new_points = []
for p in polygon.getPoints():
new_p = rotate_point(p, angle, midx, midy)
new_points.append(new_p)
return Polygon(*new_points)
Here full code which rotates
rectangle around middle point (angles: 30, 60),
triangle around one of corners (angles: 90, 180, 270).
import math
from graphics import *
def rotate_point(point, angle, midx, midy):
old_x = point.x
old_y = point.y
old_cx = old_x - midx # move middle to (0,0)
old_cy = old_y - midy # move middle to (0,0)
angle_radians = math.radians(angle)
angle_sin = math.sin(angle_radians)
angle_cos = math.cos(angle_radians)
new_cx = old_cx * angle_cos - old_cy * angle_sin
new_cy = old_cx * angle_sin + old_cy * angle_cos
new_x = new_cx + midx # move back
new_y = new_cy + midy # move back
point = Point(new_x, new_y)
return point
def rotate_polygon(polygon, angle, midx, midy):
new_points = []
for p in polygon.getPoints():
new_p = rotate_point(p, angle, midx, midy)
new_points.append(new_p)
return Polygon(*new_points)
# --- main ---
win = GraphWin()
# --- rectangle ---
x = 100
y = 100
w = 50
h = 50
midx = x + w//2 # rectangle middle
midy = y + h//2 # rectangle middle
p1 = Polygon(Point(x, y), Point(x+w, y), Point(x+w, y+h), Point(x, y+h), Point(x, y))
p1.draw(win)
p2 = rotate_polygon(p1, 30, midx, midy)
p2.draw(win)
p3 = rotate_polygon(p1, 60, midx, midy)
p3.draw(win)
# --- triangle ---
x = 60
y = 60
w = 50
h = 50
midx = x #+5 # triangle corner
midy = y #+5 # triangle corner
t1 = Polygon(Point(x, y), Point(x+w, y), Point(x+w//2, y+h), Point(x, y))
t1.draw(win)
t2 = rotate_polygon(t1, 90, midx, midy)
t2.draw(win)
t3 = rotate_polygon(t1, 180, midx, midy)
t3.draw(win)
t4 = rotate_polygon(t1, 270, midx, midy)
t4.draw(win)
# ---
win.getMouse()
win.close()

OpenGL GL_POLYGON only works when drawing clockwise?

I'm new to OpenGL and I try to use the following code (GL_POLYGON) to draw a circle using python. But it seems like it only draws it when the points are added in a clockwise manner, otherwise, it just draws nothing
This successfully draws a circle
p = self.pos + Y_VEC * self.height # center of the circle
dv = self.dir_vec * self.radius # vector point forward
rv = self.right_vec * self.radius # vector point to right
sides = 20 # sides of the circle (circle is in fact polygon)
angle = 0
inc = 2 * math.pi / sides
glColor3f(0, 1, 0)
glPointSize(10.0)
glBegin(GL_POLYGON) # GL_POLYGON drawn but not shown in top view? GL_LINE_LOOP works
# glVertex3f(*p) # used for TRIANGLE_FAN
for i in range(sides+1):
pc = p + dv * math.cos(angle) + rv * math.sin(angle)
glVertex3f(*pc)
angle -= inc
glEnd()
Nothing rendered (only change is "-=" to "+=")
angle = 0
inc = 2 * math.pi / sides
glColor3f(0, 1, 0)
glPointSize(10.0)
glBegin(GL_POLYGON) # GL_POLYGON drawn but not shown in top view? GL_LINE_LOOP works
# glVertex3f(*p) # used for TRIANGLE_FAN
for i in range(sides+1):
pc = p + dv * math.cos(angle) + rv * math.sin(angle)
glVertex3f(*pc)
angle += inc # change here
glEnd()
Is this normal? What am I doing wrong?
Make sure face culling is disabled with glDisable(GL_CULL_FACE).

PyQt QImage Border Radius with for loop

I have Rectangle PNG, i want to process this image, remove some pixels to margin and save it in png format.
Here what i want at end;
Normal Image (There is no margin - border radius):
Here is my code; i tried somethings but not working properly;
qimg = QImage(PNG_YOL)
qimg = qimg.convertToFormat(QImage.Format_ARGB32) # making png or will be there black pixels
p = QPainter()
p.begin(qimg)
w = qimg.width() - 1
h=qimg.height() -1
for x in range(int(maxx)):
dx = 1 # i changed this value to get what i want, it works but not very fine, i am looking better way
for y in range(int(maxy)):
if x == 0:
qimg.setPixel(x, y, Qt.transparent)
qimg.setPixel(w - x, y, Qt.transparent)
if x != 0 and y < int(h * 1 / x / dx):
qimg.setPixel(x, y, Qt.transparent)
qimg.setPixel(w - x, y, Qt.transparent)
if x != 0:
qimg.setPixel(x, int(h * 1 / x / dx), Qt.transparent)
qimg.setPixel(w - x, int(h * 1 / x / dx), Qt.transparent)
p.end()
qimg.save(PNG_YOL)
With this code, i can get fine result but i am looking for better way.
Note: I just want only add margins to left-top and right-top.
Instead of getting too involved in processing pixel by pixel you can use QPainter with a QPainterPath:
from PyQt5 import QtCore, QtGui
qin = QtGui.QImage("input.png")
qout = QtGui.QImage(qin.size(), qin.format())
qout.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(qout)
path = QtGui.QPainterPath()
radius = 20
r = qout.rect()
path.arcMoveTo(0, 0, radius, radius, 180)
path.arcTo(0, 0, radius, radius, 180, -90)
path.arcTo(r.width()-radius, 0, radius, radius, 90, -90)
path.lineTo(r.bottomRight())
path.lineTo(r.bottomLeft())
path.closeSubpath()
painter.setClipPath(path)
painter.drawImage(QtCore.QPoint(0, 0), qin)
painter.end()
qout.save("output.png")

Filling a circle produced by Midpoint Algorithm

In python, I have written some code that generates a circle using Bresenham's Midpoint Algorithm:
from PIL import Image, ImageDraw
radius = 100 #radius of circle
xpts = [] #array to hold x pts
ypts = [] #array to hold y pts
img = Image.new('RGB', (1000, 1000))
draw = ImageDraw.Draw(img) #to use draw.line()
pixels = img.load()
d = (5/4) - radius
x = 0
y = radius
xpts.append(x) #initial x value
ypts.append(y) #initial y value
while x < y:
if d < 0:
d += (2*x + 3)
x += 1
xpts.append(x + 500) #translate points to center by 500px
ypts.append(y - 500)
else:
d += (2 * (x - y) + 5)
x += 1
y -= 1
xpts.append(x + 500) #translate points to center by 500px
ypts.append(y - 500)
for i in range(len(xpts)): #draw initial and reflected octant points
pixels[xpts[i] ,ypts[i]] = (255,255,0) #initial octant
pixels[xpts[i],-ypts[i]] = (255,255,0)
pixels[-xpts[i],ypts[i]] = (255,255,0)
pixels[-xpts[i],-ypts[i]] = (255,255,0)
pixels[ypts[i],xpts[i]] = (255,255,0)
pixels[-ypts[i],xpts[i]] = (255,255,0)
pixels[ypts[i],-xpts[i]] = (255,255,0)
pixels[-ypts[i],-xpts[i]] = (255,255,0)
img.show()
To fill it, I had planned to use ImageDraw to draw a line horizontally within the circle from each point that is generated from the initial octant using draw.line(). I have the x and y coordinates stored in arrays. However, I am stuck interpreting each point and its reflection point to draw the horizontal line using draw.line(). Could someone clarify this?
Instead of drawing individual pixels, you would just add a line that connects the pixels corresponding to each other (either -x and +x or -y and +y). For each Bresenham step, you draw four lines (each connecting two octants).
Here is your adapted sample code. I dropped the points array and instead drew the lines directly. I also added the cx and cy variables that define the circle center. In your code, you sometimes used negative indices. This only works by coincidence because the circle is in the center:
from PIL import Image, ImageDraw
radius = 100 # radius of circle
xpts = [] # array to hold x pts
ypts = [] # array to hold y pts
img = Image.new('RGB', (1000, 1000))
draw = ImageDraw.Draw(img) # to use draw.line()
pixels = img.load()
d = (5 / 4) - radius
x = 0
y = radius
cx = 500
cy = 500
def draw_scanlines(x, y):
color = (255, 255, 0)
draw.line((cx - x, cy + y, cx + x, cy + y), fill=color)
draw.line((cx - x, cy - y, cx + x, cy - y), fill=color)
draw.line((cx - y, cy + x, cx + y, cy + x), fill=color)
draw.line((cx - y, cy - x, cx + y, cy - x), fill=color)
draw_scanlines(x, y)
while x < y:
if d < 0:
d += (2 * x + 3)
x += 1
else:
d += (2 * (x - y) + 5)
x += 1
y -= 1
draw_scanlines(x, y)
img.show()
Instead of drawing lines, you can fill all points inside a circle with radius radius in O(n^2) using:
# Your code here
for x in range(radius):
for y in range(radius):
if x**2 + y**2 < radius**2:
pixels[ x + 500 , y-500] = (255,255,0)
pixels[ x + 500 , -y-500] = (255,255,0)
pixels[ -x + 500 , y-500] = (255,255,0)
pixels[ -x + 500 , -y-500] = (255,255,0)
img.show()

Collisions in Zelle graphics.py

I am trying to make my circle bounce off of my rectangle using Zelle graphics.py. Once the circle bounces off of the rectangle I wanted it to keep moving randomly. Here is my code so far, and it's working!
Also I know that each circle graphics technically can use the points of the smallest possible square that would fit around the circle to do the collision but I'm having trouble with doing that.
from graphics import *
import random
def delay(d):
for i in range(d):
for i in range(50):
pass
#-------------------------------------------------
def main():
win=GraphWin("Moving Circle",500,400)
win.setBackground('white')
pt= Point(100,200)
cir=Circle(pt,30)
#changes the color of the circle for each game
r = random.randrange(256)
b = random.randrange(256)
g = random.randrange(256)
color = color_rgb(r, g, b)
cir.setFill(color)
cir.draw(win)
#rectangle
rec = Rectangle(Point(450,450), Point(275, 425))
rec.draw(win)
rec.setFill('black')
#-------------------------------------------------
pt5 = Point(250,30)
instruct1=Text(pt5, "click multiple times to start(rectangle can take multiple clicks to move)")
instruct1.setTextColor('black')
instruct1.draw(win)
#-------------------------------------------------
p=cir.getCenter()
p2=win.getMouse()
dx=1
dy=1
keepGoing=True
while keepGoing:
d = 100
delay(d)
cir.move(dx,dy)
p=cir.getCenter()
p2=win.checkMouse()
instruct1.setText("")
#rectanlge
isClicked= win.checkMouse()
if isClicked:
rp = isClicked
rc = rec.getCenter()
rdx = rp.getX() - rc.getX()
rdy = rp.getY() - rc.getY()
rec.move(rdx,rdy)
#circle
if((p.getX()-30)<=0.0) or ((p.getX()+30)>=500):
dx= -dx
if((p.getY()-30)<=0.0) or ((p.getY()+30)>=400):
dy=-dy
p3=win.checkMouse()
main()
I know that each circle graphics technically can use the points of the
smallest possible square that would fir around the circle to do the
collision
I'm playing with an alternate idea -- we could consider a circle around the rectangle instead of a square around the circle. The issue for me is that we not only need to detect collision, but come out with a sense of which way to move away from the other object. It's not just True and False but rather a (dx, dy) type of result.
Obviously, a circle around the rectangle is too crude, but suppose it were lots of smaller circles making up the rectangle and we measure circle center to center distance to detect a hit:
A hit on just a central (green) rectangle circle means reverse the vertical direction of the big circle. A hit on just the end (red) circle means reverse the horizontal direction of the big circle. And we can detect both kinds of hits and reverse the big circle completely.
Here's my rework of your code with the above in mind -- I also fixed your multiple clicking issue and made lots of style changes:
from random import randrange
from graphics import *
WIDTH, HEIGHT = 500, 400
RADIUS = 30
def delay(d):
for _ in range(d):
for _ in range(50):
pass
def distance(p1, p2):
return ((p2.getX() - p1.getX()) ** 2 + (p2.getY() - p1.getY()) ** 2) ** 0.5
def intersects(circle, rectangle):
dx, dy = 1, 1 # no change
center = circle.getCenter()
rectangle_radius = (rectangle.p2.getY() - rectangle.p1.getY()) / 2
rectangle_width = rectangle.p2.getX() - rectangle.p1.getX()
y = rectangle.getCenter().getY()
for x in range(int(rectangle_radius * 2), int(rectangle_width - rectangle_radius * 2) + 1, int(rectangle_radius)):
if distance(center, Point(rectangle.p1.getX() + x, y)) <= rectangle_radius + RADIUS:
dy = -dy # reverse vertical
break
if distance(center, Point(rectangle.p1.getX() + rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
elif distance(center, Point(rectangle.p2.getX() - rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
return (dx, dy)
def main():
win = GraphWin("Moving Circle", WIDTH, HEIGHT)
circle = Circle(Point(WIDTH / 5, HEIGHT / 2), RADIUS)
# change the color of the circle for each game
color = color_rgb(randrange(256), randrange(256), randrange(256))
circle.setFill(color)
circle.draw(win)
# rectangle
rectangle = Rectangle(Point(275, 425), Point(450, 450)) # off screen
rectangle.setFill('black')
rectangle.draw(win)
dx, dy = 1, 1
while True:
delay(100)
circle.move(dx, dy)
# rectangle
isClicked = win.checkMouse()
if isClicked:
point = isClicked
center = rectangle.getCenter()
rectangle.move(point.getX() - center.getX(), point.getY() - center.getY())
# circle
center = circle.getCenter()
if (center.getX() - RADIUS) <= 0.0 or (center.getX() + RADIUS) >= WIDTH:
dx = -dx
if (center.getY() - RADIUS) <= 0.0 or (center.getY() + RADIUS) >= HEIGHT:
dy = -dy
# collision bounce
x, y = intersects(circle, rectangle)
dx *= x
dy *= y
main()
Not perfect, but something to play around with, possibly plugging in a better intersects() implementation.

Categories