A virtual tripwire is a line drawn across a camera’s field of view that triggers an event — a count, an alert, a log entry — whenever a tracked object crosses it. The name comes from physical security tripwires; the virtual version works the same way without any hardware.
Tripwires are one of the most common spatial configurations in deployed CV systems, and one of the most misunderstood. This article explains how they work, the different variants, and how to define them accurately.
How a tripwire works
At its core, a tripwire is a geometry check run on each frame of a video stream:
- An object detector identifies objects and produces bounding boxes (or centroids)
- A tracker assigns persistent IDs to objects across frames
- For each tracked object, check whether its centroid (or a bounding box edge) has crossed the defined line between the previous frame and the current one
- If it has, fire an event
The “crossing” check is typically implemented as a sign change in the cross-product (or dot product) between the line’s direction vector and the vector from the line to the object’s position. When the sign flips from positive to negative (or vice versa), the object has crossed.
def crossed_line(prev_pt, curr_pt, line_start, line_end):
"""
Returns True if the path from prev_pt to curr_pt crosses the line
segment from line_start to line_end.
"""
def cross_product_sign(a, b, p):
return (b[0]-a[0])*(p[1]-a[1]) - (b[1]-a[1])*(p[0]-a[0])
d1 = cross_product_sign(line_start, line_end, prev_pt)
d2 = cross_product_sign(line_start, line_end, curr_pt)
return (d1 > 0) != (d2 > 0)
Single tripwire vs dual tripwire
Single tripwire — One line, one event type. Any crossing triggers the same event regardless of direction. Use for presence detection (“someone crossed the perimeter”) or object counting where direction doesn’t matter.
Dual tripwire — Two closely spaced parallel lines. Track which line each object crosses first:
- Crosses line A then line B → moving in direction 1 (e.g. entering)
- Crosses line B then line A → moving in direction 2 (e.g. exiting)
The gap between the two lines should be large enough that no single-frame position jump skips both lines, but small enough to fit in the monitored corridor. A 10–20 pixel gap (at typical camera resolutions) works well for pedestrians.
Placing tripwires effectively
Placement matters more than precise geometry. A tripwire at a door that doesn’t span the full doorway will miss objects that walk around its ends. A tripwire in the middle of an open corridor will catch everything that passes, including objects you didn’t intend to count.
Guidelines:
- Span the full crossing path. The tripwire must extend beyond the widest expected object. For a 1-metre-wide door, draw the line at least 10–20% wider than the physical opening to account for bounding box imprecision at different distances.
- Perpendicular to the direction of travel (usually). A line at 90° to the direction of movement minimises the dwell time an object spends near the line, reducing double-counting risk.
- Avoid zones with converging trajectories. A single tripwire in a corridor with two adjacent lanes will fire for both lanes, which may not be what you want. Use separate tripwires per lane.
Drawing tripwires in RegionKit
Tripwires are drawn as Polyline annotations in RegionKit. A polyline is an open path — like a polygon but not closed — making it the natural shape for a crossing line.
- Press
Lor click the Polyline tool - Click to place each vertex of the line (usually just two for a simple straight tripwire)
- Double-click or press
Enterto commit - Label it (
entrance_in,entrance_out, etc.) and place it on a “Tripwires” layer
For dual tripwires, draw two parallel polylines labelled line_a and line_b. Export as native JSON and read both line coordinates in your tracking code.
Reading tripwire coordinates in Python
import json
import numpy as np
with open('scene.json') as f:
scene = json.load(f)
tripwires = {}
for ann in scene['annotations']:
if ann['type'] != 'polyline':
continue
pts = ann['data']['points']
coords = list(zip(pts[::2], pts[1::2]))
tripwires[ann.get('label', 'tripwire')] = coords
for name, pts in tripwires.items():
line_start = np.array(pts[0])
line_end = np.array(pts[-1])
print(f"Tripwire '{name}': {line_start} → {line_end}")
Common implementation mistakes
Not using a tracker. A simple frame-by-frame detection without object IDs will fire on every frame where an object is near the line, generating dozens of events per crossing. Always use an object tracker (ByteTrack, DeepSORT, SORT) so each physical object has a persistent ID and you can detect the moment of crossing rather than continuous proximity.
Testing the centroid only. For large objects or objects moving at an angle, the centroid may not cross the line even when the object clearly does. Testing the leading edge of the bounding box (the edge in the direction of travel) gives better accuracy.
Ignoring the line’s orientation. Make sure your crossing direction logic matches the line’s drawn direction. In RegionKit, a polyline is stored as an ordered list of points — the direction from the first vertex to the last defines the “forward” direction for your sign-change calculation.
Related: How to Define Regions of Interest for Computer Vision · Detection Zone Editor