-
Notifications
You must be signed in to change notification settings - Fork 0
Operation Classification
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()

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

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)
