Search code examples
pythonopencvimage-processingobject-detectioncontour

How to filter contours based on their approximate shape in a Binary video frames


I'm working on a project where i have to detect a red vehicle (please see image below).

Main image

As i believe that this can be achieved with out using Deep learning (overkill in this case), i used histogram Back projection depending on the object color(red). The results were satisfying

result

except when there are objects other than the target red-vehicle having the same color distribution as the target (see example below my T-shirt) are in the scene, the algorithm thinks it is also an object of interest and thus detect both the object of interest and the irrelevant object (my T-shirt).

second case

The result are

enter image description here

In this case, it's easy to only choose the contour that belongs to the car based on ratio and area,since the contour that belongs to the T-shirt is lager and has different ratio

I applied the follwoing example code

contours = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)


area_thresh1 = 500
area_thresh2 = 1000
aspect_thresh1 = 2
aspect_thresh2 = 4
result1 = image.copy()
result2 = image.copy()
for c in contours:

    # get rotated rectangle from contour
    # get its dimensions
    # get angle relative to horizontal from rotated rectangle
    rotrect = cv2.minAreaRect(c)
    box = cv2.boxPoints(rotrect)
    box = np.int0(box)
    (center), (dim1,dim2), angle = rotrect
    maxdim = max(dim1,dim2)
    mindim = min(dim1,dim2)
    area = dim1 * dim2
    if area > 0:
        aspect = maxdim / mindim
        #print(area, aspect)

    if area > area_thresh1 and area < area_thresh2 and aspect > aspect_thresh1 and aspect < 
       aspect_thresh2:
       # draw contour on input
       cv2.drawContours(result1,[c],0,(255,255,255),1)
       # draw rectangle on input
       cv2.drawContours(result2,[box],0,(255,255,255),1)
       print(area, aspect)

However, as I'm working on a video, this doesn't work well in some frames since sometimes it detects shapes that fulfill the conditions like the case below result2

As you can see in the above binary image, an irrelevant object is detected (the below contour).

So my question is:

As you see the red vehicle to be detected always has the same shape (almost rectangle but for sure a convex shape). So how can i filter only the contour that belongs to the red vehicle using a the shape property ?(of course I mean a property other than ratio and area since some sopts of my short falls into the same area and ration boundaries of the red vehicle).

In other words, How can i filter the target object based on the exact shape of the vehicle??

Thanks in Advance


Solution

  • You can get shape descriptors and use some kind of rules (or machine learning) to decide if that's the right object you're searching for :

    import numpy as np
    import argparse
    import cv2
    import sys
    
    target = cv2.imread('YourPath\\target.jpg' , 
    cv2.IMREAD_COLOR)
    mask = cv2.imread('YourPath\\mask.jpg',cv2.IMREAD_GRAYSCALE)
    SearchImage = cv2.bitwise_and(target,target,mask = mask)
    
    cv2.imshow("Search Region" , SearchImage)
    cv2.waitKey()
    
    #convert RGBto Lab
    LabImage = cv2.cvtColor(SearchImage,cv2.COLOR_BGR2LAB)
    
    cv2.imshow("Lab(b)" , LabImage[:, :, 1])
    cv2.waitKey()
    
    ret,Binary = cv2.threshold(LabImage[:, :, 1], 0, 255, cv2.THRESH_OTSU)
    cv2.imshow('win1', Binary)
    cv2.waitKey(0)
    
     #find contours
     contours, hierarchy = cv2.findContours(Binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
    #create an empty image for contours
    img_contours = np.zeros(target.shape)
    # draw the contours on the empty image
    cv2.drawContours(img_contours, contours, -1, (0,255,0), 3)
    
    for cnt in contours:
    
       x, y, w, h = cv2.boundingRect(cnt)
       aspect_ratio = float(w) / h
    
       area = cv2.contourArea(cnt)
       x, y, w, h = cv2.boundingRect(cnt)
       rect_area = w * h
       extent = float(area) / rect_area
    
       hull = cv2.convexHull(cnt)
       hull_area = cv2.contourArea(hull)
       solidity = float(area) / hull_area
    
       equi_diameter = np.sqrt(4 * area / np.pi)
    
       (x, y), (MA, ma), Orientation = cv2.fitEllipse(cnt)
    
       print(" Width = {}  Height = {} area = {}  aspect ration = {}  extent  = {}  
       solidity = {}   equi_diameter = {}   orientation = {}".format(  w , h , area , 
       aspect_ratio , extent , solidity , equi_diameter , Orientation))
    
    
    
    cv2.imshow('win1', img_contours)
    cv2.waitKey(0)
    

    OUTPUT:

    Width = 42  
    Height = 18 
    area = 632.5  
    aspect ratio = 2.3333333333333335  
    extent  = 0.8366402116402116  
    solidity = 0.9412202380952381   
    equi_diameter = 28.37823130579125   
    orientation = 89.93299865722656
    

    enter image description here