3
votes

I am trying to crop a portion of an image as shown below using opencv / PIL . I want to crop the rectangle area as shown in red lines in the image in the below link. It is tilted at an angle.

enter image description here

I used numpy slicing logic as below. But it doesn't crop at an angle. It crops a normal straight rectangle

rect = cv2.boundingRect(pts)  
x,y,w,h = rect   
cropped = img[y:y+h, x:x+w]  

Also tried rotating the entire image at an angle and then cropping that part but it shrinks the resulting image

I am able to draw a rectangle on that image using the below code :

def draw_angled_rec(x0, y0, width, height, angle, img):

_angle = angle * math.pi / 180.0
b = math.cos(_angle) * 0.5
a = math.sin(_angle) * 0.5
pt0 = (int(x0 - a * height - b * width),
       int(y0 + b * height - a * width))
pt1 = (int(x0 + a * height - b * width),
       int(y0 - b * height - a * width))
pt2 = (int(2 * x0 - pt0[0]), int(2 * y0 - pt0[1]))
pt3 = (int(2 * x0 - pt1[0]), int(2 * y0 - pt1[1]))

cv2.line(img, pt0, pt1, (255,0,0), 3)
cv2.line(img, pt1, pt2, (255,0,0), 3)
cv2.line(img, pt2, pt3, (255,0,0), 3)
cv2.line(img, pt3, pt0, (255,0,0), 3)

Please suggest / advice a way to achieve it.

Thanks

1
Find the pixel coordinates of the 4 corners, then develop an equation of a line between each and if it is within the region bounded by all 4 lines you keep the pixel? Seems complex, but I personally wouldn't know a more sophisticated (easy) wayReedinationer

1 Answers

2
votes

Cropping image

Here's a image extraction widget that allows you to rotate the image and select a ROI by clicking and dragging the mouse. The idea is to use the mouse to select the bounding box window where we can use Numpy slicing to crop the image. Since OpenCV does not let you draw an angled rectangle, you can bypass that by first rotating the image.

Once you have selected the ROI, you can then crop the image using the bounding box coordinates. If we consider (0,0) as the top left corner of the image with left-to-right as the x-direction and top-to-bottom as the y-direction and we have (x1, y1) as the top-left vertex and (x2,y2) as the bottom-right vertex of a ROI, we can crop the image by:

ROI = image[y1:y2, x1:x2]

We are able to do this since images are stored as a Numpy array in OpenCV. Here is a great resource for Numpy array indexing and slicing.

To use the widget:

  • left mouse click + drag - select ROI
  • right mouse click - reset image
  • r - rotate image clockwise 5 degrees
  • e - rotate image counter-clockwise 5 degrees
  • c - crop selected ROI
  • q - quit program
import cv2
import numpy as np

class ExtractImageWidget(object):
    def __init__(self):
        self.original_image = cv2.imread('plane.PNG')

        # Resize image, remove if you want raw image size
        self.original_image = cv2.resize(self.original_image, (640, 556))
        self.clone = self.original_image.copy()

        cv2.namedWindow('image')
        cv2.setMouseCallback('image', self.extract_coordinates)

        # Bounding box reference points and boolean if we are extracting coordinates
        self.image_coordinates = []
        self.angle = 0
        self.extract = False
        self.selected_ROI = False

    def extract_coordinates(self, event, x, y, flags, parameters):
        # Record starting (x,y) coordinates on left mouse button click
        if event == cv2.EVENT_LBUTTONDOWN:
            self.image_coordinates = [(x,y)]
            self.extract = True

        # Record ending (x,y) coordintes on left mouse bottom release
        elif event == cv2.EVENT_LBUTTONUP:
            self.image_coordinates.append((x,y))
            self.extract = False

            self.selected_ROI = True
            self.crop_ROI()

            # Draw rectangle around ROI
            cv2.rectangle(self.clone, self.image_coordinates[0], self.image_coordinates[1], (0,255,0), 2)
            cv2.imshow("image", self.clone) 

        # Clear drawing boxes on right mouse button click and reset angle
        elif event == cv2.EVENT_RBUTTONDOWN:
            self.clone = self.original_image.copy()
            self.angle = 0
            self.selected_ROI = False

    def show_image(self):
        return self.clone

    def rotate_image(self, angle):
        # Grab the dimensions of the image and then determine the center
        (h, w) = self.original_image.shape[:2]
        (cX, cY) = (w / 2, h / 2)

        self.angle += angle
        # grab the rotation matrix (applying the negative of the
        # angle to rotate clockwise), then grab the sine and cosine
        # (i.e., the rotation components of the matrix)
        M = cv2.getRotationMatrix2D((cX, cY), -self.angle, 1.0)
        cos = np.abs(M[0, 0])
        sin = np.abs(M[0, 1])

        # Compute the new bounding dimensions of the image
        nW = int((h * sin) + (w * cos))
        nH = int((h * cos) + (w * sin))

        # Adjust the rotation matrix to take into account translation
        M[0, 2] += (nW / 2) - cX
        M[1, 2] += (nH / 2) - cY

        # Perform the actual rotation and return the image
        self.clone = cv2.warpAffine(self.original_image, M, (nW, nH))

        self.selected_ROI = False

    def crop_ROI(self):
        if self.selected_ROI:
            self.cropped_image = self.clone.copy()

            x1 = self.image_coordinates[0][0]
            y1 = self.image_coordinates[0][1]
            x2 = self.image_coordinates[1][0]
            y2 = self.image_coordinates[1][1]

            self.cropped_image = self.cropped_image[y1:y2, x1:x2]

            print('Cropped image: {} {}'.format(self.image_coordinates[0], self.image_coordinates[1]))
        else:
            print('Select ROI to crop before cropping')

    def show_cropped_ROI(self):
        cv2.imshow('cropped image', self.cropped_image)

if __name__ == '__main__':
    extract_image_widget = ExtractImageWidget()
    while True:
        cv2.imshow('image', extract_image_widget.show_image())
        key = cv2.waitKey(1)

        # Rotate clockwise 5 degrees
        if key == ord('r'):
            extract_image_widget.rotate_image(5)

        # Rotate counter clockwise 5 degrees
        if key == ord('e'):
            extract_image_widget.rotate_image(-5)

        # Close program with keyboard 'q'
        if key == ord('q'):
            cv2.destroyAllWindows()
            exit(1)

        # Crop image
        if key == ord('c'):
            extract_image_widget.show_cropped_ROI()