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 review video the pipeline writes: overhead rear-door camera, tracked boxes, IN/OUT counters. Fifteen seconds at the 09:05 stop: four people board through a crowded doorway and the counter ticks once for each. Over this whole three-minute segment the counter matched a frame-by-frame hand count: nine boardings across its three stops, nobody off (the tenth on the counter was already in the doorway when the segment began). Heads are blurred using the pipeline's own detections.
Overhead rear-door camera of the bus between stops, with the door zone the counter uses drawn in green
The overhead rear-door camera between stops, with the door zone drawn in green. A track that starts in the zone and moves into the bus is a boarding; one that comes from inside, enters the zone and vanishes is an alighting. Anything that never crosses it doesn't count.
Occupancy over the morning from door events, corrected at empty-bus resets, against people visible on the cabin and door cameras
The two-camera cross-check for 2 September. Blue: occupancy from door events alone, drifting upward. Black: corrected at the empty-bus reset at 09:46. Orange and green: people visible on the cabin and door cameras.

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.
Detector comparison, 18 October
Current model (YOLO11s, 20 epochs)Low-power retrain v2
Validation recall / mAP50, bus 76870.69 / 0.750.65 / 0.73
People found, 17 crowded door frames105118
False detections, empty bus 8090 door camera (2,708 frames)7 frames3 frames
Hand-checked segments, boarded / alighted (manual 18 / 0)22 / 122 / 2
The 8090 bus is empty, so its frames only teach "no person here": that halves false detections but does nothing for crowded doorways. More training on machine-made labels won't fix those; hand-corrected labels will.

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.