📷 03. Control de Cámara y Dataset
En la conducción autónoma, la cámara es los "ojos" del vehículo. En este coche usamos CSICamera, la cual se apoya en GStreamer para procesar vídeo en tiempo real minimizando la sobrecarga de la placa.
Guía Paso a Paso
1. Preparar Directorios (Dataset)
Antes de empezar a fotografiar cosas, es esencial preparar las carpetas con Python para que las fotos queden categorizadas mágicamente (por ej. dataset/cono).
import os
objetos = ['cono', 'senal_stop', 'senal_ceda']
base_dir = 'dataset'
if not os.path.exists(base_dir):
os.makedirs(base_dir)
for objeto in objetos:
if not os.path.exists(f'{base_dir}/{objeto}'):
os.makedirs(f'{base_dir}/{objeto}')
# Un diccionario para saber cuántas fotos tenemos ya guardadas
contador_capturas = {obj: len(os.listdir(f'{base_dir}/{obj}')) for obj in objetos}
2. Construir la Interfaz de Usuario
No tenemos pantalla en el Jetson directamente, por lo que usamos ipywidgets para crear botones virtuales y un visor en Jupyter conectando en el navegador.
import ipywidgets as widgets
from IPython.display import display
# Crear componentes sueltos
vista_previa = widgets.Image(format='jpeg', width=400, height=300)
objeto_selector = widgets.Dropdown(options=objetos, description='Objeto:')
contador_label = widgets.Label(value='Capturas: 0')
boton_captura = widgets.Button(description='📸 Hacer Foto', button_style='success')
3. Logíca de Fotos (Callbacks)
Debemos indicarle a Jupyter qué hacer cuando pulsamos en "Hacer Foto". En esencia es leer de la cámara el fotograma exacto (camera.read()) y guardarlo de forma física (cv2.imwrite()).
import cv2
camera = None # Lo instanciamos luego
def on_capturar_imagen(b):
objeto_actual = objeto_selector.value
# Por ejemplo: dataset/cono/img_0012.jpg
nombre_archivo = f'{base_dir}/{objeto_actual}/img_{contador_capturas[objeto_actual]:04d}.jpg'
imagen = camera.read()
cv2.imwrite(nombre_archivo, imagen)
contador_capturas[objeto_actual] += 1
contador_label.value = f'Capturas de {objeto_actual}: {contador_capturas[objeto_actual]}'
# Enganchar el evento OnClick al botón
boton_captura.on_click(on_capturar_imagen)
4. Lanzar Cámara y Vídeo en Subproceso
Para que la cámara envíe los cuadros (frames) continuamente al widget vista_previa sin colgar el sistema, debemos aislarlo en un hilo mediante la librería threading.
import threading
import time
from jetcam.csi_camera import CSICamera
def hilo_visor():
while True:
if camera is not None:
# Convierte la matriz numpy en una imagen comprimida JPEG visible
imagen = camera.read()
_, jpeg = cv2.imencode('.jpg', imagen)
vista_previa.value = jpeg.tobytes()
time.sleep(0.05) # Capar a 20 FPS para no saturar memoria
Código Completo
Este Notebook listo para rodar engloba todos los componentes. Ideal para recolectar datos antes de hacer inteligencia artificial de clasificación de imágenes.
import cv2
import os
import time
import threading
import ipywidgets as widgets
from IPython.display import display
from jetcam.csi_camera import CSICamera
# 1. Configurar y Crear Directorios
objetos = ['cono', 'senal_stop', 'senal_ceda']
base_dir = 'dataset'
if not os.path.exists(base_dir):
os.makedirs(base_dir)
for objeto in objetos:
if not os.path.exists(f'{base_dir}/{objeto}'):
os.makedirs(f'{base_dir}/{objeto}')
contador_capturas = {obj: len(os.listdir(f'{base_dir}/{obj}')) for obj in objetos}
camera = None
# 2. Interfaz IPyWidgets
vista_previa = widgets.Image(format='jpeg', width=400, height=300)
objeto_selector = widgets.Dropdown(options=objetos, description='Objeto:')
contador_label = widgets.Label(value='Capturas: 0')
boton_captura = widgets.Button(description='📸 Hacer Foto', button_style='success')
def actualizar_contador(change):
contador_label.value = f'Capturas de {change.new}: {contador_capturas[change.new]}'
objeto_selector.observe(actualizar_contador, names='value')
def on_capturar_imagen(b):
objeto_actual = objeto_selector.value
nombre_archivo = f'{base_dir}/{objeto_actual}/img_{contador_capturas[objeto_actual]:04d}.jpg'
imagen = camera.read()
cv2.imwrite(nombre_archivo, imagen)
contador_capturas[objeto_actual] += 1
contador_label.value = f'Capturas de {objeto_actual}: {contador_capturas[objeto_actual]}'
boton_captura.on_click(on_capturar_imagen)
def hilo_visor():
"""Bucle ininterrumpido en segundo plano para refrescar vídeo."""
while True:
if camera is not None:
imagen = camera.read()
_, jpeg = cv2.imencode('.jpg', imagen)
vista_previa.value = jpeg.tobytes()
time.sleep(0.05)
# 3. Encender la cámara y lanzar interfaz
try:
print("Iniciando cámara CSICamera...")
camera = CSICamera(
width=224,
height=224,
capture_fps=30,
capture_width=1280,
capture_height=720
)
print("Cámara inicializada exitosamente.")
# Mostrar la interfaz apilada en vertical (VBox) y Horizontal (HBox)
display(widgets.VBox([
vista_previa,
widgets.HBox([objeto_selector, boton_captura]),
contador_label
]))
# Lanzar reproducción
thread = threading.Thread(target=hilo_visor, daemon=True)
thread.start()
except Exception as e:
print(f"Error fatal al abrir la cámara CSI: {e}")