KISS – Keep it simple s…
In this tutorial we will make a simple car counter using OpenCV from Python. It will not be a perfect solution, but it will be easy to understand and in some cases better.
The counter will take advantage of the simple assumptions that objects that move through a defined box on the right side of road are cars driving in one direction. And objects moving through a defined box of the left side of the road are cars driving the other direction.
This is of course not a perfect assumption, but it makes things easier. There is no need to identify if it is car or not. This is actually an advantage, since by the default car cascade classifiers might not recognize cars from the angle your camera is set. At least, I had problems with that. I could train my own cascade classifier, but why not try to do something smart.
Step 1: Get a live feed from the webcam in OpenCV
First you need to ensure you have installed OpenCV. If you use PyCharm we can recommend you read this tutorial on how to set it up.
To get a live feed from your webcam can be achieved by the following lines of code.
import cv2
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while cap.isOpened():
_, frame = cap.read()
cv2.imshow("Car counter", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
The cv2.VideoCapture(0) assumes that you only have one webcam. If you have more, you might need to change 0 to something else.
The cap.set(…) are setting the width and height of the camera frames. In order to get good performance it is good to scale down. This can also be achieved with scaling the picture you make processing on after down.
Then cap.read() reads the next frame. It also returns a return value, but we ignore that value with the underscore (_). The cv2.imshow(…) will create a window with showing the frame. Finally, the cv2.waitkey(1) waits 1 millisecond and check if q was pressed. If so, it will break out and release the camera and destroy the window.
Step 2: Identify moving objects with OpenCV
The simple idea is that to compare each frame with the previous one. If there is a difference, we have a moving object. Of course, a bit more complex, as we also want to identify where the objects are and avoid identifying differences due to noise in the picture.
As most processing on moving images we will start by converting them to gray tones (cv2.cvtColor(…)). Then we will use blurring to minimize details in the picture (cv2.GaussianBlur(…)). This helps us with falsely identifying moving things that are just because of noise and minor changes.
When that is done, we compare that converted frame with the one from previous frame (cv2.absdiff(…)). This gives you an idea of what has changed. We keep a threshold (cv2.threshold(…)) on it and then dilate (cv2.dilate(…)) change to make it easier to identify with cv2.findContours(…).
It boils down to the following code.
import cv2
import imutils
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# We will keep the last frame in order to see if there has been any movement
last_frame = None
while cap.isOpened():
_, frame = cap.read()
# Processing of frames are done in gray
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# We blur it to minimize reaction to small details
gray = cv2.GaussianBlur(gray, (21, 21), 0)
# Need to check if we have a last_frame, if not get it
if last_frame is None:
last_frame = gray
continue
# Get the difference from last_frame
delta_frame = cv2.absdiff(last_frame, gray)
last_frame = gray
# Have some threshold on what is enough movement
thresh = cv2.threshold(delta_frame, 25, 255, cv2.THRESH_BINARY)[1]
# This dilates with two iterations
thresh = cv2.dilate(thresh, None, iterations=2)
# Returns a list of objects
contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Converts it
contours = imutils.grab_contours(contours)
# Loops over all objects found
for contour in contours:
# Get's a bounding box and puts it on the frame
(x, y, w, h) = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# Let's show the frame in our window
cv2.imshow("Car counter", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
If you don’t look out for what is happening it could turn out to a picture like this one (I am sure you take more care than me).

One thing to notice is, that we could make a lower limit on the sizes of the moving objects. This can be achieved by inserting a check before we make the green boxes.
Step 3: Creating a helper class to track counts
To make our life easier we introduce a helper class to represent a box on the screen that keeps track on how many objects have been moving though it.
class Box:
def __init__(self, start_point, width_height):
self.start_point = start_point
self.end_point = (start_point[0] + width_height[0], start_point[1] + width_height[1])
self.counter = 0
self.frame_countdown = 0
def overlap(self, start_point, end_point):
if self.start_point[0] >= end_point[0] or self.end_point[0] <= start_point[0] or \
self.start_point[1] >= end_point[1] or self.end_point[1] <= start_point[1]:
return False
else:
return True
The class will take the staring point (start_point) and the width and height (width_height) to the constructor. As we will need start_point and end_point when drawing the box in the frame we calculate that immediately in the constructor (__init__(…)).
Further, we will have a counter to keep track on how many object have passed through the box. There is also a frame_countdown, which is used to minimize multiple counts of the same moving object. What can happen is that in one frame the moving object is identified, while in the next it is not, but then it is identified again. If that all happens within the box, it will count the object twice. Hence, we will have countdown that says we need at minimum number of frames between identified moving objects before we can assume it is a new one.
Step 4: Using the helper class and start the counting
We need to add all the code together here.
It requires a few things. Before we enter the main while loop, we need to setup the boxes we want to count moving objects in. Here we setup two, which will be one for each direction the cars can drive. Inside the contours loop, we set a lower limit of the contour sizes. Then we go through all the boxes and update the appropriate variables and build the string text. After that, it will print the text in the frame as well as add all the boxes to it.
import cv2
import imutils
class Box:
def __init__(self, start_point, width_height):
self.start_point = start_point
self.end_point = (start_point[0] + width_height[0], start_point[1] + width_height[1])
self.counter = 0
self.frame_countdown = 0
def overlap(self, start_point, end_point):
if self.start_point[0] >= end_point[0] or self.end_point[0] <= start_point[0] or \
self.start_point[1] >= end_point[1] or self.end_point[1] <= start_point[1]:
return False
else:
return True
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# We will keep the last frame in order to see if there has been any movement
last_frame = None
# To build a text string with counting status
text = ""
# The boxes we want to count moving objects in
boxes = []
boxes.append(Box((100, 200), (10, 80)))
boxes.append(Box((300, 350), (10, 80)))
while cap.isOpened():
_, frame = cap.read()
# Processing of frames are done in gray
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# We blur it to minimize reaction to small details
gray = cv2.GaussianBlur(gray, (5, 5), 0)
# Need to check if we have a lasqt_frame, if not get it
if last_frame is None or last_frame.shape != gray.shape:
last_frame = gray
continue
# Get the difference from last_frame
delta_frame = cv2.absdiff(last_frame, gray)
last_frame = gray
# Have some threshold on what is enough movement
thresh = cv2.threshold(delta_frame, 25, 255, cv2.THRESH_BINARY)[1]
# This dilates with two iterations
thresh = cv2.dilate(thresh, None, iterations=2)
# Returns a list of objects
contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Converts it
contours = imutils.grab_contours(contours)
# Loops over all objects found
for contour in contours:
# Skip if contour is small (can be adjusted)
if cv2.contourArea(contour) < 500:
continue
# Get's a bounding box and puts it on the frame
(x, y, w, h) = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# The text string we will build up
text = "Cars:"
# Go through all the boxes
for box in boxes:
box.frame_countdown -= 1
if box.overlap((x, y), (x + w, y + h)):
if box.frame_countdown <= 0:
box.counter += 1
# The number might be adjusted, it is just set based on my settings
box.frame_countdown = 20
text += " (" + str(box.counter) + " ," + str(box.frame_countdown) + ")"
# Set the text string we build up
cv2.putText(frame, text, (10, 20), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)
# Let's also insert the boxes
for box in boxes:
cv2.rectangle(frame, box.start_point, box.end_point, (255, 255, 255), 2)
# Let's show the frame in our window
cv2.imshow("Car counter", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
Step 5: Real life test on counting cars (not just moving objects)
The real question is, does it work or did we oversimplify the problem. If it works we have created a very small piece of code (comparing to other implementations), which can count cars all day long.
I adjusted the parameters a bit and got the following with my first real trial.
Please notice, that it does not start from zero in the video. But it counts the number of cars in each direction correctly. As expected, it counts when each car reaches the white bar.
The number of cars is the first number, while the second is just visible for me to see if my guess of skipping frames was useable.
Are we done?
Not at all. This was just to see if we could make some simple and fast to count cars. My first problem was, that given the trained sets of car recognition (car cascade classifiers) was not happy about the angle on the cars from my window. I first thought of training my own cascade classifier, but I thought it was fun to try something more simple.
There are a lot of parameters which can be tuned to make it more reliable, but the main fact is, that it was counting correctly in the given test. I can see one challenge, if a big truck drives by from the left to the right, it might get in the way of the other counter. This could be a potential challenge with this simple approach.
Python Circle
Do you know what the 5 key success factors every programmer must have?
How is it possible that some people become programmer so fast?
While others struggle for years and still fail.
Not only do they learn python 10 times faster they solve complex problems with ease.
What separates them from the rest?
I identified these 5 success factors that every programmer must have to succeed:
- Collaboration: sharing your work with others and receiving help with any questions or challenges you may have.
- Networking: the ability to connect with the right people and leverage their knowledge, experience, and resources.
- Support: receive feedback on your work and ask questions without feeling intimidated or judged.
- Accountability: stay motivated and accountable to your learning goals by surrounding yourself with others who are also committed to learning Python.
- Feedback from the instructor: receiving feedback and support from an instructor with years of experience in the field.
I know how important these success factors are for growth and progress in mastering Python.
That is why I want to make them available to anyone struggling to learn or who just wants to improve faster.
With the Python Circle community, you can take advantage of 5 key success factors every programmer must have.

Be part of something bigger and join the Python Circle community.
Quality content right here. Thank you for this great tutorial.
Hey bro! How can talk to you about something directly? Maybe E-mail?
Hey bro! How can i talk to u about something directly? Maybe email?
Feel free to write learnpythonwithrune@gmail.com
Greetings! Very helpful advice within this post! It is the little changes which will make the most
important changes. Thanks for sharing!
No problem. Thanks.