-
Coche RC autónomo (VIII) - Usando threads para enviar y recibir datos del sensor ultrasónico e imágenes desde la Raspberry Pi
Como ya expliqué en el post Coche RC autónomo (VII) - Configurando el sensor ultrasónico HC-SR04 para detectar objetos, el siguiente paso era enviar esa información desde nuestro cliente (Raspberry Pi) a nuestro servidor, para luego poder procesar esos datos de distancia a objetos junto con las imágenes de las videocámaras (que ya éramos capaces de enviar, como se explicó en el post Coche RC autónomo (VI) - Probando la cámara usando Python, OpenCV y streaming).
La idea es, por tanto, actualizar nuestros scripts Python 3
server.py
(a ejecutar en nuestro ordenador durante el desarrollo, y luego exclusivamente desde la Raspberry Pi 3 cuando esté montada en el vehículo teledirigido) yclient.py
(a ejecutar en la Raspberry Pi 3 siempre). A tener en cuenta:- Vamos a crear en el cliente un
thread
(hilo de ejecución) que lea datos del sensor ultrasónico con la distancia a objetos enfrente de dicho sensor y la envíe al servidor por un puerto definido (el8001
en nuestro caso), y otrothread
que obtenga imágenes de la videocámara y las envíe mediante streaming a otro puerto definido (el8000
) - En el servidor crearemos igualmente dos
threads
, uno para gestionar las imágenes recibidas de la cámara y otro para obtener los datos de distancia obtenidos del sensor ultrasónico.
En el servidor, el thread principal será el que procese imágenes de la videocámara, ya que desde él podremos leer los datos recibidos del sensor ultrasónico y podremos mostrar mensajes en la propia imagen que visualicemos. Desde este thread, como veremos en futuros posts, iremos llamando a diferentes funciones para controlar el vehículo (redes neuronales que procesen las imágenes, y lógica de si el coche debe girar, frenar, acelerar, etcétera)
En el ejemplo detallado en este post, a la vez que recibimos dichos datos (imágenes y distancia a obstáculos) vamos a ir preparando esta lógica: Vamos a mostrar en la imagen si tenemos obstáculo delante o no, escribiendo un texto.
El código del cliente (Raspberry Pi 3) es el siguiente (
client.py
). Como hemos comentado, la configuración del los circuitos y código del sensor de ultrasonidos y videocámara los puedes encontrar en los correspondientes posts, mientras que el código lo puedes encontrar actualizado en Github - jorgecasas/autonomous-rc-car:# Importamos librerias RPi.GPIO (entradas/salidas GPIO de Raspberry Pi) y time (para sleeps, etc...) # Requiere previamente instalarla (pip install RPi.GPIO) import RPi.GPIO as GPIO import time import io import socket import struct import picamera import threading # Configure Raspberry Pi GPIO in BCM mode GPIO.setmode(GPIO.BCM) # Config vars. These IP and ports must be available in server firewall log_enabled = False server_ip = '192.168.1.235' server_port_ultrasonic = 8001 server_port_camera = 8000 # Camera configuration image_width = 640 image_height = 480 image_fps = 10 recording_time = 600 # Definition of GPIO pins in Raspberry Pi 3 (GPIO pins schema needed!) # 18 - Trigger (output) # 24 - Echo (input) GPIO_ultrasonic_trigger = 18 GPIO_ultrasonic_echo = 24 # Class to handle the ultrasonic sensor stream in client class StreamClientUltrasonic(): def measure(self): # Measure distance from ultrasonic sensor. Send a trigger pulse GPIO.output( GPIO_ultrasonic_trigger, True ) time.sleep( 0.00001 ) GPIO.output( GPIO_ultrasonic_trigger, False ) # Get start time start = time.time() # Wait to receive any ultrasound in sensor (echo) while GPIO.input( GPIO_ultrasonic_echo ) == 0: start = time.time() # We have received the echo. Wait for its end, getting stop time while GPIO.input( GPIO_ultrasonic_echo ) == 1: stop = time.time() # Calculate time difference. Sound has gone from trigger to object and come back to sensor, so # we have to divide between 2. Formula: Distance = ( Time elapsed * Sound Speed ) / 2 time_elapsed = stop-start distance = (time_elapsed * 34300) / 2 return distance def __init__(self): # Connect a client socket to server_ip:server_port_ultrasonic print( '+ Trying to connect to ultrasonic streaming server in ' + str( server_ip ) + ':' + str( server_port_ultrasonic ) ); # Configure GPIO pins (trigger as output, echo as input) GPIO.setup( GPIO_ultrasonic_trigger, GPIO.OUT ) GPIO.setup( GPIO_ultrasonic_echo, GPIO.IN ) # Set output GPIO pins to False GPIO.output( GPIO_ultrasonic_trigger, False ) # Create socket and bind host client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect( ( server_ip, server_port_ultrasonic ) ) try: while True: # Measure and send data to the host every 0.5 sec, # pausing for a while to no lock Raspberry Pi processors distance = self.measure() if log_enabled: print( "Ultrasonic sensor distance: %.1f cm" % distance ) client_socket.send( str( distance ).encode('utf-8') ) time.sleep( 0.5 ) finally: # Ctrl + C to exit app (cleaning GPIO pins and closing socket connection) print( 'Ultrasonic sensor connection finished!' ); client_socket.close() GPIO.cleanup() # Class to handle the jpeg video stream in client class StreamClientVideocamera(): def __init__(self): # Connect a client socket to server_ip:server_port_camera print( '+ Trying to connect to videocamera streaming server in ' + str( server_ip ) + ':' + str( server_port_camera ) ); # create socket and bind host client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((server_ip, server_port_camera)) connection = client_socket.makefile('wb') try: with picamera.PiCamera() as camera: camera.resolution = (image_width, image_height) camera.framerate = image_fps # Give 2 secs for camera to initilize time.sleep(2) start = time.time() stream = io.BytesIO() # send jpeg format video stream for foo in camera.capture_continuous(stream, 'jpeg', use_video_port = True): connection.write(struct.pack('<L', stream.tell())) connection.flush() stream.seek(0) connection.write(stream.read()) if time.time() - start > recording_time: break stream.seek(0) stream.truncate() connection.write(struct.pack('<L', 0)) finally: connection.close() client_socket.close() print( 'Videocamera stream connection finished!' ); # Class to handle the different threads in client class ThreadClient(): # Client thread to handle the video def client_thread_camera(host, port): print( '+ Starting videocamera stream client connection to ' + str( host ) + ':' + str( port ) ) StreamClientVideocamera() # Client thread to handle ultrasonic distances to objects def client_thread_ultrasonic(host, port): print( '+ Starting ultrasonic stream client connection to ' + str( host ) + ':' + str( port ) ) StreamClientUltrasonic() print( '+ Starting client - Logs ' + ( log_enabled and 'enabled' or 'disabled' ) ) thread_ultrasonic = threading.Thread( name = 'thread_ultrasonic', target = client_thread_ultrasonic, args = ( server_ip, server_port_ultrasonic ) ) thread_ultrasonic.start() thread_videocamera = threading.Thread( name = 'thread_videocamera', target = client_thread_camera, args = ( server_ip, server_port_camera ) ) thread_videocamera.start() # Starting thread client handler if __name__ == '__main__': ThreadClient()
El código del servidor es el siguiente (
server.py
):# Import libraries import threading import socketserver import socket import cv2 import numpy as np import math # Config vars log_enabled = False server_ip = '192.168.1.235' server_port_camera = 8000 server_port_ultrasonic = 8001 # Video configuration image_gray_enabled = False image_fps = 24 image_width = 640 image_height = 480 image_height_half = int( image_height / 2 ) # Global var (ultrasonic_data) to measure object distances (distance in cm) ultrasonic_sensor_distance = ' ' ultrasonic_stop_distance = 25 ultrasonic_text_position = ( 16, 16 ) # Other vars color_blue = (211, 47, 47) color_yellow = (255, 238, 88) color_red = (48, 79, 254) color_green = (0, 168, 0) # Font used in opencv images image_font = cv2.FONT_HERSHEY_PLAIN image_font_size = 1.0 image_font_stroke = 2 # Datos para lineas de control visual. Array stroke_lines contiene 3 componentes: # 0: Punto inicial (x,y) # 1: Punto final (x,y) # 2: Color de linea # 3: Ancho de linea en px # Para activarlo/desactivarlo: stroke_enable = True|False stroke_enabled = True stroke_width = 4 stroke_lines = [ [ (0,image_height), ( int( image_width * 0.25 ), int( image_height/2 ) ), color_green, stroke_width ], [ (image_width,image_height), ( int( image_width * 0.75 ), int( image_height/2 ) ), color_green, stroke_width ] ]; # Class to handle data obtained from ultrasonic sensor class StreamHandlerUltrasonic(socketserver.BaseRequestHandler): data = ' ' def handle(self): global ultrasonic_sensor_distance distance_float = 0.0 try: print( 'Ultrasonic sensor measure: Receiving data in server!' ) while self.data: self.data = self.request.recv(1024) try: distance_float = float( self.data ) except ValueError: # No es float... porque hemos recibido algo del tipo b'123.123456.456' (es decir, por lag de la red # o sobrecarga de nuestro sistema hemos recibido dos valores antes de ser capaces de procesarlo) distance_float = 1000.0 ultrasonic_sensor_distance = round( distance_float, 1) if log_enabled: print( 'Ultrasonic sensor measure received: ' + str( ultrasonic_sensor_distance ) + ' cm' ) finally: print( 'Connection closed on ultrasonic thread' ) # Class to handle the jpeg video stream received from client class StreamHandlerVideocamera(socketserver.StreamRequestHandler): def handle(self): stream_bytes = b' ' global ultrasonic_sensor_distance # stream video frames one by one try: print( 'Videocamera: Receiving images in server!' ) while True: stream_bytes += self.rfile.read(1024) first = stream_bytes.find(b'\xff\xd8') last = stream_bytes.find(b'\xff\xd9') if first != -1 and last != -1: jpg = stream_bytes[first:last+2] stream_bytes = stream_bytes[last+2:] image = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_UNCHANGED) # lower half of the image if image_gray_enabled: gray = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_GRAYSCALE) half_gray = gray[image_height_half:image_height, :] # Dibujamos lineas "control" if stroke_enabled: for stroke in stroke_lines: cv2.line( image, stroke[0], stroke[1], stroke[2], stroke[3]) # Check ultrasonic sensor data (distance to objects in front of the car) if ultrasonic_sensor_distance is not None and ultrasonic_sensor_distance < ultrasonic_stop_distance: cv2.putText( image, 'OBSTACLE ' + str( ultrasonic_sensor_distance ) + 'cm', ultrasonic_text_position, image_font, image_font_size, color_red, image_font_stroke, cv2.LINE_AA) if log_enabled: print( 'Stop, obstacle in front! >> Measure: ' + str( ultrasonic_sensor_distance ) + 'cm - Limit: '+ str(ultrasonic_stop_distance ) + 'cm' ) else: cv2.putText( image, 'NO OBSTACLE ' + str( ultrasonic_sensor_distance ) + 'cm', ultrasonic_text_position, image_font, image_font_size, color_green, image_font_stroke, cv2.LINE_AA) # Show images cv2.imshow('image', image) if image_gray_enabled: cv2.imshow('mlp_image', half_gray) if cv2.waitKey(1) & 0xFF == ord('q'): break finally: cv2.destroyAllWindows() print( 'Connection closed on videostream thread' ) # Class to handle the different threads class ThreadServer( object ): # Server thread to handle the video def server_thread_camera(host, port): print( '+ Starting videocamera stream server in ' + str( host ) + ':' + str( port ) ) server = socketserver.TCPServer((host, port), StreamHandlerVideocamera) server.serve_forever() # Server thread to handle ultrasonic distances to objects def server_thread_ultrasonic(host, port): print( '+ Starting ultrasonic stream server in ' + str( host ) + ':' + str( port ) ) server = socketserver.TCPServer((host, port), StreamHandlerUltrasonic) server.serve_forever() print( '+ Starting server - Logs ' + ( log_enabled and 'enabled' or 'disabled' ) ) thread_ultrasonic = threading.Thread( name = 'thread_ultrasonic', target = server_thread_ultrasonic, args = ( server_ip, server_port_ultrasonic ) ) thread_ultrasonic.start() thread_videocamera = threading.Thread( name = 'thread_videocamera', target = server_thread_camera, args = ( server_ip, server_port_camera ) ) thread_videocamera.start() # Starting thread server handler if __name__ == '__main__': ThreadServer()
En ambos scripts he ido incluendo variables para facilitar la configuración usada, como puede ser la activación / desactivación de logs, dimensiones de imágenes, etcétera, por lo que es recomendable revisar dicha configuración cuando estéis haciendo pruebas. Ahora sólo queda probarlo, arrancando primero el servidor y luego el cliente (accediendo previamente al entorno virtualizado python mediante
workon cv
):- En el servidor:
python server.py
- En el cliente:
python client.py
El resultado será similar al de la imagen que acompaña este post. En próximos capítulos empezaremos a detectar señales de STOP mediante redes neuronales. ¡Espero que os guste!
Más información sobre el proyecto
- Vamos a crear en el cliente un
-
Coche RC autónomo (VII) - Configurando el sensor ultrasónico HC-SR04 para detectar objetos
Además de utilizar la cámara, los coches autónomos tendrán muchos otros sensores que obtendrán datos que podrán ser utilizados para garantizar la seguridad e integridad de los vehículos y todo lo que les rodea. Así, en este post voy a detallar cómo utilizar el sensor de ultrasonidos HC-SR04 para detectar obstáculos delante del vehículo.
El mismo script y circuito que vamos a utilizar puede servir como sensor de aparcamiento, ya que nos permitirá detectar a qué distancia nos encontramos de un obstáculo o pared. También puede ser utilizado para medir cuán lleno está un pozo o un bidón de almacenamiento de líquidos.
El mecanismo de este sensor es el siguiente:
- Emite un pulso de sonido durante un pequeño período de tiempo. El pulso lo emitimos de forma periódica, y es como un disparo (trigger).
- El sonido avanza hasta chocar con un objeto y rebota.
- El sonido rebotado es el eco (echo), que se recibirá y detectará en el sensor receptor.
- Conociendo la velocidad del sonido en el aire y calculando el tiempo transcurrido desde que se lanzó el pulso sónico y se recibe su eco, podemos conocer la distancia (teniendo en cuenta que el sonido habrá ido y habrá vuelto, por lo que tendremos que dividir la distancia entre 2).
Así, en nuestro diseño definiremos una distancia límite. Si el vehículo se encuentra más cerca de un obstáculo que dicha distancia, se detendrá para no chocar.
En las pruebas que hemos realizado (ver el script a continuación, que puede ser descargado de Código en Github - jorgecasas/autonomous-rc-car), vamos a encender un led cuando estemos a menos distancia y mostraremos un mensaje de alerta. De esta forma, y con un pequeño circuito, aprenderemos también a utilizar las entradas y salidas GPIO de la Raspberry Pi.
En primer lugar, accedemos a la Raspberry Pi y accedemos al entorno virtual de Python, instalando con
pip
la librería requerida para poder gestionar las entradas y salidas GPIO:workon cv pip install RPi.GPIO
El script
sensor-ultrasonidos-HC-SR04.py
es el siguiente:# Importamos librerias RPi.GPIO (entradas/salidas GPIO de Raspberry Pi) y time (para sleeps, etc...) # Reqiuere previamente instalarla (pip install RPi.GPIO) import RPi.GPIO as GPIO import time # Ponemos la placa en modo BCM GPIO.setmode(GPIO.BCM) # Definimos los pines GPIO de la Raspberry Pi 3 (segun esquema) # 12 - Led (output) # 18 - Trigger (output) # 24 - Echo (input) GPIO_LED = 12 GPIO_TRIGGER = 18 GPIO_ECHO = 24 # Configuramos los pines como salidas (trigger y led) o entradas (detector de eco) GPIO.setup(GPIO_TRIGGER,GPIO.OUT) #Configuramos Trigger como salida GPIO.setup(GPIO_ECHO,GPIO.IN) #Configuramos Echo como entrada GPIO.setup( GPIO_LED, GPIO.OUT ) # Pin de led # Inicializamos los pines de salida (apagados) GPIO.output( GPIO_TRIGGER, False ) GPIO.output( GPIO_LED, False ) # Distancia en cm distance_limit = 15 try: print( 'Sensor ultrasonico HC-SR04' ) # Iniciamos un loop infinito while True: # Enviamos un pulso de ultrasonidos durante un poco de tiempo GPIO.output(GPIO_TRIGGER,True) time.sleep(0.00001) GPIO.output(GPIO_TRIGGER,False) # Desde que dejamos de enviar el pulso, obtenemos tiempo actual start = time.time() # Si el sensor no recibe sonido, mantenemos el tipo actual actualizado while GPIO.input(GPIO_ECHO)==0: start = time.time() # Si el sensor recibe sonido, obtenemos el tiempo fin while GPIO.input(GPIO_ECHO)==1: stop = time.time() # Obtenemos el tiempo transcurrido. # La distancia sera igual al tiempo transcurrido por la velocidad (partido por 2, porque # el sonido va y vuelve desde el sensor al objeto y del objeto al sensor): 2 D = (T x V)/2 elapsed = stop-start distance = (elapsed * 34300) / 2 # Si la distancia es menor que la distancia limite fijada... Paramos y encendemos led de alerta! if distance < distance_limit: print( 'Stop! Objeto a ' + str( distance ) +'cm' ) GPIO.output( GPIO_LED, True ) else: # En este caso, podemos continuar avanzando sin obstaculos! print( 'Go! No hay objetos delante' ) GPIO.output( GPIO_LED, False ) # Pausamos un poco para no saturar el procesador de la Raspberry Pi time.sleep( 0.25 ) except KeyboardInterrupt: # Si el usuario pulsa CONTROL+C... fin de aplicacion (limpieando los pines GPIO) print( 'Sensor Stopped' ) GPIO.cleanup()
Siguiendo el script, tenemos que crear un circuito teniendo en cuenta la distribución de los pines GPIO de la Raspberry 3:
- Conectaremos pin VCC+ al pin VCC y el pin GROUND al pin GROUND del sensor HC-SR04, para alimentarlo.
- Conectaremos el pin de salida GPIO 18 de la Raspberry Pi directamente al pin TRIGGER del sensor HC-SR04
- Conectaremos el pin de entrada GPIO 24 de la Raspberry Pi al pin ECHO del sensor HC-SR04, pero no lo haremos directamente sino mediante una resistencia de 1K.
Adicionalmente, conectaremos un led que encenderemos cuando el obstáculo se encuentre más cerca de la distancia límite:
- Conectaremos el pin de salida GPIO 12 de la Raspberry Pi directamente al pin positivo del led
- Conectaremos una resistencia de 1K del otro pin del led al pin GROUND de la Raspberry Pi
Y sólo queda probarlo ejecutando el siguiente comando. El led deberá encenderse y apagarse según acerquemos objetos al sensor.
python sensor-ultrasonidos-HC-SR04.py
Este código lo adaptaremos en los próximos pasos dentro de nuestro script global, encapsulándolo en un hilo independiente, de manera que podamos utilizar la información obtenida a la hora de controlar el vehículo.
Más información sobre el proyecto
-
Coche RC autónomo (VI) - Probando la cámara usando Python, OpenCV y streaming
Llegados a este punto ya tenemos OpenCV instalado (tanto en la Raspberry Pi como en el ordenador para hacer pruebas de forma más sencilla). Con esta librería podremos usar la cámara y Python para la visión por computador. Así, en este post vamos a comentar nuestros primeros scripts en Python para conseguir capturar imágenes de la cámara de la Raspberry Pi usando la librería
picamera
y OpenCV para procesarlos. En el post Coche RC autónomo (IV) - Configurando la videocámara en la Raspberry Pi ya habíamos conseguido enviar imágenes desde el cliente (Raspberry Pi) al servidor (nuestro ordenador), pero usábamosnetcat
para ello, y nosotros necesitaremos obtener objetos de tipo imagen para poder ser procesadas mediante OpenCV.Para ello, igual que hicimos en el post anterior, vamos a enviar imágenes entre la Raspberry Pi (IP en el interfaz WiFi:
192.168.1.199
) al servidor (nuestro ordenador, con IP192.168.1.235
, con el puerto8000
abierto en el cortafuegos). En este post vamos a crear los dos primeros scripts python, los cuales nos servirán de base para ir programando más adelante todo lo necesario para que el vehículo autónomo pueda ver y procesar lo que ve. Los scripts y todo el código que vaya a utilizar los podés descargar de Github - jorgecasas/autonomous-rc-car.Ejecutando scripts Python
Para ejecutar los scripts (tanto en la Raspberry Pi como en el ordenador, donde tenemos instalado las librerías OpenCV y los entornos virtualizados de Python), basta con ejecutar los comandos:
- Acceder al entorno virtualizado:
workon cv
- Ejecutar el script Python:
python script_python.py
Utilizando
git
para hacer el despliegue de códigoPara copiar los ficheros tanto a la Raspberry Pi como a nuestro ordenador, puede ser interesante crear un repositorio (o un branch de mi repositorio de Github - jorgecasas/autonomous-rc-car) tanto en la Raspberry Pi como en nuestro ordenador, de manera que podamos compartir nuestro código y actualizarlo fácilmente en los diferentes dispositivos. Tendremos que instalar
git
:sudo apt-get install git
Ahora clonamos el repositorio (tanto en la Raspberry Pi como en nuestro ordenador), lo que creará una carpeta
autonomous-rc-car
. En este caso necesitaremos tener antes una cuenta de Github, que es gratuita:git clone git@github.com:jorgecasas/autonomous-rc-car.git cd autonomous-rc-car
Podemos crear ahora una nueva rama (
branch
) para nuestros desarrollos. En el ejemplo esta rama de desarrollo se llamará branch-de-desarrollo (le puedes llamar como quieras):git checkout -b branch-de-desarrollo
Podemos actualizar el código y crear nuevos ficheros. Los podremos añadir y hacer
commit
:git add nuevo_fichero.py git commit -m "Nuevo fichero creado"
Por último, este nuevo fichero lo podemos subir a Github para tener una copia de seguridad y poder compartir nuestro código:
git push origin branch-de-desarrollo
Y ahora que está el código subido a Github, lo podemos descargar en la Raspberry Pi o en otro ordenador. Para ello, una vez conectados a la Raspberry Pi por SSH, necesitamos clonar el proyecto (igual que hemos hecho arriba), y acceder a nuestro branch de desarrollo:
git clone git@github.com:jorgecasas/autonomous-rc-car.git cd autonomous-rc-car git checkout branch-de-desarrollo
Y una vez en nuestra rama, podemos actualizar los ficheros a la última versión que hayamos subido a Github (a
origin
):git pull
Podemos aprender más sobre
git
con libros como Aprende Git y de paso Github, o con tutoriales como los de Atlassian - Git. Ahora que ya sabemos cómo utilizar un pocogit
para tener el código actualizado en el ordenador y en la Raspberry Pi, vamos a ver los scripts, teniendo en cuenta que la versión de Python que yo estoy utilizando es la python3.5 (ya que en versiones anteriores, como la python2.7 algunas funciones y librerías se llaman diferente y nos darán errores de ejecución de los scripts).Servidor - Ordenador con Ubuntu
En el servidor incluiremos el siguiente script stream_server_computer.py. Su función es la siguiente:
- Importamos las librerías (como OpenCV cv2 para procesar las imágenes recibidas, socketserver para poder crear un servidor de sockets…)
- Indicamos variables de configuración (como el puerto que va a utilizar el servidor, en nuestro caso el 8000)
- Creamos un hilo de ejecución (thread), que creará un servidor de socket, esperando hasta recibir la primera petición de un cliente (de la Raspberry Pi). Creamos un hilo porque en el futuro necesitaremos crear varios sockets en paralelo (uno para recibir imágenes, otro para recibir datos de distancia del sensor ultrasónico, etcétera).
- En cuanto recibamos la primera conexión, iremos leyendo el buffer de bits que nos vaya enviando el cliente (la Raspberry Pi). Este cliente irá enviando imagen a imagen (frames), que serán obtenidos como objetos de imagen de OpenCV.
- Al obtener un objeto de imagen OpenCV, lo procesaremos decodificando los datos mediante
cv2.imdecode()
(por ejemplo, lo podemos convertir a escala de grises), y lo mostraremos en una ventana mediantecv2.imshow()
- Y así hasta que pulsemos sobre la ventana la tecla
q
, que finalizará la ejecución.
Es importante tener en cuenta que tendremos que ejecutar este script servidor antes que el script cliente. Para ello, como hemos comentado, basta con ejecutar desde el entorno virtualizado
python stream_server_computer.py
.El código del servidor es el siguiente (descargable desde Github - jorgecasas/autonomous-rc-car):
# Import libraries import threading import socketserver import socket import cv2 import numpy as np import math # Config vars server_ip = '192.168.1.235' server_port = 8000 image_fps = 24 # Class to handle the jpeg video stream received from client class VideoStreamHandler(socketserver.StreamRequestHandler): def handle(self): stream_bytes = b' ' # stream video frames one by one try: while True: stream_bytes += self.rfile.read(1024) first = stream_bytes.find(b'\xff\xd8') last = stream_bytes.find(b'\xff\xd9') if first != -1 and last != -1: jpg = stream_bytes[first:last+2] stream_bytes = stream_bytes[last+2:] gray = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_GRAYSCALE) image = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_UNCHANGED) # lower half of the image half_gray = gray[120:240, :] # Mostramos imagenes cv2.imshow('image', image) #cv2.imshow('mlp_image', half_gray) # reshape image image_array = half_gray.reshape(1, 38400).astype(np.float32) if cv2.waitKey(1) & 0xFF == ord('q'): break cv2.destroyAllWindows() finally: print( 'Connection closed on videostream thread' ) # Class to handle the different threads class ThreadServer(): # Server thread to handle the video def server_thread(host, port): server = socketserver.TCPServer((host, port), VideoStreamHandler) server.serve_forever() print( '+ Starting videostream server in ' + str( server_ip ) + ':' + str( server_port ) ) video_thread = threading.Thread(target=server_thread( server_ip, server_port)) video_thread.start() # Starting thread server handler if __name__ == '__main__': ThreadServer( server_ip, server_port )
Cliente - Raspberry Pi
En el cliente incluiremos el siguiente script stream_client_raspberry.py. Su cometido es el siguiente:
- Importar librerías (como socket para crear un cliente de sockets, picamera para poder obtener imágenes de la cámara de la Raspberry Pi, etc…)
- Indicar parámetros de configuración (IP del servidor, puerto del servidor, dimensiones de la imagen a capturar, frames por segundo, duración máxima de envío…)
- Abrimos un socket a la dirección IP y puerto del servidor.
- Inicializamos la cámara de la Raspberry Pi, dándole 2 segundos para inicializarse
- Enviamos imágenes en forma de stream JPEG al servidor hasta que pase el tiempo máximo recording_time o se cierre la comunicación (pulsando
Ctrl + C
, etcétera)
El código del cliente es el siguiente (descargable desde Github - jorgecasas/autonomous-rc-car). Lo ejecutaremos mediante
python stream_client_raspberry.py
.# Import libraries import io import socket import struct import time import picamera # Config vars server_ip = '192.168.1.235' server_port = 8000 image_width = 320 image_height = 240 image_fps = 10 recording_time = 600 # Connect a client socket to server_ip:server_port print( 'Trying to connect to streaming server in ' + str( server_ip ) + ':' + str( server_port ) ); # create socket and bind host client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((server_ip, server_port)) connection = client_socket.makefile('wb') try: with picamera.PiCamera() as camera: camera.resolution = (image_width, image_height) camera.framerate = image_fps # Give 2 secs for camera to initilize time.sleep(2) start = time.time() stream = io.BytesIO() # send jpeg format video stream for foo in camera.capture_continuous(stream, 'jpeg', use_video_port = True): connection.write(struct.pack('<L', stream.tell())) connection.flush() stream.seek(0) connection.write(stream.read()) if time.time() - start > recording_time: break stream.seek(0) stream.truncate() connection.write(struct.pack('<L', 0)) finally: connection.close() client_socket.close() print( 'JPEG streaming finished!' );
Y con esto, podremos ver en nuestro ordenador las imágenes que vayamos obteniendo de la cámara. En principio no deberían tener mucho lag, por lo que para las pruebas nos va a servir.
En los siguientes posts iremos actualizando estos scripts para ir introduciendo algo de inteligencia a nuestro vehículo (por ejemplo, crear una red neuronal para detectar una señal de STOP). ¡Nos vemos!
Más información sobre el proyecto