MiniBox · Autonomous Bicycle Counter
November 2022 → November 2023
MiniBox, an open source bike counter
MiniBox is my final project for the APSIO bachelor's degree, built as a two-person team with Noémie Tandol. It is the project I am most proud of, mostly for what it set out to solve.
The problem
Cycling has been growing for several years now: the pandemic, electric bikes, better infrastructure. But to adapt that infrastructure, you first need to measure the traffic, and to measure it finely and widely enough to draw real conclusions. Professional bike counters exist, but they are expensive, which limits their deployment to a handful of strategic spots.
So the idea was to build a small, self-contained counter running on a Raspberry Pi, cheap enough to deploy in large numbers rather than a few units. The intended users: local authorities that want to assess the effect of their cycling infrastructure, and cycling advocacy groups such as 2 pieds 2 roues and the AF3V.
The project is released under the GPL-3.0 license. Open source was a requirement, not a nice-to-have: a measurement tool meant to inform public decisions should be auditable by anyone.
Why YOLO over another model
Before writing a single line, we compared the main families of object detection. Faster R-CNN and Mask R-CNN are very accurate, the latter even segments each object precisely, but they are slow. RetinaNet shines on small, dense objects, which is why it is used in aerial and satellite imagery, but that was not our case. SSD is fast and handles boxes at several scales; so does YOLO, with the added benefit of doing everything in a single pass over the image, which makes it well suited to real time.
Since our main constraint was running live on modest hardware, YOLO was the obvious choice. Its known weakness, struggling with very small objects and very busy scenes, was not a blocker for counting bikes filmed from reasonably close.
How it works
The system rests on three layers.
Detection. YOLOv5, running on PyTorch, spots objects in the video stream. It identifies bikes frame by frame, with no memory from one frame to the next.
Tracking. This is where the real difficulty lies, and where I learned the most. Detecting a bike in a single frame is not enough to count it: without tracking, the same bike appearing across thirty consecutive frames would be counted thirty times. We started with the SORT library, then moved to Norfair, which keeps an object's identity consistent across frames. That way we count passages, not detections. As a bonus, by comparing a bike's successive positions, the program infers its direction of travel.
Visualization. A Django interface receives the counts and displays them with Chart.js, as charts that can be filtered by date and by object type.
Under the hood
The program is cleanly split: a main.py entry point, a config_handler.py that reads settings from a config.ini file, and a Functions folder holding most of the logic (the interface with YOLOv5, the detection, the object tracking, the utilities).
The direction calculation is a small piece I am fairly happy with, because it is simple and effective. For each tracked object, we keep its recent positions, look at the last four, and derive an average movement. If the object moves fast enough, above a threshold that filters out noise and stationary objects, we infer one of four directions: top-left, top-right, bottom-left, bottom-right. That is what lets us say not only how many bikes pass, but which way they are going.
Between YOLO and Norfair, an adapter was needed: YOLOv5 returns its detections as a PyTorch tensor, which I convert into the objects Norfair expects (box coordinates, confidence score, class). Nothing spectacular, but it is the kind of plumbing without which two building blocks never talk to each other.
Finally, the project has unit tests that run on every commit and every pull request on the develop and master branches, to avoid breaking one feature while fixing another. And the configuration is easy to duplicate to separate a development profile from a production one.
The legal side
A counter that films the street inevitably raises legal questions, so we dug into the topic, and it is less obvious than it looks. In France, only public authorities such as city halls may film public roads. A business or a private individual is not allowed to, not even to keep an eye on their own car parked outside their home.
The CNIL (the French data protection authority) does consider it legitimate to use cameras on public roads to count pedestrians, cars and bikes for urban planning, provided the device is authorized by public services when the people filmed cannot exercise their right to object. On the GDPR side, the key rule is simple: if the video cannot identify a person, directly or indirectly, most of the framework does not apply. Irreversible blurring, in fact, counts as compliant erasure.
That is exactly what shaped the design. By processing the stream live, without ever recording an image, we only keep anonymous counts. We land on the right side of the line from the start, instead of patching compliance in afterwards.
The road there
Nothing came out right on the first try. The first version took photos at regular intervals before analyzing them: too slow, and legally awkward since we were storing images of the street. We then switched to live stream processing, which solved the GDPR issue and the speed problem at the same time. The final version leans on an Nvidia GPU to keep up.
In practice, on a test video of 3 min 54 (7096 frames) with heavy traffic of bikes, runners and cars: 20 actual bikes, the program counted 21, all processed in 3 min 34 at roughly 33 frames per second. Decent, with a few mistakes on the directions.
The limits
I would rather also say what did not work. The tracker sometimes mixes up identities and hands a car's ID to a bike. Installing it on a Raspberry Pi turned out to be more painful than expected. And we never managed to detect what we were originally after, like whether a bike is loaded or whether the rider is wearing protective gear.
What comes next
The project stops at the level of a convincing prototype, but there is no shortage of directions. The most direct one would be moving to YOLOv8, which comes with its own tracking and would make Norfair unnecessary; we started looking into it but got stuck on a library (lap) that was incompatible with our version of Python on Windows.
Beyond that, training our own model would let us detect things generic models ignore: a bike loaded with panniers, an electric bike, a person in protective gear. In the same vein, Grounding DINO opens up detection from plain sentences, like spotting everyone wearing a yellow safety vest, without retraining anything. Two more down-to-earth improvements remain: a Docker image to install everything on a Raspberry Pi without fighting dependencies, and detection zones defined at the scale of a single street to refine the statistics further.
My first open source contribution
On this project, for the first time, I reported a bug and proposed a fix on an open source repository. It let me take a first step into that world and offer my help, at my own level. For the record, I did not do everything in the right order, and my proposal did not really fix the problem, but at least it helped flag it.