pythonimageopencvimage-processingmedical

Cropping in OpenCV return images rotated 90 degrees


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.

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.


Solution

  • Hi there are two changes which will correct the output:

    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)