Visión Artificial con Arduino | Mover un servo con Python y OpenCV

En esta ocasión te mostraré por medio de un pequeño ejemplo cómo es que la visión artificial puede interconectarse con tu Arduino. Para demostrártelo, controlaremos la posición de un servomotor de acuerdo con la posición de un objeto frente a una cámara.

¿Qué es la visión artificial?

Antes de iniciar con los ejemplos, es necesario que te explique un poco sobre visión artificial. Seguramente has leído, escuchado o visto algo sobre visión artificial, y cómo no, está en todos lados ahora, desde los filtros que utilizamos al publicar una foto en Instagram, o para resaltar características de alguna imagen, pero sobre todo, para automatizar procesos sin la necesidad de un sensor propiamente dicho.

Para definir qué es la visión artificial, es necesario definir primero el procesamiento de imágenes. El procesamiento de imágenes es simplemente aplicar algoritmos matemáticos a una imagen para resaltar algunos elementos dentro de ella, ya sea aplicando filtros, eliminando datos o segmentando para separar la información. Cuando procesamos una imagen, podemos rescatar (o resaltar) cierta información, pero el qué hacemos con esa información depende de nosotros.

Es aquí cuando el siguiente concepto, visión artificial, entra en juego. La visión artificial toma a la imagen procesada, y con base en los elementos que se destacan, se construye un sistema o algoritmo matemático que puede tomar una decisión con esa información. Entonces es cuando nosotros le programamos para que pueda el sistema discernir de manera autónoma. Aquí se convierte en un sistema de visión artificial. Y el concepto de visión, pues viene de que estamos hablando de imágenes a través de una cámara por ejemplo. Recuerda que un video no son más que muchas imágenes una detrás de otra.

La visión artificial cada vez se está utilizando más y más, gracias al gran potencial que tiene de aplicación, ya que solamente necesitamos una cámara y código de programación para hacer maravillas. Es un tema que realmente me gusta mucho y puedo enseñarte algunos truquitos 😎.

Visión Artificial con Arduino

Para el ejemplo que te mostraré el día de hoy, la placa Arduino no realizará propiamente el procesamiento, simplemente ejecutará las instrucciones finales de control. El esquema sobre el que trabajaremos será el siguiente:

Sistema de visión artificial con Arduino.
Sistema de visión artificial con Arduino.

Dentro de la computadora implementaremos un código utilizando el lenguaje Python y OpenCV para acceder a la webcam, capturar en tiempo real la posición de un objeto de un color que destaque y mapearemos su posición dentro de la imagen. De acuerdo con esta posición, se le enviará al Arduino la posición mediante el puerto Serie, que se encuentra dentro del puerto USB. Este mensaje, el Arduino lo recibirá y dependiendo de que posición sea, mandará la instrucción a un servomotor para modificar su posición. La idea general es que la posición del servo «siga» a la posición del objeto clave, como si lo estuviera observando directamente.

Si no estás muy familiarizado con el procesamiento de imágenes y visión artificial o con el lenguaje de programación Python, no te preocupes, como siempre te guiaré paso a paso para lograr el objetivo.

Instalar Python en tu computadora

Lo primero que debes saber es que Python es un lenguaje de programación muy fácil de utilizar y sobre todo me gusta porque es multiplataforma, funciona en Windows®, Mac OSX® y Linux®. Pero me gusta más porque fácilmente podemos migrar su funcionamiento de una computadora a una tarjeta electrónica dedicada como las RaspberryPi®.

Para instalar Python en tu computadora tienes que simplemente descargar una versión del lenguaje y del compilador, para mi caso yo instalé la versión 3.9.2. Para revisar la versión más reciente para tu sistema operativo visita la página oficial.

Una vez que lo tienes instalado, existen varias maneras de utilizarlo en tu computadora, puede ser desde la Terminal, desde el IDE instalado junto con el compilador, o puedes instalar software de terceros para gestionar tus programas, a mí me gusta trabajar con PyCharm, con su versión gratuita.

Si quieres aprender más a detalle sobre programación en Python déjame tus comentarios!

Sea cual sea tu decisión, el código funcionará sin problemas. Pero para realizar el ejercicio de hoy, necesitaremos instalar una librería más.

¿Te está gustando este artículo?

Te invito a descargar totalmente gratis la Guía de Inicio en Arduino

Encontrarás mucha información extra, conceptos y sobre todo podrás llevarla contigo en todo momento

Instalar OpenCV en tu computadora

OpenCV es una biblioteca desarrollada especialmente para la visión por computadora (visión artificial), es libre y originalmente la desarrolló Intel®. De igual manera, es multiplataforma, tanto para sistemas operativos como para arquitecturas de hardware como RaspberryPi, entre otras.

La versión que yo instalé para este proyecto es la 4.5.1.48. Para instalarla existen muchos métodos, si estás en Windows puedes descargarlo desde aquí. O de una manera simplificada, abres tu Terminal y escribes pip install opencv-contrib-python de manera automática te instalará los paquetes básicos de OpenCV para Python y algunos paquetes extras.

Yo como estoy en el sistema operativo de Mac OSX, lo instalé directamente desde PyCharm, ya que en este software puedo elegir distintas versiones para trabajar con cada proyecto de manera independiente.

Puedes verificar si todo quedó instalado correctamente abriendo tu Terminal (o cmd en Windows) y teclear lo siguiente:

Con esto entras a la interfaz de python en su versión 3. Ahora tienes que importar la biblioteca de OpenCV y posteriormente revisar la versión:

Si en algún paso de los anteriores te saltó un error, es que la instalación no quedó correcta.

Si todo sale correcto, verás algo parecido a esto:

Terminal con instalación correcta.
Terminal con instalación correcta.

Por cierto, también necesitaremos instalar la biblioteca numpy: pip install numpy esta nos ayudará a manejar matrices de datos, ya que las imágenes básicamente son matrices de números.

Si te interesa aprender sobre procesamiento de imágenes déjame tus comentarios y puedo enseñarte todo lo que sé!

Editado el 17 de junio del 2021

También necesitaremos instalar una librería para utilizar el puerto Serie, ya que para este ejemplo utilizaremos la comunicación serial con el Arduino y la PC. Para instalarla, deberemos ejecutar el comando: pip install pyserial

Código en Python para utilizar con Arduino

Ahora pasaremos a crear el código para realizar el algoritmo de visión artificial y enviarle los datos al Arduino por medio del puerto Serie.

El código completo a utilizar es el siguiente:

Editado el 25 de abril del 2021

¿Muy cortito no? Pero para aquellos que estamos aprendiendo Python, vamos a desglosarlo.

Primero importamos las bibliotecas a utilizar, que son la de OpenCV, numpy y serial:

Posteriormente, inicializaremos el puerto Serie con la configuración adecuada. En mi caso, declararé el nombre del puerto serie donde tengo conectada mi placa Arduino. Si estás en Windows, probablemente sea un COM3 o algo así. Para verificar donde lo tienes conectado, simplemente conecta tu placa al puerto USB y dentro del IDE de Arduino, verifica el puerto de conexión, ahí podrás observar el nombre. La velocidad tú la eliges, recordando que tanto el Arduino como el código en Python deberán tener la misma. Finalmente creamos una variable llamada ser que será para el puerto serie.

Ahora vamos a crear una variable para obtener la imagen de la webcam. Si observas, utilizamos la biblioteca de OpenCV, con cv2.VideoCapture() dentro del paréntesis deberá ir un ID de acuerdo con la cantidad de dispositivos de cámaras que tengas instalados en tu computadora, normalmente con un 0 debería de funcionar; si no, ya sabes, ponle 1 o 2 etc.

Ahora crearemos dos variables donde almacenaremos los valores que vamos a utilizar como referencia de segmentación:

Si observas estamos utilizando numpy a través de np como lo importamos al inicio. Estos dos vectores de datos contienen los valores correspondientes en el Espacio de Color HSV (tinte, saturación, brillo). Sin entrar mucho en detalles de este espacio de color, nos ayuda mucho en el procesamiento de imágenes ya que podemos procesar los colores desde otras perspectiva. Para este ejemplo vamos a detectar una tapadera de garrafón de agua color azul, por lo que declaramos un nivel bajo y un nivel alto de tonos de azul de acuerdo con el espacio de color HSV mostrado en la siguiente imagen, puedes observar que los tonos azules van de un valor de entre 90 y 130. Puedes tomar de referencia la imagen para determinar los valores de H (el primer parámetro, y saturación y brillo los puedes dejar como te los muestro en el ejemplo)

Escala de color HSV.
Escala de color HSV. Fuente.

Dentro del ciclo while tendremos el código que se ejecutará continuamente. Aquí vamos a describir la primer línea: ret, frame = cap.read() con ella obtenemos dos variables ret y frame. En ret se almacena un true si se realizó una captura de manera correcta y frame es la imagen capturada por la webcam.

Posteriormente, si se realizó una captura correctamente procesamos imágenes, para esto sirve el if ret

Con frame = cv2.flip(frame, 1) reflejamos la imagen para tener una perspectiva directa del movimiento.

Con frameHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) convertimos del espacio de color BGR (blue, green, red que es el espacio de color en el que las imágenes se capturan), a HSV.

Ahora creamos una máscara mascara = cv2.inRange(frameHSV, azulBajo, azulAlto) utilizando las variables azulBajo y azulAlto como los límites inferior y superior. Ahora necesitamos obtener los contornos de la máscara obtenida anteriormente para lo que utilizamos contornos, _ = cv2.findContours(mascara, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) guardando en la variable contornos todos los encontrados de la variable mascara.

Ahora los vamos a dibujar sobre la imagen original cv2.drawContours(frame, contornos, -1, (255, 0, 0), 4), para dibujarla utilizamos la función cv2.drawContours además le agregamos el color que deseamos que utilice para destacar los contornos, en mi caso elegí un color azul muy marcado (255,0,0) recordando que OpenCV utiliza un formato (B,G,R).

Posteriormente, necesitamos verificar todos los contornos que obtuvimos de la imagen, revisar el área de cada contorno y encontrar el centroide. Para esto, primero iniciamos un bloque for y calculamos el área de cada contorno, si el área es mayor a 6000 píxeles entonces buscamos el centroide:

Este tamaño de área tú lo configuras, en mi caso me dio buen resultado con 6000, pero puedes modificarlo de acuerdo a tu implementación. Recuerda que las condiciones de luz afectan mucho para la captura de imágenes, por lo que siempre es preferible estar bajo un ambiente de buena iluminación.

Ahora calculamos los momentos M = cv2.moments(c) y revisamos que si el momento «m00» es igual a cero, lo igualamos a 1, ya que realizaremos una división y dividir entre 0 es infinito y luego aparecerá un error. En OpenCV podemos encontrar el punto central de un contorno o imagen binaria utilizando los momentos, luego lo aprovechamos para calcular las coordenadas x e y del punto central (centroide):

Realizando las divisiones anteriores encontramos entonces las coordenadas donde se encuentra el centroide. Ahora realizamos el dibujo de un círculo cv2.circle(frame, (x, y), 7, (0, 0, 255), -1) dentro de la imagen original (frame) en las coordenadas x e y calculadas, de radio 7 y de color (0,0,255) que sería un rojo intenso, y el parámetro final es para indicarle el grosor, al utilizar -1 le indicamos que simplemente rellene el círculo.

Creamos la variable para utilizar la tipografía font = cv2.FONT_HERSHEY_SIMPLEX y ahora insertamos un texto con las coordenadas cv2.putText(frame, '{},{}'.format(x, y), (x + 10, y), font, 1.2, (0, 0, 255), 2, cv2.LINE_AA) justo en la coordenada (x+10, y), el resto de parámetros son para configurar la tipografía, tamaño y color.

Ahora creamos un nuevo contorno nuevoContorno = cv2.convexHull(c) con esta función creamos un contorno de forma convexa, uniendo una serie de puntos para formar una figura, esto nos ayuda para el caso de que la figura o el contorno de la figura no sea muy regular, simplemente unirá los puntos para formar una figura cerrada. Y dibujamos el nuevo contorno cv2.drawContours(frame, [nuevoContorno], 0, (255, 0, 0), 3).

Ahora vamos a dividir nuestra imagen en rangos, para mi caso, la imagen que obtengo de mi webcam es de 1280×720, así que la dividí tomando solamente la componente en x, algo así:

Diagrama de distribución de la posición.
Diagrama de distribución de la posición.

Con esta referencia, dentro de un if comparé si la x estaba dentro de cierto rango, enviar por el puerto serie en formato de byte la palabra clave para el movimiento.

Editado el 25 de abril del 2021

Los movimientos los configuré de manera que el Arduino pueda entender e interpretar en un equivalente en grados:

ComandoGrados
izq1\n0
izq2\n30
izq3\n60
ctr\n90
der3\n120
der2\n150
der1\n180
Tabla con los comandos y su representación en grados.

Finalmente, mostramos la imagen en una ventana ya con todo dibujado, y comparamos si en el teclado presionamos la tecla s que se cierre la ventana y se detenga el código.

Por último –ahora si–liberamos la webcam y cerramos todas las ventanas

Código en Arduino para mover el servo

Ahora vamos al código que tenemos que cargar a la placa Arduino. Para este ejemplo básicamente se modificó el código que ya te mostré en este artículo. Utilizamos el pin 9 para controlar la posición del servo.

Como puedes observar, se ajustó para que la palabra recibida por el puerto serie se verifique y se compare de acuerdo a la tabla que te mostré anteriormente con la palabra y los grados (posición del servo).


¿Qué te ha parecido este artículo? ¿Para qué aplicación se te ocurre? Platícame en dónde lo quieres utilizar, recuerda que yo te muestro herramientas, diseño, algunos proyectos, pero sobre todo la imaginación es tu límite. Puedes utilizar este ejemplo para modificarlo a tu antojo y agregar más grados de libertad, o ejes de movimiento.

¡Hasta la próxima!

Recuerda visitar nuestras redes sociales

16 comentarios en «Visión Artificial con Arduino | Mover un servo con Python y OpenCV»

  1. Abraham que tal. Me ha encantado tu video, muy explicativo y bastante fácil de digerir para los que estamos iniciando en la visión artificial. Y planeo usar este principio para mi proyecto de Robótica.

    Tengo un problema con el sistema. Ambos códigos me corren bien, ninguno presenta errores y el de Python si hace las detecciones de mi objeto y su trayectoria, sin embargo por alguna razón el servomotor nunca se mueve. Ya probé con 3 diferentes y revise que no estuvieran descompuestos pero estos si funcionan, incluso use tu código del tutorial para controlar el servo con Arduino y funciono perfecto. Pero por alguna razón con este no se mueve. La placa Arduino parece que si recibe la señal ya que cuando la cámara detecta el objeto, el foco RX de la placa parpadea conforme muevo el objeto.
    Revise los pines, cambie a otros, revise el COM y no encuentro cual podría ser el problema.

    Se que mi descripción es muy vaga pero tienes alguna idea de cual podría ser el problema?

    1. Hola Alan, me da gusto que te sirva el artículo. Al parecer el Arduino está recibiendo mensajes, pero no está activando la bandera de que el mensaje ya terminó de llegar, ¿qué caracter estás utilizando para detectar el fin de mensaje? en el código manejé \n podrías poner una parte del código para revisarlo o puedes escribirme a mi correo: contacto@automatizacionparatodos.com
      Saludos!

    2. Hola Alan, contesto tu comentario por aquí también. Ya he dado respuesta a tu pregunta directamente por correo, pero para las personas que lean este comentario, el error estaba en el código, ya está corregido. Gracias de nuevo por tu comentarios. Saludos!

  2. Hola, gracias por este proyecto. Cuando intento instalar opencv con el comando que pusiste, me salta este error:

    File «», line 1
    pip install opencv-contrib-python
    ^
    SyntaxError: invalid syntax

    Lo mismo ocurre con estos comandos:

    pip install numpy
    pip install pyserial

    He probado con la version 3.7 y 3.9 de python pero siempre obtengo el mismo error. Gracias.

    1. Hola Ramón, los comandos de instalación se escriben en la terminal, si estás utilizando PyCharm, en la parte inferior tiene una pestaña de terminal, si no, necesitas abrir la ventana de comandos. En Windows se llama CMD y en MacOS se llama Terminal. Todos los comandos que inician con pip deberán ir en esa ventana. Quedo atento a tus comentarios. Saludos!

  3. Muchas gracias, efectivamente utilizando la consola de windows pude instalar las librerías.

    Otra consulta si no es molestia..

    Seria posible utilizar este proyecto para controlar dos motores paso a paso y poder controlar un telescopio para que haga seguimiento automático de un avión o un satélite?

    1. Excelente Ramón. Tu propuesta es muy interesante, si se podría utilizar. El mayor reto estaría en el tamaño del objeto con respecto a la imagen para que lo puedas segmentar correctamente, fuera de eso, el proceso es prácticamente igual. Saludos!

  4. Hola un saludo, quisiera saber si tienes un tutorial como controlar un servo motor con Arduino e IA mediante el movimiento independiente de los dedos de la mano, ya que no poseo mucho conocimiento en Python y me queda un poco difícil, agradecería tu ayuda.

    1. Hola estimado Miguel, aún no tengo un video como el que mencionas, pero lo tomaré en cuenta para un futuro video y con mucho gusto lo podré preparar. Muchas gracias! Saludos!

  5. Hola excelente proyecto quisiera saber que estoy haciendo mal porque ya instale todas las librerias y el numero del puerto pero me sale este error

    Traceback (most recent call last):
    File «C:\Users\jooze\PycharmProjects\servodf\main.py», line 7, in
    ser = serial.Serial(COM, BAUD)
    File «C:\Users\jooze\PycharmProjects\servodf\venv\lib\site-packages\serial\serialwin32.py», line 33, in __init__
    super(Serial, self).__init__(*args, **kwargs)
    File «C:\Users\jooze\PycharmProjects\servodf\venv\lib\site-packages\serial\serialutil.py», line 222, in __init__
    self.port = port
    File «C:\Users\jooze\PycharmProjects\servodf\venv\lib\site-packages\serial\serialutil.py», line 268, in port
    raise ValueError(‘»port» must be None or a string, not {}’.format(type(port)))
    ValueError: «port» must be None or a string, not

  6. Ya lo solucione cambiando la linea de código de

    COM = ‘/dev/cu.usbserial-14120’
    BAUD = 9600
    ser = serial.Serial(COM, BAUD)

    —> ser = serial.Serial(‘COM69’, 9600)

    Por si alguien tiene ese error usando Windows

    1. Muchas gracias por tu comentario, seguro que a alguien más le servirá. Cada computadora puede asignar el nombre distinto a cada puerto, es mejor revisar siempre el nombre que se le asigna.

      Saludos!

Deja un comentario