AI & Computer Vision, lesson 28 of 52
Field Trip 1: Capturing Football Data on a Real Pitch
Introduction
Today the classroom becomes a football field. With your Field Collection Brief from lesson 15, your team sets up a laptop and webcam beside a real pitch and collects a dataset of players, the ball, goals and the empty pitch. The skill isn't just pressing record: it's noticing what's going wrong and fixing it while you're still there.
By the end of this trip you'll be able to:
- Carry out a data collection plan they wrote themselves, in real conditions
- Work in the roles from the field brief (Operator, Class Manager, Spotter, Logger and Reviewer)
- Change camera positions to create several sessions per class
- Use
field_check.pyto find what is missing while they are still at the field - Follow the consent and privacy rules they wrote (lesson 14)
- Adapt when something goes wrong, and record what happened
- Back up the data and write a short trip report
Working principle
The plan is the plan, until it isn't
The approved brief says what to do. At the field the sun, the wind and the players will change things. The skill today is to notice (the on-screen blur number, the accept rate, the log) and adjust (a different angle, a lower threshold, a break in the shade) while keeping the rules you wrote.
Sessions make the dataset useful
Each time you change the camera position, press N to start a new session. In lesson 13 we will keep whole sessions together in one split, so we need several sessions per class. A target of 80 ball frames from a single position is much less useful than 80 from four positions.
Check while you can still fix it
The most expensive discovery is "we have no goal photos" back in the classroom. So at the halfway point, and again at the end, the Logger runs field_check.py and the team reacts.
Consent, again
The consent rules from lesson 14 apply at the field. Signed forms for every child in frame. The camera points at the pitch and not the stands. If anyone objects, stop, delete their frames and move. Your teacher or team leader carries the list.
What you'll need
- Your approved Field Collection Brief (lesson 15)
- A charged laptop, a webcam on a stand, and
capture.pyset to your field settings field_check.py(below)- Water, a hat and sunscreen
Preparation
Team checklist (the day before)
| Done | Item |
|---|---|
| ☐ | Laptop fully charged, charger packed |
| ☐ | capture.py runs. CAMERA_INDEX tested with the webcam you will use. |
| ☐ | Settings changed to the field configuration in the brief (blur threshold, interval, minimum difference, targets, DATASET_ROOT) |
| ☐ | Webcam fixed to a stand, with a long USB cable if needed |
| ☐ | A phone or tablet to watch the log, and to take photos of camera positions |
| ☐ | field_check.py on the laptop, with the TARGETS edited to match the brief |
| ☐ | Brief printed. Role cards printed. Names written on them. |
| ☐ | Spare storage space: at least 2 GB free |
| ☐ | Water bottle, hat, sunscreen, comfortable shoes |
Plan for the day
A suggested 2-hour schedule. Adjust to your pitch and light.
| Time | What happens | Who |
|---|---|---|
| 0:00 to 0:10 | Arrive, safety talk, agree where you may stand. Re-read the privacy rules. | Everyone |
| 0:10 to 0:25 | Set up at Position A. Test the camera. Check the blur number on a still scene. Confirm roles. | Teams |
| 0:25 to 0:50 | Position A: capture player and goal. Press N at the start. | Teams |
| 0:50 to 1:00 | Move to Position B (a different angle and distance). Press N. | Teams |
| 1:00 to 1:15 | Checkpoint 1: run field_check.py. Decide what the second half must fix. Rotate roles. Water and shade. | Logger |
| 1:15 to 1:35 | Position B or C: ball-focused capture during set pieces, close range. Press N at each move. | Teams |
| 1:35 to 1:45 | Background: empty pitch sections, no players. Press C to change class. | Teams |
| 1:45 to 1:55 | Checkpoint 2: run field_check.py again. Fill in any gaps. Back up. | Logger |
| 1:55 to 2:10 | Pack up, copy the dataset folder to a second place, count equipment, head count, leave no litter. | All |
Roles (from lesson 15)
| Role | At the field |
|---|---|
| Operator | Runs capture.py. Watches the blur score. Presses SPACE. Says when a class target is reached. |
| Class Manager | Owns every class change. Checks the class name in the banner before each burst. |
| Spotter | Watches the action and calls moments ("corner kick, get the ball!"). Keeps people out of the way of the camera. |
| Logger and Reviewer | Runs field_check.py, photographs each position, keeps the consent list, notes problems and weather. |
Rotate roles at Checkpoint 1 so everyone has a turn at the laptop.
Field tools
field_check.py: are we on track?
Put this in the folder that contains your DATASET_ROOT. Edit TARGETS to match your brief. Run it with python field_check.py field_trip_1, using the root folder name from capture.py. It reads the CSV log that capture.py writes (lesson 11), so it needs no camera.
import csv
import os
import sys
from collections import Counter, defaultdict
ROOT = sys.argv[1] if len(sys.argv) > 1 else "field_trip_1" # DATASET_ROOT in capture.py
TARGETS = {"player": 80, "ball": 80, "goal": 40, "background": 40} # copy these from your approved brief
MIN_ACCEPT_RATE = 0.40
MIN_SESSIONS_PER_CLASS = 3
log_path = os.path.join(ROOT, "capture_log.csv")
if not os.path.exists(log_path):
raise SystemExit(f"No log at {log_path}. Is DATASET_ROOT right?")
with open(log_path) as f:
rows = list(csv.DictReader(f))
tries = len(rows)
kept = [r for r in rows if r["accepted"] == "True"]
print(f"Attempts: {tries} Kept: {len(kept)} Accept rate: {len(kept) / max(tries, 1):.0%}")
reasons = Counter(r["reason"] for r in rows if r["accepted"] != "True")
if reasons:
print("Rejected because:", ", ".join(f"{k} {v}" for k, v in reasons.most_common()))
sessions = defaultdict(set)
per_class = Counter()
for r in kept:
per_class[r["class"]] += 1
sessions[r["class"]].add(r["session"])
print(f"\n{'class':<12}{'kept':>6}{'target':>8}{'sessions':>10} status")
warnings = []
for cls, target in TARGETS.items():
n, s = per_class[cls], len(sessions[cls])
status = "OK"
if n < target:
status = f"NEED {target - n} MORE"
warnings.append(f"{cls}: {n}/{target} frames")
if s < MIN_SESSIONS_PER_CLASS:
status += f" (only {s} session{'s' if s != 1 else ''}: change position!)"
warnings.append(f"{cls}: only {s} session(s), the lesson 13 split needs variety")
print(f"{cls:<12}{n:>6}{target:>8}{s:>10} {status}")
# Do the files on disk match the log?
on_disk = Counter()
for cls in TARGETS:
folder = os.path.join(ROOT, cls)
if os.path.isdir(folder):
on_disk[cls] = len([x for x in os.listdir(folder) if x.endswith(".jpg")])
mismatch = [c for c in TARGETS if on_disk[c] != per_class[c]]
if mismatch:
warnings.append("files and log disagree for: " + ", ".join(mismatch))
if len(kept) / max(tries, 1) < MIN_ACCEPT_RATE:
warnings.append(f"accept rate below {MIN_ACCEPT_RATE:.0%}: check blur number, camera shake, MIN_DIFF")
print("\nTOTAL KEPT:", len(kept), "(goal: 200 or more across the 4 classes)")
print("ALL CLEAR" if not warnings else "FIX BEFORE LEAVING THE FIELD:")
for w in warnings:
print(" -", w)It tells you, for each class: how many frames you kept, your target, and in how many sessions. It flags classes with too few frames or too few positions, and any mismatch between the files and the log.
Quality rules of thumb at the field
| Rule | Why |
|---|---|
| Watch the on-screen blur number. If it is red most of the time, steady the camera or lower the threshold a little. | Motion blur wastes your time |
| Accept rate below about 40%? Stop and change something. | Otherwise you collect a lot of nothing |
| Press N every time the camera moves | Sessions are needed for the lesson 13 split |
| Do the ball in short, close bursts around set pieces | The ball is small and quickly gone |
Capture background too | Empty pitch teaches what "nothing" looks like |
| Pause (P) during stoppages | No point saving a still scene |
After the field
Data handling (same day)
- Copy the
DATASET_ROOTfolder to a second location (a USB drive, or the school drive). Do this before anything else. - Run
field_check.pyone last time and save its output. - Do not post raw images in group chats or public places (lesson 14).
- Upload to the labelling tool only if your charter allows it, and only with consent in place.
- Write the deletion date from the consent form on the folder.
Trip report: template
Each team submits one page in the the next lesson.
| Section | What to write |
|---|---|
| 1. Numbers | Frames kept per class (from field_check.py), accept rate, number of sessions |
| 2. Versus the brief | Which targets we met, and which we missed. Why? |
| 3. What worked | Two things in our plan that helped |
| 4. What surprised us | Two things that the brief did not predict |
| 5. Settings we changed | Which capture.py settings we changed at the field, and why |
| 6. Problems | Technical, people, or weather. What we did. |
| 7. Consent and privacy | Did any issue come up? What did we do? |
| 8. Next time | Three changes for Field Trip 2 (lesson 39) |
If it goes wrong
| Error | Cause | Fix |
|---|---|---|
| Accept rate very low | Camera shaking, strong sun, low threshold mismatch | Use the stand. Check the blur number. Lower BLUR_THRESHOLD a little, as agreed in the brief. |
| Wrong class folder | Forgot to press C | Class Manager checks the banner each time. Fix by moving files, then rerun the checker. |
| Laptop overheats | Direct sun | Work in shade. Close other apps. Restart capture.py (counts rebuild from files). |
| Webcam not found | Index changed, cable loose | Try index 1 or 2. Reseat the cable. Check the OS camera permission. |
| Only one session logged | Forgot N | Press N now, move the camera, continue. Mention it in the report. |
| Frames of people who did not consent | The camera drifted into the stands | Delete the frames. Reframe. Add a note to the report. |
field_check.py says "No log" | Wrong root folder name | Use the same DATASET_ROOT as capture.py |
| Out of disk space | Large JPEG quality, many frames | Lower SAVE_QUALITY a little, or free space before leaving |
Try it
- Plot a histogram of blur scores per position from the log and bring it to the report
- Propose a better position by sketching the pitch with the camera's field of view
- Time how long each class took to reach its target
Check what you learnt
When should you press N in capture.py?
Why run field_check.py halfway through the trip?
A spectator objects to being filmed. What do you do?
Why rotate roles at the checkpoint?
What's the last thing to do before leaving?
Conclusion
In the field, the plan meets reality. Change position and start a new session often, watch the blur score and accept rate, check your class counts halfway with field_check.py, keep to the consent rules, and back the dataset up before you leave.
What next?
Next: Clean the field data & retrain. Back in the classroom, you'll clean the catch and retrain on real field data.

