AI & Computer Vision, lesson 39 of 52

Field Trip 2: Deploying at a Live Sports Event

advanced24 min read

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

RiskRule
Tripping on cablesTape cables flat, keep them out of walkways, keep the rig behind a barrier or cones
Tripod fallsLegs locked, weighted, never on a slope without a spotter
Balls and playersKeep the rig out of the line of play. A ball can hit a tripod and a laptop.
Heat and sunShade for the laptop (it throttles when hot), water for people
Rain and wetCover the gear, or stop. No wet electronics near mains.
PowerMains only through a properly rated, dry, indoor-or-outdoor-approved extension, and only if the venue allows it. Otherwise batteries.
CrowdsDon'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

RoleJob
LeaderTalks to the organiser, makes go/no-go calls, owns safety
Camera crewThe rig: tripod, camera, framing. Hands off once recording.
Tech crewPower, laptop, preflight, backup
AnalystCalibration and the live display
ScribeThe event log and notes
Safety marshalWatches 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.py and 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 first

Tool 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.limit

Tool 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

TimeWhatWho
T minus 60Arrive. Meet the organiser. Show the permission. Walk the venue.Teacher, leader
T minus 50Choose the camera spot (lesson 28 notes). Tripod down, legs locked, cables taped, nothing in a walkway.Camera crew
T minus 40Power: battery full, power bank charged, spare. Laptop on mains if the venue allows it.Tech crew
T minus 35Record raw video first. Start recording to disk before any AI runs.Camera crew
T minus 30Calibrate (lesson 33): click the corners on a real frame. Save calibration.json with today's date.Analyst
T minus 20Run preflight.py. Fix FAILs. Note WARNs in the log.Tech crew
T minus 10Dry run: walk across the pitch in front of the camera, check detection and zones.Everyone
KickoffStart the live system and the event log. Say "kickoff" in the log.Scribe
DuringWatch the health light. Log everything unusual. Do not touch the camera.All
Final whistleStop. Save. Back up the video and the log before packing.Tech crew
T plus 20Pack. 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

ErrorCauseFix
Preflight says FAIL on cameraAnother program is using it, or a wrong indexClose other apps; try index 1
Laptop slows down in the sunHeat throttlingShade; lower the frame size; use lesson 25 speed-ups
Detections look wrong all of a suddenThe camera was knocked, or the light changedNote it in the log; recalibrate; check the health light
Battery dies mid-matchNo power planPower bank, mains if allowed, and the raw video is already safe
Disk fullLarge video filesCheck free space in preflight; keep 5 GB spare
Organiser says no at the gatePermission wasn't in writing or wasn't sharedLeave. Run Plan B (a school-ground match, lesson 28 style).
Spectators ask what you are doingNormal curiosityLeader explains; don't point the camera at the crowd
Log is emptyScribe forgotAdd 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

  1. What do you do first, before running any AI?

  2. preflight.py shows a FAIL. What now?

  3. Who can stop the activity?

  4. Why keep a log of human notes during the day?

  5. 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.