I had to make a couple of edits to get it to run, but it works decently, albeit in need of some additional tuning. It can track the target at low camera rotation speeds, but will lose it at higher speeds. Pretty impressive for AI.
For future reference of anyone looking at this thread, this is the edited code that runs:
# Pulsing IR beacon tracker for the OpenMV N6 (PAG7936).
#
# Locks onto a beacon flashing at 16 Hz (5 ms on / 57.5 ms off) seen
# through an IR bandpass filter, and rejects every other IR source:
# constant sources never blink, and sources blinking at other rates
# don't land on the 62.5 ms period grid.
import csi
import time
from ulab import numpy as np
BEACON_HZ = 16
PERIOD_US = 1000000 // BEACON_HZ # 62500 us between rising edges
TOL_US = 4000 # how close an interval must be to the grid (~ +/-1 Hz)
MIN_OFF_US = 15000 # dark gap that separates two pulses (off time is 57.5 ms)
TRACK_TIMEOUT_US = 5 * PERIOD_US
MAX_EDGES = 8 # rising edges kept per track (~0.5 s of history)
MIN_EDGES = 4 # edges needed before a track can qualify
LOCK_SCORE = 0.75 # fraction of intervals that must land on the grid
GATE_PX = 40 # association radius; raise if the gimbal slews fast
EXPOSURE_US = 800 # must stay under the 2.1 ms frame time at 470 FPS
GAIN_DB = 24 # tune for beacon brightness at your range
THRESHOLD = (200, 255)
DRAW = True # IDE overlay; set False for maximum frame rate
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
csi0.framerate(470)
csi0.snapshot(time=2000) # Wait for settings to take effect.
# Auto gain/exposure hunt and change the blob brightness between frames,
# which breaks thresholding - fix both.
csi0.auto_gain(False, gain_db=GAIN_DB)
csi0.auto_exposure(False, exposure_us=EXPOSURE_US)
csi0.snapshot(time=500)
class Track:
def __init__(self, x, y, t):
self.x = x
self.y = y
self.last_seen = t
self.edges = [t]
self.score = 0.0
def update(self, x, y, t):
self.x = x
self.y = y
if time.ticks_diff(t, self.last_seen) > MIN_OFF_US:
self.edges.append(t) # was dark long enough -> new pulse
if len(self.edges) > MAX_EDGES:
self.edges.pop(0)
self.score = self._score()
self.last_seen = t
def _score(self):
if len(self.edges) < MIN_EDGES:
return 0.0
ints = np.array([time.ticks_diff(b, a)
for a, b in zip(self.edges, self.edges[1:])])
# Snap each interval to the nearest multiple of the period. Using
# the grid instead of requiring exactly one period keeps the lock
# when a pulse is occasionally missed (dropped frame, occlusion).
n = np.floor(ints / PERIOD_US + 0.5)
r = ints - n * PERIOD_US
on_grid = np.sum(r * r < TOL_US * TOL_US) / len(ints)
# But a subharmonic (e.g. an 8 Hz flasher) also lands on the grid,
# so most intervals must be exactly one period.
r1 = ints - PERIOD_US
if np.sum(r1 * r1 < TOL_US * TOL_US) < len(ints) / 2:
return 0.0
return on_grid
tracks = []
clock = time.clock()
last_print = time.ticks_ms()
while True:
clock.tick()
img = csi0.snapshot()
t = time.ticks_us()
blobs = img.find_blobs([THRESHOLD], pixels_threshold=2,
area_threshold=4, merge=True)
for blob in blobs:
bx, by = blob.cx, blob.cy
best, best_d = None, GATE_PX * GATE_PX
for tr in tracks:
d = (tr.x - bx) ** 2 + (tr.y - by) ** 2
if d < best_d:
best, best_d = tr, d
if best:
best.update(bx, by, t)
else:
tracks.append(Track(bx, by, t))
tracks = [tr for tr in tracks
if time.ticks_diff(t, tr.last_seen) < TRACK_TIMEOUT_US]
beacon = None
for tr in tracks:
if tr.score >= LOCK_SCORE and (beacon is None or tr.score > beacon.score):
beacon = tr
if beacon:
# Normalized -1..1 error from the image center - feed these to
# your pan/tilt PID loops (see scripts/libraries/pid.py).
w = img.width()
h = img.height()
x_err = (beacon.x - w / 2) / (w / 2)
y_err = (beacon.y - h / 2) / (h / 2)
if DRAW:
img.draw_cross((int(beacon.x), int(beacon.y)), size=10, color=255)
if time.ticks_diff(time.ticks_ms(), last_print) > 250:
last_print = time.ticks_ms()
if beacon:
print("beacon (%d, %d) score %.2f fps %.1f"
% (beacon.x, beacon.y, beacon.score, clock.fps()))
else:
print("searching... %d tracks, fps %.1f" % (len(tracks), clock.fps()))
I get a framerate of 400fps in QVGA and 100FPS in VGA.