Herramienta de dibujo interactivo que usa solo una cámara para seguir tus manos y proyectar sobre pantallas. Incluye segmentación de regiones con una red ResUNet para aplicar efectos sobre lo que el usuario selecciona.
- Python 3.10 o superior
- pip actualizado
- Sistema operativo: Windows (para overlay con
pywin32)
-
Crear entorno virtual (recomendado):
python -m venv .venv .\.venv\Scripts\Activate.ps1 -
Instalar dependencias principales:
pip install opencv-contrib-python mediapipe numpy pywin32 torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
> **Nota GPU**: Si tu GPU no soporta CUDA o prefieres CPU, instala PyTorch sin CUDA:
> ```powershell
> pip install opencv-contrib-python mediapipe numpy pywin32 torch torchvision torchaudio
> ```
3. **Verificar instalación**:
```powershell
python src/neural_paint.py --help
Antes de usar NeuralPaint, debes calibrar la superficie de proyección. Existen dos métodos:
-
Prepara tu superficie: proyecta un rectángulo blanco en pantalla completa o usa un papel/cartulina blanco sobre fondo oscuro.
-
Ejecuta calibración:
python src/neural_paint.py --camera 0 --calibration-only --calibration-mode contour --calibration-warp
-
Durante la calibración:
- Ajusta la cámara para que el rectángulo brillante sea claramente visible
- Usa
--calibration-debugsi necesitas ver los bordes detectados - Presiona
spara guardar cuando veas el contorno verde correcto - Presiona
qpara cancelar
-
Imprime o proyecta una rejilla de AprilTags (disponible en
assets/o genera una con bibliotecas AprilTag estándar) -
Ejecuta calibración:
python src/neural_paint.py --camera 0 --calibration-only --calibration-mode apriltag
-
Requisitos:
- La rejilla debe tener al menos 4 tags visibles
- Colócala alineada con la superficie de proyección
- Presiona
spara guardar cuando se detecten suficientes tags
--calibration-warp: Muestra vista previa de la superficie rectificada--calibration-debug: Visualiza mapa de bordes (útil con iluminación difícil)--force-calibrate: Fuerza nueva calibración aunque ya exista una guardada--calibration-min-area 0.15: Ajusta área mínima del contorno (0-1)
La calibración se guarda en calibration/homography.npz y se carga automáticamente en siguientes sesiones.
python src/neural_paint.py --camera 0NeuralPaint soporta configuraciones de doble monitor donde:
- Monitor principal: muestra UI completa (vista de cámara, estado, cursor)
- Proyector/monitor secundario: muestra solo el canvas con dibujos (sin UI)
-
Configura Windows en modo extendido:
- Presiona
Win + P→ selecciona "Extender" - Abre Configuración de pantalla (
Win + I→ Sistema → Pantalla) - Arrastra y ordena los monitores según tu setup físico
- Anota qué número tiene cada monitor (1, 2, etc.)
- Presiona
-
Lanza con dual-monitor:
python src/neural_paint.py --camera 0 --dual-monitor
Por defecto usa:
- Monitor 0: UI principal
- Monitor 1: Proyector
-
Personalizar monitores:
python src/neural_paint.py --camera 0 --dual-monitor --main-monitor 1 --projector-monitor 0
Setup 1: Monitor izquierdo = principal, derecho = proyector
python src/neural_paint.py --camera 0 --dual-monitorSetup 2: Tres monitores (centro = principal, derecha = proyector)
python src/neural_paint.py --camera 0 --dual-monitor --main-monitor 1 --projector-monitor 2Setup 3: Calibración + dual-monitor
# Primero calibra (usa single-monitor automáticamente)
python src/neural_paint.py --camera 0 --calibration-only --calibration-mode apriltag
# Luego ejecuta con dual-monitor
python src/neural_paint.py --camera 0 --dual-monitorMonitor principal:
- ✅ Vista previa de cámara (esquina superior izquierda)
- ✅ Canvas con dibujos
- ✅ Cursor/puntero de mano
- ✅ Mensajes de estado (modo actual, instrucciones)
- ✅ Resultados de segmentación (efectos aplicados en regiones)
Proyector/monitor secundario:
- ✅ Canvas con dibujos
- ✅ Máscaras y efectos de segmentación aplicados
- ❌ Sin vista de cámara
- ❌ Sin cursor
- ❌ Sin texto de estado
Consejo: El proyector muestra una versión limpia ideal para presentaciones y arte en vivo.
NeuralPaint usa MediaPipe Holistic para detectar pose y manos. Los gestos se controlan con:
- Mano derecha: Puntero (índice) para dibujar/interactuar
- Brazo izquierdo: Comandos de modo
- Activar: Levanta ambos índices → modo
REGION_SELECT - Definir rectángulo: el índice izquierdo marca una esquina (ancla) y el índice derecho la esquina opuesta (candidato)
- Confirmar + elegir efecto (por flanco, sin soltar los índices):
- Dedo medio derecho arriba →
colorize - Dedo medio izquierdo arriba →
underline - Meñique izquierdo arriba →
aura
El sistema:
- Captura la región real de pantalla en las coordenadas seleccionadas
- La envía a la ResUNet de segmentación (
models/segmentation/fine_tuning_checkpoint_epoch_10.pthpor defecto) - La ResUNet produce una máscara binaria identificando trazos/texto en la región
- Aplica el efecto elegido (colorize/underline/aura) usando el color activo del pincel
- Superpone el resultado en el canvas exactamente en las mismas coordenadas de pantalla (persiste hasta limpiar con gesto o tecla
c)
| Tecla | Acción |
|---|---|
q |
Salir de la aplicación |
c |
Limpiar todo el canvas (igual que gesto de brazo arriba) |
python src/neural_paint.py --camera 0 \
--brush-thickness 6.0 \ # Grosor del trazo (px)
--erase-radius 50.0 \ # Radio de borrado (px)
--command-hold-frames 8 \ # Cuadros para confirmar gesto (evita falsos positivos)
--mode-toggle-delay 4.0 \ # Segundos para volver a inactivo tras gesto repetido
--smoothing 0.7 \ # Suavizado del puntero (0-1, mayor = más suave)
--min-detection 0.7 \ # Umbral de confianza MediaPipe
--preview-scale 0.3 # Escala de vista previa (0-0.5)NeuralPaint integra visión por computadora, procesamiento de gestos y redes neuronales en un flujo en tiempo real:
- OpenCV captura frames BGR del dispositivo (
--camera) - Se convierte a RGB para MediaPipe Holistic
- MediaPipe devuelve landmarks de:
- Pose (33 puntos): cuerpo completo, brazos
- Manos (21 puntos × 2): izquierda y derecha
- Homografía guardada (
calibration/homography.npz) mapea cámara → superficie virtual - Se calculó previamente con:
- Contorno brillante: detecta rectángulo blanco, ajusta 4 esquinas
- AprilTags: detecta rejilla de marcadores fiduciales, calcula homografía precisa
- Cada landmark se proyecta con
cv2.perspectiveTransform()a coordenadas del canvas
- Puntero primario: punta del índice derecho (landmark 8)
- Fallback: muñeca derecha si no hay mano detectada (pose landmark 16)
- Se aplica suavizado exponencial (
--smoothing) para estabilizar jitter
classify_left_arm_command(): analiza geometría del brazo izquierdo (hombro-codo-muñeca)- Brazo horizontal + antebrazo arriba →
DRAW_MODE - Brazo horizontal + antebrazo abajo →
ERASE_MODE - Brazo horizontal + antebrazo horizontal →
COLOR_PICKER - Brazo vertical arriba →
CLEAR_ALL
- Brazo horizontal + antebrazo arriba →
ArmGestureClassifier: acumula--command-hold-framesconsecutivos del mismo gesto para confirmar (evita falsos positivos)- Selección de región:
both_index_fingers_up()detecta ambos índices levantadosSelectionGestureTrackermaneja el flujo: índices arriba (ambos) → ancla izquierda + candidato derecho → confirma con dedo medio/meñique (selecciona efecto)
- Estado global:
InteractionMode(IDLE, DRAW, ERASE, COLOR_SELECT, REGION_SELECT) - Transiciones:
- Gesto confirmado → cambia modo
- Gesto repetido en modo activo → vuelve a IDLE tras
--mode-toggle-delaysegundos
- Modo DRAW: agrega puntos al trazo activo; finaliza al salir del modo
- Modo ERASE: itera trazos y borra segmentos que intersectan círculo de radio
--erase-radius - Modo COLOR_SELECT: muestra
ColorPicker(rueda HSV); detecta hover 3s → cambia color activo
StrokeCanvas: lista de trazos (secuencias de puntos(x,y)+ color)- Renderiza con
cv2.polylines()usando grosor--brush-thickness - Límite de
--max-strokes(por defecto 200); descarta los más antiguos al exceder - Canvas es RGB de tamaño
surface_width × surface_height
- Captura de región:
- Modo REGION_SELECT → usuario define rectángulo con dos manos
- Al confirmar con gesto: captura píxeles de pantalla reales con Win32 API (
OverlayWindow.capture_region()) - Región capturada se procesa directamente (sin homografía/warping, es lo que el usuario ve en pantalla)
- Red ResUNet (
Segmenter):- Script:
Reconocimiento de Caracteres/scripts/inference/testing.py(llamado vía subprocess) - Modelo por defecto:
models/segmentation/fine_tuning_checkpoint_epoch_10.pth(ResUNet con base=24, GroupNorm, SiLU, SE blocks) - Input: región capturada BGR (se redimensiona a 256×256 o procesa con sliding window si es más grande)
- Output: máscara de probabilidad float32 (0-1) del tamaño original
- Genera archivos debug en
masc_produced/:*_original.png(input),*_mask.png(máscara cruda)
- Script:
- Aplicación de efectos:
- Usuario selecciona efecto con gesto de mano durante la confirmación de la región
- No hay clasificación automática: el color/efecto aplicado es el color activo del pincel
apply_effect()colorea la máscara con el color BGR actual del usuario- Efecto se almacena como BGRA (color + alpha de máscara) y se superpone en el canvas en las coordenadas exactas de pantalla
- Efectos persisten hasta que se limpie el canvas (gesto brazo arriba o tecla
c)
OverlayWindow(requierepywin32en Windows):- Crea ventana transparente (WS_EX_LAYERED, WS_EX_TRANSPARENT)
- Usa
SetLayeredWindowAttributes()para hacer fondo transparente - Renderiza sobre el escritorio:
- Canvas con trazos
- Cursor circular (color actual + radio erase si aplica)
- Vista previa de cámara (esquina superior izquierda,
--preview-scale) - Banner de estado (modo actual, instrucciones)
- Dual-monitor:
- Monitor principal: overlay completo (UI + canvas)
- Proyector: overlay limpio (solo canvas + máscaras aplicadas, sin UI)
- Mensajes de estado:
- "Calibrando..." / "Guardado con 's', Cancelar con 'q'" (durante calibración)
- "MODO: DIBUJAR / BORRAR / COLORES / SELECCIÓN" (en overlay)
- "Segmentando región..." (durante procesamiento de segmentación)
- Indicadores:
- Cursor: círculo relleno del color activo (o rojo con radio en modo ERASE)
- Rectángulo de selección: borde amarillo animado durante ajuste
- Máscaras aplicadas: overlays coloreados sobre el canvas
mientras running:
1. Capturar frame de cámara
2. Procesar con MediaPipe → landmarks
3. Extraer puntero derecho + clasificar brazo izquierdo
4. Proyectar puntero a superficie con homografía
5. Actualizar clasificador de gestos → comando confirmado
6. Gestionar transiciones de modo según comando
7. Ejecutar lógica del modo activo:
- DRAW: agregar puntos al trazo
- ERASE: borrar trazos en radio
- COLOR_SELECT: detectar hover en rueda
- REGION_SELECT: manejar ancla + resize + confirmación
8. Renderizar canvas + overlay
9. Actualizar ventana(s) overlay (principal + proyector si dual-monitor)
10. Manejar input de teclado ('q', 'c')
| Archivo | Responsabilidad |
|---|---|
src/neural_paint.py |
Entry point CLI, parsing args, lanza calibración + app |
src/neuralpaint/app.py |
Loop principal, orquesta MediaPipe, gestos, canvas, overlay, segmentación |
src/neuralpaint/calibration.py |
Detección de contorno/AprilTags, cálculo de homografía |
src/neuralpaint/gestures.py |
Clasificación de comandos de brazo, detección de dedos, SelectionGestureTracker |
src/neuralpaint/strokes.py |
StrokeCanvas: gestión de trazos, renderizado, borrado |
src/neuralpaint/overlay.py |
OverlayWindow: ventana transparente Win32, captura de pantalla, dual-monitor |
src/neuralpaint/segmentation.py |
Segmenter: wrapper de ResUNet, invoca subprocess con checkpoint de segmentación |
src/neuralpaint/color_picker.py |
ColorPicker: rueda HSV, detección de hover, selección de color |
Reconocimiento de Caracteres/scripts/inference/testing.py |
Script de segmentación ResUNet (llamado por Segmenter vía subprocess) |
calibration/homography.npz: matriz 3×3 de homografía cámara→superficiemodels/segmentation/fine_tuning_checkpoint_epoch_10.pth: ResUNet fine-tuned (Stage 2 con weighted BCE), modelo por defectomodels/segmentation/checkpoint_epoch_70.pth: ResUNet de Stage 1 (BCE + edge loss), alternativamasc_produced/: inputs/outputs temporales de segmentación para debug (*_original.png,*_mask.png)
- Edita
classify_left_arm_command()en gestures.py - Añade un nuevo
CommandTypeal enum - Maneja el comando en app.py dentro del loop principal
- Define el gesto de confirmación en
SelectionGestureTracker.update()(gestures.py) - Implementa el efecto visual en
apply_effect()(segmentation_options.py) - El efecto recibe la máscara y el color del pincel activo para generar BGRA
- Entrena nuevo modelo con
Reconocimiento de Caracteres/scripts/training/train_segmentation_clean.py - Actualiza ruta en
Segmenter.__init__()(segmentation.py) - Asegura compatibilidad de entrada 256×256 y salida de máscara
overlay.py: modificarender_projector_overlay()para proyectorapp.py: ajustadraw_status_banner()para mensajes custom- Cambia colores en
StrokeCanvas.render()(strokes.py)
Entrenamiento de modelos: Ver Reconocimiento de Caracteres/README.md y STRUCTURE.md para pipeline de datos y HPO.




