Cropping in OpenCV return images rotated 90 degrees

Question:

I want to crop images according to their right frame. I have about 10000 of hand X-ray images to preprocess, and what I have done so far:

  1. Apply Gaussian Blur and Threshold (Binary + Otsu) on the image.
  2. Apply dilation to get a single object (in this case a hand).
  3. Used cv2.findContours() to draw outline along the edges around the hand.
  4. Used cv2.boundingRect() to find the right frame, and then cv2.minAreaRect() and cv2.boxPoints to get the right points for the bounding box.
  5. Used cv2.warpPerspective to adjust image according to height and width.

The code below describes the above:

import os
import cv2
import numpy as np
from matplotlib import pyplot as plt

# Load image, create mask, grayscale, Gaussian blur, Otsu's threshold
img_path = "sample_image.png"

image = cv2.imread(image_path)
original = image.copy()
blank = np.zeros(image.shape[:2], dtype = np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (33,33), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Merge text into a single contour
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations = 3)

# Find contours
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key = lambda x: cv2.boundingRect(x)[0])

for c in cnts:
    # Filter using contour area and aspect ratio (x1 = width, y1 = height)
    x, y, x1, y1 = cv2.boundingRect(c)
    if (x1 > 500) and (y1 > 700):
        rect = cv2.minAreaRect(c)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        width = int(rect[1][0])
        height = int(rect[1][1])
        src_pts = box.astype("float32")
        dst_pts = np.array([[0, height-1], [0, 0],
                            [width-1, 0], [width-1, height-1]], dtype="float32")

        M = cv2.getPerspectiveTransform(src_pts, dst_pts)
        warped = cv2.warpPerspective(image, M, (width, height))
        plt.imshow(warped)

If you have a look at some of the images in the folder, those are the inputs. When I run these images through the code above, I get an output like this. Some of them are cropped nicely (straightened), however, some of them are cropped with 90 degree rotations. Is there a code to counter the ‘rotating 90 degrees output’ problem?

Here are some images:

Image Inputs: Four X-ray examples

Image Outputs: Returns images that are 90 degrees rotated

Image Outputs wanted: Straightened image (Just used Photoshop to straighten them. Dont want to do this for 10000 images…)

UPDATE:

I edited the code according to below-mentioned suggestions. After running the some samples, it now returns images that are now 90 degrees slanted to the right.

  • Input images: enter image description here

  • Output images: enter image description here

I doubt it’s because of the quality of the images. Maybe it’s got to do with OpenCV’s minAreaRect()? or boxPoints?

FINAL UPDATE:

According to @Prashant Maurya, the code was updated with a function added to detect whether the position of the hand is left or right. And then mapping src_pts to right dst_pts. Full code is shown below.

Asked By: Bathtub

||

Answers:

Hi there are two changes which will correct the output:

  • The width and height taken in the code is in the wrong order ie: width: 1470 & height: 1118 just switch the values:
  • Map src_pts to right dst_pts the current code is mapping top left
    corner to bottom left therefore the image is being rotated.
  • Added function to detect whether image is right tilted or left and rotate and rotate it accordingly

Full code with changes is:

import os
import cv2
import numpy as np
from matplotlib import pyplot as plt

# Load image, create mask, grayscale, Gaussian blur, Otsu's threshold
img_path = "xray1.png"

image = cv2.imread(img_path)
cv2.imshow("image original", image)
cv2.waitKey(10000)
original = image.copy()
blank = np.zeros(image.shape[:2], dtype = np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (33,33), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Merge text into a single contour
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations = 3)

# Find contours
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key = lambda x: cv2.boundingRect(x)[0])

def get_tilt(box):
    tilt = "Left"
    x_list = [coord[0] for coord in box]
    y_list = [coord[1] for coord in box]

    print(x_list)
    print(y_list)

    x_list = sorted(x_list)
    y_list = sorted(y_list)

    print(x_list)
    print(y_list)

    for coord in box:
        if coord[0] == x_list[0]:
            index = y_list.index(coord[1])
            print("Index: ", index)
            if index == 1:
                tilt = "Left"
            else:
                tilt = "Right"

    return tilt


for c in cnts:
    # Filter using contour area and aspect ratio (x1 = width, y1 = height)
    x, y, x1, y1 = cv2.boundingRect(c)
    if (x1 > 500) and (y1 > 700):
        rect = cv2.minAreaRect(c)
        print("rect",rect)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        # print("rect:", box)
        tilt = get_tilt(box)

        src_pts = box.astype("float32")

        if tilt == "Left":
            width = int(rect[1][1])
            height = int(rect[1][0])
            dst_pts = np.array([[0, 0],
                                [width-1, 0], [width-1, height-1], [0, height-1]], dtype="float32")
        else:
            width = int(rect[1][0])
            height = int(rect[1][1])
            dst_pts = np.array([[0, height-1], [0, 0],
                            [width-1, 0], [width-1, height-1]], dtype="float32")

        print("Src pts:", src_pts)
        print("Dst pts:", dst_pts)
        M = cv2.getPerspectiveTransform(src_pts, dst_pts)
        warped = cv2.warpPerspective(image, M, (width, height))
        print("Showing image ..")
        # plt.imshow(warped)
        cv2.imshow("image crop", warped)
        cv2.waitKey(10000)
Answered By: Prashant Maurya