Manish Sharma
Manish Sharma

Reputation: 45

How to extract diagram from an image?

enter image description here

enter image description here

enter image description here

enter image description here

I've used Contour based approach but its detecting so many contours. How can I extract my ROI contour?

image = cv2.imread('ULTI.png')
original = image.copy()
cv2.imwrite("bg.png",bg)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
canny = cv2.Canny(blurred, 120, 255, 1)
kernel = np.ones((5,5),np.uint8)
dilate = cv2.dilate(canny, kernel, iterations=1)

# Find contours
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

# Iterate thorugh contours and filter for ROI
image_number = 0
cnts = max(cnts, key = cv2.contourArea)
print("no ",len(cnts))
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
    ROI = original[y:y+h, x:x+w]

    #cv2.imwrite("ROI_{}.png".format(image_number), ROI)
    image_number += 1

Upvotes: 1

Views: 1127

Answers (1)

Red
Red

Reputation: 27577

You can get your ROI by specifying that you only want to use the contour that has the greatest area, that is, if your diagram will produce a contour with an area greater then the rest of the components in your image.

Here is an example:

import cv2

def preprocess(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)
    img_canny = cv2.Canny(img_blur, 50, 50)
    return img_canny

def get_roi(img, pad=3):
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    max_area = 0
    
    for cnt in contours:
        peri = cv2.arcLength(cnt, True)
        approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
        x, y, w, h = cv2.boundingRect(approx)
        rect_area = w * h
        if rect_area > max_area:
            max_area = rect_area
            dim = x, y, w, h

    if max_area:
        x, y, w, h = dim
        return x - pad, y - pad, w + pad * 2, h + pad * 2 

img = cv2.imread("ULTI.png")

img_processed = preprocess(img)
x, y, w, h = get_roi(img_processed)

cv2.imshow("Image", img[y:y + h, x:x + w])
cv2.waitKey(0)

Output:

enter image description here

Explanation:

  1. Import the necessary module(s):
import cv2
  1. Define a function that will take in an image, and return a processed version of the image that will allow python to properly detect the necessary contours (you can tweak the values to further meet your needs):
def preprocess(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)
    img_canny = cv2.Canny(img_blur, 50, 50)
    return img_canny
  1. Now, let's see how we can define a function that will retrieve the ROI of the image. First, define it so that a processed image array and a pad amount (optional) can be passed in as parameters:
def get_roi(img, pad=3):
  1. Find the contours of the processed image, and define a variable to store the greatest area of the contours:
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    max_area = 0
  1. Use a for loop to loop through the contours, and find the area of the contour of each iteration of the loop:
    for cnt in contours:
        peri = cv2.arcLength(cnt, True)
        approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
        area = cv2.contourArea(approx)
  1. Use an if statement to check if the area is greater than the defined variable that should store that greatest area. If the are of the contour of that iteration is greater than the variable, update the value of the variable to be equal to the new area. Also, save the contour of that iteration to a variable:
        if area > max_area:
            max_area = area
            max_cnt = approx
  1. After the for loop, if the max_area variable doesn't equal to 0, then a max_cnt has also been defined. Use the cv2.boundingRect to get the x, y, w and h properties:
    if max_area:
        x, y, w, h = cv2.boundingRect(max_cnt)
        return x - pad, y - pad, w + pad * 2, h + pad * 2 
  1. Finally, after reading the image into a variable, you can utilize the 2 functions we defined, and display the resulting image:
img = cv2.imread("ULTI.png")

img_processed = preprocess(img)
x, y, w, h = get_roi(img_processed)

cv2.imshow("Image", img[y:y + h, x:x + w])
cv2.waitKey(0)

Note: The code likely will not work for all diagrams. But again, you can tweak the values in the preprocess function to meet your needs.

Upvotes: 2

Related Questions