How to Straighten a Rotated Rectangle Area of an Image Using Opencv in Python

How to straighten a rotated rectangle area of an image using OpenCV in Python?

You can use the warpAffine function to rotate the image around a defined center point. The suitable rotation matrix can be generated using getRotationMatrix2D (where theta is in degrees).

Start Image After finding the desired rectangle

You then can use Numpy slicing to cut the image.

Rotated Image Result

import cv2
import numpy as np

def subimage(image, center, theta, width, height):

'''
Rotates OpenCV image around center with angle theta (in deg)
then crops the image according to width and height.
'''

# Uncomment for theta in radians
#theta *= 180/np.pi

shape = ( image.shape[1], image.shape[0] ) # cv2.warpAffine expects shape in (length, height)

matrix = cv2.getRotationMatrix2D( center=center, angle=theta, scale=1 )
image = cv2.warpAffine( src=image, M=matrix, dsize=shape )

x = int( center[0] - width/2 )
y = int( center[1] - height/2 )

image = image[ y:y+height, x:x+width ]

return image

Keep in mind that dsize is the shape of the output image. If the patch/angle is sufficiently large, edges get cut off (compare image above) if using the original shape as--for means of simplicity--done above. In this case, you could introduce a scaling factor to shape (to enlarge the output image) and the reference point for slicing (here center).

The above function can be used as follows:

image = cv2.imread('owl.jpg')
image = subimage(image, center=(110, 125), theta=30, width=100, height=200)
cv2.imwrite('patch.jpg', image)

straightening contoured rectangles in opencv python

Here the code that solves your problem:

import cv2
import numpy as np

# get the minimum bounding box for the chip image
image = cv2.imread("./chip1.png", cv2.IMREAD_COLOR)
image = image[10:-10,10:-10]
imgray = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)[...,0]
ret, thresh = cv2.threshold(imgray, 20, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
mask = 255 - thresh
_, contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

maxArea = 0
best = None
for contour in contours:
area = cv2.contourArea(contour)
if area > maxArea :
maxArea = area
best = contour

rect = cv2.minAreaRect(best)
box = cv2.boxPoints(rect)
box = np.int0(box)

#crop image inside bounding box
scale = 1 # cropping margin, 1 == no margin
W = rect[1][0]
H = rect[1][1]

Xs = [i[0] for i in box]
Ys = [i[1] for i in box]
x1 = min(Xs)
x2 = max(Xs)
y1 = min(Ys)
y2 = max(Ys)

angle = rect[2]
rotated = False
if angle < -45:
angle += 90
rotated = True

center = (int((x1+x2)/2), int((y1+y2)/2))
size = (int(scale*(x2-x1)), int(scale*(y2-y1)))

M = cv2.getRotationMatrix2D((size[0]/2, size[1]/2), angle, 1.0)

cropped = cv2.getRectSubPix(image, size, center)
cropped = cv2.warpAffine(cropped, M, size)

croppedW = W if not rotated else H
croppedH = H if not rotated else W

image = cv2.getRectSubPix(
cropped, (int(croppedW*scale), int(croppedH*scale)), (size[0]/2, size[1]/2))

# show result
while True:
cv2.imshow("result", image)
k = cv2.waitKey(30) & 0xff
if k == 27:
break

here the result image:
chip rectified

Crop Rectangle returned by minAreaRect OpenCV [Python]

You have not given sample code, so I am answering without code also.
You could proceed as follows:

  1. From corners of rectangle, determine angle alpha of rotation against horizontal axis.
  2. Rotate image by alpha so that cropped rectangle is parallel to image borders. Make sure that the temporary image is larger in size so that no information gets lost (cf: Rotate image without cropping OpenCV)
  3. Crop image using numpy slicing (cf: How to crop an image in OpenCV using Python)
  4. Rotate image back by -alpha.

How to rotate a rectangle in an image

Use angle argument:

patches.Rectangle((200, 200), 40, 30, linewidth=1, edgecolor='r', facecolor='none', angle=30)

Output:

Sample Image

Generate new image from rotated rectangle in python

I will be using the following picture for example:

Sample Image

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)
Sample Image

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:
Sample Image

How to rotate an image to align the text for extraction?

Here's a simple approach:

  1. Obtain binary image. Load image, convert to grayscale,
    Gaussian blur, then Otsu's threshold.

  2. Find contours and sort for largest contour. We find contours then filter using contour area with cv2.contourArea() to isolate the rectangular contour.

  3. Perform perspective transform. Next we perform contour approximation with cv2.contourArea() to obtain the rectangular contour. Finally we utilize imutils.perspective.four_point_transform to actually obtain the bird's eye view of the image.


Binary image

Result

To actually extract the text, take a look at

  1. Use pytesseract OCR to recognize text from an image

  2. Cleaning image for OCR

  3. Detect text area in an image using python and opencv

Code

from imutils.perspective import four_point_transform
import cv2
import numpy

# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread("1.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Find contours and sort for largest contour
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None

for c in cnts:
# Perform contour approximation
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
displayCnt = approx
break

# Obtain birds' eye view of image
warped = four_point_transform(image, displayCnt.reshape(4, 2))

cv2.imshow("thresh", thresh)
cv2.imshow("warped", warped)
cv2.waitKey()

Crop all characters from an image

There appears to be an error in your crop_minAreaRect function.
I haven't debugged your code any further than the return of crop_minAreaRect, so the letters may or may not be correctly rotated according following your approach, but this change fixes the underlying problem.

The proposed function is taken from the following question and modified: How to straighten a rotated rectangle area of an image using OpenCV in Python?

import cv2
import numpy as np

def subimage(image, center, theta, width, height):
'''
Rotates OpenCV image around center with angle theta (in deg)
then crops the image according to width and height.
'''
width = int(width)
height = int(height)
# Uncomment for theta in radians
# theta *= 180/np.pi

shape = (image.shape[1], image.shape[0]) # cv2.warpAffine expects shape in (length, height)

matrix = cv2.getRotationMatrix2D(center=center, angle=theta, scale=1)
image = cv2.warpAffine(src=image, M=matrix, dsize=shape)

x = int(center[0] - width / 2)
y = int(center[1] - height / 2)

image = image[y:y + height, x:x + width]

return image

def sort_contours(cnts, method="left-to-right"):
# from https://pyimagesearch.com/2015/04/20/sorting-contours-using-python-and-opencv/
reverse = False
i = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
i = 1
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))
return (cnts, boundingBoxes)

im_name = 'letters.png'
im = cv2.imread(im_name)
im_copy = im.copy()

imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

cv2.drawContours(im_copy, contours, -1, (0, 255, 0), 2)
cv2.imshow("contours", im_copy)

# print("num contours: " + str(len(contours)))
i = 0

sorted_cnts, bounding_boxes = sort_contours(contours, method="left-to-right")

for cnt in sorted_cnts:
size = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect = cv2.minAreaRect(cnt)

im_cropped = subimage(im, center=rect[0], theta=rect[2], width=rect[1][0], height=rect[1][1])

h, w = im_cropped.shape[0], im_cropped.shape[1]
if w > h:
im_cropped = cv2.rotate(im_cropped, cv2.ROTATE_90_CLOCKWISE)

# print("w: " + str(w) + ", h: " + str(h))
if w > 0 and h > 0:
cv2.imshow("cropped" + str(i), im_cropped)
i += 1
# cv2.waitKey(0)

cv2.waitKey(0)


Related Topics



Leave a reply



Submit