How to Monitor Car Speed with Python and Raspberry Pi

How to Monitor Car Speed with Python and Raspberry PiTranslated by Python Tribe (python.freelycode.com), reproduction is prohibited, forwarding is welcome.

Published on March 25, 2016

It all started from a post by my brother-in-law on Facebook:

“Facebook, I need your help.

How do you deal with those SBs who keep accelerating and decelerating on your street?

I called the police, and they said there aren’t enough officers. Of course, I could complain to the state highway patrol, but they might be in the same situation.

How can you make these SBs slow down when you lack strong evidence as the cars pass by?

75? Really? But this got me thinking—can you record the speed of cars on residential streets to provide support for police intervention?

Well, I have a Raspberry Pi and a Raspberry Pi camera module, I should be able to use them to measure the speed of cars, and then write a program to record the speed of cars passing in front of the camera.

How to Monitor Car Speed with Python and Raspberry PiRequirements:

This is almost entirely a software implementation, so the hardware requirements are quite simple:

Raspberry Pi 2 (3 should be better, but I don’t have one)

Raspberry Pi camera

The software requirements are also quite straightforward:

Raspbian

OpenCV

Python

Steps:

Install Raspbian on the Raspberry Pi; there are plenty of resources on this topic online. The latest release is Jessie, and I recommend using it for this project.

Install OpenCV 3 and Python 3 on the Raspberry Pi. Many thanks to Adrian Rosebrock for this detailed blog post on installing OpenCV and Python on Raspberry Pi: http://www.pyimagesearch.com/2015/10/26/how-to-install-opencv-3-on-raspbian-jessie/. My Raspberry Pi 2 Model B system uses Python 3.4.1 and OpenCV 3.1.0.

Copy carspeed.py to your /home/pi directory; my program is based on the motion detection program on pyimagesearch.com, but modified for speed detection. The program not only needs to detect motion but also to record the time the car moves in front of the camera, and it also needs to know how far the car has traveled. Obviously, the same horizontal distance in the camera looks different when it’s one foot away from the lens compared to being 50 feet away. Using the camera’s field of view and some trigonometry, this “width” can be calculated regardless of how far it is from the lens:

How to Monitor Car Speed with Python and Raspberry PiThe field of view (FOV) of the Picamera is about 53.5 degrees. We assume the road is 60 feet (D) away from our camera; the horizontal distance (C) covered by the image at 60 feet from the lens would be:

2*60*tan(53.5 * 0.5)

120*tan(26.75)

120*0.50

60 feet

So the horizontal distance covered by the Picamera image is roughly the distance from the road to the lens. If the distance is 10 feet from the lens, then the image covers about 10 feet, similarly if it’s 47 feet away, then the coverage is also 47 feet.

Of course, other cameras may have a different field of view, making the conversion not so straightforward.

Once the horizontal distance is known, dividing it by the number of pixels in the width of the frame gives the distance represented by each pixel. Thus, you can calculate the speed using the time the object crosses the pixel area and the number of pixels.

Here’s what we want the motion detection program to do:

1. Use the logic from the pyimagesearch website to detect motion

2. Start a timer

3. Track the moving object until it crosses the other side of the frame

4. Calculate speed

5. Save an image marked with the speed

The first version of the program was tested by placing the camera outside my front window. Soon, a problem arose—the program worked so well that it tracked birds flying by, squirrels foraging in the yard, and even my neighbor opening the garage door.

The program should only detect motion on the street, not the entire surrounding environment.

I added a part of the logic to allow the mouse to draw an area to limit the monitored image area, and this change effectively eliminated almost all irrelevant motion detection.

However, the program still had issues when monitoring cars exceeding 35 km/h; it couldn’t read two or more image frames of a car quickly passing through the monitored area. I tested the program and found that using imshow to display the image on the screen and using waitKey() slowed down the image processing flow. I immediately modified the code to prevent image display updates when a car passes through the monitored area. Seeing a car enter the field of view, reach the edge of the monitored area, and then disappear until it exits the monitored area seemed a bit strange, but this modification doubled the processing speed, allowing for faster speed measurements. In my tests, when the speed reached 40 mph, there was a +/-1 error. Speeds greater than 40 mph could still be recorded, but accuracy dropped significantly because the car passed through the frames too quickly. The program is limited to processing one car at a time, so if you live near a busy highway, it might struggle to produce accurate results. The Raspberry Pi 3 was just announced at the time this article was written, and based on its enhanced processing power, recording high-speed vehicles should be more accurate.

Usage

Point the Picamera at the road, and before running carspeed.py, modify the DISTANCE constant (representing the distance from the Picamera lens to the center of the road), you may also need to adjust vflip and hflip to match your camera orientation.

Run the following code in the terminal:

python carspeed.py

Use a mouse to draw a rectangular area where you want to monitor; I recommend a height sufficient to capture the entire car, and a width of about half a frame, centered in the area, press the “c” key to start monitoring the road.

When a car passes through the monitored area, an image will be written to disk.

Press the “q” key to exit.

Car Speed Program

The program code can be downloaded here; below is a description of the internal logic of carspeed.py.

# import the necessary packages
from picamera.array import PiRGBArray
from picamera import PiCamera
import time
import math
import datetime
import cv2

The program starts by importing the necessary packages.

# place a prompt on the displayed image
def prompt_on_image(txt):
    global image
    cv2.putText(image, txt, (10, 35),
    cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 255), 1)
 
# calculate speed from pixels and time
def get_speed(pixels, ftperpixel, secs):
    if secs > 0.0:
        return ((pixels * ftperpixel)/ secs) * 0.681818  
    else:
        return 0.0
 
# calculate elapsed seconds
def secs_diff(endTime, begTime):
    diff = (endTime - begTime).total_seconds()
    return diff

The next step is to define some methods and functions. The “prompt_on_image” method displays a message on the image in simple formatting. The “get_speed” function returns the speed calculated based on the number of pixels traveled in a fixed time (if you’re not using feet and mph, but using meters and kph, replace 0.681818 with 3.6). Finally, the “secs_diff” function returns the number of seconds between two time points.

# mouse callback function for drawing capture area
def draw_rectangle(event,x,y,flags,param):
    global ix,iy,fx,fy,drawing,setup_complete,image, org_image, prompt
 
    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix,iy = x,y
 
    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing == True:
            image = org_image.copy()
            prompt_on_image(prompt)
            cv2.rectangle(image,(ix,iy),(x,y),(0,255,0),2)
  
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        fx,fy = x,y
        image = org_image.copy()
        prompt_on_image(prompt)
        cv2.rectangle(image,(ix,iy),(fx,fy),(0,255,0),2)

The draw_rectangle method handles mouse events used to define the detection area on the image. It simply allows the user to draw a rectangle on the image. The image is refreshed from the origin, so as the mouse moves, the rectangle expands. If this refresh isn’t done, when another rectangle is drawn after the first one is completed, the mouse movement would cause a filled rectangle.

# define some constants
DISTANCE = 76  #<---- enter your distance-to-road value here
THRESHOLD = 15
MIN_AREA = 175
BLURSIZE = (15,15)
IMAGEWIDTH = 640
IMAGEHEIGHT = 480
RESOLUTION = [IMAGEWIDTH,IMAGEHEIGHT]
FOV = 53.5
FPS = 30

The next step is to define some constants used in the program. You need to estimate the distance from the camera to the center of the road and put this value into the DISTANCE on line 49. THREADHOLD, MIN_AREA, and BLURSIZE are the best working values during testing.

# the following enumerated values are used to make the program more readable
WAITING = 0
TRACKING = 1
SAVING = 2
UNKNOWN = 0
LEFT_TO_RIGHT = 1
RIGHT_TO_LEFT = 2

I prefer to use enumerated values to make the program easier to read. Here, some enumerated values are defined for use; the first three monitor the current tracking process state, and the next two define the direction of motion in the image. The actual values of these variables aren’t that important (what matters is that using enumeration makes the program easier to read).

# calculate the the width of the image at the distance specified
frame_width_ft = 2*(math.tan(math.radians(FOV*0.5))*DISTANCE)
ftperpixel = frame_width_ft / float(IMAGEWIDTH)
print("Image width in feet {} at {} from camera".format("%.0f" % frame_width_ft,"%.0f" % DISTANCE))

This completes the initialization of the program constants and methods; now it’s time to calculate the width of the frame and how many feet each pixel corresponds to. Note: For those using meters, the same logic can be applied to calculate distances, which will just give you how many meters each pixel represents.

# state maintains the state of the speed computation process
# if starts as WAITING
# the first motion detected sets it to TRACKING
 
# if it is tracking and no motion is found or the x value moves
# out of bounds, state is set to SAVING and the speed of the object
# is calculated
# initial_x holds the x value when motion was first detected
# last_x holds the last x value before tracking was was halted
# depending upon the direction of travel, the front of the
# vehicle is either at x, or at x+w 
# (tracking_end_time - tracking_start_time) is the elapsed time
# from these the speed is calculated and displayed 
 
state = WAITING
direction = UNKNOWN
initial_x = 0
last_x = 0
 
#-- other values used in program
base_image = None
abs_chg = 0
mph = 0
secs = 0.0
show_bounds = True
showImage = True
ix,iy = -1,-1
fx,fy = -1,-1
drawing = False
setup_complete = False
tracking = False
text_on_image = "No cars"
loop_count = 0
prompt = ""

The next step is the initialization of global variables.

# initialize the camera 
camera = PiCamera()
camera.resolution = RESOLUTION
camera.framerate = FPS
camera.vflip = True
camera.hflip = True

rawCapture = PiRGBArray(camera, size=camera.resolution)
# allow the camera to warm up
time.sleep(0.9)

Initialize the Picamera.

# create an image window and place it in the upper left corner of the screen
cv2.namedWindow("Speed Camera")
cv2.moveWindow("Speed Camera", 10, 40)

We want to know what the program is processing, so we create a window and move it to the upper left corner of the display area.

# call the draw_rectangle routines when the mouse is used
cv2.setMouseCallback("Speed Camera",draw_rectangle)
 
# grab a reference image to use for drawing the monitored area"s boundry
camera.capture(rawCapture, format="bgr", use_video_port=True)
image = rawCapture.array
rawCapture.truncate(0)
org_image = image.copy()

prompt = "Define the monitored area - press \"c\" to continue" 
prompt_on_image(prompt)
 
# wait while the user draws the monitored area"s boundry
while not setup_complete:
    cv2.imshow("Speed Camera",image)
 
    #wait for for c to be pressed  
    key = cv2.waitKey(1) &amp; 0xFF
  
    # if the `c` key is pressed, break from the loop
    if key == ord("c"):
        break

Original text in English: https://gregtinkers.wordpress.com/2016/03/25/car-speed-detector/ Translator: pyBean

How to Monitor Car Speed with Python and Raspberry PiTo learn more about wild dogs, please click to read the original.

Leave a Comment

×