skip to Main Content

I’m using OpenCV 4 – python 3 – to find an specific area in a black & white image.

This area is not a 100% filled shape. It may hame some gaps between the white lines.

This is the base image from where I start processing:
base

This is the rectangle I expect – made with photoshop -:
expected

Results I got with hough transform lines – not accurate –
wrong result

So basically, I start from the first image and I expect to find what you see in the second one.

Any idea of how to get the rectangle of the second image?

3

Answers


  1. In Python/OpenCV, you can use morphology to connect all the white parts of your image and then get the outer contour. Note I have modified your image to remove the parts at the top and bottom from your screen snap.

    import cv2
    import numpy as np
    
    # read image as grayscale
    img = cv2.imread('blackbox.png')
    
    # convert to grayscale
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    # threshold
    _,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)
    
    # apply close to connect the white areas
    kernel = np.ones((75,75), np.uint8)
    thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
    
    # get contours (presumably just one around the outside) 
    result = img.copy()
    contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]
    for cntr in contours:
        x,y,w,h = cv2.boundingRect(cntr)
        cv2.rectangle(result, (x, y), (x+w, y+h), (0, 0, 255), 2)
    
    # show thresh and result    
    cv2.imshow("thresh", thresh)
    cv2.imshow("Bounding Box", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    # save resulting images
    cv2.imwrite('blackbox_thresh.png',thresh)
    cv2.imwrite('blackbox_result.png',result)
    

    Input:

    enter image description here

    Image after morphology:

    enter image description here

    Result:

    enter image description here

    Login or Signup to reply.
  2. I’d like to present an approach which might be computationally less expensive than the solution in fmw42’s answer only using NumPy’s nonzero function. Basically, all non-zero indices for both axes are found, and then the minima and maxima are obtained. Since we have binary images here, this approach works pretty well.

    Let’s have a look at the following code:

    import cv2
    import numpy as np
    
    # Read image as grayscale; threshold to get rid of artifacts
    _, img = cv2.threshold(cv2.imread('images/LXSsV.png', cv2.IMREAD_GRAYSCALE), 0, 255, cv2.THRESH_BINARY)
    
    # Get indices of all non-zero elements
    nz = np.nonzero(img)
    
    # Find minimum and maximum x and y indices
    y_min = np.min(nz[0])
    y_max = np.max(nz[0])
    x_min = np.min(nz[1])
    x_max = np.max(nz[1])
    
    # Create some output
    output = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    cv2.rectangle(output, (x_min, y_min), (x_max, y_max), (0, 0, 255), 2)
    
    # Show results
    cv2.imshow('img', img)
    cv2.imshow('output', output)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    I borrowed the cropped image from fmw42’s answer as input, and my output should be the same (or most similar):

    Output

    Hope that (also) helps!

    Login or Signup to reply.
  3. Here’s a slight modification to @fmw42’s answer. The idea is connect the desired regions into a single contour is very similar however you can find the bounding rectangle directly since there’s only one object. Using the same cropped input image, here’s the result.

    enter image description here

    We can optionally extract the ROI too

    enter image description here

    import cv2
    
    # Grayscale, threshold, and dilate
    image = cv2.imread('3.png')
    original = image.copy()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    # Connect into a single contour and find rect
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
    dilate = cv2.dilate(thresh, kernel, iterations=1)
    x,y,w,h = cv2.boundingRect(dilate)
    ROI = original[y:y+h,x:x+w]
    cv2.rectangle(image, (x, y), (x+w, y+h), (36, 255, 12), 2)
    
    cv2.imshow('image', image)
    cv2.imshow('ROI', ROI)
    cv2.waitKey()
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search