"Después del juego es antes del juego"
Sepp Herberger

martes, 21 de octubre de 2014

Sé lo que hicistéis la última sesión...

En algunos momentos nos interesa registrar quién esta sentado en el ordenador y que está haciendo. Para llevar a cabo esto he estado trabajando en la idea de hacer un script que se ejecute en el arranque del sistema y, cada cierto tiempo, haga una captura con la webcam y del escritorio, fusione ambas imágenes en una sola y la guarde en un directorio. Esas imágenes podremos consultarlas luego o bien procesarlas en el momento, enviándolas por correo electrónico con mailsend o subiéndolas a algún servidor.

El resultado buscado debe ser tal que así:

Vamos a ver los distintos pasos que he dado.

1) Capturar un fotograma con la webcam.

Para capturar una foto desde la webcam hay multitud de programas. He probado en concreto guvcview, fswebcam, uvccapture y streamer con diferente fortuna. Todos están disponibles en los repositorios y se pueden instalar con
sudo apt-get install <programa>
Un inconveniente encontrado es que si la cámara tiene led se encenderá un instante al hacer la captura, lo cual será un problema si queremos que el programa funcione "silenciosamente". Hay opciones para deshabilitar ese led en algunas webcam, pero eso depende del modelo de la misma y yo no he tenido suerte con las probadas. A cada cual le tocará investigar por su cuenta si existe esa posibilidad y como llevarla a cabo.
 
Aquí están los comandos para capturar un fotograma con los distintos programas probados:
guvcview -d /dev/video0 --nodisplay -m 1 -r 1 -f yuyv -i "snapshot.jpg" -c 2 --exit_on_close

uvccapture -d/dev/video0 -x320 -y240 -t300 -q75 -B31 -C29 -S80 -w -v -t0  -o"snapshot-1.jpg"

fswebcam --scale "320x240" "snapshot-1.jpg"

streamer -c /dev/video0 -o "snapshot-1.jpg"
He probado con un portátil con cámara integrada y dos webcam usb, una china sin marca y una Eyetoy de la consola Sony PlayStation. Dependiendo de la cámara usada he tenido algunas peripecias con los distintos programa, que paso a relatar:
  • En mi portátil uvccapture deja el led de la webcam encendido al hacer la primera captura y ya se queda asi para siempre. Es bastante molesto.
  • uvccapture, fswebcam o streamer a veces no hacen la captura y lo único que sacan es un jpeg con algunas rayas en la parte superior y el resto de la imagen en verde, quédandose en ocasiones así para siempre y obligándome a parar el script y entrar  en guvcview, que resetea la cámara. Por lo que he visto es un problema del driver uvcvideo y depende de la cámara. De ellos, uvccapture es el que se mantiene mas estable, pero aún asi a veces falla.
  • guvcview, que en principio no es un programa de captura de linea de comandos (ya que tiene entorno gráfico, aunque podemos anularlo) es el que mejor me ha funcionado de todos y el que menos tendencia tiene al error que deja la imagen capturada en verde, por eso es el que he dejado en el script. Es un poco aparatoso pero así garantizo que funciona con la cámara que voy a usar.
  • Con la cámara Eyetoy no funcionan guvcview ni uvccapture, en cambio fswebcam funciona de una forma excepcionalmente estable.
Así que cada cual debe probar con los cuatro programas de captura de webcam y elegir el que mejor resultado le de. En mi caso ha sido guvcview usado sin entorno gráfico.

En el caso de que la cámara no esté conectada o esté deshabilitada al ejecutar el script utilizo la siguiente imagen rellenar el hueco:

2) Configurar niveles brillo, contraste, etc, de la webcam desde línea de comandos.

Al comienzo del script es conveniente configurar la cámara con los niveles de brillo, contraste, saturación etc, adecuados. Eso se puede hacer con v4l2-ctl y con uvcdynctrl, habiéndome dado mejores resultados el segundo. Los valores adecuados se pueden obtener ejecutando guvcview y  jugando con los controles hasta obtener resultados satisfactorios, tomando nota de ellos luego. Aquí va una guía rápida:
 
1) Con v4l2-ctl
 
Listar parámetros configurables:
v4l2-ctl --list-ctrls
 
Ejemplo de configuración de parámetros:
v4l2-ctl --set-ctrl brightness=33,contrast=29,saturation=80,hue=-16,gamma=112,sharpness=24,backlight_compensation=1
 
2) Con uvcdynctrl (sólo si la cámara usa el módulo del kernel uvcvideo)
 
Listar parámetros configurables:
uvcdynctrl -d /dev/video0 -c
 
En mi caso son: Brightness, Contrast, Saturation, Hue, Gamma, Power Line Frequency, Sharpness y Backlight Compensation
 
Dar valor a un parámetro concreto:
uvcdynctrl -d /dev/video0 -s "Brightness" -- 1
 
Guardar en un fichero configuración actual, para cargarla mas tarde:
uvcdynctrl -d /dev/video0 -W uvcvalues.txt
 
Cargar de un fichero configuración:
uvcdynctrl -d /dev/video0 -L uvcvalues.txt
 
La mejor forma de configurar la cámara es abrir guvcview y retocar los controles hasta que queden como queremos, luego guardamos la configuración con el comando uvcdynctrl -d /dev/video0 -W uvcvalues.txt,. Esa es la configuración que cargaremos luego en el script con uvcdynctrl -d /dev/video0 -L uvcvalues.txt
 
 
Algunas cámaras tienen configuraciones adicionales (por ejemplo, control del led) con el parámetro --import=filename, si algún hacker ha definido el fichero XML adecuado con los parámetros de tu webcam.
 
3) Hacer una captura de pantalla desde línea de comandos y desde fuera de la sesión Xwindows.
 
Hay muchas utilidades para hacer capturas de pantalla desde línea de comandos, pero la elegida tenía que cumplir estas condiciones:
  • Permitirme hacer capturas desde la consola de texto, fuera de la sesión de las X, ya que el script se ejecuta en el arranque del sistema.
  • Permitirme hacer capturas con el usuario root y con él capturar la sesión X de otro usuario distinto, que será el que habrá iniciado la sesión en el entorno gráfico.
Probé varios programas con mal resultado, pero al final encontré el programa que me permitía esto: scrot. El comando a ejecutar es para capturar la sesión del usuario que está usando el entorno gráfico desde un demonio de consola sería:
chvt $tty; sleep 5; DISPLAY=:0 XAUTHORITY=$home/.Xauthority scrot "$ruta/screenshot.jpg"
Siendo $tty el terminal virtual donde se ejecuta la sesión a capturar (si está en tty7, seria "7"), y $home el home del usuario que está ejecutando dicha sesión. El comando chvt hace un cambio de terminal virtual (equivalente a un CTRT-ALT-Fx manual) y el DISPLAY=:0 XAUTHORITY=$home/.Xauthority da permiso a scrot para realizar la captura de una sesión X ajena.
 
Cabe destacar que además, antes de realizar la captura compruebo si algún usuario ha iniciado realmente la sesión en las X (que sería la sesión "(:0)"), ya que en caso contrario no hay nada que capturar. Esto lo hago con:
user=$(who | grep "(:0)" | cut -f1 -d" ")

4) Juntando todo.

La fusión de ambas imágenes y la adición de una "barra de título" con la fecha, hora y usuario logueado las hago con utilidades del paquete imagemagick, en concreto con la navaja suiza llamada convert.

Adicionalmente he incluido estas otras características:

  • Sólo se hacen capturas entre las 8 y las 23 horas.
  • Si ningún usuario ha iniciado sesión en las X (busco usuarios con who que estén en el terminal "(:0)"), no hago captura de pantalla, sólo de webcam.
  • Miro la existencia de un fichero llamado "semaforo" en el directorio donde guardo las imágenes. Antes de cada captura, se comprueba si no existe o existe y su contenido es "1", en cuyo caso se hacen las capturas. En caso contrario no se hacen. Esto es para poder entrar por ssh a la máquina y desactivar las capturas de una manera sencilla.

Aquí está el script resultante, con comentarios inline adicionales sobre diversos aspectos del funcionamiento:

#!/bin/bash

dispositivo="/dev/video0"
resolucion_snapshot="320x240" #La resolución es mínima pero para mi suficiente. Por supuesto, es modificable sin problema.
resolucion_screenshot="426x240"
delay=300s
ruta="/root/capture"
semaforo="$ruta/semaforo"

#Resetea el dispositivo con valores de brillo, contraste, etc, que den mejor resultado
#Esta orden pude meterse en el bucle para evitar que el usuario los manipule entre capturas.
uvcdynctrl -d $dispositivo -L "$ruta/uvcvalues.txt"

# Si el USB se pone en suspensión y apaga la cámara, hacer esto para evitarlo (hay que hacerlo como root):
#   echo -1 > /sys/module/usbcore/parameters/autosuspend

while true
do
  #Fecha y hora actual
  fichero=$(date +"%Y-%m-%d.%T")
  hora=$(date +"%H")

  #Mira en el fichero semaforo. Si su contenido es 1 o no existe, hace la captura. Si existe y es distinto de 1, no la hace
  capturar=1
  test -f $semaforo && capturar=$(cat $semaforo)

  #Solo hacemos capturas entre las 8:00 y las 22:59, modificar según necesidades.
  if [ $hora -ge 8 -a $hora -le 23 ] && [ $capturar -eq 1 ]
  then

    #Limpia snapshot previos.
    cd $ruta
    rm -f snapshot*.jpg

    #Si existe el dispositivo de video (camara conectada y activada)
    if [ -e $dispositivo ]
    then
       guvcview -d $dispositivo --no_display -m 1 -r 1 -f yuyv -i "$ruta/snapshot.jpg" -c 2 --exit_on_close  
       #La salida de guvcview se guarda en snapshot-1.jpg, ya que numera los ficheros de salida.
    
       #Otras opciones posibles:
       #    uvccapture -d/dev/video0 -x320 -y240 -t300 -q75 -B31 -C29 -S80 -w -v -t0  -o"$ruta/snapshot-1.jpg"
       #    fswebcam --scale $resolucion_snapshot "$ruta/snapshot-1.jpg"
       #    streamer -c /dev/video0 -o "$ruta/snapshot-1.jpg"

    else #Si no encontramos la cámara, ponemos la carta de ajuste por poner algo.
       cp "$ruta/carta-ajuste.jpg" "$ruta/snapshot-1.jpg"
    fi

    #Mira si hay usuario logueado en las X, en el escritorio :0
    user=$(who | grep "(:0)" | cut -f1 -d" ")
    if [ -n "$user" ]
    then
          #Hay usuario logueado, vamos a hacer una captura de la pantalla de la sesión X donde está
          tty=$(who | grep "(:0)" | awk '{print $2}')
          tty=${tty:3}  # Quita los tres primeros caracteres del ttyX, o sea: "tty8" => "8"
          home=$(cat /etc/passwd | grep $user | cut -d":" -f6)

          #Cambia al tty donde está el usuario, espera un rato hasta que se carga, usa el XAUTHORITY del usuario para tener acceso a las X
          #y hace el screenshot mediante el comando "scrot".

          chvt $tty; sleep 5; DISPLAY=:0 XAUTHORITY=$home/.Xauthority scrot "$ruta/screenshot.jpg"

          #Ajusta las imagenes a la resolucion correcta
          convert "$ruta/screenshot.jpg" -resize $resolucion_screenshot "$ruta/${fichero}-screen.jpg"
          convert "$ruta/snapshot-1.jpg" -resize $resolucion_snapshot "$ruta/snapshot.jpg"

          #Une las imagenes y pone el banner.
          convert "$ruta/snapshot.jpg" "$ruta/${fichero}-screen.jpg" +append "$ruta/$fichero.jpg"
          etiqueta="$fichero - usuario: $user"
          convert "$ruta/$fichero.jpg"   -background Orange  label:"$etiqueta" +swap  -gravity Center -append "$ruta/${fichero}-snap.jpg"

          #Limpieza
          rm  "$ruta/snapshot.jpg" "$ruta/${fichero}-screen.jpg" "$ruta/$fichero.jpg" "$ruta/screenshot.jpg"
     else
          #No se hace captura de pantalla, solo se usa la de la cámara.
          etiqueta="$fichero - Sin usuario logueado"
          #Une las imagenes y pone el banner.
          convert "$ruta/snapshot-1.jpg" -resize $resolucion_snapshot "$ruta/snapshot.jpg"
          convert "$ruta/snapshot.jpg"   -background Orange  label:"$etiqueta" +swap  -gravity Center -append "$ruta/${fichero}-snap.jpg"

          #Limpieza
          rm "$ruta/snapshot.jpg"
     fi
  fi
  #Espera antes de repetir
  sleep $delay
done

El script debe ser arrancado como root en el inicio del sistema. La forma mas sencilla es poniéndolo en /etc/rc.local, antes del "exit 0". En este caso es necesario invocar al script añadiendo & al final, para que quede ejecutándose como root en el background.

/root/capture/capture.sh &

En el directorio /root/capture tendremos los siguientes ficheros:

  • capture.sh: es el script mostrado hace unas líneas. Debe estar con permisos 750.
  • carta-ajuste.jpg, descargable desde aquí.
  • uvcvalues.txt, generado por nosotros a mano con el comando uvcdynctrl -d /dev/video0 -W uvcvalues.txt tras ajustar el brillo, contraste, etc, de la cámara a mano abriendo guvcview.

5) Ampliaciones interesantes.

Dos cuestiones que quedo pendientes y que tendría que explorar algun día son:
 
1) Añadir una opción para guardar las imágenes solamente cuando hay una cara humana delante del ordenador. Existen varias APIs open source para la "face detection" buscando en Google, pero no me he puesto en serio con ello todavía.
 
2) Apagar el LED de la cámara cuando se toma la foto. Esto es muy dependiente del modelo de cámara y no siempre se puede hacer. Si es una Logitech, buscando V4L2_CID_LED1_MODE_LOGITECH en Google puede aparecer información sobre como hacerlo con mpeg_streamer. Un punto de partida sería este enlace:
 
 
Por otro lado, uvcdynctrl con el parametro --import puede importar un fichero XML que permite acceder a configuraciones particulares de algunas cámaras, entre ellas el control del estado de los led. Por desgracia las cámaras con las que he probado no tienen XML generado para ellas, asi que no he podido probarlos. Quizá tú tengas mas suerte con tu cámara, pero tendrás que googlear para encontrarlo.

6) Referencias.

Como siempre, me he subido a hombros de gigantes. De aquí he sacado las ideas para llevarlo todo a cabo:

Y ya está, como decía el Control Central de Programas en Tron:

Fin de impresión.

2 comentarios:

  1. una pregunta...
    por que la linea guvcview -d /dev/video0 --nodisplay -m 1 -r 1 -f yuyv -i "snapshot.jpg" -c 2 --exit_on_close
    las opciones --nodisplay --exit_on_close no me las reconoce????
    claro al quitarlas pues el programa sale en primer plano y hasta que no lo cierro no hace otro ciclo...
    me puedes ayudar??

    ResponderEliminar
    Respuestas
    1. Hola, prueba a hacer esto:

      # guvcview -?

      guvcview 1.7.1
      file guvcview_video.mkv has extension type 1
      file guvcview_image.jpg has extension type 0
      Uso:
      guvcview [OPCIÓN…] - local options

      Opciones de ayuda:
      -?, --help Mostrar opciones de ayuda
      --help-all Muestra todas las opciones de ayuda
      --help-gtk Mostrar opciones GTK+

      Opciones de la aplicación:
      --version Imprimir versión
      -v, --verbose Exhibir information de depuracion
      -d, --device=VIDEO_DEVICE Dispositivo de video [pred: /dev/video0]
      -a, --add_ctrls Salir después de añadir los controles de extensión UVC (requiere root/sudo)
      -o, --control_only No hacer streaming de vídeo (sólo controles)
      --no_display No mostrar interfaz gráfica
      -r, --capture_method=[1 | 2] Método de captura (1-mmap (predeterminado) 2-read)
      -g, --config=FILENAME Archivo de configuración
      -w, --hwd_acel=[1 | 0] Accel. del hardware (habilitar(1)|deshabil.(0))
      -f, --format=FORMAT Pixel format(mjpg|jpeg|yuyv|yvyu|uyvy|yyuv|yu12|yv12|nv12|nv21|nv16|nv61|y41p|grey|y10b|y16 |s501|s505|s508|gbrg|grbg|ba81|rggb|bgr3|rgb3)
      -s, --size=WIDTHxHEIGHT Resolución (defect: 640x480)
      -i, --image=FILENAME Archivo de Imagen
      -c, --cap_time=TIME Intervalo de captura Imagen en segundos
      -m, --npics=NUMPIC Número de imagens a capturar
      -n, --video=FILENAME Archivo video (captura del arranque)
      -t, --vid_time=TIME Tiempo de la captura video (segundos)
      --exit_on_close Salir de guvcview después de cerrar el vídeo
      -j, --skip=N_FRAMES Número de fotogramas iniciales a omitir
      -p, --show_fps=[1 | 0] Exhibir FPS (habilitar(1) | deshabilitar(0))
      -l, --profile=FILENAME Cargar perfil al inicio
      --display=VISOR Visor [display] X que usar

      Como puedes ver las opciones --nodisplay y --exit_on_close si que me salen con guvcview 1.7.1. ¿Cual tienes tu?.

      Fijate que ambas comienzan por -- (dos guiones) en lugar de - (un solo guión), que suele ser lo normal.

      Eliminar