Object tracking sample application
This recipe detects and tracks vehicles in a video and writes an annotated copy to
output.avi. It uses the external
videoflow-contrib package for the
TensorFlow detector and the SORT tracker. See the full script in
examples/object_tracking.py.
import numpy as np
import videoflow
from videoflow.core import Flow
from videoflow.core.constants import BATCH
from videoflow.consumers import VideofileWriter
from videoflow.producers import VideofileReader
from videoflow.processors.vision.annotators import TrackerAnnotator
from videoflow.utils.downloader import get_file
URL_VIDEO = "https://github.com/videoflow/videoflow/releases/download/examples/intersection.mp4"
class BoundingBoxesFilter(videoflow.core.node.ProcessorNode):
def __init__(self, class_indexes_to_keep, **kwargs):
self._class_indexes_to_keep = class_indexes_to_keep
super().__init__(**kwargs)
def process(self, dets):
f = np.array([dets[:, 4] == a for a in self._class_indexes_to_keep])
f = np.any(f, axis=0)
return dets[f]
class FrameIndexSplitter(videoflow.core.node.ProcessorNode):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def process(self, data):
index, frame = data
return frame
def build_flow():
from videoflow_contrib.detector_tf import TensorflowObjectDetector
from videoflow_contrib.tracker_sort import KalmanFilterBoundingBoxTracker
input_file = get_file("intersection.mp4", URL_VIDEO)
reader = VideofileReader(input_file, name='reader')
frame = FrameIndexSplitter(name='frame')(reader)
detector = TensorflowObjectDetector(name='detector')(frame)
# keep only vehicle classes
filtered = BoundingBoxesFilter([1, 2, 3, 4, 6, 8, 10, 13], name='filter')(detector)
tracker = KalmanFilterBoundingBoxTracker(name='tracker')(filtered)
annotator = TrackerAnnotator(name='annotator')(frame, tracker)
writer = VideofileWriter("output.avi", fps=30, name='writer')(annotator)
return Flow([writer], flow_type=BATCH)
if __name__ == "__main__":
from videoflow.engines.local import LocalProcessEngine
flow = build_flow()
flow.run(LocalProcessEngine())
flow.join()
Walking through the graph:
readerreads the video file frame by frame (as(index, frame)tuples), andframestrips the index off. Note the reader fans out: both the detector branch and the final annotator consumeframe.detectorruns a model and emits bounding boxes;filter(a custom node) keeps only vehicle classes;trackerassigns a stable id to each box across frames.annotatoris a join: it receives both the originalframeand thetrackerboxes, and draws the boxes onto the frame.writerencodes the annotated frames into a new video.
Two things to note for a real deployment:
The whole flow uses
BATCHmode. A video file must never run inREALTIMEmode — the reader emits frames far faster than the detector can process them, and realtime’s drop policy would discard most of the video. See Batch versus realtime mode.The
trackeris stateful (it correlates boxes across frames), so it subclassesOneTaskProcessorNodeand always runs as a single worker. Thedetector, being stateless, can be scaled withnb_tasksto keep up with the reader.
To run this on Kubernetes instead, build an image with your detector/tracker
dependencies (FROM videoflow-base) and deploy the same factory, pointing every
node at it:
videoflow deploy examples/object_tracking.py:build_flow \
--nats nats://nats.videoflow.svc:4222 --namespace videoflow \
--image ghcr.io/acme/tracking:v1