A private bus operator · Mumbai & Delhi NCR · September – October 2026
Bus passenger counter: boardings and alightings from the DVR cameras
A bus operator loses fares he can't see: conductors collect cash, and the numbers they report don't match who gets on. The buses already record three cameras to a DVR that nobody watches. I built a pipeline that reads the overhead door camera, tracks every person, counts door crossings, and checks itself against a second camera and against hand counts.
- YOLO11
- RT-DETR
- BoT-SORT
- Computer Vision
- Video Analytics
- 53 / 45
- boardings / alightings counted on 2 Sept, rear door
- ~90%
- boarding accuracy on hand-checked segments
- 0
- false counts on 45 minutes of an empty bus
- 12 ms
- per frame on the laptop GPU, fine-tuned YOLO11s


The leak
Fares are cash. The conductor writes the number down, and the owner has no way to check it against the crowd that actually boarded. He can't stand at the door all day, and he can't watch 97 minutes of footage per bus per morning either.
He has never had a figure for how much goes unreported. His estimate is that a count he could trust would recover about 30% of what leaks, per bus. That is the number this is for: not a better report, a smaller gap.
Each bus records three cameras: a cabin camera at the back looking forward, an overhead camera above the rear door, and a third that is fogged over in every recording. The question was whether that footage, as it is, could give an honest count per day. No new hardware, no app for the conductor.
What I built
- Decode: the DVR writes raw H.264 segments (704×576, 25 fps, about three minutes each). They're decoded through an ffmpeg pipe and analysed at 8 fps, back to back, so a person halfway through the door at a segment boundary isn't lost.
- Detect: COCO-trained YOLO weights find almost nobody in the steep overhead view. RT-DETR, a transformer detector, does, so I used it to pseudo-label 816 frames from all 34 segments and both cameras, then fine-tuned YOLO11s on them. On 17 crowded door frames the fine-tuned model finds 105 people where stock YOLO11x finds 6, at 12 ms a frame.
- Track: BoT-SORT with appearance re-identification, so a person keeps their id through short occlusions.
- Count door crossings, not people in frame. Each camera has a door zone and a bus zone, tested on the feet, which is the true floor position under an overhead camera. Board = first seen in the door zone, then into the bus. Alight = from the bus into the door zone, then gone. A track that starts and ends inside the bus never counts, so seat changes and aisle walking are harmless.
- Rules for crowded doors: a side change needs two consecutive frames and 12 px past the edge; an exit is only final after six seconds missing; a track that vanishes at the door edge followed within five seconds by a new one on the bus side is the same person; a tracker id that jumps onto a newcomer is split in two.
- Re-linking: a broken track inherits the state of the one that vanished nearby, and a five-minute appearance gallery gives the same person id back to someone who reappears after being hidden behind a seat.
- Two-camera check: the cabin camera gives an independent count of people visible. Whenever both cameras see an empty bus for a minute, the door counter's leftover occupancy is booked as missed events and the count restarts from zero.
- Per day: every board and alight with clock time, per-segment counts, running occupancy, and a review video with IN/OUT counters drawn on the frames so a hand count can be checked against it.
Results so far
2 September, 09:00 to 10:38, rear door: 53 boardings and 38 alightings from the door counter alone. The bus emptied at the terminus at 09:46 with seven people still on the counter, so seven missed exits were booked and the corrected totals are 53 boarded, 45 alighted.
Ground truth is slow to make, so it's partial. I hand-counted the first two segments: 18 boardings, 0 alightings. The pipeline found 22 and 1. A frame-by-frame review of the 13 events in the first segment showed 11 real entrants (my own count of 9 was low) and 2 duplicates. Boarding accuracy on the checked segments is roughly 90%.
A second bus, 8090, recorded 45 minutes with nobody on board but a seated staff member. The counter reports 0 boardings and 0 alightings on it: no false counts on an empty vehicle. Its frames became hard negatives for training.
What went wrong, and what I did about it
- The third camera is fogged over in every segment, on both buses. Disabled.
- Mass exits break identity tracking. Around 09:41 about ten people left through the rear door in 30 seconds. The tracker kept a single id on the door spot for 32 seconds while different people passed through it, so only one exit was counted there. Identity-based counting cannot recover this. The fix is better boxes in crowded doorways, or a dedicated crossing-line flow counter; the two-camera reset catches the total in the meantime.
- The machine-made labels are only 70–80% complete on the overhead door camera. So I built a local labelling tool and pulled the 160 busiest door frames. The first seven hand-corrected frames changed nothing measurable (24 / 0 against 22 / 1 before), which told me a few dozen are needed before a retrain shows an effect.
- Training on the laptop's GPU ran it hot. A low-power recipe (frozen backbone, 576 px, small batches, a pause after each batch) trains in six minutes with no thermal warning, but it doesn't count better, so it isn't the default. The same job is packaged for a free Colab T4.
- Only the rear door has a usable camera. After the terminus the cabin camera sees up to four more people than the rear-door count explains: they boarded through the front door. A camera over the front door is needed for complete counts.
| Current model (YOLO11s, 20 epochs) | Low-power retrain v2 | |
|---|---|---|
| Validation recall / mAP50, bus 7687 | 0.69 / 0.75 | 0.65 / 0.73 |
| People found, 17 crowded door frames | 105 | 118 |
| False detections, empty bus 8090 door camera (2,708 frames) | 7 frames | 3 frames |
| Hand-checked segments, boarded / alighted (manual 18 / 0) | 22 / 1 | 22 / 2 |
The rest of the fleet
The operator's vehicles already report to the manufacturer's telematics portal: distance, fuel, idling, gear usage, driving-economy scores, harsh braking and acceleration, breakdown tickets. The monthly summary even prices idling in litres and rupees. It arrives as PDFs and spreadsheets that nobody has time to read, the same way the DVR footage sat unwatched.
The passenger count is the first of these signals turned into a number the owner can act on. The next is tyres: retrofit Bluetooth tyre-pressure sensors on the same buses, read them from a gateway on board, and log distance per tyre against the odometer, so pressure drift and wear show up before a burst, and rotations happen on distance rather than guesswork. I'm comparing sensor manufacturers now; both this and the passenger count are live projects.
Where it is now
One full morning counted and cross-checked on one bus, and an empty run verified on a second. The step that turns this into money for the owner is next: a week of days with the conductor's reported tickets beside the counted boardings, trip by trip. That gap is the number he has never had, and until that week runs, the 30% stays his estimate rather than a result. Tyre pressure and per-tyre distance are the layer after that.