Skip to content

Operation Classification

Ciaran-OBrien edited this page Nov 28, 2018 · 1 revision

This post is unfortunately going to be the last post in this series exploring my contribution towards classifying vehicles.

In order to classify each vehicle, we needed to define our rules. Below we can see how we've decided, thanks to our analysis of each vehicle, how to place each vehicle into it's category.

def classify(ReportList):
    for index,row in ReportList.iterrows():
        if row['Wheel Count'] > 2:
            print("It's an Articulated Lorry!")
            return "Articulated Lorry"
        elif row['Wheel Count'] < 2:
            print("Not enough wheels captured")
            exit()
        else:
            #print("It's something else, let's keep gueessing")
            if row['Wheel Distance Ratio'] > 1.8:
                print("It's a Bus !")
                return "Bus"
            else:
                print("It's a Car !")
                return "Car"

In terms of a user friendly output, we needed to create an easy way to comprehend what vehicle the user's image is and what relevant details to display. We added together previously used shape overlays, such as the line drawn here along the height and width of the detected vehicle. Next we cycle through each wheel detected, and draw the circle on top of the image. Lastly, we print all these details as a subtitle to the plotted image.

def finalOutput(wheelDetails, wheelSizings,extractedImage, vehicleType):
    for i in range(0,wheelDetails.shape[0]):
        cv2.circle(extractedImage,(wheelDetails['X Coords'][i],wheelDetails['Y Coords'][i]),wheelDetails['Wheel Diam'][i],(0,255,0),2)
        # draw the center of the circle
        cv2.circle(extractedImage,(wheelDetails['X Coords'][i],wheelDetails['Y Coords'][i]),2,(0,0,255),3)

    cv2.line(img = extractedImage, pt1 = wheelSizings['Hpoint1'][0], pt2 = wheelSizings["Hpoint2"][0], color = (0,0,255), thickness = 9) 
    cv2.line(img = extractedImage, pt1 = wheelSizings['Vpoint1'][0], pt2 = wheelSizings['Vpoint2'][0], color = (0,0,255), thickness = 9)
    length = vehicle_sizes.iloc[0][0]
    height = vehicle_sizes.iloc[0][1]
    
    textstr = ("It's a " + vehicleType  + 
            "\nWith " + 
             str(wheelDetails.shape[0]) + 
             " wheels\n" + 
            "The vehicle's lenght is: " + 
             str(length) + 
             "\nThe height is: " + 
            str(height))
    extractedImage = cv2.cvtColor(extractedImage,cv2.COLOR_RGB2BGR)
    plt.figure()
    plt.xlim(0,extractedImage.shape[1])
    plt.ylim(extractedImage.shape[0], 0)
    plt.suptitle(textstr)
    plt.subplots_adjust(left=0.25)
    plt.imsave("Output.jpg",extractedImage)
    plt.imshow(extractedImage)
    plt.show()

img img img

Note on these last one, we stopped execution of the script if not enough wheels where found. img img

So without further ado, please see our fully commented, final code submission.

# MasterScript code  
# Submitted 25/11/2018
# Written by Ciaran O Brien, Diarmuid O Foghlu and Robert Deegan

# import the necessary packages:
import numpy as np
import cv2
from matplotlib import pyplot as plt
from matplotlib import image as image
import easygui
import pandas as pd

"""Apply Canny Edge Thresholding.
    
    FXN applies the Canny Edge algorithm to a passed image. Two sets of thresholding for the hysteresis procedure are applied
    First threshold is a value of 100, and second is 200. The smalleset values between these those thresholds are used for edge linking.
    
    Args:
        arg1 (numpy.ndarray): User Selected image

    Returns:
        numpy.ndarray: Image with the above thresholding applied to it.

    """
def threshold(image):
	edge_image = cv2.Canny(image,100,200)
	blur = cv2.blur(edge_image, (3, 3)) # blur the image
	ret, thresh = cv2.threshold(blur, 50, 255, cv2.THRESH_BINARY)
	return thresh


"""Calculate sizing details.
    
    FXN compares calculates the lenght, height, and ratio of a detected vehicle. 
    
    Args:
        arg1 (numpy.ndarray): CannyEdge tresholding mask. See: threshold(image)
        arg2 (numpy.ndarray): RGB Image to calculate

    Returns:
        DataFrame: Vehicle Length,Vehicle Height, and Ratio

    """
def find_length_and_height(thresh,image):
	
	# Finding contours for the thresholded image
	im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
	c = max(contours, key=cv2.contourArea)

	# determine the most extreme points along the contour
	extLeft = tuple(c[c[:, :, 0].argmin()][0])
	extRight = tuple(c[c[:, :, 0].argmax()][0])
	extTop = tuple(c[c[:, :, 1].argmin()][0])
	extBot = tuple(c[c[:, :, 1].argmax()][0])

	# Find the length and height
	length = extRight[0] - extLeft[0]
	height = extBot[1] - extTop[1]
	float_length = float(extRight[0] - extLeft[0])
	float_height = float(extBot[1] - extTop[1])
	sizings = [length,height, round(float_length/float_height,2),extLeft,(extLeft[0] + length, extLeft[1]),extBot,(extBot[0], extBot[1] - height)]
	ratio = float_length/float_height
	# Draw lines on the orginal image showing the height and length of the vehicle
	cv2.line(img = image, pt1 = extLeft, pt2 =(extLeft[0] + length, extLeft[1] ), color = (0,0,255), thickness = 9) 
	cv2.line(img = image, pt1 = extBot, pt2 =(extBot[0], extBot[1] - height), color = (0,0,255), thickness = 9)
	sizeData = pd.DataFrame([sizings], columns=['Vehicle Length','Vehicle Height','Ratio','Hpoint1','Hpoint2','Vpoint1','Vpoint2'])
	return sizeData
	
"""Background Extraction.
    
    FXN applies OpenCV's grabCut algorithm.
    
    Args:
        arg1 (numpy.ndarray): User Selected image

    Returns:
        numpy.ndarray: Original image with the background extracted

    """
def background_extract(image) :
	colour_image = None
	try: 
		if(len(image.shape) == 3):
			colour_image = image
		elif(len(image.shape) < 3):
			try:
				colour_image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
			except Exception as error:
				print("An error has occured trying to convert to BGR\n",error)
		else:
			raise Exception("Unsupported image shape passed")
	except Exception as error:
		print("An error has occured\n",error)
	mask = np.zeros(colour_image.shape[:2],np.uint8)
	bgdModel = np.zeros((1,65),np.float64)
	fgdModel = np.zeros((1,65),np.float64)
	 
	x,y,z = np.shape(colour_image)
	rect = (1,1,y,x)
	colour_image = np.uint8(colour_image)
	cv2.grabCut(colour_image,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
	mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
	colour_image = colour_image*mask2[:,:,np.newaxis]
	return colour_image

"""HoughCircle Wheel Detection .
    
    FXN applies OpenCV's Hough Circle deteciton algorithm. The parameteres for houghCIrlce algorithm were obtained
    through anaylis of itereating through varying values for each argument. Below are the most flexible parameters
    to capture all wheels from all test pictures, with respect to false positives.
    
    Each outer circle is marked as the wheel's diameter, and each inner dot is marked as the X&Y Coords for each wheel
    
    Args:
        arg1 (numpy.ndarray): User Selected image converted to GrayScale

    Returns:
        DataFrame: Wheel Number, X & Y Coordinates for each wheel, and an approxiamte wheel diameter

    """
def getAllActualWheels(img):

    greyscale_image = None
    try: 
        if(len(img.shape)<3):
            greyscale_image = img
        elif(len(img.shape) == 3):
            greyscale_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        else:
            raise Exception("Unsupported image shape passed")
    except Exception as error:
        print("An error has occured\n",error)

    circleLocations = []
    lowerThirdBuffer = greyscale_image.shape[0] * 0.66
    biggerWheel = 0
    
    # Working best if we take the greyscaled image as HoughCircles fxn preforms canny edge on the image anyway
    img = cv2.medianBlur(greyscale_image,5)
    cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
    circles = cv2.HoughCircles( img,
                                cv2.HOUGH_GRADIENT,
                                1,
                                30,
                                param1=160,
                                param2=25,
                                minRadius=10,
                                maxRadius=50)

    circles = np.uint16(np.around(circles))
    for index,i in enumerate(circles[0,:]):
        if(i[1] > lowerThirdBuffer):
            # draw the outer circle
            cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
            # draw the center of the circle
            cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
            if(i[2] > biggerWheel):
                biggerWheel = i[2]
            # append to list that'll be added to the dataframe
            circleLocations.append([index+1, i[0], i[1], biggerWheel])
    #plt.imshow(cimg)
    wheelData = pd.DataFrame(circleLocations, columns=['Wheel No.', 'X Coords', 'Y Coords','Wheel Diam'])
    return(wheelData)

"""Get Calculated Wheel Distance Ratio.
    
    FXN calculates a ratio to represent the distance between the two wheels of a vehicle 
    and it's own length. 
    
    Args:
        arg1 (DataFrame): Calculated count of wheels detected. See: getWheelDistance(wheelDetails)
        arg2 (DataFrame): Calculated vehicle length/width ratio. See: find_length_and_height(thresh,image)
    Returns:
        float: scaled calcalculated wheel distnace ratio
    """
def getWheelDistanceRatio(wheelDetails,vehicleDetails):
    try:
        return (abs( vehicleDetails.iloc[0][0] /(wheelDetails["X Coords"][0] - wheelDetails["X Coords"][1])))
    except:
        return 0 

"""Get Wheel Count from Wheel Details.
    
    FXN returns a count of all wheels captured
    
    Args:
        arg1 (DataFrame): All captured wheel details. See:getAllActualWheels(greyscale_image):
    Returns:
        int: Wheel Count
    """
def getWheelCount(wheelDetails):
    return(wheelDetails.shape[0])


"""Get Wheel Diameter relative to the Vehicle's Lengtha and Height.
    
    FXN calculates the ratio of the captured wheel diameter compared to the lenght and height of the vehicle respectively.
    The returned values are used in the calssification of the vehicle. 
    
    Args:
        arg1 (DataFrame): All Vehicle Size Details. See: find_length_and_height(thresh,image)
        arg2 (DataFrame): All Wheel Size Details. See: getAllActualWheels(greyscale_image)

    Returns:
        List: WheelDiameter/Lenght Ratio, WheelDiameter/Height Ratio 

    """
def getWheelDiamaterVehicleRatio(wheel_details,vehicle_details):
    return ([round((vehicle_details['Vehicle Length'][0]/wheel_details['Wheel Diam'][0]),1),round((vehicle_details['Vehicle Height'][0]/wheel_details['Wheel Diam'][0]),1)])

def classify(ReportList):
    for index,row in ReportList.iterrows():
        if row['Wheel Count'] > 2:
            print("It's an Articulated Lorry!")
            return "Articulated Lorry"
        elif row['Wheel Count'] < 2:
            print("Not enough wheels captured")
            exit()
        else:
            #print("It's something else, let's keep gueessing")
            if row['Wheel Distance Ratio'] > 1.8:
                print("It's a Bus !")
                return "Bus"
            else:
                print("It's a Car !")
                return "Car"

def finalOutput(wheelDetails, wheelSizings,extractedImage, vehicleType):
    for i in range(0,wheelDetails.shape[0]):
        cv2.circle(extractedImage,(wheelDetails['X Coords'][i],wheelDetails['Y Coords'][i]),wheelDetails['Wheel Diam'][i],(0,255,0),2)
        # draw the center of the circle
        cv2.circle(extractedImage,(wheelDetails['X Coords'][i],wheelDetails['Y Coords'][i]),2,(0,0,255),3)

    cv2.line(img = extractedImage, pt1 = wheelSizings['Hpoint1'][0], pt2 = wheelSizings["Hpoint2"][0], color = (0,0,255), thickness = 9) 
    cv2.line(img = extractedImage, pt1 = wheelSizings['Vpoint1'][0], pt2 = wheelSizings['Vpoint2'][0], color = (0,0,255), thickness = 9)
    length = vehicle_sizes.iloc[0][0]
    height = vehicle_sizes.iloc[0][1]
    
    textstr = ("It's a " + vehicleType  + 
            "\nWith " + 
             str(wheelDetails.shape[0]) + 
             " wheels\n" + 
            "The vehicle's lenght is: " + 
             str(length) + 
             "\nThe height is: " + 
            str(height))
    extractedImage = cv2.cvtColor(extractedImage,cv2.COLOR_RGB2BGR)
    plt.figure()
    plt.xlim(0,extractedImage.shape[1])
    plt.ylim(extractedImage.shape[0], 0)
    plt.suptitle(textstr)
    plt.subplots_adjust(left=0.25)
    plt.imsave("Output.jpg",extractedImage)
    plt.imshow(extractedImage)
    plt.show()

## TESTING

# Opening an image using a File Open dialog:
f = easygui.fileopenbox()
image = cv2.imread(f)
FullReport = []
cols = ['Image Length','Vehicle Length','Image Height','Vehicle Height','Vehicle Ratio','Wheel Distance Ratio','Wheel Count','Wheel Diameter','Wheel_Diam Length Ratio','Wheel_Diam Height Ratio','Wheel_Vehicle Ratio','Classification']

imageHeight, imageWidth, channels = image.shape
orignal_image = image.copy()
extracted = background_extract(image)
thresholded = threshold(extracted)

# Allocate all the vehicle details to be displayed as a dataframe
wheel_details = getAllActualWheels(cv2.cvtColor(orignal_image,cv2.COLOR_BGR2GRAY))
vehicle_details = find_length_and_height(thresholded, image)
wheel_count = getWheelCount(wheel_details)
vehicle_sizes = find_length_and_height(thresholded, image)
vehicle_wheel_distance = getWheelDistanceRatio(wheel_details,vehicle_details)   
vehicle_wheel_distsance_ratio = round(getWheelDistanceRatio(int(vehicle_sizes['Ratio']),vehicle_details),2)  
WheelDiamRatio = getWheelDiamaterVehicleRatio(wheel_details,vehicle_details)
# Create the dataFrame of all the vehicle's information 
FullReport.append([ imageWidth,
                    int(vehicle_sizes['Vehicle Length']),
                    imageHeight,
                    int(vehicle_sizes['Vehicle Height']),
                    int(vehicle_sizes['Ratio']),
                    vehicle_wheel_distance,
                    wheel_count,
                    WheelDiamRatio[0],
                    WheelDiamRatio[1],
                    wheel_details['Wheel Diam'][0],
                    vehicle_wheel_distsance_ratio,
                    None])
Report = pd.DataFrame(FullReport,columns=cols)

## EVALUTATION
print(Report)
vehicleClassification = classify(Report)
finalOutput(wheel_details, vehicle_sizes,extracted, vehicleClassification)

Clone this wiki locally