Skip to main content
Back to all posts
Oct. 20268 min read

Last Seen: I taught an eleven-year-old drone to find a person from one photo, and to ask before it follows anyone

Last Seen gives a 2015 DJI Phantom 3 one photo and a search area. OpenCV 5 finds and compares every person, Claude decides where to look next, and a human answers "Is this them?" before the drone follows anyone. Flown live in a park: found on the first scan at 0.85, followed for 90 seconds, 49 pins on the coordinator's map.

OpenCV AI CompetitionOpenCV 5ClaudeDronesDJI Mobile SDKComputer VisionAWS LambdaSearch and RescueHackathon
I wrote this post for the OpenCV AI Competition 2026 (October 2026), and built Last Seen for the same submission. The source, the flight logs and every mission's trace are on GitHub at github.com/JonathanSolvesProblems/last-seen; the simulator runs with no aircraft and no key. The coordinator's page is deployed to AWS from the repository for a mission, so there is no standing live button here.
The claim in one sentence: flown live on 25 October 2026, the drone found me on its first scan at a 0.85 match, a human confirmed it, and it followed for 90 seconds with 49 pins on the coordinator's map, every number read from the run files in the repository by a script that fails the build when the write-up and the data disagree. Those are my own flights with me as the subject, not graded by anyone else; pin error against a phone's GPS was planned and not measured, and a photo taken at eye level scored the same person only 0.24 from the air.
Title card: a circular headshot above the Last Seen wordmark, with the line "A photo in, a person found, and nothing happens until a human says that's them." and jonathansolvesproblems.com.
Last Seen: an 11-year-old drone searches a park for a person from one photo, and a human confirms before it follows anyone.

One photo and an old drone

Picture a search coordinator at a park at dusk. A man with dementia walked away from his care home an hour ago. She has one photo from this morning and one volunteer with an old consumer drone. She is a composite; the job is real. The aircraft is real too: a DJI Phantom 3 Standard from 2015, with no obstacle sensing and a 720p video link, driven by a Kotlin bridge app on the phone that carries video out and stick commands in. The person the drone finds in the video is me, with consent.

OpenCV sees, every frame

Three loops are kept apart on purpose. The first is OpenCV 5 on every frame. cv2.dnn, the new graph engine, runs YOLO11n for people at about 41 to 50 ms a frame on a laptop CPU. Each person is then compared with the photo two ways: the OpenCV Model Zoo person re-identification network and HSV clothing histograms by body band, fused into one score. A camera model turns the feet of each box into a point on the ground, which is what lands on the coordinator's map.

Aerial view of a baseball infield: a man in a black leather jacket walking on red clay inside an orange box labelled person 0.79, with an overlay reading OpenCV 5 DNN, YOLO person detector, 41 ms per frame, 24 fps, 1 person.
OpenCV 5's DNN engine runs a YOLO person detector on the drone's own video: 41 ms a frame, 24 fps, on a laptop CPU.
Aerial view of a man walking away on clay, boxed with a 0.39 score, and a panel comparing the drone's crop with the reference photo: re-ID network 0.48, clothing colour 0.19, match 0.39.
Each person is compared with the photo two ways: the OpenCV model zoo re-ID network and the colours of their clothes.

Claude decides where to look, never who someone is

The second loop is Claude, every few seconds. It has six strict tools: scan_here, fly_to, look_closer, ask_coordinator, follow_confirmed and end_mission. It sees the candidate table and the crops and chooses what to do next. While it thinks, the runtime keeps the aircraft holding and perceiving, and every decision is written to trace.jsonl. The third loop is a person, once. ask_coordinator puts "Is this them?" on the coordinator's phone with the photo beside what the drone sees. The page is a Lambda function URL, and the answer is a DynamoDB conditional write, so a second answer to the same question is refused.

The coordinator's page: "Is this them?" with the drone's crop beside the reference photo, Strong match 0.85, buttons That's them and Not them, the drone's description of the person, and a map with rings 10 m apart around the point last seen.
Run 3 found me on its first scan at 0.85 and asked. The page is served by AWS Lambda; DynamoDB keeps the answer.

Three missions in a park

On 25 October 2026 I flew three missions in a public park, playing the missing person. Run 1 used a photo taken at eye level. From the air I never scored above 0.24, so it looked closer twice, searched two corners, never asked anyone, and I called it back. That is the right behaviour for a weak match, and it is the hard case. Run 2 used an aerial photo: it looked closer at 0.76, asked, was confirmed, and followed for 90 seconds with 49 pins. In its own summary, the agent wrote that the lock held only 47% of the time and that it lost me seven times. Run 3 found me on the first scan at 0.85, asked, was confirmed, and followed until the battery reached its 18% floor after 64 seconds, when the envelope aborted and the aircraft landed itself.

Aerial view of the edge of a clay infield and grass, with a tripod and a man in a black jacket boxed with a low 0.15 score.
Run 1, with a photo taken from the ground: I never scored above 0.24, so it never asked anyone. That is the hard case.
Aerial view of a park in autumn with a man in a black jacket at the edge of the infield, and an inset labelled "the map on her phone, live" showing the drone's white track looping around pins labelled P1, P2, P4 and P5 inside rings 10 m apart.
After a human says that's them, it follows and drops a pin every second. Run 2: 90 s, 49 pins, lock held 47%.

The camera's own check

I printed ArUco markers at 190 mm and checked them with a ruler. The full calibration was not flown, but the markers were caught in the onboard video, so the camera check ran on the footage afterwards. OpenCV's solvePnP on a single marker puts the camera 5.9 m above it where the barometer said 8.0 m. The gap is a steady 2 m, which points at the spec-sheet lens model rather than noise, and the report says so.

Slide titled The camera's own check, explaining that solvePnP on the flight footage puts the camera 5.9 m above the 190 mm ArUco markers where the barometer said 8.0 m, beside an aerial photo of two printed markers pinned to the clay.
solvePnP on the printed markers in the flight footage puts the camera 5.9 m up where the barometer said 8.0 m.

Safety that a prompt cannot promise

follow_confirmed raises NotConfirmed for anyone the coordinator did not confirm. That is code, not a prompt, and it is tested. The envelope (geofence, ceiling, speed, battery floor and a stale-link abort) runs on every control tick in pilot.py, and the agent cannot widen it. The coordinator can recall the aircraft from the page, and the pilot's mode switch overrides everything: flip it back and the sticks are mine. Safety never depends on the cloud round trip.

Slide titled Safety that a prompt cannot promise, showing the follow() check in runtime.py that raises NotConfirmed for anyone not confirmed, and the per-tick battery and altitude checks in pilot.py that raise Abort.
Safety lives in code, not the prompt: no follow without a confirmed person, and the envelope checks every control tick.

What is not claimed

Every number in this post comes from my own three flights, with me as the only subject, so none of it is graded by anyone else; what the repository does guarantee is that the write-up matches the run files, because a script fails the build when they disagree. Pin error against the subject's phone GPS and the gimbal-angle check against the printed markers were planned and not measured. Identification is clothing and build, not a face: a photo in different clothes will not match, two people dressed alike are both put to the coordinator, and a photo taken at eye level does not match the same person seen from above, which is the next problem. One person was sometimes split into two tracks, and the agent reported it rather than hiding it. Autonomous takeoff failed on every attempt on 30 September and worked on 25 October. There is no obstacle sensing: the aircraft is kept over open ground by the geofence and the ceiling. The Phantom is registered with Transport Canada and was flown under a Basic pilot certificate, in visual line of sight, away from bystanders.

Related project

Last Seen: an eleven-year-old drone finds a person from one photo with OpenCV 5, asks a human "Is this them?", and only then follows

View the project