Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenCV join contours when rectangle overlaps another rect

I have a following input image:

enter image description here

My aim is to draw contours across the red area. To do this I have following code: import cv2

# Read image
src = cv2.imread("images.jpg", cv2.IMREAD_GRAYSCALE)

# Set threshold and maxValue
thresh = 150 
maxValue = 200

# Basic threshold example
th, dst = cv2.threshold(src, thresh, maxValue, cv2.THRESH_BINARY);

# Find Contours
countours,hierarchy=cv2.findContours(dst,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

for c in countours:
    rect = cv2.boundingRect(c)
    if rect[2] < 10 or rect[3] < 10: continue
    x,y,w,h = rect
    cv2.rectangle(src,(x,y),(x+w,y+h),(255,255,255),2)

# Draw Contour
#cv2.drawContours(dst,countours,-1,(255,255,255),3)

cv2.imshow("Contour",src)
cv2.imwrite("contour.jpg",src)
cv2.waitKey(0)

I am getting following output:

enter image description here

My target is to remove all the rectangles which fall inside bigger rectangle and connect the bigger rectangles for example like this:

enter image description here

How do I do that ?

like image 335
Ajinkya Avatar asked Jan 26 '23 00:01

Ajinkya


2 Answers

If you use cv2.RETR_EXTERNAL instead of cv2.RETR_TREE in findContours, the function will only return outer contours. So it won't return contours that are inside of another contour.

To merge contours, a very easy approach is to draw the contours filled white on a black mask, and then perform a new findContours on that mask. It will return the outline of the combined contours.

To exclude small contours: you can get the size of the contour using contourArea and compare it to a value you set. In the code below I added a trackbar so you can set the minimum value dynamically.

Result:
enter image description here

Note the small rectangle to the right size. It doesn't overlap, but is above the minContourSize. If you want to exclude that contour, you can increase minContourSize, but you might also start excluding contours you do want. A solution is to set a second check on the contourSize, this time on the mask. As the mask has combined contours, you can set the threshold much higher.

If you would rather merge that contour to the larger one: you can can make the contours join on the mask by drawing a filled contour and also a non filled rectangle with an outline several pixels wide. Though a more appropriate approach would be to look into Morphological Transformations, which you can apply to the mask.

Code:

import cv2
import numpy as np
# Read image
src = cv2.imread("3E3MT.jpg", cv2.IMREAD_GRAYSCALE)

# Set threshold and maxValue
thresh = 150 
maxValue = 200
# set an initial minimal contour size
minContourSize = 250
# create a window  (needed for use with trackbar)
cv2.namedWindow("Contour")

def setMinSize(val):
        # set the minimal contour size and find/draw contours
        global minContourSize
        minContourSize = val
        doContours()

def doContours():
        # create a copy of the image (needed for use with trackbar)
        res = src.copy()
        # find contours - external only
        countours,hierarchy=cv2.findContours(dst,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
        # create an empty mask
        mask = np.zeros(src.shape[:2],dtype=np.uint8)
        # draw filled boundingrects if the contour is large enough
        for c in countours:
                if cv2.contourArea(c) > minContourSize:
                        x,y,w,h  = cv2.boundingRect(c)
                        cv2.rectangle(mask,(x,y),(x+w,y+h),(255),-1)

        # find the contours on the mask (with solid drawn shapes) and draw outline on input image
        countours,hierarchy=cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
        for c in countours:
                        cv2.drawContours(res,[c],0,(255,255,255),2)
        # show image
        cv2.imshow("Contour",res)

# create a trackbar to set the minContourSize - initial is set at 250,
# maximum value is currently set at 1500, you can increase it if you like
cv2.createTrackbar("minContourSize", "Contour",250,1500,setMinSize)
# Basic threshold example
th, dst = cv2.threshold(src, thresh, maxValue, cv2.THRESH_BINARY)
# Find Contours
doContours()
# waitkey to prevent program for exiting by itself
cv2.waitKey(0)
cv2.destroyAllWindows()
like image 113
J.D. Avatar answered Jan 28 '23 16:01

J.D.


You can use below code as a starting point. It's not perfect but that's an opportunity for you to improved it even further.

# Read image
src = cv2.imread("demo.jpg")
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)

# binary thresholding
img_thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)[1]

# Find Contours
contours,hierarchy = cv2.findContours(img_thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

mask = np.zeros(src.shape, dtype="uint8") 
for c in contours:
    # get the bounding rect
    x, y, w, h = cv2.boundingRect(c)

    if w>80 and w<100:
        cv2.rectangle(mask, (x, y), (x+w-13, y+h), (255, 255, 255), -1)
    elif w>100:
        cv2.rectangle(mask, (x+10, y+10), (x+w, y+h), (255, 255, 255), -1)

thresh = cv2.threshold(cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY), 150, 255, cv2.THRESH_BINARY_INV)[1]
thresh = np.float32(thresh)

# corner detection in the above mask(find Harris corners)
dst = cv2.cornerHarris(thresh, 5, 3, 0.04)
# thresholding for an optimal value
ret, dst = cv2.threshold(dst, 0.1*dst.max(), 255, 0)
dst = np.uint8(dst)

# find centroids
ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)

# refines the corners detected with sub-pixel accuracy
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(thresh, np.float32(centroids), (5,5), (-1,-1), criteria)

#for i in corners:
#    res_corners = cv2.circle(mask, (int(i[0]), int(i[1])), 2, (255, 0, 255), 2)

# convert detected corner coordinates values from float to int 
corners = np.int32(corners)

# corner coordinate values forming a horizontal line will share same y coordinate value
# corner coordinate values forming a vertical line will share same x coordinate value
# dictionaries 
# dict1 is a dictionary where key is x in (x, y) coordinate
# For example - (12, 20) and (12, 40) forming a vertical line; 
# dict1 contains a key 12 and its corresponding element [20, 40]
dict1 = dict() 
# dict2 is a dictionary where key is y in (x, y) coordinate
# For example - (12, 20) and (40, 20) forming a horizontal line; 
# dict1 contains a key 20 and its corresponding element [12, 40]
dict2 = dict() 

# populate dictionary with coordinates values detected above.
# Sample data of dictionary:
# {9: [9, 332],
#  46: [499, 584],
#  75: [332, 206]}
for i in range(len(corners)):
    dict1.setdefault(corners[i][0], []).append(corners[i][1])
    dict2.setdefault(corners[i][1], []).append(corners[i][0])

# empty image of same shape as original image on which we draw horizontal and vertical lines using dict1 and dict2
empty = np.zeros(src.shape, dtype="uint8")    
for key, value in dict1.items():
    if len(value)==2:
        cv2.line(empty, (key, value[0]), (key, value[1]), (255,255,255), 2)

for key, value in dict2.items():
    if len(value)==2:
        cv2.line(empty, (value[0], key), (value[1], key), (255,255,255), 2)

#cv2.imshow("corner detected",res_corners)
#cv2.imshow("intermediate mask",mask)
cv2.imshow("resultant mask",empty)
cv2.waitKey(0)

Output:

Figure 1: Intermediate mask

enter image description here

Figure 2: Corners detected using Harris Corner detection algorithm

enter image description here

Figure 3: Final Result

enter image description here

like image 32
Anubhav Singh Avatar answered Jan 28 '23 14:01

Anubhav Singh