Plate ID OCR is a lightweight OpenFilter-based filter that overlays OCR-processed license plate text and cropped plate images onto video frames.
It integrates seamlessly with:
- 🖍️ Overlays OCR license plate text on the main frame
- 🖼️ Displays cropped license plate image as a top-left inset
- 🧠 Filters OCR output using a regex pattern for valid plates (e.g.,
ABC1234) - 🔄 Processes all received topics and returns
mainfirst - 🔀 Optional pass-through of upstream non-image frames via
forward_upstream_data - 🧩 Designed to run after detection, cropping, and OCR filters in OpenFilter pipelines
- ⚙️ Fully configurable via CLI, code, or environment variables
Install the latest version from PyPI:
pip install filter-license-annotation-demoOr install from source:
# Clone the repo
git clone https://github.com/PlainsightAI/filter-license-annotation-demo.git
cd filter-license-annotation-demo
# (Optional but recommended) create a virtual environemnt:
python -m venv venv && source venv/bin/activate
# Install the filter
make installRun a full license plate pipeline using the CLI:
openfilter run \
- VideoIn \
--sources 'file://example_video.mp4!loop' \
- filter_license_plate_detection.filter.FilterLicensePlateDetection \
- filter_crop.filter.FilterCrop \
--detection_key license_plate_detection \
--detection_class_field label \
--detection_roi_field box \
--output_prefix cropped_ \
--mutate_original_frames false \
--topic_mode main_only \
- filter_optical_character_recognition.filter.FilterOpticalCharacterRecognition \
--topic_pattern 'license_plate' \
--ocr_engine easyocr \
--forward_ocr_texts true \
- filter_license_annotation_demo.filter.FilterLicenseAnnotationDemo \
--cropped_topic_suffix license_plate \
- WebvisOr:
make runThen open http://localhost:8000 to view annotated results.
Run the full pipeline in one process (with safe port spacing):
WEBVIS_PORT=8002 \
VIDEO_INPUT=./example_video.mp4 \
FILTER_CROPPED_TOPIC_SUFFIX=license_plate \
FILTER_FORWARD_UPSTREAM_DATA=true \
python scripts/filter_usage.pyOpen http://localhost:8002.
After installing with:
pip install filter-license-annotation-demoYou can run the filter directly in code:
from filter_license_annotation_demo.filter import FilterLicenseAnnotationDemo
if __name__ == "__main__":
FilterLicenseAnnotationDemo.run()from openfilter.filter_runtime.filter import Filter
from filter_license_plate_detection.filter import FilterLicensePlateDetection
from filter_crop.filter import FilterCrop
from filter_optical_character_recognition.filter import FilterOpticalCharacterRecognition
from filter_license_annotation_demo.filter import FilterLicenseAnnotationDemo
from openfilter.filter_runtime.filters.video_in import VideoIn
from openfilter.filter_runtime.filters.webvis import Webvis
if __name__ == '__main__':
Filter.run_multi([
(VideoIn, dict(
sources='file://example_video.mp4!loop',
outputs='tcp://*:5550',
)),
(FilterLicensePlateDetection, dict(
sources='tcp://localhost:5550',
outputs='tcp://*:5552',
)),
(FilterCrop, dict(
sources='tcp://localhost:5552',
outputs='tcp://*:5554',
detection_key='license_plate_detection',
detection_class_field='label',
detection_roi_field='box',
output_prefix='cropped_',
mutate_original_frames=False,
topic_mode='main_only',
)),
(FilterOpticalCharacterRecognition, dict(
sources='tcp://localhost:5554',
outputs='tcp://*:5556',
topic_pattern='license_plate',
ocr_engine='easyocr',
forward_ocr_texts=True,
)),
(FilterLicenseAnnotationDemo, dict(
sources='tcp://localhost:5556',
outputs='tcp://*:5558',
cropped_topic_suffix='license_plate',
)),
(Webvis, dict(
sources='tcp://localhost:5558',
)),
])| Field | Type | Description | Example |
|---|---|---|---|
cropped_topic_suffix |
str |
Topic with cropped plate images | "license_plate" |
font_scale |
float |
Font size multiplier for overlay text | 1.0 |
font_thickness |
int |
Thickness of text stroke | 2 |
inset_size |
tuple |
Width and height of the inset image | (200, 60) |
inset_margin |
tuple |
Offset from top-left corner | (10, 10) |
debug |
bool |
Enable verbose debug logging | true |
forward_upstream_data |
bool |
Forward non-image frames from upstream | true |
Environment variables (examples):
export FILTER_CROPPED_TOPIC_SUFFIX=license_plate
export FILTER_FORWARD_UPSTREAM_DATA=true
export FILTER_FONT_SCALE=1.2
export FILTER_INSET_MARGIN=20x10All fields are also supported as environment variables using the FILTER_ prefix (e.g., FILTER_FONT_SCALE=1.2).
Run all tests:
make testRun individual test files:
pytest -v tests/test_filter_license_annotation_demo.py| Filter | Role |
|---|---|
filter-license-plate-detection |
Detects license plates in frames |
filter-crop |
Crops detected license plates |
filter-optical-character-recognition |
Applies OCR to cropped license plate images |
**This Filter** |
Overlays cropped plate image and OCR text on main frame |
OCR text is filtered using a regex (^[A-Z]{3}[0-9]{4}$). If no valid OCR is detected in the current frame, the last valid plate is reused for continuity.
We welcome contributions! See our CONTRIBUTING.md.
Tips:
- Format with
black - Lint with
ruff - Add type hints and docstrings
- Write tests for all features
- Sign commits using DCO (
git commit -s)
Licensed under the Apache 2.0 License.
Thank you for using the Plate ID OCR Filter!
Questions or feedback? Open an issue.