AI & Computer Vision, lesson 39 of 52
Field Trip 2: Deploying at a Live Sports Event
Introduction
This time you're not just collecting data: your whole system runs live at a real sports event, detecting, tracking and triggering as the match happens. A live event gives no second takes, so you'll prepare like a pilot: a pre-flight checklist, a flight plan, and a black box recording that keeps everything, whatever goes wrong.
By the end of this lesson you'll be able to:
- Get and show the permissions needed before filming a live event
- Run a safety check of the rig and the venue
- Run a preflight check and act on its result
- Record raw video first, then run the live system
- Keep an event log with human notes
- Back up the data before packing, and write a trip report
Working principle
A live event is not a rehearsal
Until now, if something failed we reran the clip. A real match does not wait. This lesson is about being ready and behaving well as a guest in someone else's place. The technology is mostly done: what decides success is preparation.
Permission comes first
Before the trip, in writing:
- The venue and organiser agree to a camera on the sidelines. A school ground, a club or a tournament may all have rules.
- The school approves the trip under its usual trip policy: transport, supervision ratio, parent consent forms, first aid, emergency contacts.
- Parents or guardians of the players know that a camera will be filming, what for, and how to say no (lesson 14). Under India's data protection law, children's data needs verifiable parental consent once the relevant rules apply (lesson 14 has the dates; check them when you plan).
- Anyone who says no is left out of the footage: they can play on a side away from the camera, or the team skips them in analysis.
The lesson cannot decide these for you. Ask your school and the venue. If any answer is "no" or "not sure", you do not go.
Safety is part of the project
| Risk | Rule |
|---|---|
| Tripping on cables | Tape cables flat, keep them out of walkways, keep the rig behind a barrier or cones |
| Tripod falls | Legs locked, weighted, never on a slope without a spotter |
| Balls and players | Keep the rig out of the line of play. A ball can hit a tripod and a laptop. |
| Heat and sun | Shade for the laptop (it throttles when hot), water for people |
| Rain and wet | Cover the gear, or stop. No wet electronics near mains. |
| Power | Mains only through a properly rated, dry, indoor-or-outdoor-approved extension, and only if the venue allows it. Otherwise batteries. |
| Crowds | Don't block anyone's view. Never film spectators on purpose. |
Record raw first
The live AI is the fragile part. The recording is the safe part. Start the recording before the AI and keep it running whatever happens. Everything else can be rebuilt from the recording later; nothing can be rebuilt from a match you didn't record.
One log for the day
Write down everything with a time: kickoff, goals, health warnings, who moved the camera, when it rained. At the debrief, a number without a note is a mystery; a number with a note is a lesson.
Roles, again
| Role | Job |
|---|---|
| Leader | Talks to the organiser, makes go/no-go calls, owns safety |
| Camera crew | The rig: tripod, camera, framing. Hands off once recording. |
| Tech crew | Power, laptop, preflight, backup |
| Analyst | Calibration and the live display |
| Scribe | The event log and notes |
| Safety marshal | Watches cables, crowds and weather. Can stop the activity. |
Offline first
The detector, zones and trigger must work with no internet. Claude is a bonus (lesson 37, lesson 38). Test with the Wi-Fi switched off, in the car park, the day before.
Plan B
Write it down: if it rains, if the battery dies, if the organiser says no at the gate, if a student is ill, if the camera is knocked. For each, a one-line answer. A plan written beforehand is calm; the same plan invented on the day is panic.
What you'll need
- Your complete system from Units 5 to 8, tested in lesson 36
- Permission from the event organisers and consent from everyone in frame
preflight.pyand the run sheet (below)- Charged laptops, a camera stand, spare power
Build it
This is a field trip lesson: most of the hour is outdoors. The code is two small tools, for the night before and the day itself.
Tool 1: preflight.py (go / no go)
Run it at home the night before and again at the venue before kickoff. A FAIL means do not start. A WARN means go, but know what will be off.
"""Run this at home the night before, and again at the venue before kickoff."""
import importlib
import os
import shutil
import socket
import sys
from dataclasses import dataclass
@dataclass
class Result:
name: str
status: str # "PASS", "WARN" (go, but know it) or "FAIL" (do not start)
detail: str
def check_python(minimum=(3, 9)):
ok = sys.version_info >= minimum
return Result("python version", "PASS" if ok else "FAIL", sys.version.split()[0])
def check_packages(names=("cv2", "numpy"), optional=("ultralytics", "anthropic", "serial")):
out = []
for n in names:
try:
importlib.import_module(n)
out.append(Result(f"package {n}", "PASS", "installed"))
except ImportError:
out.append(Result(f"package {n}", "FAIL", "pip install it before you leave"))
for n in optional:
try:
importlib.import_module(n)
out.append(Result(f"package {n}", "PASS", "installed"))
except ImportError:
out.append(Result(f"package {n}", "WARN", "missing: that feature will be off"))
return out
def check_files(paths):
return [Result(f"file {p}", "PASS" if os.path.isfile(p) else "FAIL", "found" if os.path.isfile(p) else "missing") for p in paths]
def check_disk(folder=".", need_gb=5.0):
free = shutil.disk_usage(folder).free / 1e9
return Result("disk space", "PASS" if free >= need_gb else "FAIL", f"{free:.1f} GB free, need {need_gb}")
def check_camera(open_camera):
"""open_camera: a function returning (ok, width, height). Injected, so we can test without a camera."""
try:
ok, w, h = open_camera()
except Exception as e:
return Result("camera", "FAIL", f"{type(e).__name__}: {e}")
return Result("camera", "PASS" if ok else "FAIL", f"{w}x{h}" if ok else "could not read a frame")
def check_api_key():
has = bool(os.environ.get("ANTHROPIC_API_KEY"))
return Result("Claude API key", "PASS" if has else "WARN", "set" if has else "not set: commentary will be off")
def check_internet(host="api.anthropic.com", port=443, timeout=2.0, connect=socket.create_connection):
try:
connect((host, port), timeout=timeout).close()
return Result("internet", "PASS", f"reached {host}")
except OSError:
return Result("internet", "WARN", "no connection: Claude features will be off (the detector works offline)")
def run(results):
flat = []
for r in results:
flat.extend(r if isinstance(r, list) else [r])
for r in flat:
print(f"{r.status:<5} {r.name:<28} {r.detail}")
fails = [r for r in flat if r.status == "FAIL"]
warns = [r for r in flat if r.status == "WARN"]
print(f"\n{len(flat) - len(fails) - len(warns)} pass, {len(warns)} warnings, {len(fails)} failures")
print("GO" if not fails else "NO GO: fix the failures first")
return not fails
def real_camera(index=0):
import cv2
cap = cv2.VideoCapture(index)
ok, frame = cap.read()
cap.release()
return (ok, frame.shape[1], frame.shape[0]) if ok else (False, 0, 0)
if __name__ == "__main__":
go = run([
check_python(),
check_packages(),
check_files(["best.pt", "calibration.json", "zones.json"]),
check_disk(".", need_gb=5.0),
check_camera(real_camera),
check_api_key(),
check_internet(),
])
sys.exit(0 if go else 1)Each check is a function, and the camera check takes a function as input so it can be tested without a camera. Example output from our test with one file missing:
PASS python version 3.13.16
PASS file best.pt found
FAIL file zones.json missing
PASS camera 1280x720
WARN internet no connection: Claude features will be off (the detector works offline)
3 pass, 1 warnings, 1 failures
NO GO: fix the failures firstTool 2: event_log.py (one CSV for the day)
Goals, warnings, Claude's lines and human notes all go in one file. Every line is flushed to the disk at once, so a crash or a flat battery loses nothing.
"""One CSV for the whole day. Every line is flushed at once, so a crash or a flat battery loses nothing."""
import csv
import os
import time
class EventLog:
def __init__(self, path="event_log.csv", clock=time.time):
self.path, self.clock = path, clock
new = not os.path.exists(path) or os.path.getsize(path) == 0
self.f = open(path, "a", newline="")
self.w = csv.writer(self.f)
self.t0 = clock()
if new:
self.w.writerow(["wall_time", "match_seconds", "kind", "text"])
self.f.flush()
def add(self, kind, text="", match_seconds=None):
now = self.clock()
if match_seconds is None:
match_seconds = now - self.t0
self.w.writerow([time.strftime("%H:%M:%S", time.localtime(now)), round(match_seconds, 1), kind, text])
self.f.flush() # to the operating system now, not "when the program ends"
os.fsync(self.f.fileno()) # and onto the disk
def note(self, text): # a human typed it: "kickoff", "camera knocked", "rain started"
self.add("note", text)
def close(self):
self.f.close()During the match one student is the scribe: they call log.note("kickoff"), log.note("camera knocked, recalibrating"), log.note("rain started"). In the debrief (lesson 40), those notes explain every odd number.
Tool 3: the health check from lesson 36
Reuse health.py from lesson 36 every few seconds, and write a line to the log when a problem starts and when it ends.
"""A cheap self-check: is the picture good enough to trust? Run it every second or so, show a warning light."""
import cv2
import numpy as np
def picture_health(frame, dark_below=70, bright_above=200, blurry_below=20.0, noisy_above=3000.0):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
brightness = float(gray.mean())
sharpness = float(cv2.Laplacian(gray, cv2.CV_64F).var()) # low = blurry
problems = []
if brightness < dark_below:
problems.append("too dark")
if brightness > bright_above:
problems.append("too bright")
if sharpness < blurry_below:
problems.append("blurry")
if sharpness > noisy_above: # noise makes edges everywhere, so this number shoots UP
problems.append("noisy")
return {"brightness": round(brightness, 1), "sharpness": round(sharpness, 1), "problems": problems}
class BallWatchdog:
"""Warn if the ball has not been seen for `limit` frames in a row (covered, lost, or camera problem)."""
def __init__(self, limit=90):
self.limit, self.missed = limit, 0
def update(self, ball):
self.missed = 0 if ball else self.missed + 1
return self.missed >= self.limitTool 4: tests (no camera, no network)
import csv, os, tempfile
import numpy as np
from event_log import EventLog
from health import picture_health
from preflight import *
bad = 0
def check(name, ok, extra=""):
global bad; bad += not ok; print("PASS" if ok else "FAIL", name, extra)
d = tempfile.mkdtemp()
open(os.path.join(d, "best.pt"), "w").write("x")
files = check_files([os.path.join(d, "best.pt"), os.path.join(d, "zones.json")])
check("file found / missing", [r.status for r in files] == ["PASS", "FAIL"])
check("disk: impossible demand fails", check_disk(d, need_gb=10**9).status == "FAIL")
check("disk: tiny demand passes", check_disk(d, need_gb=0.0001).status == "PASS")
check("camera ok", check_camera(lambda: (True, 1920, 1080)).status == "PASS")
check("camera not ok", check_camera(lambda: (False, 0, 0)).status == "FAIL")
def boom(): raise RuntimeError("busy")
check("camera exception is a FAIL, not a crash", check_camera(boom).status == "FAIL")
def no_net(*a, **k): raise OSError("unreachable")
check("no internet is a WARN", check_internet(connect=no_net).status == "WARN")
class S:
def close(self): pass
check("internet ok", check_internet(connect=lambda *a, **k: S()).status == "PASS")
os.environ.pop("ANTHROPIC_API_KEY", None)
check("no key is a WARN", check_api_key().status == "WARN")
check("python ok", check_python().status == "PASS")
check("missing optional package is only a WARN", all(r.status != "FAIL" for r in check_packages(names=(), optional=("nonexistent_pkg_xyz",))))
check("missing required package is a FAIL", check_packages(names=("nonexistent_pkg_xyz",), optional=())[0].status == "FAIL")
print("--- sample report")
go = run([check_python(), files, check_camera(lambda: (True, 1280, 720)), check_internet(connect=no_net)])
check("run() says NO GO when a file is missing", go is False)
print("---")
# event log: crash safe
p = os.path.join(d, "log.csv"); t = [1000.0]
log = EventLog(p, clock=lambda: t[0])
t[0] = 1003.5; log.add("goal", "north goal", match_seconds=5.7)
t[0] = 1010.0; log.note("camera knocked, recalibrating")
rows = list(csv.reader(open(p))) # read while it is still open: nothing waits in a buffer
check("rows visible before close (crash safe)", len(rows) == 3 and rows[1][2] == "goal" and rows[2][3].startswith("camera"), rows)
log.close()
log2 = EventLog(p); log2.add("start", "second run"); log2.close()
rows = list(csv.reader(open(p)))
check("second run appends, header only once", len(rows) == 4 and sum(r[0] == "wall_time" for r in rows) == 1, len(rows))
frame = np.full((360, 640, 3), 30, np.uint8)
check("health check from lesson 36 reused", "too dark" in picture_health(frame)["problems"])
print("failures:", bad)All should print PASS. They prove the tools themselves behave on the day, including when the camera or the internet is missing.
The run sheet
| Time | What | Who |
|---|---|---|
| T minus 60 | Arrive. Meet the organiser. Show the permission. Walk the venue. | Teacher, leader |
| T minus 50 | Choose the camera spot (lesson 28 notes). Tripod down, legs locked, cables taped, nothing in a walkway. | Camera crew |
| T minus 40 | Power: battery full, power bank charged, spare. Laptop on mains if the venue allows it. | Tech crew |
| T minus 35 | Record raw video first. Start recording to disk before any AI runs. | Camera crew |
| T minus 30 | Calibrate (lesson 33): click the corners on a real frame. Save calibration.json with today's date. | Analyst |
| T minus 20 | Run preflight.py. Fix FAILs. Note WARNs in the log. | Tech crew |
| T minus 10 | Dry run: walk across the pitch in front of the camera, check detection and zones. | Everyone |
| Kickoff | Start the live system and the event log. Say "kickoff" in the log. | Scribe |
| During | Watch the health light. Log everything unusual. Do not touch the camera. | All |
| Final whistle | Stop. Save. Back up the video and the log before packing. | Tech crew |
| T plus 20 | Pack. Count equipment. Thank the organiser. | Everyone |
Fallback rule
Raw video is recorded first and always. If the live AI fails, if the laptop overheats, if Claude is unreachable, the match is still on disk. The live system is a bonus. The recording is the project.
Questions: which preflight WARN would you accept? Which FAIL surprised you? What is your plan if it rains?
If it goes wrong
| Error | Cause | Fix |
|---|---|---|
| Preflight says FAIL on camera | Another program is using it, or a wrong index | Close other apps; try index 1 |
| Laptop slows down in the sun | Heat throttling | Shade; lower the frame size; use lesson 25 speed-ups |
| Detections look wrong all of a sudden | The camera was knocked, or the light changed | Note it in the log; recalibrate; check the health light |
| Battery dies mid-match | No power plan | Power bank, mains if allowed, and the raw video is already safe |
| Disk full | Large video files | Check free space in preflight; keep 5 GB spare |
| Organiser says no at the gate | Permission wasn't in writing or wasn't shared | Leave. Run Plan B (a school-ground match, lesson 28 style). |
| Spectators ask what you are doing | Normal curiosity | Leader explains; don't point the camera at the crowd |
| Log is empty | Scribe forgot | Add notes from memory straight after, and mark them as late |
Try it
- Add a second camera angle for the calibration
- Add a live scoreboard to the dashboard (lesson 34)
- Write the preflight check for a new item (battery level)
Check what you learnt
What do you do first, before running any AI?
preflight.py shows a FAIL. What now?
Who can stop the activity?
Why keep a log of human notes during the day?
A parent says their child may not be filmed. What happens?
Conclusion
A live event is not a rehearsal. Get permission first, put safety first, record raw video before anything else, run a pre-flight check and don't start on a FAIL, keep one log of the day with human notes, and stick to your roles.
What next?
Next: Live data debrief & analysis. You'll debrief the day: what your system claimed versus what really happened.

