Detección de paso con el sensor de distancia

Después del parón del verano volvemos con un proyecto pequeñito. Después de añadir movimiento (Añadiendo movimiento: servos) descubrimos que podemos orientar la cámara en una habitación (Una cámara móvil), pero casi nunca vemos nada interesante (es difícil encontrar el momento adecuado).

Sentía curiosidad por ver cómo funcionaría un sensor de movimiento y decidí comprar un par de HC-SR04, que funcionan con ultrasonidos.

Ojos que no ven

A post shared by Fernando Tricas García (@ftricas) on

El objetivo es disparar una fotografía cuando alguien pase por delante de la cámara: para ello medimos la distancia al obstáculo que haya enfrente del sensor y cuando cambie tenemos que suponer que hay alguien o algo en la trayectoria.

Tras unas primeras pruebas con la Raspi el resultado no era satisfactorio: se obtienen medidas aproximadas (y relativamente sencillas de filtrar, eliminando los valores desvidados) pero que no resultaban apropiados para lo que teníamos pensado.
Se puede ver, por ejemplo, un tutorial en HC-SR04 Ultrasonic Range Sensor on the Raspberry Pi.

La conexión del sensor a nuestra raspi.

Probando el sensor de distancia #raspi

A post shared by Fernando Tricas García (@ftricas) on

Parece que el problema de precisión tiene que ver con la medición del tiempo (que es lo que se usa para medir la distancia con este tipo de sensores: el tiempo que tarda en ir y volver hasta el obstáculo un frente de ondas sonoras), que no es muy preciso en la Raspberry con su GNU/Linux.

Afortunamdamente, teníamos a mano un Arduino y decidimos probar si era más adecuado. Esto nos permitiría:

– Medir distancias de manera más precisa.
– Aprender a comunicar la Raspberry y el Arduino.

Y, seguramente, abrir la puerta a nuevos proyectos.

El arduino conectado al sensor:

Probando el sensor de distancia #arduino #raspi

A post shared by Fernando Tricas García (@ftricas) on

Siguiendo las pistas de HC-SR04 Ultrasonic Sensor fue fácil preparar el programita (sketch) de arduino y hacer las conexiones (se puede ver en sketch.ino en su versión actual, puede haber cambios después).

Efectivamente, la medición era mucho más precisa: de vez en cuando, manteniendo fijo el sistema hay una variación de un centímetro o dos. Pero eso no es un problema cuando tratamos de detectar la presencia de un objeto más o menos grande (como una persona) en la trayectoria porque debería haber una modificación de más de 20 cm.

Ahora había que comunicar el arduino con la Raspberry (para reaprovechar código existente y no tener que empezar de cero en el nuevo aparatito).
Parece que hay varias formas de comunicarlos: mediante un puerto serie sobre USB (Connect Raspberry Pi and Arduino with Serial USB Cable), mediante I2C (Raspberry Pi and Arduino Connected Using I2C) y mediante GPIO (Raspberry Pi and Arduino Connected Over Serial GPIO).
Elegí la primera porque me pareció la más sencilla de poner en funcionamiento (aunque no descarto probar las otras).

Lo que envía el arduino se puede leer fácilmente en la Raspberry como una entrada de texto y sólo tenemos que procesar adecuadamente:

distAnt=0
...

while 1:
	distAnt = dist
	dist = int(ser.readline().strip().strip())
	
	if abs(distAnt-dist)>10:
		print "Alert!!"

Esto es: conservamos la medición anterior (distAnt), obtenemos una nueva (dist = …) y si la diferencia es mayor que diez, lanzamos una alerta.

Como lo que queríamos hacer era una fotografía justo en ese momento, hemos reutilizado código de Una cámara en la Raspberry Pi y, siguiendo viejas costumbres, la envíamos por correo (Enviar una imagen por correo en Python).

El código puede verse en serialPicture.py.

Por la forma de enviar el mensaje esto dejaba el sistema inutilizado mucho tiempo: no podemos evitar el tiempo que consume la cámara (que no es despreciable tampoco) porque no queremos poner más de una; pero sí que podemos evitar la espera de la conexión al servidor de correo, y el envío de la imagen.
Esto lo conseguimos mediante la creación de un subproceso (ver multiprocessing) que se ocupa del envío.

camera(name,cam)
p = Process(target=mail, args=(name,who))
p.start()

Esto es, tomamos la foto y lanzamos un proceso hijo que se ocupa del envío.
No tenía experiencia con esta parte de Python y no estoy seguro de si hace falta terminar el proceso de alguna forma, pero como no hay que hacer sincronizaciones ni esperar a que termine para hacer otras cosas, parece que funciona razonablemente.

Para terminar: ninguno de los procesos es muy rápido; que nadie espere poder utilizar esto como ‘trampa’ para capturar el paso de un ave volando (o incluso un niño corriendo).

¿Qué podemos hacer ahora?
Podríamos embarcar el sensor de distancia en uno de nuestros motores (como en Una cámara móvil) y con ello hacer un ‘mapa’ de la habitación: se me ocurre que serviría para detectar cambios de otra forma diferente.
Otra cosa que se me ocurre es que, una vez que alguien o algo ha cruzado la ‘barrera’ podríamos hacer un barrido con la cámara móvil y tomar varias imágenes (o incluso grabar un vídeo; me está dando pereza, pero es algo que hay que probar).
Y, por supuesto, escuchar ideas de quien lea esto o buscarlas por ahí.
Todavía hay otro problema y es que el sensor funciona aunque no haya luz; tal vez podríamos añadir un sensor de luminosidad para evitar el disparo cuando sabemos que no se va a poder tomar una imagen (o, tal vez, iluminar la escena de alguna forma).

Anuncios

Una cámara móvil

Una vez que tenemos un bot que nos permite controlar remotamente nuestro proyecto (Segundo bot: avanzamos) y sabemos mover los motores (Movimiento suave con los servos) llega el momento de montar la cámara encima (Una cámara en la Raspberry Pi) y controlarla remotamente.
Recordemos que el control se hace mediante XMPP (por ejemplo, con programas como Pidgin, Google Talk o nuestro cliente de mensajería favorito); la idea era evitar abrir puertos en el router para controlarlo vía web y, sin embargo, poder enviar instrucciones desde internet sin problemas.

Para el montaje seleccionamos un par de cajas (como forma barata y simple de proporcionar el soporte para todo). En una caja más grande hicimos un par de agujeros (para poder colocar dos motores, aunque finalmente sólo hemos utilizado uno):

Hemos pintado la caja #raspi

A post shared by Fernando Tricas García (@ftricas) on

Dentro de la caja van las conexiones (baterías para alimentar los motores, y cables para controlarlos desde la Raspberry Pi, que se queda fuera de la caja).

Caja como soporte para los motores

A post shared by Fernando Tricas García (@ftricas) on

La cámara se monta en una caja más pequeña que se sujeta al servo seleccionado.

Y tenemos un prototipo de mejor aspecto #raspi

A post shared by Fernando Tricas García (@ftricas) on

Cuando se le da la instrucción de movimiento, la cámara va a la posición elegida, se detiene para hacer la foto y la envía por correo. Finalmente, vuelve a su posición inicial.
Toda la secuencia puede verse en el siguiente vídeo.

El código del proyecto está disponible en err-plugins (puede evolucionar más adelante; el código en su estado actual puede verse en pruebas.py).

Recientemente se publicó un proyecto similar en “Raspberry Eye” Remote Servo Cam. Tiene dos diferencias fundamentales: se han incluido movimientos en dos ejes (nuestro proyecto sólo se mueve a derecha e izquierda) y se controla mediante una página web.

¿Qué haremos a continuación?
Tengo varias ideas, pero no se todavía qué haré: sería interesante que la cámara tuviera cierta autonomía (¿detección de movimiento o cambios en la escena?); tampoco me importaría pensar en movilidad real (¿embarcar la cámara en algún tipo de dispositivo con ruedas? me encantó este hexápodo).
Yendo más allá, tal vez podríamos pensar en otros dispositivos de control (¿wearables?).

Por supuesto, ideas, comentarios, sugerencias… Serán bienvenidos.

Movimiento suave con los servos

Uno de los problemas que tienen los servos es que su movimiento es bastante brusco, como podía apreciarse en el vídeo que pusimos en Añadiendo movimiento: servos. Con el montaje que había pensado eso era un problema, porque la cámara tiene un cierto peso y en alguna ocasión podía salir disparada, o desencajarse, como puede imaginarse viendo:

Más pruebas #frikeando #voz #motores #raspi #c3po

A post shared by Fernando Tricas García (@ftricas) on

La solución para el problema es relativamente sencilla: cuando queremos llevar el motor a una determinada posición podemos ir haciéndolo con pequeños pasos. La idea es indicarle una sucesión de posiciones cada vez más próxima al objetivo. Así, aunque los movimientos son bruscos, al ser pasos cortos no afectan tanto al objeto que va montado sobre el motor (en este caso la cámara).
El código podría ser como el de esta función:

def move(self, servo, pos, posIni=MIN, inc=10):

	servoGPIO=18
	servoGPIO=17
	posFin=posIni + (MAX-MIN)*pos
	steps=abs(posFin - posIni) / inc

	print "Pos ini", posIni
	print "Pos fin", posFin
	print "Steps", steps
	print int(steps)

	if pos < 0:
		pos = -pos
		sign = -1
	else:
		sign = 1

	for i in range(int(steps)):
		servo.set_servo(servoGPIO,posIni+10*i*sign)
		time.sleep(VEL)

	print "Pos ini", posIni
	print "Pos fin", posFin
	print "Steps", steps
	print int(steps)

	servo.stop_servo(servoGPIO)

Esto es, suponiendo que partimos de una posición inicial (posIni) y que queremos movernos un cierto porcentaje (un número real entre 0 y 1) calculamos la posición final sabiendo el recorrido total (MAX-MIN):

posFin=posIni + (MAX-MIN)*pos

Y calculamos el número de pasos que daremos basándonos en un incrementos de 10 (inc=10):


steps=abs(posFin - posIni) / inc

Se usa el valor absoluto porque el movimiento puede ser en ambas direcciones (según en qué punto esté el motor), de lo que se ocupa el condicional:


if pos < 0:
...

Finalmente, se hacen los movimientos con el bucle:

for i in range(int(steps)):
	servo.set_servo(servoGPIO,posIni+10*i*sign)
	time.sleep(VEL)

El resultado se parece al que se aprecia en este vídeo:

Montamos la cámara en el motor que se mueve más despacio #raspi

A post shared by Fernando Tricas García (@ftricas) on

Donde se puede observar un movimiento de ida y vuelta con un improvisado modelo. Se puede regular la velocidad con el tiempo que se tarda entre uno de estos pequeños movimientos y el siguiente (parámetro VEL).

Seguramente habría sido mejor elegir otro tipo de motores, pero esto resuelve de manera razonable nuestro problema.

Segundo bot: avanzamos

En Raspberry Pi: ¿qué temperatura hace en mi casa? hablábamos del primer bot que nos permitía interactuar con la Raspberry Pi desde donde nos encontráramos.

Estuve probando SleekXMPP y phenny pero ambos tenían algunas limitaciones así que continué buscando hasta que encontré otro proyecto interesante, err que, además de estar en ese momento con cierta actividad proporciona una arquitectura modular para añadir características e incluso tiene una comunidad en Google+, Err.

Lo primero que hice fue adaptar las pruebas que ya había hecho con phenny a la nueva arquitectura, y añadirle un par de cosas más relacionadas con la toma de fotos y su envío. Puede verse el código del módulo en err-plugins (en el futuro podría cambiar así que nos fijaremos en la versión actual con sus dos ficheros.

El primero es pruebas.plug, que contiene información sobre el módulo en sí, con la sintaxis que define el bot:

[Core]
Name = Pruebas
Module = pruebas

[Documentation]
Description = let’s try things !

Y el fichero pruebas.py que contiene el código en sí. Esencialmente es un fichero con código en Python. Por ejemplo, para que el bot tome una fotografía nos la envíe, sería:

<br />
@botcmd<br />
def foto(self, msg, args):<br />
	&quot;&quot;&quot;Take a picture&quot;&quot;&quot;<br />
	quien=msg.getFrom().getStripped()<br />
	yield &quot;I'm taking the picture, wait a second &quot;<br />
	if(args):<br />
		try:<br />
			cam=int(args)<br />
		except:<br />
			cam=0<br />
	else:<br />
		cam=0<br />
	yield &quot;Camera %s&quot;%cam<br />
	self.camera(&quot;/tmp/imagen.png&quot;,cam)<br />
	yield &quot;Now I'm sending it&quot;<br />
	self.mail(&quot;/tmp/imagen.png&quot;, quien)<br />
	my_msg = &quot;I've sent it to ... %s&quot;%quien<br />
	yield my_msg<br />

Con la primera línea decimos que es una instrucción para el bot y luego definimos una función que es la que se ocupa de las acciones correspondientes. El nombre de la función será la instrucción (con un prefijo configurable, que permite al bot diferenciar entre lo que se le indica a él y lo que son otros textos que no interpretará).

En nuestro caso, la instrucción:

.foto

Ejecutaría una función que es muy parecida a la que comentábamos el otro día en Una cámara en la Raspberry Pi. Las diferencia serían:

  • Recibe los parámetros a través de la llamada a la función

    def foto(self, msg, args):
  • Responde al que le envió la orden:

    quien=msg.getFrom().getStripped()
  • El argumento puede ser 0, 1 o ninguno (no se hace validación) porque tenemos dos cámaras en la Raspberry. Por defecto dispara con la cámara 0.
  • A continuación nos indica qué cámara hemos elegido:

    yield "Camera %s"%cam
  • Invoca a la función que realmente realiza la fotografía, cuyos parámetros son muy similares a los que ya comentamos en su momento (el nombre del fichero donde se almacenará la foto y la cámara elegida):

    self.camera("/tmp/imagen.png",cam)
  • Invoca a la función de envío por correo, cuyos parámetros son el fichero donde está la imagen y a quién se envía el mensaje.

    self.mail("/tmp/imagen.png", quien)
  • Finalmente responde al que dio la orden, nuevamente con yield.

Si miramos el código, la diferencia de estas dos funciones es que no van precedidas con @bootcmd, así que no están disponibles como instrucciones. Por lo demás necesitan (igual que contábamos en su momento) los parámetros de configuración adecuados.

Errbot nos proporciona un mecanimos para eso, mediante:

<br />
def get_configuration_template(self):<br />
return{'ADDRESS' : u'kk@kk.com', 'FROMADD' : u'kk@kk.com',<br />
'TOADDRS' : u'kk@kk.com', 'SUBJECT' : u'Imagen',<br />
'SMTPSRV' : u'smtp.gmail.com:587', 'LOGINID' : u'changeme',<br />
'LOGINPW' : u'changeme'}<br />

Podemos definir en un diccionario los parámetros que podremos configurar (vistos, recuerden en Una cámara en la Raspberry Pi).

Y, tecleando en nuestro cliente de mensajería:

.config Pruebas

En este caso, Pruebas es el nombre del módulo y el punto (.) es el indicador que hemos elegido para las instrucciones. La instrucción config nos devuelve la configuración que el módulo tenga en ese momento (si no está configurado muestra los valores que hemos puesto en la definición; si ya lo hemos configurado mostrará los valores que tenga). Los valores que se devuelven pueden utilizarse como patrón para configurar el módulo:

.config Pruebas {'TOADDRS': u'mydir@mail.com', 'SMTPSRV':
u'smtp.gmail.com:587', 'LOGINPW': u'mypassword',
...
}

Con esto, ya falta menos para hacer el montaje final. Seguiremos
informando.

Añadiendo movimiento: servos

Tener una cámara (o dos) conectadas a nuestra raspi nos hace descubrir rápidamente una de las molestas limitaciones de las camaritas: ¡no se mueven!.
Afortunadamente, hay disponibles un buen montón de posibilidades para añadir este aspecto. Yo me incliné por adquirir unos servomotores

Motor #raspi

A post shared by Fernando Tricas García (@ftricas) on

Son baratos, ocupan poco sitio y son algo ruidosos.

Como ya hay muchas páginas por ahí explicando los fundamentos de su funcionamiento, recordaremos aquí dos o tres cosillas: tienen un determinado radio de giro (en este caso 180 grados) y la manera que tienen de moverse es yendo a una determinada posición, que se indica mediante el envío de pulsos cuya longitud determina la amplitud del giro (para gente interesada lo cuentan en, por ejemplo How do servos work? -en inglés- o en Trabajar con Servos -en español-).

Desde un programa nuestra misión será encontrar la forma de enviar esos pulsos a través de las conexiones adecuadas (recuerden interacición entre el mundo físico y el ordenador).

Afortunadamente la red está llena de ejemplos y consejos sobre como hacerlo.

Por ejemplo, los programas: servo, servo2, servoYT, y servoYT2 están basados en lo que se ve en el vídeo Servo control using Raspberry pi (y también, pero menos, en Servo Control with the Raspberry Pi).

Los pasos fundamentales serían, siguiendo el tercer programa, primero indicar los módulos que utilizaremos:

import RPi.GPIO as GPIO
import time

El primero sirve para mandar instrucciones a gravés de los pines adecuados de la raspi. El segundo para manejar los datos relativos al tiempo.

Haremos referencia a los pines por su numeración y ponemos el 11 en modo salida:

GPIO.setmode(GPIO.BOARD)

GPIO.setup(11,GPIO.OUT)

Definimos el controlador con una frecuencia de 50Hz y lo ponemos en marcha, llevándolo a la posición central:

p = GPIO.PWM(11,50)

p.start(7.5)

Finalmente, cambiamos de posición cada segundo, poniéndolo en movimiento:

try:
    while True:
        print "Uno"
        p.ChangeDutyCycle(7.5)
        time.sleep(1)
        print "Dos"
        p.ChangeDutyCycle(12.5)
        time.sleep(1)
        print "Tres"
        p.ChangeDutyCycle(2.5)
        time.sleep(1)

Esto es, empieza en el centro y se va moviendo hacia los extremos. Desde el centro va a uno de los extremos, luego al otro y finalmente vuelve a la posición inicial, como puede verse en este vídeo:

Como se ve hacia el final del vídeo, nada nos impide añadir otro motor (más allá de los pines que haya disponibles) que es el código incluido en el cuarto programa, servoYT2).
Allí se ha añadido el pin 12 y se genera otro controlador (ahora son p1 y p2) y simplemente se van alternando las instrucciones enviadas a uno y a otro. En este mini-vídeo los dos motores en acción:

Dos motores #raspi

A post shared by Fernando Tricas García (@ftricas) on

Próximamente explicaremos el montaje para poner la cámara sobre uno de los motores y poder finalizar el proyecto.

Una cámara en la Raspberry Pi

Ha pasado bastante tiempo desde la última entrada en esta bitácora y mucho más desde que empecé a hablar de mis pinitos con la Raspberry Pi (Raspberry Pi: ¿qué temperatura hace en mi casa? pero eso no quiere decir que haya abandonado el aparatito, que me proporciona mis buenos ratos (y también mosqueos, con las cosas que no se consiguen, o no funcionan como uno espera).

Después de aquel experimento donde accedíamos remotamente a algunos sensores e información de lo que sucedía en casa, pensé en avanzar por dos vías: buscar algún bot que fuera más sencillo de programar (phenny me gustó pero parece que su desarrollo se ha parado; lo de sencillo de programar va en gustos y formas para cada uno, claro). También pensé añadir una cámara al sistema de ‘vigilancia’ casera, y esto me ha consumido bastante tiempo.

La referencia para comprar una cámara que funcione con nuestra Raspberry es RPi USB Webcams. Entre los modelos que vi allí (y estaban disponibles en una tienda cercana) elegí la Logitech C270. En aquel momento entendí que funcionaba bien directamente conectada al puerto USB, cosa que resultó no ser correcta (ahora dice que necesita un hub USB alimentado que es lo que solucionó mis problemas después de muchas pruebas).

En el intermedio, compré (de segunda mano, en una tienda de esas de videojuegos que venden complementos para consolas) una cámara para Playstation PS4 (creo). Había leído por ahí que era menos exigente con los requisitos energéticos y que tal vez podría funcionar (y eran 5 euros de coste, qué caray).

Las cámaras:

Probando otra cámara (PS3)

A post shared by Fernando Tricas García (@ftricas) on

Es cierto que la cámara de la Playstation funcionaba mejor que la otra sin alimentación externa, pero la Raspberry seguía colgándose aleatoriamente (no hay que olvidar que en el otro puerto USB había conectado un dispositivo WiFi, que utilizo para conectarme al cacharrito para poder gestionarlo cuando estoy en casa; y que la conecta a internet de manera casi permanente).

La calidad de imagen es algo peor que la de la de Logitech y tiene dos posiciones (un zoom) que se gestiona manualmente (así que no está accesible para un programa que utilice la cámara). La he conservado como cámara secundaria.

Así que después de un montón de días probando a ratos perdidos y con algo de frustración, fui a la tiena a por un hub USB alimentado (tenía uno que me regalaron pero no estaba en muy buenas condiciones). El elegido fue uno de Trust (el EasyConnect 7 Port USB2 Powered Hub).

Como decía la canción…

A post shared by Fernando Tricas García (@ftricas) on

Mientras tanto, claro, iba probando las diferentes alternativas para manejar la cámara. Vale la pena recordar que salió la cámara oficial del proyecto Raspberry que se supone que nos evitaría los problemas de alimentación pero que no llegué a considerar seriamente: me parecía algo cara (una vez que ya había comprado una, claro) y además utiliza el sistema de conexión con un bus que parece poco práctico (sobre todo si luego queremos usarla para otras cosas).

Seguramente funciona bien, es integrable en proyectos más ambiciosos. Se puede ver información en Raspberry Pi Camera Module.

Para gestionar la cámara hay bastantes programas y ejemplos por ahí:

Y alguno más que seguramente estoy olvidando.

También descubrí OpenCV que permite, entre otras cosas, manejar cámaras desde un programa (en particular se puede hacer en Python): más de una cámara, si tiene parámetros configurables se pueden gestionar desde el propio programa (luminosidad, zoom, contraste, …).

Podemos ver un programita de ejemplo en: cam.py. El programa graba la imagen que toma en un fichero cuyo nombre se le puede pasar en la propia llamada. Si no se pone nombre, elige uno predefinido.

Por si vale la pena, comentamos algunas líneas del código.

el nombre del fichero se define en:


imgFile = '/tmp/imagen.png'

El nombre siempre es el mismo. Podríamos utilizar algo como:


imgFile = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())+'.png'

Para poder conservar las diferentes imágenes que se vayan tomando (cuidado con llenar el espacio de almacenamiento).

Luego hay código para leer el nombre de la línea de instrucciones (si se suministra), aunque no es nada robusto (no se hace niguna validación), para después parsar a inicializar la cámara:


cam=cv2.VideoCapture(0)

La captura de la imagen se hace en una función:


def get_image():
retval, im = cam.read()
return im

Nuevamente es poco robusto, no se comprueba el código de error que puede devolver la función read y simplemente devuelve la imagen capturada (que podría no existir porque algo hubiera fallado).

Esta función se utiliza en:


img=get_image()

Y, finalmente,


cv2.imwrite(imgFile, img)

escribe la imagen en el fichero que hemos definido arriba.

Creo que esta entrada ya es un poco larga. Otro día seguiremos con el bot.