Carolina Herrero nos propone un sistema de un sistema automatizado para riego que ademas no precisa de conexión a Internet porque todas las medidas se gestionan de forma interna y por tanto no precisan ningún servicio de IoT para su funcionamiento ( como por ejemplo Cayenne.com del que hemos hablado numerosas ocasiones en este blog)
La idea principal de Carolina era construir un sistema de riego automático, controlado por diferentes tipos de sensores, de forma que el sistema tomase decisiones de modo automático guiándose en función de las condiciones del ambiente y la necesidad de riego que tiene la tierra según el grado de humedad de modo que cuando las condiciones fuesen óptimas comenzase el riego(, siempre y cuando exista agua en el depósito)
Además su creadora también quería que los datos se almacenaran de forma periódica en una base de datos local mySQL , y a través de una aplicación Web, con sus credenciales poder acceder y ver un histórico gráfico de las mediciones de los sensores .
Para conseguir esto básicamente ha utilizado:
Varios Sensores
Una placa Microcontroladora
Un Servidor local
Sensores
El sistema utiliza diferentes tipos de sensores, porque se requiere controlar diversos valores como son :
Valores de la humedad de la tierra para lo que se usa un sensor conocido como YL-69, que consiste en dos picas que se encuentran enterradas en tierra de manera que controlando la resistencia de la tierra se puede conocer la humedad.Esta es una herramienta indispensable para un jardín conectado pues por si mismo nos puede recordar que debe regar las plantas de interior o para monitorear la humedad del suelo en su jardín . Se alimenta a: 3.3V-5V y el modo de módulo de salida dual, salida digital, salida analógica más precisa
Para recoger los valores de humedad y temperatura ambiente se utiliza un simple sensor DHT11 (si bien un DHT22 hubiese sido mas recomendable por su mayor precisión , aunque es cierto que su coste es algo mayor)
De la temperatura del suelo se encarga el DS18B20, un sensor sumergible y resistente a la humedad, que se usará para controlar la temperatura de la tierra.
Por seguridad, y para evitar que las bombas funcionen en vacío, y puedan dañarse, es imprescindible controlar el nivel de agua que hay en el depósito, y estos se consigue con un sensor de nivel .Estos sensores pueden medir humedad entre el 10% -90% sin condensación con un consumo de menos de 20mA y ofreciendo una salida analógica con un área de detección de 40mmx16mm
Por último controlar si hay luz o no, para evitar el riego de noches se ha usado un sensor de luz, también conocido como resistencia LDR.
Arduino
El encargado de recoger todos los valores procedentes de los sensores descritos y tomar las acciones necesarias es una placa sistema Arduino que ha sido programado para recoger datos, actuar en función de los valores de dichos datos, y en última instancia se encarga de mandarlos a un servidor ( una Raspberry Pi) para su seguimiento estadístico de modo que en principio si no nos interesa seguir esa traza perfectamente el proyecto quedaría únicamente con esta placa y sus sensores .
Aquí su autora comparte una parte del código encargado de recoger los datos, y enviarlos por el puerto serie.
//Función que se encarga de leer datos de todos los sensores
void leer_datos_sensores(){
valor_ha = dht.readHumidity();//Se lee la humedad en el ambiente
valor_ta = dht.readTemperature();//Se lee la temperatura en el ambiente
valor_ht1 = analogRead(hum_tierra1);//Se lee humedad en tierra en la zona1
valor_na = analogRead(nivel_agua);//Se mide el nivel de agua existente en el depósito
valor_luz = analogRead(luz_ldr);//Se lee la luz
DS18B20.requestTemperatures();//Prepara el sensor para la lectura
valor_tt1 = DS18B20.getTempCByIndex(0);//Se lee la temperatura en tierra en la zona 1
}
//Función para enviar valores de los sensores por el puerto serie
void enviar_datos(){
Serial.print(valor_ha);Serial.print(«,»);
Serial.print(valor_ta);Serial.print(«,»);
Serial.print(valor_ht1);Serial.print(«,»);
Serial.print(valor_na);Serial.print(«,»);
Serial.print(valor_luz);Serial.print(«,»);
Serial.print(valor_tt1);Serial.print(«,»);
}
Servidor web y BBDD
Como servidor no podía ser de otra manera que optar por una Raspberry PI conRaspbian, basada en Debian y que hace de servidor Base de Datos, y además también de de Servidor Web.
Como servidor web se usa el Servidor Web Apache funcionando junto con MySQL como servidor de BBDD.
Además para que Arduino y Raspberry se comuniquen entre sí, se requiere un script en Python, que se encarga de recibir los datos por el puerto Serie que Arduino está enviando de forma constante .
Básicamente este script recibe los datos de Arduino , se conecta con la BBDD MySql e inserta los datos.
#!/usr/bin/python
#-*- coding: UTF-8 -*-
import MySQLdb
import serial
# Establecemos la conexión con la base de datos
bd = MySQLdb.connect(«host»,«user»,«pass»,«db»)
# Preparamos el cursor que nos va a ayudar a realizar las operaciones con la base de datos
cursor = bd.cursor()
#Inicia la comunicación con el puerto serie
PuertoSerie= serial.Serial(‘/dev/ttyACM0’,9600)
#Lectura de datos
sArduino = PuertoSerie.readline()
#Separa la cadena en valores, cada valor hasta la coma es almacenado en una variable
#Almacenamos los valores en tabla datos de la base de datos huerto
sql1=«INSERT INTO datos_huerto(hum_ambiente,temp_ambiente,hum_tierra,nivel_agua,luz,temp_tierra,id_zona) VALUES (%f,%f,%d,%d,%d,%f,%d)» % (ha,ta,ht1,na,luz,tt1,zona1)
try:
# Ejecutamos el comando
cursor.execute(sql1)
bd.commit()
except:
print«Error»
bd.rollback()
# Nos desconectamos de la base de datos
bd.close()
Para se hacer esto de forma periódica pero no constante, puede usarse la herramienta Cron integrada en Raspbian, de manera que cada “X” minutos se ejecute el script en Python.
Para la parte de visualización de los datos la autora opto por o una aplicación Web sencilla, programada n Php, junto con pequeñas funciones en Javascript para controlar y validar ciertos campos. En el aspecto visual uso el framework Bootstrap, asi como la librería HighCharts para la creación de gráficas y así conseguir visualización de los datos muy atractiva.
Es muy importante que si le damos salida a internet a La Rasberry PI nos cercioremos de que está segura. Para ello es interesante :
Modificar el archivo de configuración de Apache, para que ante un ataque muestre la mínima información posible sobre el servido
Encriptar el tráfico entre cliente y servidor mediante certificados SSL
Forzar para que el acceso siempre sea seguro vía peticiones del tipo HTTPS.
Finalmente esta es la lista de componentes utilizados para el sistema:
Recipientes para depósito de agua y electrónica x2
Tarjeta SD para Raspberry PI x1
Tubos de goteo x2
Plantasx 6
Fuente de alimentación 220V x1
Le damos nuestra mas sincera enhorabuena a Laura por su sistema que animamos a que siga perfeccionando así como compartiendo con la comunidad todos sus progresos
Kemal Ficici nos demuestra con su proyecto escrito en python, que por cierto ha publicado con su código completo en github, como es posible usando la librería OpenCv construir un detector de carril que incluso maneja carriles con curvas.
A pesar del gran avance , sin embargo el autor reconoce que la salida de su sistema todavía se ve afectada por sombras y drásticos cambios en la textura de la carretera lo cual invalida en gran parte su resultado , lo cual le hace pensar en futuras actualizaciones de su proyecto haciendo uso de técnicas de aprendizaje automático para llegar a desarrollar un sistema de detección de vehículo e increíblemente robusto carril.
En el siguiente video podemos ver el resultado de su trabajo:
En cualquier escenario de conducción, las líneas de carril son un componente esencial de lo que indica el flujo de tráfico y donde se debe conducir un vehículo así que también es un buen punto de partida en el desarrollo de niveles de automatismos de ayuda a la conducción ( Sistemas ADAS).
En un proyecto anterior de detección de carril Kemal había implementado un sistema de detección de carril que funcionaba decentemente en perfectas condiciones, sin embargo no detectaba curvas carriles con precisión y no era robusta a obstrucciones y sombras, de modo que esta nueva versión mejora su primera propuesta puesto que ha implementado detección de lineas curvas en los carriles , de modo que funciona mucho mejor y es más robusto para entornos exigentes.
El sistema de detección de carril ha sido escrito en Python usando la librería OpenCV y ha seguido resumidamente las siguientes etapas en el procesamiento de imagen:
Corrección de distorsión
Deformación de la perspectiva
Filtro de Sobel
Detección de picos del histograma
Búsqueda de ventana deslizante
Ajuste de curvas
Superposición de carril detectado
Aplicar el resultado a la salida al vídeo
Respecto al hardware utililizado :
Nvidia Jetson TX2×1
Raspberry Pi 3 Model B×1
Corrección de distorsión
Las lentes de las cámaras distorsionan la luz entrante al enfocarla en el sensor de la cámara o CCD . Aunque esto es muy útil porque nos permite capturar imágenes de nuestro entorno, a menudo terminan distorsionando la luz ligeramente de forma imprecisa lo cual puede ofrecernos medidas inexactas en aplicaciones de visión por ordenador . No obstante fácilmente podemos corregir esta distorsión calibrando la imagen de un objeto conocido ( por ejemplo tablero de ajedrez asimétrico,)y generando un modelo de distorsión que represente las distorsiones de la lente.
La cámara utilizada en la prueba video fue utilizada para tomar 20 imágenes de un tablero de ajedrez, que fueron utilizados para generar el modelo de distorsión.El autor comenzó por convertir la imagen a escala de grises y entonces aplico la función cv2.findChessboardCorners .Como sabemos que este tablero de ajedrez es un objeto tridimensional con líneas rectas exclusivamente podemos aplicar algunas transformaciones a las esquinas detectadas para alinearlos correctamente utilizando cv2.CalibrateCamera() obteniendo así los coeficientes de distorsión y la matriz de cámara de modo que así ya estaba calibrada la cámara
Realizado el proceso anterior se puede utilizar cv2.undistort() para corregir el resto de sus datos de entrada.
Como demostración en la imagen se puede ver la diferencia entre la imagen original del tablero de ajedrez y la imagen corregida a continuación:
Aquí está el código exacto que usó el autor para esto:
defundistort_img():# Prepare object points 0,0,0 ... 8,5,0
obj_pts = np.zeros((6*9,3), np.float32)
obj_pts[:,:2] = np.mgrid[0:9, 0:6].T.reshape(-1,2)
# Stores all object points & img points from all images
objpoints = []
imgpoints = []
# Get directory for all calibration images
images = glob.glob('camera_cal/*.jpg')
for indx, fname in enumerate(images):
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
if ret == True:
objpoints.append(obj_pts)
imgpoints.append(corners)
# Test undistortion on img
img_size = (img.shape[1], img.shape[0])
# Calibrate camera
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size, None,None)
dst = cv2.undistort(img, mtx, dist, None, mtx)
# Save camera calibration for later use
dist_pickle = {}
dist_pickle['mtx'] = mtx
dist_pickle['dist'] = dist
pickle.dump( dist_pickle, open('camera_cal/cal_pickle.p', 'wb') )
defundistort(img, cal_dir='camera_cal/cal_pickle.p'):#cv2.imwrite('camera_cal/test_cal.jpg', dst)with open(cal_dir, mode='rb') as f:
file = pickle.load(f) mtx = file['mtx']
dist = file['dist']
dst = cv2.undistort(img, mtx, dist, None, mtx)
return dst
undistort_img()
img = cv2.imread('camera_cal/calibration1.jpg')
dst = undistort(img) # Undistorted image
Y ahora podemos ver la corrección de distorsión aplicada a una imagen de la carretera.
Solo se puede notar diferencias leves, pero esto como veremos puede tener un impacto enorme en el tratamiento de la imagen.
Deformación de la perspectiva
La detección de carriles con trazados curvas en espacios de la cámara espacio no es uan tarea fácil asi que la idea es conseguir una vista de pájaro de las pistas , lo cual se e puede hacer aplicando una transformación de perspectiva en la imagen. Aquí es lo que parece:
Como vemos nos es nada espectacular debido a que el carril esta sobre una superficie plana en 2D, asi que podemos encajar un polinomio que puede representar fielmente el carril en el espacio del carril
Puede aplicar estas transformaciones a cualquier imagen usando la función cv2.getPerspectiveTransform() para obtener la matriz de transformación, y aplicar la función cv2.warpPerspective() a una imagen.
Aquí está el código que uso el autor para ello:
defperspective_warp(img,
dst_size=(1280,720),
src=np.float32([(0.43,0.65),(0.58,0.65),(0.1,1),(1,1)]),
dst=np.float32([(0,0), (1, 0), (0,1), (1,1)])):
img_size = np.float32([(img.shape[1],img.shape[0])])
src = src* img_size
# For destination points, I'm arbitrarily choosing some points to be# a nice fit for displaying our warped result# again, not exact, but close enough for our purposes
dst = dst * np.float32(dst_size)
# Given src and dst points, calculate the perspective transform matrix
M = cv2.getPerspectiveTransform(src, dst)
# Warp the image using OpenCV warpPerspective()
warped = cv2.warpPerspective(img, M, dst_size)
return warped
Filtro de Sobel
En otras versiones una opción era filtrar las líneas de carril con el color peor sin embargo, esto no siempre es la mejor opción. Si el camino utiliza luz de color concreta en lugar de asfalto, el camino pasa fácilmente a través del filtro de color, y esta la percibirá como una línea de carril blanco, pero eso no es correcto.
En su lugar, podemos utilizar un método similar al detector de borde, esta vez para filtrar hacia fuera de la carretera. Las líneas de carril suelen tienen un alto contraste en la carretera, por lo que podemos utilizar esta peculiaridad para nuestro beneficio. La funcion detector de borde Canny utilizado anteriormente hace uso de Operador de Sobel , para obtener el gradiente de una función de la imagen. La documentación de OpenCV tiene una fantástica explicación sobre cómo funciona asi que utilizaremos esto para detectar zonas de alto contraste para las marcas de carril filtro e ignorar el resto del camino .
Todavía utilizaremos el espacio de color HLS nuevamente, esta vez para detectar cambios en la saturación y la ligereza. Los operadores de sobel se aplican a estos dos canales, y extraemos el gradiente con respecto al eje x y añadiremos los píxeles que pasan nuestro umbral de degradado a una matriz binaria que representa a los píxeles de nuestra imagen. Aquí está como se ve en cámara espacio y lane
:
Tenga en cuenta que las partes de la imagen que estaban más lejos de la cámara no conserven su calidad muy bien. Debido a las limitaciones de resolución de la cámara, datos de los objetos más lejos son muy borrosos y ruidosos pero no necesitamos concentrarnos en la imagen, para que podamos utilizar sólo una parte de esta.
Detección de picos del histograma
Ahora aplicaremos un algoritmo especial llamado SlidingWindowAlgorithm ( algo asi como algoritmo Desplazamiento de Ventana )para detectar nuestras líneas de carril. Sin embargo, antes de que lo podemos aplicar, debemos determinar un buen punto de partida para el algoritmo pues este funciona bien si comienza en un lugar donde haya píxeles de lineas presentes, pero ¿cómo podemos detectar la ubicación de estos píxeles de carril en primer lugar?
Estará recibiendo un histograma de la imagen con respecto al eje X. Cada parte del histograma siguiente muestra píxeles blancos en cada columna de la imagen. Entonces tomamos los picos más altos de cada lado de la imagen, uno para cada línea de carril y tendríamos resulto esta parte
Aquí vemos como el histograma parece, al lado de la imagen binaria:
Búsqueda de ventana deslizante
Ahora necesitamos utilizar el algoritmo de ventana deslizante para distinguir entre los límites del carril de la izquierda y derecha para que podemos caber dos curvas diferentes que representan los límites del carril.
El algoritmo sí mismo es muy simple. A partir de la posición inicial, la primera ventana mide cuántos píxeles se encuentran dentro de la ventana. Si la cantidad de píxeles alcanza un cierto umbral, desplaza la siguiente ventana a la posición lateral media de los píxeles detectados. Si no se detectan los suficientes píxeles, comienza la siguiente ventana en la misma posición lateral.
Esto continúa hasta que las ventanas alcanzan el otro borde de la imagen .Asimismo los píxeles que corresponden a las ventanas reciben un marcador.
En las imágenes de abajo, los píxeles marcados azules representan el carril derecho, y los rojos representan la izquierda:
Ajuste de curvas
El resto del proyecto es ya mas fácil. Aplicamos la regresión polinomial para los pixeles rojos y azules individualmente usando np.polyfit() , y entonces el detector se hace sobre todo
Esto es lo que parecen las curvas:
Superposición de carril detectado
Ya estamos en la parte final del sistema de detección: la interfaz de usuario. Simplemente creamos una superposición que llena en la parte detectada del carril, y luego finalmente lo aplicamos al vídeo.
Este es el resultado final
!Sin duda un resultado realmente espectacular que puede servir de partida para proyectos mas ambiciosos!
Debe estar conectado para enviar un comentario.