lunes, 4 de mayo de 2015

Demos de funcionamiento

Aquí os dejo 2 vídeos de demo del funcionamiento del detector (imagen en negro = no hay puerta, imagen en rojo = hay puerta). El decisor tiene como umbral que si al menos 1/3 de los pixels de la imagen filtrada son 1, considera que hay puerta.




Explicación de los videos: las imágenes superiores son los canales H S y V de la imagen tomada en cada instante. Abajo a la izquierda está la imagen original que se obtiene de la cámara, en el centro abajo el filtro en el canal H y a la derecha el detector antes mencionado (rojo o negro, puerta o no puerta).

Más que la precisión del detector lo importante es ver como sin modificar los parámetros los resultados varían debido a los cambios de iluminación debido:
  1. Cambio de la imagen de rango visual a infrarrojo cuando disminuye la iluminación
  2. Cambio de las intensidades de la imagen por cambio del tiempo de obturación de la imagen
El primer caso se puede arreglar fijando si se va a trabajar en el visual o en el infrarrojo. Sin embargo el segundo problema afecta a medida que se van presentando objetos y escenas con mayor o menor cantidad de luz, lo que hace que la cámara cambie el tiempo de obturación y cambiando los valores de los pixels afectando al resultado del filtro.

domingo, 3 de mayo de 2015

Tasa de refresco

En las primeras pruebas al representar las imágenes con Python nos encontramos con el problema de que a pesar de que recibíamos varias imágenes tal y como le pedíamos a la cámara a través de Arduino, éstas eran durante varios ciclos del bucle la misma imágen. Aquí se puede ver un ejemplo tomando imágenes sin ninguna espera entre imágenes con un vídeo delante de la cámara.


Tras muchas pruebas se han llegado a las siguientes conclusiones.

  • La cámara es capaz de devolverte imágenes independientemente de el tiempo de espera entre cada imagen. Ahora bien, si no esperas entre cada toma de imagen un cierto tiempo, la cámara te devolverá una imagen, si, pero puede ser la misma imagen que la vez anterior ya que necesita un tiempo de refresco.
  • La cámara, aunque no hayamos esperado el tiempo suficiente para refrescar el frame, si le pedimos que tome otra foto y nos la mande nos responderá con las cabeceras correspondientes de que así se va a hacer, pero la imagen obtenida será la misma en varios ciclos. Esto quiere decir que la cámara ya está preparada para recibir nuevas peticiones, pero sin embargo el proceso de tomar una nueva imagen (en alguno de los pasos del proceso) no.
  • Es necesario realizar una espera entre cada toma de imagen para que la cámara se refresque, por lo que se ha probado con el código antes comentado, descomentando la línea que realiza una espera fija (delay(X);). Probando diferentes valores de X se ha llegado a la conclusión de que cuanto menor es la espera (X tiende a 0), mayor es el número de frames en los que la cámara envía la misma imagen. Sin embargo, si ponemos que X>=33 (milisegundos) los frames que obtenemos son siempre diferentes.

De las hipótesis manejadas la que más plausible nos ha parecido es pensar que esto se debe al software de la cámara y el comportamiento que establece. Se cree que en la primera imágen, puesto que la cámara aún no ha tomado ninguna imágen, hace una llamada bloqueante que no acaba hasta que ha terminado de tomar y procesar la imagen a JPEG, y obtenemos el primer frame. Sin embargo, para las peticiones sucesivas, se hacen 2 cosas, mandar orden de hacer foto y mandar orden de transmitir los datos. Al no haber ninguna espera entre estos 2 comandos y la cámara tener una imagen ya en memoria, al solicitarle datos del JPEG, la cámara nos devuelve la imagen que detecta que tiene en memoria (la del frame anterior) ya que aún no le ha dado tiempo a tomar el siguiente frame (necesita un tiempo para tomar la imagen de los sensores y hacer el procesado para pasar a JPEG). Esto ocurre durante varios ciclos, ya que el único momento que tiene la cámara para procesar la nueva imagen es el intervalo en el que arduino a recibido todos los datos y aún no le ha pedido la siguiente imagen (que son unos milisegundos en lo que tarda en volver al bucle). Durante ese periodo, continua el procesado del JPEG y cuando lo termina nos comienza a dar la nueva imágen.

Por todo lo anterior, la aproximación con un mejor rendimiento ha sido establecer una comunicación entre arduino y python para que el primero no pida una nueva imagen a la cámara hasta que el segundo no se lo diga. De esta forma podemos controlar la tasa de refresco con la espera que se utiliza para mostrar un frame en pantalla durante un tiempo (waitKey de OpenCV). De esta forma, hay que añadir al código de python y arduino la lógica bloqueante para la espera de intercambio de mensajes.

Lectores serial de Python


Los códigos de Python desarrollados se encuentran en bitbucket y son los siguientes:
  • serieport.py: muestra los bytes recibidos en varias de sus posibles representaciones (char, hexadecimal y decimal). Útil para hacerse una idea de lo que se está transmitiendo y con qué tipo.

  • imagereader.py: detecta la cabecera de comienzo y fin de un JPEG para almacenar la imagen en un fichero auxiliar llamado output.jpg. El fichero se va sobrescribiendo a medida que se van tomando nuevas imágenes.
  • imagesreader.py: similar al anterior pro guardamos las imágenes en una carpeta images con un índice para distinguirlos.
  • imagevisor: va mostrando las imágenes que detecta por el puerto serie en una ventana mediante OpenCV.
El código utilizado para estas pruebas es un bucle sin espera en el que vamos pidiendo imágenes a la cámara y transmitiendolas por el puerto serie al PC sin ninguna espera.


En este punto podemos ver como recibimos imágenes de la cámara y las representamos con OpenCV.
Sin embargo, se han encontrado problemas relacionados con la tasa de refresco de las imágenes, que se contará más adelante.

domingo, 26 de abril de 2015

Python y sus tipos básicos

Uno de los problemas encontrados al realizar el lado cliente de la comunicación serie es el limitado abanico de tipos de Python.
Esta tabla es muy representativa al respecto:
Type codeC TypePython TypeMinimum size in bytes
'b'signed charint1
'B'unsigned charint1
'u'Py_UNICODEUnicode character2 (see note)
'h'signed shortint2
'H'unsigned shortint2
'i'signed intint2
'I'unsigned intint2
'l'signed longint4
'L'unsigned longint4
'f'floatfloat4
'd'doublefloat8


De cara al uso de streams caben destacar dos peculiaridades:
  * No existe char, es un str (string) de longitud 1.
  * No existe byte, solo int.

Por tanto, cuando leemos de un Stream, se lee y escribe en modo str.

Esta situación nos lleva a una peculiaridad ya que para comparar bytes y visualizar las imágenes necesitamos int (==char), y por tanto un casting.
Pero si luego queremos guardar dichas imágenes es necesario volver a str (==char).

Estas conversiones de tipos se consiguen con ord() y char().

Páginas relevantes:
https://docs.python.org/3.1/library/array.html
http://www.tutorialspoint.com/python/python_variable_types.htm

De vuelta, y funcionando

Mucho ha llovido desde la última vez.
Mayoritariamente porque la actividad se ha movido a bitbucket y a google groups.

Sin embargo, merecía la pena contar las buenas noticias.
Tras bastante desarrollo, incluida la API bloqueante, la corrección de numerosos bugs y de localizar las causas por las que la cámara podía bloquearse, ya se tiene una versión plenamente funcional.
Código disponible en:
https://bitbucket.org/jjv_team/link_sprite
https://bitbucket.org/snippets/jjv_team/KrbA/lscam-triggered-photo-loop

Este es el vídeo demo:

Se han descubierto muchas limitaciones. Pero eso ya se discutirá más adelante.

miércoles, 15 de abril de 2015

Python - Serial port

Se quiere conectar el puerto serie de Arduino con algún programa externo para poder depurar y visualizar las imágenes que se están obteniendo a través de la cámara LinkSprite. Se ha decidido desarrollar unos pequeños códigos de prueba en Python con este propósito ya que se puede desarrollar rápidamente en este lenguaje y tiene la librería de OpenCV para un posterior procesado si se da el caso.

Para la conexión de Python con Arduino se ha hecho uso de la librería Pyserial que simplifica enormemente la comunicación.

- Página oficial: http://pyserial.sourceforge.net/
- Instalación para Windows: http://petrimaki.com/2013/04/28/reading-arduino-serial-ports-in-windows-7/
- Ejemplo de uso (con fallo): http://stackoverflow.com/questions/12117669/python-read-serial-output-from-arduino

Inicialización de puerto serie en Python en windows:
ser = Serial('COM6',115200,timeout=2)  # El número del COM lo miramos en Arduino->Herramientas->Puerto
Inicialización de puerto serie en Python en linux:
ser = Serial('/dev/tty.usbmodem621',115200,timeout=2)  # El número del COM lo miramos en Arduino->Herramientas->Puerto

Es importante tener en cuenta el timeout, ya que si le ponemos valor 0 lo estaremos configurando para hacer lectura no bloqueante, por lo que hay que establecer algún timeout (o dejarle con valor None para espera indefinida) para hacerlo bloqueante y ser consistente con el código de la librería LinkSpriteCam.

sábado, 11 de abril de 2015

LinkSprite - Logs de ejecución (deprecated)

La mayor parte de los logs de ejecución de estas fechas tempranas de desarrollo están contaminadas por dos problemas:
  1. Un error de programación, por el cual no se estaba reseteando a dirección de lectura (a pesar de que ya se conocía este mismo error en el código original y ya se disponía de una función que contemplaba este caso).
  2. Un problema del dispositivo, que al estar brikeado (soft-brick), toda prueba era errónea.


Se han movido dichos logs a una sección secundaria para facilitar la lectura del blog: