Initialize background subtractor

Object tracking is a common computer vision task with many real-world applications, from monitoring traffic to tracking people in a retail store. One simple but effective approach to object tracking is known as centroid tracking. In this post, we‘ll take an in-depth look at how centroid tracking works and use Python and OpenCV to build our own centroid tracker and counter system.

What is Centroid Tracking?

Centroid tracking is an algorithm used to track multiple objects over time in video streams. The key idea is to detect objects in each frame, calculate the "centroid" (center point) of each object‘s bounding box, and then track each object across frames by matching the centroids.

By assigning a unique ID to each tracked object, a centroid tracker can follow objects through a scene, even if they are momentarily lost or obscured. This makes centroid tracking useful for applications like:

  • Counting the number of objects (e.g. vehicles, people) that enter or leave a designated area
  • Analyzing traffic flow and patterns
  • Detecting anomalous events or behaviors based on object movement
  • Improving other computer vision tasks like object detection by incorporating temporal information

How the Centroid Tracking Algorithm Works

Now let‘s walk through the centroid tracking algorithm step-by-step:

  1. Object Detection: The first step is to detect the objects of interest in each frame of the video. This is typically done using an object detection model (e.g. Haar cascades, HOG + Linear SVM, deep learning models like YOLO or Mask R-CNN). The detector draws a bounding box around each detected object.

  2. Centroid Calculation: For each detected object, the centroid (center point) of its bounding box is calculated. The centroid is simply the average of the x and y coordinates of the bounding box.

  3. Centroid Matching: The calculated centroids from the current frame are compared to the centroids from the previous frame(s). The goal is to match each new centroid to an existing tracked object based on the minimum Euclidean distance between them. If a centroid is sufficiently close to an existing object centroid, they are considered to be the same object.

  4. ID Assignment: Each tracked object is assigned a unique ID. If a new centroid does not match any existing objects, it is given a new ID and considered a new object to track.

  5. Update Tracked Objects: The coordinates of each tracked object are updated based on the new matched centroid locations. Any objects that have not been matched to a new centroid for some number of frames (a defined "disappeared" threshold) are considered to have left the scene and are removed.

These steps are repeated for each frame of the video, allowing objects to be tracked over time. The algorithm is relatively simple to implement and computationally efficient compared to some other tracking methods.

Implementing a Centroid Tracker in Python

Now that we understand how the centroid tracking algorithm works conceptually, let‘s see how to implement it in Python. We‘ll be using the OpenCV library for video/image processing and drawing.

First, here is a Python class called CentroidTracker that encapsulates the logic of the centroid tracking algorithm:

from scipy.spatial import distance as dist

class CentroidTracker: def init(self, maxDisappeared=50): self.nextObjectID = 0 self.objects = OrderedDict() self.disappeared = OrderedDict() self.maxDisappeared = maxDisappeared

def register(self, centroid):
    self.objects[self.nextObjectID] = centroid
    self.disappeared[self.nextObjectID] = 0
    self.nextObjectID += 1

def deregister(self, objectID):
    del self.objects[objectID]
    del self.disappeared[objectID]

def update(self, rects):
    if len(rects) == 0:
        for objectID in list(self.disappeared.keys()):
            self.disappeared[objectID] += 1
            if self.disappeared[objectID] > self.maxDisappeared:
                self.deregister(objectID)
        return self.objects

    inputCentroids = np.zeros((len(rects), 2), dtype="int")
    for (i, (startX, startY, endX, endY)) in enumerate(rects):
        cX = int((startX + endX) / 2.0)
        cY = int((startY + endY) / 2.0)
        inputCentroids[i] = (cX, cY)

    if len(self.objects) == 0:
        for i in range(0, len(inputCentroids)):
            self.register(inputCentroids[i])
    else:
        objectIDs = list(self.objects.keys())
        objectCentroids = list(self.objects.values())
        D = dist.cdist(np.array(objectCentroids), inputCentroids)
        rows = D.min(axis=1).argsort()
        cols = D.argmin(axis=1)[rows] 

        usedRows = set()
        usedCols = set()

        for (row, col) in zip(rows, cols):
            if row in usedRows or col in usedCols:
                continue

            objectID = objectIDs[row]
            self.objects[objectID] = inputCentroids[col]
            self.disappeared[objectID] = 0

            usedRows.add(row)
            usedCols.add(col)

        unusedRows = set(range(0, D.shape[0])).difference(usedRows)
        unusedCols = set(range(0, D.shape[1])).difference(usedCols)

        if D.shape[0] >= D.shape[1]:
            for row in unusedRows:
                objectID = objectIDs[row]
                self.disappeared[objectID] += 1

                if self.disappeared[objectID] > self.maxDisappeared:
                    self.deregister(objectID)
        else:
            for col in unusedCols:
                self.register(inputCentroids[col])

    return self.objects    

The key methods of this class are:

  • register: Adds a new object to the tracker with the specified centroid
  • deregister: Removes an object from the tracker
  • update: Updates the tracker with a new set of bounding box rectangles for the current frame. It handles matching the input centroids to existing object centroids, registering new objects, and deregistering objects that have disappeared for too many frames.

To use this centroid tracker, you would first detect objects in each frame (using your preferred object detection method), then pass the bounding box coordinates to the update method of the CentroidTracker. It will return a dictionary mapping object IDs to their centroid coordinates.

Building a Vehicle Counter with Centroid Tracking

As an example, let‘s use our CentroidTracker to build a simple vehicle counter system in Python. We‘ll use OpenCV‘s background subtraction functionality to detect moving vehicles in a video of a highway, then apply centroid tracking to count the vehicles.

import numpy as np
import cv2
from centroidtracker import CentroidTracker

cap = cv2.VideoCapture(‘traffic.mp4‘)

bgsubMOG2 = cv2.createBackgroundSubtractorMOG2()

ct = CentroidTracker()

while True: ret, frame = cap.read() if not ret: break

# Apply background subtraction to get foreground mask
fgmask = bgsubMOG2.apply(frame)

# Find contours in foreground mask
contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Filter contours by area to remove noise and small objects
min_area = 500
filtered_contours = [c for c in contours if cv2.contourArea(c) > min_area]

# Get bounding boxes of filtered contours  
rects = [cv2.boundingRect(c) for c in filtered_contours]

# Update centroid tracker with new bounding boxes
objects = ct.update(rects)

# Draw objects on frame and display count
for (objectID, centroid) in objects.items():
    cv2.putText(frame, str(objectID), (centroid[0] - 10, centroid[1] - 10),
        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    cv2.circle(frame, (centroid[0], centroid[1]), 4, (0, 255, 0), -1)

text = f"Total Vehicles: {len(objects)}"
cv2.putText(frame, text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

cv2.imshow(‘Frame‘, frame)
if cv2.waitKey(1) == ord(‘q‘):
    break

cap.release()
cv2.destroyAllWindows()

This script does the following:

  1. Opens a video file of traffic footage
  2. Initializes a background subtractor to detect moving objects in each frame
  3. Initializes our CentroidTracker
  4. For each video frame:
    • Applies background subtraction to get foreground mask of moving objects
    • Finds contours of objects in foreground mask
    • Filters contours by area to remove small objects/noise
    • Gets bounding boxes of filtered contours
    • Updates the centroid tracker with the bounding boxes
    • Draws object IDs and centroids on the frame
    • Displays the total count of tracked vehicles
  5. Cleans up when done

So with less than 50 lines of code (not counting the CentroidTracker class), we have a working prototype of a vehicle counter system! The tracker keeps a tally of the number of unique vehicles seen throughout the video.

Of course, this is just a simple proof-of-concept. For a production-ready vehicle counter, you would want to use more robust object detection, define specific count lines/regions, handle edge cases, and optimize performance. But it illustrates the power and relative simplicity of the centroid tracking approach.

Limitations of Centroid Tracking

While centroid tracking is appealingly straightforward, it does have some significant limitations compared to more sophisticated object tracking algorithms:

  • No motion model: Centroid tracking has no concept of physics or expected object motion, meaning it can easily lose track of fast-moving or unpredictable objects. More advanced trackers often use Kalman filters or other motion models.

  • Sensitive to detection quality: The tracker is highly dependent on the accuracy of the object detector. If objects are routinely missed or have unstable bounding boxes, tracking performance will suffer.

  • No re-identification: If an object leaves the frame and re-enters, it will be assigned a new ID by the centroid tracker. Advanced trackers often incorporate appearance-based re-identification to address this.

  • Struggles with occlusion: If objects overlap or occlude one another, the centroid matching can fail. This is a hard problem for any tracker, but centroid tracking is particularly brittle to this.

For these reasons, in many real-world applications, more advanced tracking algorithms are used. In particular, deep learning-based object trackers like SORT and DeepSORT have become very popular in recent years, leveraging the power of deep neural networks for more robust tracking.

Conclusion

In this post, we took a deep dive into centroid tracking, a simple but powerful object tracking algorithm. We explored how the algorithm works conceptually, then implemented a complete centroid tracker in Python.

We then applied our centroid tracker to build a prototype of a vehicle counter system using OpenCV – with just a few dozen lines of code, we had a working system to detect, track, and count vehicles in traffic video!

While centroid tracking has limitations and may not be the best choice for the most demanding object tracking applications, it is a great place to start learning about object tracking. And for simpler use cases, it can be an easy-to-implement and efficient solution.

I hope this post has given you a solid understanding of centroid tracking and how you can leverage it in your own computer vision and video analytics projects using Python and OpenCV. Thanks for reading, and happy tracking!

How useful was this post?

Click on a star to rate it!

Average rating 1 / 5. Vote count: 1

No votes so far! Be the first to rate this post.

Similar Posts