I'm trying to count the number of cars in the image automatically using OpenCV and Python.
Initially I thought I could do this with some segmentation, but I didn't have much success. I then thought that a Hough Transform might help compute the border around the cars but it only really picked out the parking space lines. The only thing I can think of would be to start training some matches on templates of cars and non-cars, but I'm hoping there is something simpler that will do well here. I also tried edge detection which looks positive but not really sure how to continue:
A simple but accurate method of traffic counting comprises people manually counting vehicles. A person either uses an electronic hand held counter or records data using a tally sheet. They may stand at the side of the road, or, more commonly watch a video of the road and count from that.
tim2-tim1 gives the total time taken for car to pass polygon region and we already know that distance between those horizontal lines is 3 meters as we defined earlier it calculates speed in (m/s) using simple mathematical formula speed=distance/time and prints it to the console.
Ok, so.... I probably did too much work on this, but it seemed simple enough.
For my implementation I decided it would be better to find empty parking spaces, and assume that all other spaces are occupied. To determine if a spot is empty I simply compared it to a parking space sized portion of the road. This means that this same algorithm should work whether its bright or dark, etc. because the template is extracted directly from the image.
At this point I just do template matching (I tried various methods, but cv2.TM_CCORR_NORMED
worked best). This gives a decent result, and now we need to process it.
I create the ROIs (regions of Interest) around the parking lot rows. I then collapse them to a single vector by taking statistics per column. I look at the mean.
Its a pretty good indicator, you can already clearly see where the empty spaces are. But dark colored cars still present some issues, so now we decide to look at another statistic, how about variance? The parking lot is pretty constant throughout the entire region. A car on the other hand has a few changes, the windows, roofrack mirrors make for some variance. So I plot "inverted" variance. So rather than no change having a variance of 0, it has a variance of 1. It looks something like this
This looks pretty promising! But you know whats even better? Combining the two! so lets just multiply them together, i called this result the "probability" since it should range between 0 and 1
Now you can really see the difference between a blank space, and a dark car. So lets do some simple threshold. This is great, but it doesn't give you the NUMBER of vehicles/empty spaces. At this point we go through the "probability" column by column, and we look for a certain number of consecutive pixels over the threshold. How many pixels? about as many pixels as a car is wide. This "hysteresis" type model should suppress any peaks or spurious data points.
And now it all comes together, we assume the number of spaces is constant(reasonable assumption I think) and we just say the number of cars = number of spaces - number of empty spaces
and mark the image
and print some results
found 24 cars and 1 empty space(s) in row 1
found 23 cars and 0 empty space(s) in row 2
found 20 cars and 3 empty space(s) in row 3
found 22 cars and 0 empty space(s) in row 4
found 13 cars and 9 empty space(s) in row 5
and of course, the code. It might not be the most efficient, but I'm typically a matlab person, its my first openCV/Python project
import cv2
import numpy as np
from matplotlib import pyplot as plt
# this just keeps things neat
class ParkingLotRow(object):
top_left=None
bot_right=None
roi=None
col_mean=None
inverted_variance=None
empty_col_probability=None
empty_spaces=0
total_spaces=None
def __init__(self,top_left,bot_right,num_spaces):
self.top_left = top_left
self.bot_right = bot_right
self.total_spaces = num_spaces
############################ BEGIN: TWEAKING PARAMETERS ###########################################
car_width = 8 #in pixels
thresh = 0.975 #used to determine if a spot is empty
############################### END: TWEAKING PARAMETERS ###########################################
parking_rows = []
# defines regions of interest, row 1 is on top, row 5 is on bottom, values determined empirically
parking_rows.append(ParkingLotRow(( 1, 20),(496, 41),25)) #row 1
parking_rows.append(ParkingLotRow(( 1, 87),(462,105),23)) #row 2
parking_rows.append(ParkingLotRow(( 1,140),(462,158),23)) #row 3
parking_rows.append(ParkingLotRow(( 1,222),(462,240),22)) #row 4
parking_rows.append(ParkingLotRow(( 1,286),(462,304),22)) #row 5
#read image
img = cv2.imread('parking_lot.jpg')
img2 = img.copy()
#creates a template, its jsut a car sized patch of pavement
template = img[138:165,484:495]
m, n, chan = img.shape
#blurs the template a bit
template = cv2.GaussianBlur(template,(3,3),2)
h, w, chan = template.shape
# Apply template Matching
res = cv2.matchTemplate(img,template,cv2.TM_CCORR_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
#adds bounding box around template
cv2.rectangle(img,top_left, bottom_right, 255, 5)
#adds bounding box on ROIs
for curr_parking_lot_row in parking_rows:
tl = curr_parking_lot_row.top_left
br = curr_parking_lot_row.bot_right
cv2.rectangle(res,tl, br, 1, 5)
#displays some intermediate results
plt.subplot(121),plt.imshow(res,cmap = 'gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
plt.title('Original, template in blue'), plt.xticks([]), plt.yticks([])
plt.show()
curr_idx = int(0)
#overlay on original picture
f0 = plt.figure(4)
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)),plt.title('Original')
for curr_parking_lot_row in parking_rows:
#creates the region of interest
tl = curr_parking_lot_row.top_left
br = curr_parking_lot_row.bot_right
my_roi = res[tl[1]:br[1],tl[0]:br[0]]
#extracts statistics by column
curr_parking_lot_row.col_mean = np.mean(my_roi, 0)
curr_parking_lot_row.inverted_variance = 1 - np.var(my_roi,0)
curr_parking_lot_row.empty_col_probability = curr_parking_lot_row.col_mean * curr_parking_lot_row.inverted_variance
#creates some plots
f1 = plt.figure(1)
plt.subplot('51%d' % (curr_idx + 1)),plt.plot(curr_parking_lot_row.col_mean),plt.title('Row %d correlation' %(curr_idx + 1))
f2 = plt.figure(2)
plt.subplot('51%d' % (curr_idx + 1)),plt.plot(curr_parking_lot_row.inverted_variance),plt.title('Row %d variance' %(curr_idx + 1))
f3 = plt.figure(3)
plt.subplot('51%d' % (curr_idx + 1))
plt.plot(curr_parking_lot_row.empty_col_probability),plt.title('Row %d empty probability ' %(curr_idx + 1))
plt.plot((1,n),(thresh,thresh),c='r')
#counts empty spaces
num_consec_pixels_over_thresh = 0
curr_col = 0
for prob_val in curr_parking_lot_row.empty_col_probability:
curr_col += 1
if(prob_val > thresh):
num_consec_pixels_over_thresh += 1
else:
num_consec_pixels_over_thresh = 0
if (num_consec_pixels_over_thresh >= car_width):
curr_parking_lot_row.empty_spaces += 1
#adds mark to plt
plt.figure(3) # the probability graph
plt.scatter(curr_col,1,c='g')
plt.figure(4) #parking lot image
plt.scatter(curr_col,curr_parking_lot_row.top_left[1] + 7, c='g')
#to prevent doubel counting cars, just reset the counter
num_consec_pixels_over_thresh = 0
#sets axis range, apparantlly they mess up when adding the scatters
plt.figure(3)
plt.xlim([0,n])
#print out some stats
print('found {0} cars and {1} empty space(s) in row {2}'.format(
curr_parking_lot_row.total_spaces - curr_parking_lot_row.empty_spaces,
curr_parking_lot_row.empty_spaces,
curr_idx +1))
curr_idx += 1
#plot some figures
plt.show()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With