Sprint 4 · Datastructuren & foutafhandeling · US4: Live besturing
Als piloot, wil ik de drone live met het toetsenbord besturen terwijl ik het camerabeeld met de belangrijkste waarden zie, zodat ik meteen kan bijsturen en de vlucht later kan terugkijken.
Taak 2: Video opnemen met VideoWriter
cv2.imshow() toont frames, maar slaat ze niet op. Met cv2.VideoWriter schrijf je elk frame naar een videobestand — terwijl de stream gewoon doorloopt.
Van tonen naar opslaan
Met cv2.imshow() zie je frames live op het scherm. Maar zodra de loop stopt, is alles weg. cv2.VideoWriter lost dit op: het schrijft elk frame naar een bestand terwijl de loop loopt.
Het 3-stap patroon
# Stap 1 — éénmalig aanmaken (vóór de loop)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
writer = cv2.VideoWriter('opname.mp4', fourcc, 25.0, (640, 480))
# Stap 2 — elk frame schrijven (in de loop)
writer.write(frame)
# Stap 3 — afsluiten (na de loop)
writer.release()
Elk frame dat je doorgeeft aan writer.write() wordt direct naar het bestand geschreven. Na writer.release() is het bestand klaar om af te spelen.
De vier parameters van VideoWriter
cv2.VideoWriter(
'opname.mp4', # bestandsnaam
fourcc, # codec
25.0, # fps — frames per seconde
(640, 480) # (breedte, hoogte) van elk frame
)
De codec geeft je aan hoe frames gecomprimeerd worden. cv2.VideoWriter_fourcc(*'mp4v') is de meest gebruikte keuze voor .mp4-bestanden.
Kritische eis: de breedte en hoogte in de constructor moeten exact overeenkomen met de afmetingen van elk frame dat je doorgeeft. Klopt het niet, dan worden frames genegeerd en het bestand blijft leeg.
Demo: opname simulatie
Druk op REC om frames te schrijven naar het gesimuleerde bestand. Stop de opname om de bestandsstats te zien.
StatusNiet opgenomen
Frames0
Duur0.0 s
Bestandsgrootte0 KB
Bestand—
Vuistregel: maak de VideoWriter aan vóór de loop, roep write() aan in de loop, en release()na de loop. Vergeet release()? Dan is het bestand waarschijnlijk corrupt.
VideoWriter: parameters, codecs en valkuilen
Waarom VideoWriter en niet losse PNG's?
In S3 sloeg je losse frames op als PNG-bestanden. Dat werkt voor een handvol afbeeldingen, maar bij een stream van 25fps geeft dat 25 bestanden per seconde. Een 10-secondenopname wordt dan 250 afzonderlijke bestanden. VideoWriter bundelt alle frames in één bestand, met compressie, en je kunt het direct afspelen in elke mediaspeler.
Bovendien schrijft VideoWriter frames in realtime weg — het vertraagt de loop nauwelijks. PNG's op schijf schrijven kost per frame meer overhead.
FOURCC — vier letters die de codec bepalen
Een videobestand bevat gecomprimeerde frames. Hoe die compressie werkt, bepaalt de codec. OpenCV identificeert codecs via een FOURCC-code: vier letters die samen één integer vormen.
De sterretje-notatie (*'mp4v') is een Python-truc: het spreidt de vier karakters van de string uit als vier losse argumenten, zodat de functie precies vier chars ontvangt.
Veelgebruikte combinaties
FOURCC
Extensie
Platform
Aanbevolen
mp4v
.mp4
Mac/Linux
✓ Ja
XVID
.avi
Windows/Linux
✓ Ja
MJPG
.avi
Alle
⚠ Groot bestand
H264
.mp4
Mac (met extra libs)
⚠ Niet altijd beschikbaar
DIVX
.avi
Windows
⚠ Verouderd
Op een Tello met Windows werkt XVID + .avi het meest betrouwbaar. Wil je een .mp4-bestand, gebruik dan mp4v.
Frame size: de meest gemaakte fout
De (breedte, hoogte) die je meegeeft aan de VideoWriter constructor is een belofte: "elk frame dat ik aanlever heeft precies deze afmetingen." OpenCV controleert dit niet en geeft geen foutmelding — frames met de verkeerde grootte worden stilzwijgend genegeerd. Het bestand groeit dan niet, of het bestand is kapot bij afspelen.
Fout — grootte klopt niet
# Frame is 960x720 (Tello default)
# Maar writer verwacht 640x480
writer = cv2.VideoWriter(
'out.avi', fourcc, 25.0, (640, 480)
)
writer.write(frame) # frame 960x720
# → frame genegeerd, bestand leeg
Goed — grootte stemt overeen
# Lees eerst de werkelijke afmetingen
hoogte, breedte = frame.shape[:2]
writer = cv2.VideoWriter(
'out.avi', fourcc, 25.0, (breedte, hoogte)
)
writer.write(frame) # zelfde grootte: OK
Let op: NumPy-arrays (frames) gebruiken shape als (rijen, kolommen) = (hoogte, breedte). VideoWriter wil het omgekeerde: (breedte, hoogte). Dit is een klassieke volgorde-verwisseling.
isOpened() — controleer of het gelukt is
Als het aanmaken van de VideoWriter mislukt (schijf vol, codec niet beschikbaar, ongeldige bestandsnaam), geeft de constructor geen fout. Het object bestaat, maar is onbruikbaar. Gebruik isOpened() om dit te detecteren:
writer = cv2.VideoWriter('opname.avi', fourcc, 25.0, (breedte, hoogte))
if not writer.isOpened():
print("Fout: kon bestand niet aanmaken")
else:
# Loop met write()...
fps: instelling versus realtime
De fps-waarde die je opgeeft is een afspeelsnelheid, geen garantie over hoe snel frames worden aangeleverd. Als je in de loop 25 frames per seconde aanmaakt maar de camera geeft er slechts 15, zal het bestand versneld afspelen.
Gebruik de camera's eigen fps als referentie (Tello geeft ~30fps).
Bij een langzamere loop: verlaag de fps instelling zodat afspelen klopt.
Bij een snellere loop: de schrijfsnelheid van de schijf wordt de bottleneck.
Veelgemaakte fouten
Fout 1 — release vergeten
for frame in stream:
writer.write(frame)
# writer.release() vergeten
# → bestand corrupt of
# niet afspeelbaar
Goed — altijd afsluiten
for frame in stream:
writer.write(frame)
writer.release() # finaliseert bestand
Kies een cameraresolutie en een VideoWriter-instelling. Zie wat er gebeurt als ze niet matchen.
Camera: welke resolutie?
VideoWriter: welke grootte?
Vuistregel: lees de werkelijke framegrootte uit frame.shape[:2] (geeft hoogte, breedte) en geef die omgedraaid mee aan VideoWriter als (breedte, hoogte). Zo zijn ze altijd gesynchroniseerd.
Python — Probeer het uit
Console: Klaar voor input…
Opdracht: opname aan/uit via toets
Je hebt uit T1 een drone-keyboard-loop die draait terwijl de stream actief is. De piloot wil nu ook kunnen opnemen: druk op R om de opname te starten, nog een keer R om te stoppen. Bestand: drone_record.py
De drone streamt. Bouw de loop zodat de piloot tegelijk de drone kan besturen én een opname aan en uit kan zetten. Denk na over: wanneer je de writer aanmaakt (niet per frame), hoe je bijhoudt of de opname actief is, en hoe je op het juiste moment release() aanroept.
Acceptatiecriteria
Drukken op 'r' start de opname; opnieuw 'r' stopt en slaat het bestand op.
Tijdens opname toont de terminal elke seconde (elke ~30 frames) het aantal opgenomen frames.
Framegrootte voor de writer komt uit frame.shape, niet hardcoded.
'q' stopt de loop — als er een actieve opname is, wordt die netjes afgesloten.
Het bestand opname.avi is na afloop afspeelbaar.
Extra — alleen als je klaar bent
Meerdere opnames na elkaar overschrijven nu allemaal opname.avi. Geef elke opname een unieke bestandsnaam op basis van een teller: opname_1.avi, opname_2.avi, enzovoort.
Bovendien: stop de opname automatisch na 10 seconden, ook als de piloot vergeet op 'r' te drukken. Gebruik het aantal geschreven frames en de fps-waarde om de verstreken tijd bij te houden — een extra import is niet nodig.
Acceptatiecriteria
Elke nieuwe opname krijgt een eigen bestandsnaam met een oplopend nummer.
Opname stopt automatisch na 10 seconden als de piloot dat niet zelf doet.
Automatisch stoppen geeft een melding in de terminal.
Na automatisch stoppen kan de piloot direct een nieuwe opname starten met 'r'.