Skip to article content Blogs

How We Turned Dumb CCTV Footage into a Smart API

Solving a real estate-management problem with computer vision, FastAPI, and AWS: from uploaded CCTV footage to a timestamp and a flat number.

Security-camera footage flowing into a machine that prints a structured incident card

Summary

A housing society wanted to know who was throwing garbage out of their windows, without paying guards to watch hours of video. We built an API that takes CCTV footage and returns the time and the flat number. This is how it works, and what I would tighten before calling it finished.

The problem: catching bad neighbours

The client was a housing society management company. They already had a CRM for their residents, and one very manual problem: people were throwing garbage out of their windows, and the only way to catch them was a security guard watching footage.

They asked for one API endpoint. The CRM uploads CCTV footage from the estate and gets back the exact timestamps and the flat numbers involved, so the society can issue warnings and fines.

A video is a flipbook

The idea that made this manageable was to stop treating a video as a movie. It is a flipbook of thousands of pictures. Processing a whole video is heavy and slow. Processing a handful of compressed pictures is fast, and it scales.

So the whole thing became a data pipeline: break the video into frames, look at the frames, and return a structured report.

The asynchronous video-to-incident pipelineA video is uploaded and queued, analysed frame by frame, mapped to flats, and reported back to the CRM.3. Report

Group detections into incidents

JSON: timestamps + flat numbers

Back to the CRM

2. Analysis

OpenCV: compress, pull frames

Vision model: find the object

Match the box to a camera and flat

1. Upload

Society CRM

API Gateway + FastAPI

RabbitMQ queue

The asynchronous video-to-incident pipelineA video is uploaded and queued, analysed frame by frame, mapped to flats, and reported back to the CRM.3. Report

Group detections into incidents

JSON: timestamps + flat numbers

Back to the CRM

2. Analysis

OpenCV: compress, pull frames

Vision model: find the object

Match the box to a camera and flat

1. Upload

Society CRM

API Gateway + FastAPI

RabbitMQ queue

The asynchronous video-to-incident pipeline. Accepting a video and analysing it are separate steps, joined by a queue.
  1. The society’s CRM uploads a video to the API.
  2. The API accepts it and puts a job on a RabbitMQ queue.
  3. A worker uses OpenCV to compress the video and pull frames at intervals.
  4. A vision model finds the object in each frame and draws a box around it.
  5. The box is matched against the building map to get the camera and the flat.
  6. Detections are grouped into incidents.
  7. A JSON report with timestamps and flat numbers goes back to the CRM.

We used OpenCV to compress the video and pull frames at set intervals, and the Google Vision API to find objects in each frame. The interval is a real trade-off. Sample too rarely and a bag falling past a window slips between two frames. Sample every frame and the bill for decoding and inference multiplies.

Why the queue mattered

Uploads arrive in bursts and analysis is slow, so the two cannot share a request. We started on Django and later moved to FastAPI for its async performance, put the service behind AWS API Gateway and Lambda, and used RabbitMQ to separate accepting a video from working on it.

Sai, our data scientist, packaged the vision logic as its own API, and workers pulled jobs from the queue at their own pace. A spike in uploads turned into a longer queue instead of a crashed server.

A queue has a catch that is easy to miss: it can hand you the same job twice. RabbitMQ’s reliability guide is clear about this. A worker has to cope with a duplicate, a temporary failure, and a file it will never be able to read, and each of those needs a different exit.

What a worker does with each deliveryA delivery is claimed or dropped as a duplicate. Processing ends in success, a bounded retry, or the dead-letter path.already claimedyessuccesstransient erroryesnounsupported media

Queue delivers a job, maybe twice

Claim succeeds?

Ack and drop the duplicate

Process with stored artifacts

Outcome

Complete and ack

Retry budget left?

Dead-letter for inspection

What a worker does with each deliveryA delivery is claimed or dropped as a duplicate. Processing ends in success, a bounded retry, or the dead-letter path.already claimedyessuccesstransient erroryesnounsupported media

Queue delivers a job, maybe twice

Claim succeeds?

Ack and drop the duplicate

Process with stored artifacts

Outcome

Complete and ack

Retry budget left?

Dead-letter for inspection

What a worker does with each delivery. A duplicate, a temporary failure, and an unreadable file each leave by a different door.
  1. The queue delivers a job, possibly more than once.
  2. The worker tries to claim it.
  3. If it is already claimed, acknowledge and drop the duplicate.
  4. Otherwise process it using stored artifacts.
  5. On success, complete the job and acknowledge.
  6. On a transient error, retry while the retry budget lasts.
  7. Unsupported media and exhausted retries go to the dead-letter path.

The unglamorous part: teaching it where the flats are

A vision model draws a box around falling garbage. A box is just pixels. It has no idea which flat that window belongs to.

That context did not exist, so we made it. The team spent long weekends labelling every camera viewpoint by hand, marking the pixel coordinates of each flat on each feed. That table of coordinates is what turns a detection into an answer. The model gets the credit, but the map is the product.

From a box of pixels to a flat numberA bounding box is matched against a hand-labelled map of each camera to find the flat.

Bounding box from the vision model

Which labelled region contains it?

Hand-labelled map: every flat on every camera

Camera ID + flat number

Timestamp and flat go into the report

From a box of pixels to a flat numberA bounding box is matched against a hand-labelled map of each camera to find the flat.

Bounding box from the vision model

Which labelled region contains it?

Hand-labelled map: every flat on every camera

Camera ID + flat number

Timestamp and flat go into the report

From a box of pixels to a flat number. The model gives pixels. The hand-labelled map of each camera turns them into a flat.
  1. The vision model returns a bounding box.
  2. A hand-labelled map records the pixel region of every flat on every camera.
  3. The box is matched to the region that contains it.
  4. That gives a camera ID and a flat number.
  5. The timestamp and flat go into the report.

Two things I would add with hindsight. A camera that gets nudged or replaced silently breaks its map, so each map needs a version and a date. And when a box sits across two windows, the honest answer is both flats, with a person deciding between them.

One video, more than one incident

Our first response returned a timestamp and a flat number as plain strings. Then an edge case came up: what happens when more than one incident occurs in the same video?

We worked with the client’s onsite team to change the contract. The payload moved from strings to lists, so one upload could report several timestamps and flats. It is a small change, but the client’s side had to change with it. I now design a response for the messy case first.

The reverse also matters. One falling bag shows up in several frames in a row, and that is one incident, not twelve. So detections are grouped before the report is built.

From frame detections to a list of incidentsDetections are grouped so that repeats collapse into one incident and separate incidents stay separate.

Detections from many frames

Group by camera, flat, and time

Same bag in several frames: one incident

Two flats in one video: two incidents

List of incidents in the report

From frame detections to a list of incidentsDetections are grouped so that repeats collapse into one incident and separate incidents stay separate.

Detections from many frames

Group by camera, flat, and time

Same bag in several frames: one incident

Two flats in one video: two incidents

List of incidents in the report

From frame detections to a list of incidents. Many detections become a short list of incidents.
  1. Collect the detections from many frames.
  2. Group them by camera, flat, and time.
  3. The same bag seen in several frames becomes one incident.
  4. Two flats in one video stay as two incidents.
  5. The report carries the list of incidents.
{
  "job_id": "job_01J...",
  "status": "review_required",
  "incidents": [
    {
      "incident_id": "inc_01J...",
      "camera_id": "camera-east-02",
      "occurred_at": "2026-07-29T09:14:32.480Z",
      "candidate_locations": [
        { "zone_id": "flat-7b", "confidence": 0.82 }
      ],
      "model_confidence": 0.88,
      "evidence": {
        "frame_ids": ["frame-1834", "frame-1842"],
        "calibration_version": "east-02-v4"
      },
      "review": { "status": "pending" }
    }
  ]
}

A score is not an accusation

The output of this system ends up as a warning or a fine sent to a resident. A vision model does not return facts. It returns a confidence score. My view now is that the API should hand back a candidate along with the frames that support it, and a person should look before anyone is fined.

It is also footage of people’s homes and routines. Keep only the frames needed for review, delete the rest on a schedule, and limit who can open them. The NIST Privacy Framework is a reasonable place to start. The law that applies to you needs someone qualified.

What I took from it

The system was in its testing phase when I first wrote about it, and the project lined up with me clearing the AWS Certified Cloud Practitioner exam. Understanding how the cloud shapes a distributed system was exactly what this API needed.

  • Treat video as a background job, never as a request.
  • The labelled map of each camera matters more than the model.
  • Return lists from day one. There is always a second incident.
  • Keep a person between the model and the fine.

References

  1. How We Turned Dumb CCTV Footage into a Smart API

    Abhishek Choudhury. Original project article, published February 2025.

  2. OpenCV VideoCapture class reference

    OpenCV. Video-file, image-sequence, camera, and stream capture behavior.

  3. RabbitMQ Reliability Guide

    Broadcom. Acknowledgements, redelivery, publisher safety, and consumer idempotency.

  4. Detect multiple objects with Object Localization

    Google Cloud. A dated managed-service example of object bounds and scores.

  5. NIST Privacy Framework

    National Institute of Standards and Technology. A voluntary framework for identifying and managing privacy risk.