Y que hacemos todos cuando no tenemos tiempo para un proyecto?... Claro. Montamos algo rapidito en Python o una aplicación web. Yo me decanté por Python... los entresijos de WebRTC los dejaremos para otra ocasión. Y bueno, esto es lo que me salio.
Módulos
Una de las grandes ventajas de Python es que hay módulos para todo. En su día este era el caso de Perl y en ciertos ámbitos Java.... pero ahora es Python así que vamos a ello.
Para este proyecto decidí usar OpenCV
para la captura de las imágenes, y PyGame
, para el interfaz. La verdad que solo vamos a utiliza una ínfima parte de ambas librerías... pero es lo que hay si queremos que nuestro FotoMatón ocupe solo 50 líneas.
Como este artículo va de cosas mú rápido... Pues os miráis como instalar estos dos módulos en vuestros sistemas. En Ubuntu sería algo tal que así:
$ sudo apt-get install python-opencv $ pip install pygame
Declaraciones e Inicializaciones
Lo primero que nos encontramos es el clásico bloque de imports
y la declaración de algunas variables... Nada especial
import pygame
from pygame.locals import *
import cv2
import numpy as np
import time
display_width = 1920 # Resolución horizontal de la pantalla
display_height = 1080 # Resolución vertical de la pantalla
cnt = 1 # Contador de captura
show_capture = 0 # Flag que indica se mostramos el video o la captura
Los nombres son auto-explicativos... creo... si sabes algo de inglés.... Lo siento... en inglés todo ocupa menos.
A continuación inicializamos la captura de video utilizando el módulo cv2
# Inicializamos Captura de video y resolución de cámara
camera = cv2.VideoCapture(0)
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT,720)
También auto-explicativo no?. Como podéis imaginar, si disponéis de más de una cámara, podréis acceder a cada una de ellas cambiando el índice en la llamada a VideoCapture
.
# Inicializamos pygame y pantalla
pygame.init()
pygame.mixer.quit() # Gracias Stackoverflow. Reduce use de CPU
screen = pygame.display.set_mode([display_width,display_height])
Finalmente inicializamos PyGame
y creamos la ventana de la aplicación con la resolución que configuramos en las variables al principio del script. La llamada a pygame.mixer.quit()
reduce el uso de CPU drásticamente. Parece ser algún tipo de bug, pero como siempre, stackoverflow
nos ha salvado la vida.
El Bucle Principal
El bucle principal del programa es igualmente sencillo. Mientras no hayamos sacado una foto, captura imágenes de la cámara. Al pulsar la barra espaciadora, grabamos la imagen actual y dejamos de capturar por dos segundo de forma que podemos ver la foto que acabamos de sacar.
Todo muy sencillo. Aquí tenéis el código de la parte que captura las imágenes y las visualiza:
try:
while True:
if show_capture == 1:
t1 = time.time()
if t1 - t0 > 2: # Mostramos la captura por 2 segundos
show_capture = 0
else:
ret, frame_orig = camera.read()
frame = cv2.cvtColor(frame_orig, cv2.COLOR_BGR2RGB)
frame = np.rot90(frame)
frame = cv2.resize (frame, (display_height, display_width))
frame = pygame.surfarray.make_surface(frame)
screen.blit(frame, (0,0))
pygame.display.update()
Como podemos ver, para poder visualizar correctamente la imagen capturada, debemos hacerle un par de cosillas. Lo primero es re-ordenar las componentes de color de los pixels. Probad a eliminar esa línea. Bueno, OpenCV almacena las imágenes capturadas como BGR, pero pygame necesita RGB para generar las surfaces
que luego mostraremos en la pantalla.
Igualmente, tenemos que rotar la imagen 90 grados para que la imagen se muestre correctamente.
Finalmente, la llamada a resize
escala nuestra imagen al tamaño de la ventana, de forma que el video se muestra a pantalla completa.
Interactuando con el Usuario
La última parte del programa se encarga de procesar los eventos. Específicamente la tecla ESC
para salir de la aplicación, y la tecla SPACE
para capturar una imagen.
# Procesa eventos
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_SPACE: # SPACE graba la foto e inicializa timer
show_capture = 1
t0 = time.time()
cv2.imwrite ("captura{:06}.jpg".format(cnt), frame_orig)
cnt +=1
if event.key == K_ESCAPE: # ESC termina la aplicación
raise (SystemExit)
except KeyboardInterrupt,SystemExit:
pygame.quit()
cv2.destroyAllWindows()
De nuevo, sin sorpresas aquí. Si pulsamos la barra, inicializamos el temporizador (tomamos la hora actual), indicamos que hemos tomado una foto, de forma que el otro bloque de código en el bucle principa pare de capturar imágenes por un rato y guardamos la imagen actual como un JPG en el disco. La variable cnt
la utilizamos para generar nombres de fichero distintos. Probablemente fuera mejor idea utilizar la hora actual que, después de todo, estamos obteniendo justo antes de grabar la imagen. Pista: time.strftime.... ahí lo dejo.
Si pulsamos ESC
lanzamos la excepción SystemExit
, para salir del bloque try
. Lo normal es utilizar sys.exit(0)
, pero para evitarnos el import sys
al principio y ahorra una línea .... bueno, pues lo hemos hecho así.
Aires de Fiesta
Como diría Karina... Los chicos y chicas cargados de felicidad. Ya se me va la pinza otra vez. Al lío. Pulsar la barra está muy chulo, pero, asumámoslo, tener un montón de gente borracha tocando tu portátil y bebidas a punto de derramarse continuamente no es el entorno más seguro para nuestro preciado ordenador.
Así que vamos a añadir un botón a nuestro FotoMatón para que nos quede perfecto para nuestra fiesta. Y luego que ya cada uno ponga el botón físico que más guste.
Tenemos algunas opciones para hacer esto. Podríamos usar algún SBC y conectar nuestro botón a un GPIO y escribir un pequeño programa que haga uso de uinput
. O podemos utilizar un microcontrolador que simule un teclado y que al pulsar el botón genere el código de la barra espaciadora.
La solución del microcontrolador ya la vimos en el número 6 de la primera época, así que vamos a explorar la opción de uinput
, la cual, por otra parte nos abre un infinito mundo de posibilidades: desde botones remotos a disparo por voz!
Pulsando la Barra sin Pulsarla
Vamos a seguir con Python, porque ya sabéis que es mejor no mezclar. Para ello debemos instalar el módulo python-uinput
pip2 install python-uinput
Y ahora ya podemos crear un script que pulse la barra
#!/usr/bin/env python2
import uinput
import time
with uinput.Device([uinput.KEY_SPACE, uinput.KEY_ESC]) as device:
time.sleep(1)
device.emit_click(uinput.KEY_SPACE)
Si estáis usando algún SBC podéis combinar este código con la lectura de una GPIO para lanzar la captura de una imagen. Observad que hemos tenido que añadir una pequeña espera antes de enviar el código de la barra espaciadora. Parece ser que es necesario esperar un poco desde que el dispositivo se crea hasta que se puede utilizar.... Si no sabéis de que estamos hablando quedaros con que hay que esperar un rato... al menos en nuestro sistema.
Para probar este script la forma más sencilla es con una línea como esta:
$ ./fb_simple.py & (sleep 4; sudo ./barra.py)
Observad que el uso de uinput
requiere permisos especiales, así que lo ejecutamos con sudo... debemos ejecutarlo al menos una vez para introducir el password que nos pide sudo. También podéis lanzar el sleep
y el script desde otro terminal y luego lanzar el FotoMatón. Quizás necesitéis un retardo más largo.
Si queréis evitar el problema con sudo
, deberéis hacer lo siguiente:
- Crear un grupo de usuarios por ejemplo
uinput
- Añadir el usuario con el que queráis acceder al dispositivo a este grupo
- Cambiar los permisos de dispositivo (
/dev/uinput
) para dar acceso al grupo - Normalmente tendréis que reiniciar la sesión para que los cambios en los grupos tengan efecto
Rizando el Rizo
Con estos dos scripts podemos hacer algunas virguerías interesantes como disparar nuestro FotoMatón usando voz o a través de la red... con nuestro teléfono por ejemplo.
Una forma sencilla de hacerlo sería la siguiente. En primer lugar, lanzamos nuestro FotoMaton. En otra consola ejecutamos el siguiente comando (suponemos que hemos arreglado lo de los permisos y no es necesario usar root... de lo contrario ejecuta todo el comando como root para probar):
$ nc -w 2 -v -l 5000 ; ./barra.py
Esto iniciará netcat
en modo servidor con un timeout de 2 segundos. El timeout lo necesito yo para dar el focus a la ventana del FotoMaton (mover el ratón a la otra ventana :) una vez que haya intentado acceder a http://127.0.0.1:5000
desde mi explorador.... De lo contrario el espacio se enviaría al browser, en lugar de al Fotomatón. Si siempre me ha gustado el Sloopy Focus. Si Estáis utilizando otro dispositivo, como un teléfono móvil, el timeout no es necesario.
Mejorando el interfaz
Esta solución funciona perfectamente, pero es un poco fea ya que en nuestro explorador vamos a obtener un error de conexión y vamos a dar una imagen un poco cutre en nuestra fiesta. Así que vamos a hacer que netcat sirva una página por defecto para que la cosa tenga mejor pinta.
Para ello creamos un fichero con el siguiente contenido:
HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 Server: FotoMatón Occam's <!doctype html> <html><body> <center> <h1>CHEESE!</h1> <a href="index.html#">Foto</a> </center> </body></html>
Como podéis ver, el fichero contiene todo lo que queremos enviar al browser. Primero las cabeceras HTTP y luego la página HTML. Ahora podemos lanzar nuestro teclado virtual con una línea como esta:
$ cat respuesta.http | nc -w 2 -v -l 5000 ; ./barra.py
Lo cual funciona perfectamente.

Así que ahora solo tenemos que poner esta línea en un bucle y tira millas.
$ while (true); do cat respuesta.http | nc -w 2 -v -l 5000 ; sudo ./barra.py; done
Durante las pruebas utilicé firefox en mi ordenador... para no tener que andar con otro dispositivo a vueltas y observe una cosa muy rara. Cada vez pasaba el ratón sobre el enlace para sacar fotos, se producía una nueva conexión a netcat claro, se sacaba una foto. Este problema no lo tenemos con el teléfono ya que no existe el concepto de hover
y chromium no lo hace, así que pude terminar mis pruebas sin mayores inconvenientes.
ESTO ES TODO.... O NO?
Aquí lo vamos a dejar, pero sin antes daros algunas ideas para extender este pequeño proyecto. Ahí van:
- Añadir una galería
- Control por voz
- Botón remoto con ESP-32
- Multi-Foto
- Compartir directamente en redes sociales
Esperamos vuestras experiencias. No seáis tímidos. Hasta el próximo número
■