Title: Map a trapezoidal image onto an isosceles trapezoid in Python
As my post Map a trapezoidal image onto a rectangle in Python explains, I sometimes take pictures that are not quite square. That post explains how you can use OpenCV and Python to map the picture onto a square area.
Sometimes I want a picture taken at an angle and that case can have a similar problem. I want the result to be an isosceles trapezoid--one where the sides have the same lengths so the shape is symmetric across its vertical axis. Unfortunately, the result is a bit off. As was the case in the previous post, the error can be small (much smaller than shown in the picture), but the eye is pretty good about noticing the difference.
Fortunately, the same techniques used by the previous post can help us here, too.
Run the program and use the File menu's Open command to load an image file. Then click four times to define the trapezoid.
When you select the fourth point, the program performs the mapping. Use the File menu's Save As command to save the resulting image. Use the File menu's Reset command to restore the original image so you can try again.
IMPORTANT: You must click to define the trapezoid's points in the order: upper left, upper right, lower right, and lower left. If you use a different order, the program will not preserve the image properly. It may rotate the image, reflect it, or turn it inside out. (Try it, it's pretty interesting.)
|
To perform the mapping, the program builds two numpy arrays, one holding points that define the trapezoid and one holding points that define the rectangle where you want to map the trapezoid. It calls the cv2.getPerspectiveTransform to get a transformation matrix to map from the first array of points to the second and then uses cv2.warpPerspective to apply the transformation to the image.
The following rectify_trapezoid is similar to the previous post's rectify method. The differences are highlighted in blue.
def rectify_trapezoid(self):
image = cv2.imread(self.filename)
if image is None:
messagebox.showinfo('File Error', f'Cannot find file {self.filename}.')
return
# Get the result rectangle's dimensions.
xmin = min([p[0] for p in self.corners])
xmax = max([p[0] for p in self.corners])
ymin = min([p[1] for p in self.corners])
ymax = max([p[1] for p in self.corners])
wid = xmax - xmin
hgt = ymax - ymin
# Make margins 10% of width/height.
margin_x = int(wid * 0.1)
margin_y = int(hgt * 0.1)
# Calculate the horizontal distance from xmin to the first point.
dx1 = self.corners[0][0] - xmin
dx2 = xmax - self.corners[1][0]
dx = (dx1 + dx2) / 2
pts1 = np.float32(self.corners) # Source points
pts2 = np.float32([
[margin_x + dx, margin_y],
[margin_x + wid - dx, margin_y],
[margin_x + wid, margin_y + hgt],
[margin_x, margin_y + hgt]]) # Destination points
# Get the transformation matrix.
matrix = cv2.getPerspectiveTransform(pts1, pts2)
# Get output image dimensions.
result_wid = int(wid + 2 * margin_x)
result_hgt = int(hgt + 2 * margin_y)
# Transform.
new_image = cv2.warpPerspective(image,
matrix, (result_wid, result_hgt))
# Convert colors from BGR to RGB.
new_image = cv2.cvtColor(new_image, cv2.COLOR_BGR2RGB)
# Remove the current corners.
self.corners = []
# Convert to PIL image and save.
self.current_pil_image = Image.fromarray(new_image)
self.show_current_image()
After loading the image file as before, the code gets the input trapezoid's minimum and maximum X and Y coordinates. It uses those to decide how wide and tall to make the output area. It then calculates margins much as before.
Next, the code calculates the X distances between the trapezoid's top points and the corresponding bottom points as shown in Figure 1. The program sets dx equal to the average of those two horizontal distances.
The code then creates the arrays of points that define the input quadrilateral and the output trapezoid. To make the output trapezoid line up nicely (and isosceles), the program ensures that the two bases (the upper and lower sides) are horizontal and that the endpoints of the upper base have X coordinates distance dx from the X coordinates of the corresponding lower base points as shown in Figure 2.
Finally, the program maps the input area to the output area as before. The result is a new trapezoidal shape that is isosceles as desired. The picture on the right shows the result for the cookie picture at the top of the post.
As in the previous post, three-dimensional images may be distorted by the process. If the input and output trapezoids are similar, the distortion won't be noticeable. However, if the picture holds many three-dimensional objects (like a chess board covered in pieces) and the adjustment is large, the pieces will probably show some distortion.
As in the previous post, the program includes there a lot of details that I haven't described here. For example, many of the images I work with are too big to fit on the screen so the program lets you scale the image. Download the example to see how the program does that, builds its interface, loads and saves files, lets you click to select points, and more.
|