1

I am working on the export feature for an image viewer. I have 4 points on an image that always form a rectangle, but the rectangle might be rotated. I also have the rotation of the rectangle. Now I want to generate a new image from the input image and the values I explained above. How can I achieve this is python ? The image below might make it a lot more clearer what I actually want to do:

  • you could do the inverse rotation to the image and keep the rectangle straight. Are you rotating the rectangle around it's center? – Guilherme Correa Apr 08 '22 at 12:33
  • no clue how to use PIL for that. -- use OpenCV, `warpAffine` and appropriately calculated affine transformation matrix. it's not complicated. you could use `getRotationMatrix2D` or calculate the values on your own. – Christoph Rackwitz Apr 08 '22 at 13:00
  • @ChristophRackwitz can you specify further how exactly I could use an affine transformation in my case? I looked it up in the opencv docs and it all looks very overwhelming coming from virtually no previous knowledge in image processing – Mergen Studios Apr 08 '22 at 13:07
  • how exactly is your rotated rectangle defined? I know you have the corner points, but those are calculated. what are they derived from? if you're unsure, I'd suggest a center point, an angle, and a width and height for the box, symmetric so the center is the center of the box, not placed anywhere else in the box. – Christoph Rackwitz Apr 08 '22 at 13:07
  • @ChristophRackwitz I ultimately want to use this in the export feature of an image viewer I am building. I want to give the user the ability to save what they are currently seeing in the image viewer to an actual image file. I am getting the roatation and the coordinates for the rectangle from a builtin "get_in_view()" function from the gui library I am using. In the end I just have a list with my 4 points, the rotation and the path to the image to perform the image manipulation on. – Mergen Studios Apr 08 '22 at 14:21
  • ok, so it's four points. good. do you want to preserve scale (have just rotation+translation) or does the output need to be a specific size? I'll build an answer later. if you don't want a solution using OpenCV, let me know, because I'll use OpenCV. the principles probably apply to PIL too but I'm not terribly familiar with that library. – Christoph Rackwitz Apr 08 '22 at 14:32

1 Answers1

1

I will be using the following picture for example:

enter image description here

Creating the wanted rectangle

Informative, you say you are getting the rectangle itself I am just creating here for the example as follows:

# creating source points - informative
xx = [50, 50, 200, 200]
yy = [30, 60, 30, 60]
alpha  = np.pi/6  # 30 degrees
rotMat = np.array([[np.cos(alpha), -1*np.sin(alpha)],
                   [np.sin(alpha), np.cos(alpha)]])
src_pts = []
for [x, y] in zip(xx,yy):
    src_pts.append(rotMat.dot(np.array([x, y])) + np.array([3, 7]))
src_pts = np.array(src_pts)

After generating the rectangle coordinate:

[[ 31.30127019  57.98076211]
 [ 16.30127019  83.96152423]
 [161.20508076 132.98076211]
 [146.20508076 158.96152423]]

we get the sides by checking if the dot product of the vectors is 0 indicating 90 degrees between them:

if np.isclose((src_pts[1] - src_pts[0]).dot(src_pts[2] - src_pts[0]), 0):
    side1 = np.around(np.linalg.norm(src_pts[1] - src_pts[0]), 2)
    side2 = np.around(np.linalg.norm(src_pts[2] - src_pts[0]), 2)
    dst_points = np.array([[0, 0],[0, side1], [side2, 0]])
    src_pts    = src_pts[[0, 1, 2]]

As a sanity check we can print the sides and see that:

print(side1, side2)  # 30.0 150.0 ; match the original rectangle sides

And we can see the three points in the picture and the new location after the rotation + shift (Affine transformation) enter image description here

Where we can see the original points in blue and the rotated + shifted points in orange.

Affine transformation

Now we can find the affine transformation (rotation + translation) to take us there using opencv:

# finding affine transformation
affine = cv2.getAffineTransform(np.float32(src_pts), np.float32(dst_points))
print(affine)
#[[  0.86602543   0.50000001 -56.09807697]
#[ -0.49999998   0.8660254  -34.56217897]]

To check that we got a correct affine transformation and that our coordinate points were alighted we can see that the left 2X2 matrix corresponds to a rotation matrix (in this case the 30 degrees we rotated to create the reectangle) and the right 2X1 vector is the shift or translation vector.

Applying the transformation

All is set up for the grand finale, tranforming the picture and cropping. All is done nicely using opencv:

# Applying affine transform to the image
img_new = cv2.warpAffine(img, affine, dsize=(int(side2), int(side1)))

Plotting the result we get: enter image description here

Tomer Geva
  • 1,764
  • 4
  • 16