speed-camera: a Raspberry Pi motion tracker that estimates vehicle speed from video
A Unix, Windows, Raspberry Pi Object Speed Camera using python3, opencv, video streaming, motion tracking. Includes a Standalone Web Server , Image Search using opencv template match and a whiptail Admin Menu Interface Includes picam and webcam Plugins for motion track security camera configuration including rclone sync scripts.
At a glance
- What is it?
- speed-camera is a Python and OpenCV program that tracks the largest moving object in a camera view and estimates its speed from a calibration, with a whiptail admin menu, a standalone web server and SQLite storage. It suits a maker or a rural road watcher with a spare Pi, and it is an application rather than a library.
- Who is it for?
- speed-camera fits someone with a spare Raspberry Pi, a camera pointed at a road or driveway and an afternoon to calibrate it, because the whole measurement chain from crop region to calibration constants to speed figure is exposed in a single config.py rather than hidden.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 30 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
How the measurement is actually made
The program description is short and it is worth reading closely, because the method is the whole thing.
It is written in python3 and uses openCV to detect and track the x,y coordinates of the largest moving object in the camera view above a minimum pixel area, and then calculates speed based on calibration settings.
So the mechanism is a pixel measurement turned into a speed by a calibration, not a radar or a loop detector. The system finds the biggest moving thing in the frame, measures how far it travels in pixels between frames, and converts that into a physical distance using a constant you supply, and then into a speed using the frame rate.
That has an obvious strength and an obvious limit. The strength is that it works with any camera, including a cheap USB webcam, and it needs no hardware beyond the camera and the computer. The limit is that everything rests on the calibration, and the calibration is a physical measurement you take with a tape measure and enter by hand.
The calibration variables are `CAL_OBJ_PX_` and `CAL_OBJ_MM_`, and there are separate values for L2R and R2L motion direction. The two directions being separate is a detail that matters more than it looks, because a camera mounted on one side of a road sees traffic approaching and receding, and the pixel geometry of the two cases differs enough that using one constant for both produces visibly wrong numbers in one direction.
Everything else is in a single file. User variables are stored in the `config.py` file, and the documentation links to it for the details of every setting. That is the design decision that makes the project usable: one Python file of constants rather than a configuration format, a database of settings or a web interface.
The crop region decides what counts as traffic
Motion detection is restricted between `MO_CROP_Y_UPPER`, `MO_CROP_Y_LOWER`, `MO_CROP_X_LEFT` and `MO_CROP_X_RIGHT` variables, that is, the road or the area of interest. And `MO_CROP_AUTO_ON` set to `True` overrides the manual settings and will auto calculate a rough crop area based on image size.
This is the part of the system that decides whether the numbers mean anything, and the auto mode is a rough approximation rather than a solution. A crop region is how you tell the tracker which pixels are the road, so that a branch moving in the wind, a shadow crossing the frame, or a person walking past on the pavement is not counted as a vehicle.
The tracking parameters are related. Motion tracking is controlled by the `MO_TRACK_EVENT_COUNT` variable in config.py, and this sets the number of track events and the track length in pixels. The documentation says this may need to be tuned for camera view, cpu speed and so on, and that is an honest warning: a longer track gives a better speed estimate because the measurement spans more distance, and it also makes the system slower and more sensitive to the object changing shape.
The two configuration variables that interact with the crop are the calibration pair and the auto-crop flag, and the initial state of a fresh install is worth knowing. The initial default config.py setting is `CALIBRATE_ON=True`, and you may also need to align the camera using the `ALIGN_CAM_ON=True` setting. Once calibration is complete you set `CALIBRATE_ON=False`.
So the intended sequence is: install, run with calibration on to align the camera, take the physical measurement and enter the calibration constants, then turn calibration off. Leaving `CALIBRATE_ON` on after that would mean the system keeps expecting a calibration target in frame, which is a state that produces confusing results rather than an error.
The camera stack is also version dependent, and the documentation calls it out. `speed-cam.py` will run using pi camera with libcamera, picamera2 under Raspberry Pi OS Bullseye, Bookworm and later, and USB cameras and IP/RTSP cameras are also supported. For picamera library support on Bullseye only, the instruction is to run `sudo raspi-config`, Interface Options, then enable the Legacy Camera option and reboot.
The install script, and the apt step it deliberately does not do
The Raspberry Pi quick install is a single curl piped to bash, and the documentation is careful about one thing in the middle of it.
The important note says that a raspbian `sudo apt-get update` and `sudo apt-get upgrade` will NOT be performed as part of `speed-install.sh`, so it is highly recommended you run these prior to install to ensure your system is up-to-date.
That is a small sentence with a real reason behind it. An install script that silently upgrades a running system as a side effect is an install script that can change the behaviour of everything else on the machine, and a camera program that depends on a specific OpenCV or picamera2 version is exactly the kind of thing an upgrade can break. Leaving the update to the operator means the state of the system before the install is known.
The install command itself is:
curl -L https://raw.github.com/pageauc/speed-camera/master/source/speed-install.sh | bashThe instructions for running it are practical: on the RPi, in a putty SSH or terminal session, right click, select paste, then Enter to download and run the script.
For a Mac or a Windows system there are two documented paths. There is a Docker install quick start, and there are Windows or Unix distro installs without Docker. Both are pointed at from the same place in the documentation.
The repository layout is small and follows the script structure. There is a `source/` directory holding the program and its config, an `assets/` directory, a `versions/` directory, a `strmvidfile.py` file at the root, and a `Readme.md` with the license in a separate `LICENSE.MD` file. The Apache-2.0 licence and the last push on 2026-09-23 complete the picture.
The admin menu, the web server and supervisor
The project is a set of small programs around one Python script, and the division of labour is explicit.
`speed-cam.py` is the camera program, and its wiki page describes it as the way to view operation and log messages in real time in a terminal session. It can be used to monitor, troubleshoot and tune the speed camera, with the settings in config.py.
`menubox.sh` is a whiptail menu system to allow easier management of program settings and operation. Whiptail is the standard Linux dialog tool, so this is a text menu drawn in the terminal, which is the right choice for a program that lives on a headless Pi over SSH.
`speed-web.py` allows viewing images, video and/or data from a web browser, and it will display web server messages. The description calls it a standalone web server, and it is what makes the captured images and the data reachable from a phone on the same network.
The two shell scripts manage the Python programs through supervisor. `speed-cam.sh` uses supervisorctl to manage start, stop, status and so on of `speed-cam.py`, configured to autostart, for example due to interruption of the RTSP stream. The configuration files are in the supervisor folder. There is a setup step: you must run `./speed-cam.sh` `install` to initialize symbolic links to the /etc/supervisor/conf.d folder. And there is a warning: stop running any speed-cam and/or webserver processes before running `./speed-cam.sh` `start`. The same pattern applies to `speed-web.sh`.
That supervisor arrangement is the operational backbone of the project. It is what gives you a camera program that comes back after a network drop, a Raspberry Pi that survives a power cut, and a web server that restarts when the RTSP stream is interrupted.
The web server and the camera program are configured to auto-restart, which is the detail that matters for a device in a car park or a driveway. Nothing in the documentation says the system is designed to run unattended forever without attention, and the presence of the restart configuration, the rclone sync and the graph scripts all point the other way, toward a small appliance you maintain.
Storage, remote sync and the graph reports
The data path has three stages, and each is optional in a way that suits different uses.
First the CSV. If `LOG_DATA_TO_CSV` is `True` then a `speed-cam.csv` file will be created or updated with event data stored in CSV format, and the documentation notes it can be imported into a spreadsheet, database or other program for further processing. That is the simplest option and the one to use if you want the data in your own tools.
Then the database. Release 8.9 adds a sqlite3 database to store speed data, with the default being `data/speed_cam.db` and the data in the `speed` table. The database setting can be managed from config.py, and the database is automatically created from the config.py settings, with a wiki page on managing it.
Then the remote copy. The `rclone` script manages settings and setup for optional remote file sync to a remote storage service like Google Drive, Dropbox and many others.
And then the reports. Two scripts query the SQLite database and generate matplotlib graph images saved into the media/graphs folder. `sql-make-graph-count-totals.py` displays counts by hour, day or month for specified previous days and speed over, and `sql-make-graph-speed-ave.py` displays average speed by hour, day or month for specified previous days and speed over. Multiple reports can be managed from the config.py `GRAPH_RUN_LIST` variable under the matplotlib image settings section.
The separation is sensible. The camera program writes rows, the database holds them, the graph scripts read them, and rclone moves the results somewhere else. Each stage is a plain file format or a standard tool, so you can replace any one of them without replacing the rest.
The image capture path is the fourth element and it is also configurable. A video stream frame image will be captured and saved in `media/images` in dated subfolders, optional, per the `IM_SUBDIR_MAX_FILES` variable set to 2000. So the evidence is kept alongside the measurements, with a cap on how many images per dated folder.
The topics also list openalpr, which is a licence plate recognition library, as something associated with the project, and strmvidfile.py at the root of the repository is the script that builds a video file from the captured frames.
Editorial conclusion
speed-camera fits someone with a spare Raspberry Pi, a camera pointed at a road or driveway and an afternoon to calibrate it, because the whole measurement chain from crop region to calibration constants to speed figure is exposed in a single config.py rather than hidden. Skip it if you need a certified device for enforcement purposes, since a single camera and a two-point calibration cannot be that, and skip it if you want continuous unattended operation without maintenance, since the project depends on supervisor, a web server and a working camera stack. Before you rely on it, run the calibration with `ALIGN_CAM_ON=True` and then set `CALIBRATE_ON=False`, and note the licence is Apache-2.0 with the last push on 2026-09-23.
Frequently asked questions
How does speed-camera calculate speed?
It is written in python3 and uses openCV to detect and track the x,y coordinates of the largest moving object in the camera view above a minimum pixel area, and calculates speed from the CAL_OBJ_PX_ and CAL_OBJ_MM_ calibration variables, which have separate values for L2R and R2L motion direction.
How do I install speed-camera on a Raspberry Pi?
Run `curl -L https://raw.github.com/pageauc/speed-camera/master/source/speed-install.sh | bash` and paste it into a terminal session. The script does not run apt-get update or upgrade, so the documentation recommends doing that yourself first.
What configuration does speed-camera use?
A single config.py file holding all user variables, including the crop region settings MO_CROP_Y_UPPER, MO_CROP_Y_LOWER, MO_CROP_X_LEFT and MO_CROP_X_RIGHT, the MO_CROP_AUTO_ON flag, the MO_TRACK_EVENT_COUNT tracking parameter and the calibration values.
Where does speed-camera store its data?
Optionally as CSV in speed-cam.csv when LOG_DATA_TO_CSV is True, and in a sqlite3 database at data/speed_cam.db with data in the speed table, with optional rclone sync to a remote storage service and matplotlib graph reports generated from the database.
Which cameras does speed-camera support?
The pi camera with libcamera and picamera2 under Raspberry Pi OS Bullseye, Bookworm and later, plus USB cameras and IP/RTSP cameras. Picamera library support on Bullseye requires enabling the Legacy Camera option in raspi-config and rebooting.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pageauc-speed-camera)Was this useful?