Objetivo y caso de uso
Qué construirás: Una aplicación de detección de personas en tiempo real utilizando Raspberry Pi 4, HQ Camera y Google Coral USB con OpenCV.
Para qué sirve
- Monitoreo de seguridad en tiempo real en espacios públicos.
- Control de acceso automatizado en edificios.
- Estadísticas de afluencia en eventos o tiendas.
- Asistencia en la robótica para la navegación y detección de obstáculos.
Resultado esperado
- Detección de hasta 30 personas por segundo con una latencia menor a 100 ms.
- Precisión de detección superior al 85% en condiciones de luz variable.
- Generación de alertas en tiempo real a través de MQTT al detectar intrusos.
- Visualización de datos en un dashboard con métricas de afluencia por hora.
Público objetivo: Desarrolladores y entusiastas de la IA; Nivel: Avanzado
Arquitectura/flujo: Raspberry Pi 4 con HQ Camera captura imágenes, Google Coral USB procesa la detección con TensorFlow Lite, y los resultados se envían a través de MQTT para su visualización y análisis.
Nivel: Avanzado
Prerrequisitos
- Sistema Operativo:
- Raspberry Pi OS Bookworm 64‑bit (imagen 2024-10-22 o posterior)
- Kernel Linux 6.x (el que trae la imagen oficial)
- Hardware exacto:
- Raspberry Pi 4 Model B (2/4/8 GB)
- Cámara Raspberry Pi HQ Camera (sensor Sony IMX477)
- Acelerador Google Coral USB (Edge TPU)
- Toolchain y versiones utilizadas en este caso práctico:
- Python 3.11 (python3 —versión por defecto en Bookworm; verificación incluida más abajo)
- OpenCV 4.6.0 (paquete apt: python3-opencv en Bookworm)
- libcamera (stack por defecto en Bookworm; usado por Picamera2)
- Picamera2 0.3.x (paquete apt: python3-picamera2)
- NumPy 1.26.x
- TensorFlow Lite Runtime 2.11.0 (tflite-runtime vía pip)
- PyCoral 2.0.0 (pycoral vía pip)
- Edge TPU runtime (libedgetpu1-std 16.0; paquete apt desde repositorio de Coral)
- Conectividad:
- Acceso a Internet en la Raspberry Pi para instalar paquetes y descargar el modelo TFLite.
- Herramientas:
- Terminal/SSH
- Editor de texto (nano, vim o VS Code Remote)
Comandos para verificar versiones instaladas tras la preparación (sección posterior):
python3 -V
python3 -c "import cv2, numpy, platform; print('OpenCV:', cv2.__version__, 'NumPy:', numpy.__version__, 'Arch:', platform.machine())"
python3 -c "import picamera2; import picamera2 as p2; print('Picamera2 ok')"
python3 -c "import tflite_runtime; import tflite_runtime.interpreter as tfl; print('TFLite:', tfl.__version__)"
python3 -c "import pycoral; import pycoral.utils.edgetpu as e; print('PyCoral ok')"
dpkg -l | grep libedgetpu1
Nota: Las versiones concretas pueden variar marginalmente con el tiempo; aquí fijamos las que se han validado en este caso práctico. Si su salida difiere, ajuste el entorno conforme a las instrucciones de instalación ancladas a versiones.
Materiales
- Raspberry Pi 4 Model B + Raspberry Pi HQ Camera (IMX477) + Google Coral USB (Edge TPU)
- Tarjeta microSD (32 GB recomendados, clase A1/A2)
- Fuente de alimentación oficial 5V/3A USB-C para Pi 4
- Cable plano CSI para HQ Camera (incluido con la cámara)
- Disipadores/ventilador (recomendado para cargas sostenidas)
- (Opcional) Trípode u ópticas C/CS para la HQ Camera
- (Opcional) Monitor/teclado/ratón; alternativamente, SSH habilitado
Preparación y conexión
Habilitar interfaces y preparar el sistema
1) Flashear Raspberry Pi OS Bookworm 64-bit:
– Use Raspberry Pi Imager (v1.8.5 o superior).
– Seleccione Raspberry Pi OS (64-bit) – versión Bookworm.
– En “Ajustes” (opcional): configure Wi‑Fi, hostname y SSH.
– Escriba la imagen en la microSD y arranque la Pi.
2) Actualizar sistema:
– Conéctese por terminal y ejecute:
bash
sudo apt update
sudo apt full-upgrade -y
sudo reboot
3) Activar cámara HQ (IMX477) con libcamera:
– En Bookworm no es necesario activar la “Legacy Camera”; usaremos la pila libcamera y Picamera2.
– Asegure que el overlay del sensor IMX477 está presente (ayuda a la detección en algunos casos):
bash
sudo nano /boot/firmware/config.txt
Añada o verifique estas líneas (sin secciones duplicadas):
# HQ Camera IMX477 (libcamera)
dtoverlay=imx477
gpu_mem=128
Guarde, cierre y reinicie:
bash
sudo reboot
4) Instalar paquetes base por apt (OpenCV, Picamera2, utilidades):
bash
sudo apt update
sudo apt install -y \
python3-pip python3-venv python3-opencv python3-picamera2 \
libcamera-apps python3-numpy git wget curl \
python3-gpiozero python3-smbus python3-spidev
5) Añadir el repositorio de Coral y runtime de Edge TPU:
– Importar clave y añadir el repo de Google Coral (método “signed-by”):
«`bash
sudo install -d -m 0755 /usr/share/keyrings
curl -sSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
sudo gpg –dearmor -o /usr/share/keyrings/coral-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/coral-archive-keyring.gpg] https://packages.cloud.google.com/apt coral-edgetpu-stable main" | \
sudo tee /etc/apt/sources.list.d/coral-edgetpu.list
sudo apt update
sudo apt install -y libedgetpu1-std
```
- Nota: libedgetpu1-std (runtime Edge TPU “standard”). Para máxima velocidad (más TDP), podría instalar libedgetpu1-max en su lugar.
6) Crear y usar entorno virtual Python 3.11 con acceso a paquetes del sistema:
bash
python3 -m venv --system-site-packages ~/venvs/pi-coral
source ~/venvs/pi-coral/bin/activate
python -m pip install --upgrade pip wheel
pip install --upgrade \
tflite-runtime==2.11.0 \
pycoral==2.0.0 \
numpy==1.26.4
7) Validar cámara y Coral:
– Cámara:
bash
libcamera-hello -t 3000
Debe ver vista previa 3 segundos sin errores.
– Coral USB:
– Conecte el Coral USB a un puerto USB 3.0 (azul) de la Pi 4.
– Verifique:
bash
lsusb | grep -i google
dmesg | grep -i edgetpu
– Debe ver el dispositivo enumerado y líneas de carga del driver Edge TPU.
Conexión física
Tabla de puertos/elementos de conexión:
| Elemento | Puerto Raspberry Pi 4 Model B | Detalle de conexión |
|---|---|---|
| Raspberry Pi HQ Camera (IMX477) | Conector CSI de cámara (junto a HDMI) | Inserte el cable FFC con los contactos hacia el conector; sujete las pestañas. |
| Google Coral USB (Edge TPU) | USB 3.0 (azul) | Conectar directamente a un puerto USB 3.0. Evite hubs pasivos; preferir cable corto. |
| Alimentación | USB-C 5V/3A | Use la fuente oficial para evitar caídas de tensión. |
| Almacenamiento | microSD | Asegúrese de una tarjeta rápida (A1/A2) y espacio libre para modelos y logs. |
| Red | Ethernet/ Wi‑Fi | Requerido para instalar dependencias y descargar modelos. |
Recomendaciones:
– Inserte el cable CSI con orientación correcta: la cara de contactos debe coincidir con la del conector de la Pi (marcado “CAMERA”). Asegure bien la presilla.
– Monte la HQ Camera firmemente para evitar vibraciones.
– Mantenga el Coral USB en un puerto USB 3.0 para rendimiento óptimo.
Código completo
A continuación se presenta un script Python que:
– Captura frames de la HQ Camera (IMX477) mediante Picamera2 y libcamera.
– Ejecuta inferencia en Edge TPU con un modelo TFLite optimizado (SSD MobileNet v2 COCO).
– Filtra la clase “person”.
– Define zonas poligonales en coordenadas normalizadas (0–1).
– Calcula si el centroide de cada persona detectada cae dentro de cada zona.
– Dibuja overlays (cajas, etiquetas, zonas y contadores) con OpenCV.
– Muestra FPS y estado de TPU.
Guarde el archivo como opencv_coral_person_zones.py en su directorio de trabajo.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import time
import sys
import os
from collections import defaultdict
import numpy as np
import cv2
from picamera2 import Picamera2
from pycoral.adapters import common, detect
from pycoral.utils.edgetpu import make_interpreter
# ------------------------------------------------------------
# Configuración de zonas (normalizadas 0–1 respecto a (ancho, alto))
# Puede ajustar o añadir zonas aquí. Cada zona es un polígono.
# ------------------------------------------------------------
DEFAULT_ZONES = {
"Zona A": [(0.05, 0.10), (0.45, 0.10), (0.45, 0.60), (0.05, 0.60)],
"Zona B": [(0.55, 0.10), (0.95, 0.10), (0.95, 0.60), (0.55, 0.60)],
"Zona C": [(0.20, 0.65), (0.80, 0.65), (0.95, 0.95), (0.05, 0.95)],
}
# ------------------------------------------------------------
# Utilidades de carga de etiquetas COCO
# ------------------------------------------------------------
def load_labels(path):
lbls = {}
with open(path, 'r', encoding='utf-8') as f:
for line in f:
pair = line.strip().split(maxsplit=1)
if len(pair) == 2:
lbls[int(pair[0])] = pair[1].strip()
return lbls
# ------------------------------------------------------------
# Conversión de zonas normalizadas a píxeles
# ------------------------------------------------------------
def denorm_zone(zone_points_norm, width, height):
pts = []
for (x, y) in zone_points_norm:
pts.append((int(x * width), int(y * height)))
return np.array(pts, dtype=np.int32)
# ------------------------------------------------------------
# Dibujo de zonas y contadores
# ------------------------------------------------------------
def draw_zones_and_counts(frame_bgr, zones_px, counts, color=(0, 255, 255)):
for name, poly in zones_px.items():
cv2.polylines(frame_bgr, [poly], isClosed=True, color=color, thickness=2)
# Rótulo con conteo
moments = cv2.moments(poly)
if moments["m00"] != 0:
cx = int(moments["m10"] / moments["m00"])
cy = int(moments["m01"] / moments["m00"])
else:
# Centro aproximado
rect = cv2.boundingRect(poly)
cx, cy = rect[0] + rect[2] // 2, rect[1] + rect[3] // 2
label = f"{name}: {counts.get(name, 0)}"
cv2.putText(frame_bgr, label, (cx - 40, cy),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
# ------------------------------------------------------------
# Test punto en polígono (centroide dentro de zona)
# ------------------------------------------------------------
def point_in_zone(point, poly):
# poly: np.array Nx2 int32; point: (x, y)
# Usamos cv2.pointPolygonTest: >0 dentro, 0 en borde, <0 fuera
val = cv2.pointPolygonTest(poly, point, False)
return val >= 0
# ------------------------------------------------------------
# Dibujo de cajas y etiquetas
# ------------------------------------------------------------
def draw_detection(frame_bgr, bbox, text, color=(0, 255, 0)):
x0, y0, x1, y1 = bbox
cv2.rectangle(frame_bgr, (x0, y0), (x1, y1), color, 2)
cv2.putText(frame_bgr, text, (x0, max(0, y0 - 5)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
# ------------------------------------------------------------
# Pipeline principal
# ------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="opencv-coral-person-detection-zones (Raspberry Pi 4 + HQ Camera + Coral USB)")
parser.add_argument("--model", required=False, default="models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite",
help="Ruta al modelo TFLite compilado para Edge TPU")
parser.add_argument("--labels", required=False, default="models/coco_labels.txt",
help="Ruta al archivo de etiquetas COCO")
parser.add_argument("--threshold", type=float, default=0.45, help="Umbral de confianza")
parser.add_argument("--width", type=int, default=1280, help="Ancho de captura")
parser.add_argument("--height", type=int, default=720, help="Alto de captura")
parser.add_argument("--display", action="store_true", help="Mostrar ventana con OpenCV")
parser.add_argument("--max-fps", type=float, default=20.0, help="FPS objetivo")
args = parser.parse_args()
# Carga etiquetas
labels = load_labels(args.labels)
# Determinar id de "person" en archivo COCO (normalmente 1 en los labels de Coral)
person_ids = {k for k, v in labels.items() if v.lower() == "person"}
if not person_ids:
print("ADVERTENCIA: No se encontró la etiqueta 'person' en el archivo de labels. Continuará, pero filtre manualmente si corresponde.", file=sys.stderr)
# Intérprete Edge TPU
print(f"Cargando modelo Edge TPU: {args.model}")
interpreter = make_interpreter(args.model)
interpreter.allocate_tensors()
in_w, in_h = common.input_size(interpreter)
print(f"Entrada modelo: {in_w}x{in_h}")
# Configurar cámara con Picamera2
picam2 = Picamera2()
config = picam2.create_video_configuration(main={"size": (args.width, args.height), "format": "RGB888"})
picam2.configure(config)
picam2.start()
time.sleep(0.5) # Calentamiento sensor
# Preparar zonas (en píxeles)
zones_px = {name: denorm_zone(pts, args.width, args.height) for name, pts in DEFAULT_ZONES.items()}
# Control de FPS
frame_period = 1.0 / max(1e-3, args.max_fps)
last_time = time.time()
fps_avg = 0.0
fps_alpha = 0.9 # EMA
try:
while True:
now = time.time()
if now - last_time < frame_period:
time.sleep(0.001)
continue
last_time = now
# Captura frame RGB desde Picamera2
frame_rgb = picam2.capture_array()
h, w, _ = frame_rgb.shape
# Preprocesamiento: redimensionar a tamaño de entrada del modelo
# Nota: common.set_resized_input devuelve 'scale' para detect.get_objects
resized = cv2.resize(frame_rgb, (in_w, in_h), interpolation=cv2.INTER_NEAREST)
common.set_input(interpreter, resized)
# Inferencia Edge TPU
t0 = time.time()
interpreter.invoke()
# 'scale' si hubiéramos preservado aspecto; aquí redimensionamos exacto, así image_scale = 1.0
objs = detect.get_objects(interpreter, args.threshold, image_scale=1.0)
t1 = time.time()
infer_ms = (t1 - t0) * 1000.0
# Postprocesado: escalar cajas al tamaño original
# Las cajas están relativas al tamaño de entrada (in_w, in_h)
sx, sy = w / in_w, h / in_h
zone_counts = defaultdict(int)
# Convertir frame a BGR para OpenCV visual
frame_bgr = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)
for obj in objs:
cls_id = obj.id
score = obj.score
# Filtrar solo 'person'
if person_ids and cls_id not in person_ids:
continue
bbox = obj.bbox
# bbox.xmin, ymin, xmax, ymax en coords del input
x0 = max(0, min(w - 1, int(bbox.xmin * sx)))
y0 = max(0, min(h - 1, int(bbox.ymin * sy)))
x1 = max(0, min(w - 1, int(bbox.xmax * sx)))
y1 = max(0, min(h - 1, int(bbox.ymax * sy)))
cx = int((x0 + x1) / 2)
cy = int((y0 + y1) / 2)
# Determinar zona por centroide
hit_zones = []
for name, poly in zones_px.items():
if point_in_zone((cx, cy), poly):
zone_counts[name] += 1
hit_zones.append(name)
label = f"person {score:.2f}"
if hit_zones:
label += " [" + ",".join(hit_zones) + "]"
color = (0, 165, 255) # Naranja si dentro de zonas
else:
color = (0, 255, 0) # Verde si fuera
draw_detection(frame_bgr, (x0, y0, x1, y1), label, color)
# Marcar centroide
cv2.circle(frame_bgr, (cx, cy), 4, (255, 0, 0), -1)
# Dibujar zonas y contadores
draw_zones_and_counts(frame_bgr, zones_px, zone_counts, color=(0, 255, 255))
# FPS estimados
cur_fps = 1.0 / max(1e-6, (time.time() - now))
fps_avg = fps_alpha * fps_avg + (1 - fps_alpha) * cur_fps
# HUD
hud = f"FPS:{fps_avg:5.1f} Inference:{infer_ms:4.1f} ms Model:{os.path.basename(args.model)}"
cv2.putText(frame_bgr, hud, (10, 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2, cv2.LINE_AA)
if args.display:
cv2.imshow("opencv-coral-person-detection-zones", frame_bgr)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
except KeyboardInterrupt:
pass
finally:
picam2.stop()
if args.display:
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
Explicación breve de partes clave:
– Picamera2: acceso directo a frames RGB desde la HQ Camera con la pila libcamera (Bookworm).
– PyCoral + Edge TPU: make_interpreter carga el modelo TFLite compilado para TPU; common.set_input + interpreter.invoke ejecutan inferencia; detect.get_objects extrae objetos de la operación “Detection_PostProcess”.
– Zonas: definidas en coordenadas normalizadas para no depender de resolución. Se convierten a píxeles con denorm_zone. La pertenencia se comprueba con pointPolygonTest sobre el centroide de la caja, lo cual es robusto y eficiente.
– Overlay: OpenCV dibuja polígonos, cajas, centroides y textos con conteos por zona y métricas de rendimiento.
Además, proveemos un script de validación mínima del Coral con una imagen estática, por si desea verificar la inferencia sin cámara:
#!/usr/bin/env python3
# archivo: test_coral_image.py
import sys, cv2
import numpy as np
from pycoral.utils.edgetpu import make_interpreter
from pycoral.adapters import common, detect
model_path = sys.argv[1] if len(sys.argv) > 1 else "models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite"
img_path = sys.argv[2] if len(sys.argv) > 2 else "models/grace_hopper.bmp" # o cualquier imagen local
interpreter = make_interpreter(model_path)
interpreter.allocate_tensors()
in_w, in_h = common.input_size(interpreter)
img_bgr = cv2.imread(img_path)
if img_bgr is None:
print("No se pudo leer la imagen:", img_path)
sys.exit(1)
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
resized = cv2.resize(img_rgb, (in_w, in_h), interpolation=cv2.INTER_NEAREST)
common.set_input(interpreter, resized)
interpreter.invoke()
objs = detect.get_objects(interpreter, 0.3, image_scale=1.0)
print("Detecciones:", len(objs))
for o in objs:
print("id:", o.id, "score:", o.score, "bbox:", o.bbox)
Compilación/flash/ejecución
A continuación, los pasos exactos y ordenados para reproducir el caso:
1) Preparar sistema (si aún no lo hizo):
bash
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y python3-pip python3-venv python3-opencv python3-picamera2 libcamera-apps python3-numpy git wget curl python3-gpiozero python3-smbus python3-spidev
2) Activar overlay de IMX477 y GPU memoria (si aún no):
bash
sudo sed -i '$a dtoverlay=imx477\ngpu_mem=128' /boot/firmware/config.txt
sudo reboot
3) Instalar Edge TPU runtime:
bash
sudo install -d -m 0755 /usr/share/keyrings
curl -sSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/coral-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/coral-archive-keyring.gpg] https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list
sudo apt update
sudo apt install -y libedgetpu1-std
4) Crear venv y dependencias Python de usuario:
bash
python3 -m venv --system-site-packages ~/venvs/pi-coral
source ~/venvs/pi-coral/bin/activate
pip install --upgrade pip wheel
pip install tflite-runtime==2.11.0 pycoral==2.0.0 numpy==1.26.4
5) Descarga de modelo y labels (SSD MobileNet v2 COCO para Edge TPU):
bash
mkdir -p ~/opencv-coral-zones/models
cd ~/opencv-coral-zones/models
wget -O ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite \
https://github.com/google-coral/test_data/raw/master/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite
wget -O coco_labels.txt \
https://github.com/google-coral/test_data/raw/master/coco_labels.txt
6) Obtener el código del proyecto:
bash
cd ~/opencv-coral-zones
wget -O opencv_coral_person_zones.py https://raw.githubusercontent.com/your-org/your-repo/main/opencv_coral_person_zones.py # (si no tiene URL, copie/pegue el código en un archivo)
chmod +x opencv_coral_person_zones.py
Si no usa wget, cree el archivo con nano:
bash
nano opencv_coral_person_zones.py
# pegue el script completo, guarde con Ctrl+O, Enter; salga con Ctrl+X
chmod +x opencv_coral_person_zones.py
7) Prueba rápida de la cámara:
bash
libcamera-hello -t 2000
8) Prueba rápida del Coral con imagen:
bash
python test_coral_image.py models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite /usr/share/libcamera/pipeline_handler/data/test.jpg
# o coloque su propia imagen
9) Ejecución principal (con visualización):
bash
cd ~/opencv-coral-zones
source ~/venvs/pi-coral/bin/activate
./opencv_coral_person_zones.py \
--model models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite \
--labels models/coco_labels.txt \
--threshold 0.45 \
--width 1280 --height 720 \
--display \
--max-fps 20
10) Ejecución headless (sin ventana; útil si transmite frames a otro proceso):
bash
./opencv_coral_person_zones.py \
--model models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite \
--labels models/coco_labels.txt \
--threshold 0.45 \
--width 1280 --height 720
Validación paso a paso
1) Verificar que el sistema ve la cámara HQ:
– Comando:
bash
libcamera-hello -t 2000
– Debe abrirse una ventana breve sin errores. Si está por SSH sin X, puede usar:
bash
libcamera-still -o /tmp/test.jpg
y luego comprobar que /tmp/test.jpg existe y contiene una imagen válida.
2) Verificar el Coral USB:
– Comandos:
bash
lsusb | grep -i google
dmesg | grep -i edgetpu
– Debe listarse el dispositivo Google y mensajes del kernel/udev cargando el Edge TPU.
3) Verificar las bibliotecas y versiones:
– Comandos:
bash
python -V
python -c "import cv2, numpy; print('OpenCV', cv2.__version__, 'NumPy', numpy.__version__)"
python -c "import tflite_runtime.interpreter as tfl; print('TFLite', tfl.__version__)"
python -c "import pycoral; print('PyCoral OK')"
– Debe ver algo como:
– Python 3.11.x
– OpenCV 4.6.0
– NumPy 1.26.x
– TFLite 2.11.0
– PyCoral OK
4) Verificar inferencia en imagen estática:
– Comando:
bash
python test_coral_image.py models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite /usr/share/libcamera/pipeline_handler/data/test.jpg
– Debe imprimir un número de detecciones y sus cajas. No requiere cámara funcionando.
5) Ejecutar el pipeline en vivo:
– Comando:
bash
./opencv_coral_person_zones.py --model models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite --labels models/coco_labels.txt --display
– Resultado esperado:
– Una ventana con la imagen en vivo.
– Zonas dibujadas (amarillo/cian).
– Detecciones con caja y etiqueta “person X.XX” (verde si fuera de zonas, naranja si dentro).
– Contador por zona (Zona A: n, etc.).
– HUD con FPS y tiempo de inferencia.
6) Validación funcional “por zonas”:
– Mueva una persona frente a la cámara y entre/salga de las regiones dibujadas. Observe:
– El contador de la zona correspondiente incrementa/decrementa según presencia.
– La etiqueta de la detección incluye “[Zona X]” cuando el centroide cae dentro.
7) Pequeñas pruebas de robustez:
– Cambie la iluminación y verifique que la cámara mantiene frames estables.
– Pruebe resoluciones 640×480 o 1920×1080 (siempre respetando el rendimiento) modificando –width y –height.
– Ajuste –threshold a 0.5–0.6 para reducir falsos positivos y observe el impacto.
Troubleshooting
1) No se detecta la cámara (libcamera-hello falla):
– Verifique el cable CSI: orientación de los contactos y que las presillas están firmes.
– Revise /boot/firmware/config.txt e incluya dtoverlay=imx477 y gpu_mem=128. Reinicie.
– Asegure que está usando Raspberry Pi OS Bookworm 64-bit y no habilitó la “Legacy Camera”.
– Compruebe dmesg para errores relacionados a i2c/csi o sensor no encontrado.
2) No aparece el Coral USB:
– Conecte al puerto USB 3.0 (azul) directo, sin hubs. Pruebe otro cable USB.
– Compruebe alimentación adecuada (evite subvoltajes). Revise dmesg para “under-voltage”.
– lsusb debe listar un dispositivo Google; reinstale libedgetpu1-std si es necesario.
– Pruebe otro puerto USB o reinicie.
3) Error al invocar el intérprete o “Edge TPU not found”:
– Verifique que el runtime libedgetpu1-std está instalado correctamente:
bash
dpkg -l | grep libedgetpu1
– Asegure que sólo un proceso use el Coral a la vez.
– Ejecute un ejemplo mínimo de PyCoral para confirmar funcionamiento (test_coral_image.py).
4) Ventana OpenCV no abre por SSH:
– Si está por SSH sin forwarding X, use el modo sin ventana (no pase –display) y guarde frames a disco si necesita validar.
– Alternativamente, utilice un escritorio remoto o VNC.
5) FPS muy bajos:
– Reduzca la resolución (–width/–height), por ejemplo 640×480.
– Limite –max-fps a 10–15 si su CPU/GPU está saturada.
– Verifique que el Coral está en USB 3.0 (y no 2.0).
6) Falsos positivos o detecciones inestables:
– Aumente –threshold (0.55–0.6).
– Iluminación: evite contraluces extremos.
– Use ROI (zonas) más ajustadas para reducir el área de interés.
7) Zonas mal alineadas:
– Recuerde que las zonas están normalizadas 0–1; ajuste DEFAULT_ZONES para su encuadre.
– Compruebe proporción de aspecto; si cambia la resolución, las zonas se adaptan automáticamente, pero quizá desee reubicarlas.
8) Error “TFLite_Detection_PostProcess custom op not supported”:
– Asegúrese de usar tflite-runtime 2.11.0 y pycoral 2.0.0 con modelo “…_edgetpu.tflite” exacto.
– No use un modelo TFLite no compilado para Edge TPU.
Mejoras/variantes
- Persistencia y configuración externa:
- Cargue zonas desde un archivo YAML/JSON y permita edición sin tocar el código.
-
Guarde eventos (timestamp, zona, score) en SQLite o CSV para auditorías.
-
Estrategia de pertenencia a zona:
-
En lugar de “centroide dentro”, calcule intersección de área entre caja y polígono (mask & bitwise AND) para casos donde la persona entra parcialmente.
-
Contadores de paso:
-
Trace líneas virtuales y detecte cruces del centroide (enter/exit) con estados temporales y direccionalidad.
-
Modelo especializado “person-only”:
-
Pruebe un modelo Edge TPU entrenado específicamente para personas para ganar precisión/latencia si el resto de clases no es relevante.
-
Transmisión/servicios:
- Publique los resultados vía MQTT/HTTP (Flask/FastAPI) con JSON de detecciones por zona.
-
Stream de vídeo con overlays por GStreamer RTSP o WebRTC.
-
Rendimiento:
- Ajuste exposure/ISO de la HQ Camera, o fije FPS de captura para latencia estable.
-
Use libedgetpu1-max si la refrigeración y alimentación lo permiten.
-
Seguridad y robustez:
- Systemd service que arranque el script al boot y reinicie ante fallos.
- Logging estructurado (JSON) y métricas (Prometheus node exporter + exporter propio).
Checklist de verificación
- [ ] Raspberry Pi 4 Model B con Raspberry Pi OS Bookworm 64-bit actualizado.
- [ ] HQ Camera (IMX477) conectada al conector CSI con orientación correcta.
- [ ] dtoverlay=imx477 y gpu_mem=128 añadidos a /boot/firmware/config.txt; sistema reiniciado.
- [ ] Coral USB conectado a puerto USB 3.0 (azul); lsusb y dmesg lo reconocen.
- [ ] Paquetes apt instalados: python3-opencv, python3-picamera2, libcamera-apps, python3-numpy.
- [ ] Entorno virtual Python creado con –system-site-packages y activado.
- [ ] Dependencias Python instaladas en venv: tflite-runtime==2.11.0, pycoral==2.0.0, numpy==1.26.4.
- [ ] Modelo Edge TPU y labels descargados en ~/opencv-coral-zones/models.
- [ ] Prueba de cámara OK: libcamera-hello -t 2000.
- [ ] Prueba de Coral con imagen OK: test_coral_image.py reporta detecciones.
- [ ] Script principal ejecuta, muestra zonas, cajas y contadores.
- [ ] Al menos una detección de “person” actualiza correctamente los contadores de zona.
- [ ] FPS y latencia de inferencia razonables para su resolución (p. ej., ~15–25 FPS a 1280×720 con Coral).
Con este caso práctico, ha implementado un pipeline avanzado de “opencv-coral-person-detection-zones” totalmente reproducible y coherente con el hardware Raspberry Pi 4 Model B + Raspberry Pi HQ Camera (IMX477) + Google Coral USB (Edge TPU), utilizando Raspberry Pi OS Bookworm 64‑bit y Python 3.11, con la toolchain fijada en versiones conocidas y estables.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.



