Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
INSTITUCIÓN: TECNOLÓGICO DE LA LAGUNA CARRERA: MECATRÓNICA MATERIA: SENSORES INTELIGENTES DOCENTE: HERRERA CARRILLO NAZLE EDITH ALUMNO: RODRÍGUEZ GUERRA EDUARDO ANTONIO MATRICULA: 19131252 PRÁCTICA 8: DETECCIÓN DE TRES COLORES EN VIDEO FECHA DE ENTREGA: 20 DE ABRIL 2023 Descripción de la práctica: Retomando lo aprendido en la practica 7, elaboraremos un programa en Python capaz de reconocer tres colores diferentes y activar tres actuadores de acuerdo a los colores detectados. Cabe destacar que los actuadores los activaremos desde el Arduino, gracias a la librería serial que ofrece Python. En mi caso decidí activar un servo capaz de moverse en ángulos diferentes dependiendo de la ubicación el color azul fuerte, también detecta el color rojo, accionando un motor a pasos y por último dos leds que se activan solo con la presencia del color amarillo, si esta a la izquierda prende el led 1, y si están a la derecha prende el led dos. DIAGRAMA ELÉCTRICO DE CONEXIONES: Se empleo del puente H (LS298N) en sustitución del driver correspondiente del motor a pasos (28BYJ-48), también use un servomotor de 180° y por último se usó de 2 leds. ANALOG IN ATMEGA328P-PU 1121 ~ ~ ~ ~ ~ ~ T X R X Reset BTN ON www.TheEngineeringProjects.com P D 0 /R X D 0 P D 1 /T X D 1 P D 2 /I N T 0 2 P D 3 /I N T 1 3 P D 4 /T 0 /X C K 4 P D 5 /T 1 5 P D 6 /A IN 0 6 P D 7 /A IN 1 7 P B 0 /I C P 1 /C L K O 8 P B 1 /O C 1 A 9 P B 2 /S S /O C 1 B 1 0 P B 3 /M O S I/ O C 2 A 1 1 P B 4 /M IS O 1 2 P B 5 /S C K 1 3 A R E F P C 5 /A D C 5 /S C L A 5 P C 4 /A D C 4 /S D A A 4 P C 3 /A D C 3 A 3 P C 2 /A D C 2 A 2 P C 1 /A D C 1 A 1 P C 0 /A D C 0 A 0 R E S E T V C C G N D ARD1 ARDUINO UNO +88.8 220 35V RVT +12V GND +5V OUT4 OUT3 OUT1 OUT2 ENA IN1 IN2 IN3 IN4 ENB www.TheEngineeringProjects.com L1 L298 MOTOR DRIVER D1 LED-BLUE D2 LED-RED B1 5V +88.8 5V GND 5 V G N D GND G N D 5 V Ya que es la primera vez que usaba un motor a pasos opte por acudir a la datasheet correspondiente Y realizar las conexiones conforme se indica Código de Arduino: #include <Servo.h> Servo servo1; //_____________________________________ #include <Stepper.h> //libreria a usar Stepper motorpasos(2048, 8, 9, 10, 11); //______________________________________ String entradaSerial = ""; // String para almacenar entrada bool entradaCompleta = false; // Indicar si el String está completo int pin = 6; // pin de conexión PWM al servo //int angulo = 0; // Variable para guardar el angulo que deseamos de giro int IN1=2; int IN2=4; void setup() { servo1.attach(pin, 580, 2500); Serial.begin(9600); pinMode(IN1, OUTPUT); //DECLARAMOS COMO SALIDAS LOS PINES QUE HARAN pinMode(IN2, OUTPUT); //girar al motor de directa motorpasos.setSpeed(3); //velocidad motor a pasos, valor maximo } void loop() { if (entradaCompleta) { if (entradaSerial == "izq1\n") { servo1.write(0); } else if (entradaSerial == "izq2\n") { servo1.write(30); } else if (entradaSerial == "izq3\n") { servo1.write(60); } else if (entradaSerial == "ctr\n") { servo1.write(90); } else if (entradaSerial == "der3\n") { servo1.write(120); } else if (entradaSerial == "der2\n") { servo1.write(150); } else if (entradaSerial == "der1\n") { servo1.write(180); } else if (entradaSerial == "dcder1\n") { digitalWrite(IN1,LOW); //delay(10); digitalWrite(IN2,HIGH); } else if (entradaSerial == "dciz2\n") { digitalWrite(IN2,LOW); //delay(10); digitalWrite(IN1,HIGH); } else if (entradaSerial == "pasos1\n") { motorpasos.step(64); delay(20); } else if (entradaSerial == "pasos2\n") { motorpasos.step(-64); delay(20); } else { Serial.println("El dato recibido es inválido!!"); } entradaSerial = ""; entradaCompleta = false; } //digitalWrite(IN2,LOW); //EN CASO DE QUE NO HAYA INSTRUCCIONES //digitalWrite(IN1,LOW); //DETENDRE EL MOTOR DC } // Función que se activa al recibir algo por // el puerto serie, Interrupción del Puerto Serie. void serialEvent() { while (Serial.available()) { // Obtener bytes de entrada: char inChar = (char)Serial.read(); // Agregar al String de entrada: entradaSerial += inChar; // Para saber si el string está completo, se detendrá al recibir // el caracter de retorno de línea ENTER \n if (inChar == '\n') { entradaCompleta = true; } } } Básicamente establecemos conexión con el puerto serial y en caso de recibir alguna de las instrucciones correspondientes activar un accionador, ahora bien, el código se ejecuta una y otra vez a una muy alta velocidad, por ejemplo, si ponemos un objeto de color amarillo enfrente, el led no se quedará activado si no que se activará una y otra vez, pero a una alta velocidad que simula quedarse activado, además de que apenas retiremos el objeto el led se apagará. Cabe destacar que al seleccionar un motor a pasos presente algunas dificultades, ya que al seleccionar que el motor recorriera muchos pasos al registrar la presencia del color rojo, me encontré que si bien ejecutaba la instrucción el programa presentaba un delay muy grande, pues, el tiempo que demoraba en hacer el recorrido, era un tiempo en que el Arduino no recibía las instrucciones por parte de Python. Para solucionar eso fue cuestión de reducir la cantidad de pasos recorridos en cada loop del código, y a la hora de poner un objeto de color rojo, el programa funcionaba más fluido. Por último, hacer mención a que se usaron dos librerías (Servo.h) y (Stepper.h). Código de Python: #PRÁCTICA 8 DE SENSORES RECONOCIMIENTO DE 3 COLORES #INGRESAMOSLAS TRES LIBERIAS A UTILIZAR import cv2 import numpy as np import serial #SELECCIONAMOS EL PUERTO COM DONDE SE CONECTE EL ARDUINO COM = 'COM5' #ESTABLECEMOS LA VELOCIDAD DE COMUNICACION BAUD = 9600 #CREAMOS VARIABLE PARA GUARDAR LOS DATOS DEL PUERTO SERIAL ser = serial.Serial(COM, BAUD) #con la biblioteca open cv creamos variable para optener prestada la imagen de la camara web, en el caso #de mi equipo solo tengo una camara conectada por eso el cero. cap = cv2.VideoCapture(0) #definimos dos variables por color basadas enLA ESCALA de color HSV #azul azulBajo = np.array([90, 100, 20], np.uint8) azulAlto = np.array([125, 255, 255], np.uint8) #amarillo amarilloBajo = np.array([25, 100, 20], np.uint8) amarilloAlto = np.array([35, 255, 255], np.uint8) #rojo rojoBajo = np.array([165, 100, 20], np.uint8) rojoAlto = np.array([180, 255, 255], np.uint8) #Dentro del while tenemos el ciclo que se repetira constantemente while True: #obtenemos dos variables, en ret se guarda un true o false indicando si se capturo una imagen #en frame guarda la imagen capturada por la web cam ret, frame = cap.read() #el if ns permitira pocesar la imagen en caso de ser capturda if ret: #reflejamos la imagen para tener una perspectiva directa el movimiento frame = cv2.flip(frame, 1) #convertimos espacio de color BGR Formato de la imagen capturada con web cam a HSV frameHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) #Creamos una mascara con el valor del color bajo y el color alto como los limites superior e inferior mascaraazul = cv2.inRange(frameHSV, azulBajo, azulAlto) ################################################################ mascaraamarilla = cv2.inRange(frameHSV, amarilloBajo, amarilloAlto)############################################################### mascararoja = cv2.inRange(frameHSV, rojoBajo, rojoAlto) ############################################################### #en la variable contornos guardamos todos los contornos encontrados en la mascara contornosazul, _ = cv2.findContours(mascaraazul, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ############################################################### contornosamarillo, _ = cv2.findContours(mascaraamarilla, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ############################################################### contornosrojo, _ = cv2.findContours(mascararoja, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ############################################################### #dibujamos el contorno sobre la imagen usando se comando, ademas de asignar un color para el contorno en ese caso es un azul fuerte en formato BGR cv2.drawContours(frame, contornosazul, -1, (0, 0, 255), 4) ############################################################### cv2.drawContours(frame, contornosamarillo, -1, (0, 255, 0), 4) ############################################################### cv2.drawContours(frame, contornosrojo, -1, (255, 0, 0), 4) ############################################################### #verificamos los contornos obtenidos de la imagen for c in contornosazul: #revisamos el area de dichos contornos area = cv2.contourArea(c) #en caso de que el area sea mayor a 6000 buscamos un centroide if area > 6000: #calculamos los momentos M = cv2.moments(c) #si el momento m00 es cero, para evitar errores le asignamos un 1 if M["m00"] == 0: M["m00"] = 1 #con esta divisiones encontramos las coordenadas X Y del centroide x = int(M["m10"] / M["m00"]) y = int(M['m01'] / M['m00']) #dibujamos un circulo de color rojo de radio -7 ademas de rellenarlo con la variable -1 cv2.circle(frame, (x, y), 7, (0, 0, 255), -1) #usamos una variable para insertar la tipografia font = cv2.FONT_HERSHEY_SIMPLEX #ingresamos el teto con las direcciones del centroide justo en la cooredenada (x + 10, y) cv2.putText(frame, '{},{}'.format(x, y), (x + 10, y), font, 1.2, (0, 0, 255), 2, cv2.LINE_AA) #creamos un nuevo contorno de forma convexa cerrando tos las aperturas que tenia el contorno real a fin de cerrar el contorno y respetar la forma de la imagen de color nuevoContorno = cv2.convexHull(c) #dibujamos el nuevo contorno cv2.drawContours(frame, [nuevoContorno], 0, (255, 0, 0), 3) #con este if dependiendo de la ubicacion del centroide en el eje de las X, el programa enviara un mensaje especifico por el serial port a fin de decirle al servo para donde tiene que moverse #por ultimo escribimos la posicion en la consola de spyder if x < 85: print("SERVO izquierda 100%") ser.write(b"izq1\n") elif 85 > x > 170: print("SERVO izquierda 60%") ser.write(b"izq2\n") elif 170 > x > 255: print("SERVO izquierda 30%") ser.write(b"izq3\n") elif 255 < x < 340: print("SERVO al centro") ser.write(b"ctr\n") elif 340 < x < 425: print("SERVO derecha 30%") ser.write(b"der3\n") elif 425 < x < 510: print("SERVO derecha 60%") ser.write(b"der2\n") elif x > 510: print("SERVO derecha 100%") ser.write(b"der1\n") ############################################################### for c2 in contornosamarillo: #revisamos el area de dichos contornos areaamarilla = cv2.contourArea(c2) #en caso de que el area sea mayor a 6000 buscamos un centroide if areaamarilla > 6000: #calculamos los momentos M2 = cv2.moments(c2) #si el momento m00 es cero, para evitar errores le asignamos un 1 if M2["m00"] == 0: M2["m00"] = 1 #con esta divisiones encontramos las coordenadas X Y del centroide x2 = int(M2["m10"] / M2["m00"]) y2 = int(M2['m01'] / M2['m00']) #dibujamos un circulo de color rojo de radio -7 ademas de rellenarlo con la variable -1 cv2.circle(frame, (x2, y2), 7, (0, 0, 255), -1) #usamos una variable para insertar la tipografia font2 = cv2.FONT_HERSHEY_SIMPLEX #ingresamos el teto con las direcciones del centroide justo en la cooredenada (x + 10, y) cv2.putText(frame, '{},{}'.format(x2, y2), (x2 + 10, y2), font2, 1.2, (0, 255, 0), 2, cv2.LINE_AA) #creamos un nuevo contorno de forma convexa cerrando tos las aperturas que tenia el contorno real a fin de cerrar el contorno y respetar la forma de la imagen de color nuevoContornoamarillo = cv2.convexHull(c2) #dibujamos el nuevo contorno cv2.drawContours(frame, [nuevoContornoamarillo], 0, (0, 255, 0), 3) #con este if dependiendo de la ubicacion del centroide en el eje de las X, el programa enviara un mensaje especifico por el serial port a fin de decirle al servo para donde tiene que moverse #por ultimo escribimos la posicion en la consola de spyder if x2 < 300: print("MOTOR DC DERECHA") ser.write(b"dcder1\n") elif x2 > 300: print("MOTOR DC IZQUIERDA") #el mensaje que le mandamos al arduino debe ser en este formato ser.write(b"dciz2\n") ############################################################### ############################################################### for c3 in contornosrojo: #revisamos el area de dichos contornos arearoja = cv2.contourArea(c3) #en caso de que el area sea mayor a 6000 buscamos un centroide if arearoja > 6000: #calculamos los momentos M3 = cv2.moments(c3) #si el momento m00 es cero, para evitar errores le asignamos un 1 if M3["m00"] == 0: M3["m00"] = 1 #con esta divisiones encontramos las coordenadas X Y del centroide x3 = int(M3["m10"] / M3["m00"]) y3 = int(M3['m01'] / M3['m00']) #dibujamos un circulo de color rojo de radio -7 ademas de rellenarlo con la variable -1 cv2.circle(frame, (x3, y3), 7, (0, 255, 0), -1) #usamos una variable para insertar la tipografia font3 = cv2.FONT_HERSHEY_SIMPLEX #ingresamos el teto con las direcciones del centroide justo en la cooredenada (x + 10, y) cv2.putText(frame, '{},{}'.format(x3, y3), (x3 + 10, y3), font3, 1.2, (0, 255, 0), 2, cv2.LINE_AA) #creamos un nuevo contorno de forma convexa cerrando tos las aperturas que tenia el contorno real a fin de cerrar el contorno y respetar la forma de la imagen de color nuevoContornorojo = cv2.convexHull(c3) #dibujamos el nuevo contorno cv2.drawContours(frame, [nuevoContornorojo], 0, (255, 0, 0), 3) #con este if dependiendo de la ubicaciondel centroide en el eje de las X, el programa enviara un mensaje especifico por el serial port a fin de decirle al servo para donde tiene que moverse #por ultimo escribimos la posicion en la consola de spyder if x3 < 300: print("PASOS DERECHA") ser.write(b"pasos1\n") elif x3 > 300: print("PASOS IZQUIERDA") ser.write(b"pasos2\n") ############################################################### #cv2.imshow('mascaraAzul', mascaraazul) cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('s'): ser.close() break cap.release() cv2.destroyAllWindows() Todo ese código, lo que hace es emplear de tres librerías (numpy, serial y cv2) para establecer una comunicación serial, además de procesar las imágenes obtenidas desde la cámara del equipo y plasmarlas al usuario. Ya que usamos la comunicación serial del arduino establecemos una velocidad de transmisión de 9600, y tomamos prestada la cámara de la computadora (0), en caso de que tengamos diferentes cámaras solo asignamos un numero deacuerdo a la cámara que deseemos usar. De acuerdo con la escala de colores HSV: Seleccionamos un rango de colores a detectar, indicado los limites superior e inferior. Después establecemos un ciclo while que se repetirá una y otra vez hasta resetear el Kernel de spyder. Dentro de dicho ciclo, primero convertimos la escala de colores de la imagen obtenida en hsv ya que las computadoras usan un tipo BGR, después inicializamos una mascara para cada color indicando el rango de color. En caso de que detecte alguna superficie con un área mayor a 600 y de uno de los colores programados, procederá a dibujar su contorno por encima de la imagen, para más tarde calcular el centroide de la imagen, y una vez lo tengamos dibujarlo en la imagen plasmada al usuario. Ahora bien, dependiendo de la ubicación que tenga dicho centroide en el eje de las x, mandará un mensaje diferente, en el caso del servomotor tengo programado que envié 7 mensajes diferentes, en caso de que sea el motor a pasos dos posibles respuestas, que activen el giro en un sentido u otro. Las ultimas partes del código están diseñadas a fin de cerrar el contorno dibujado ya que hay ua alta posibilidad de detectar áreas irregulares, entonces rellenaremos los huecos de contorno con trazos curvos. Y los centroides dibujados son de diferentes colores. EJECUCIÓN DE LA PRÁCTICA: Para comprender aun mejor el funcionamiento del código en el caso de los bordes del área azul decidí dejar el primer borde detectado en color rojo y el dibujado al final con curvas en color azul para ser capaces de distinguir las diferencias entre un borde y otro. Para energizar los actuadores decidí usar una fuente ajena al Arduino para evitar fallas. Conclusiones: Para comenzar me sorprendió el hecho de que el programa pudiera reconocer los tres colores al mismo tiempo, que si bien presentaba un delay menor a la hora de poner en marcha los programas. Decidí manipular aun más los pasos que daba en cada instrucción el motor a pasos hasta llevarlo a la menor cantidad, donde logre reducir dicho delay considerablemente, algo más que me parece trascendental destacar es el hecho de que dependiendo de la cámara que uses será la efectividad obtenida con la práctica, pues la cámara de mi laptop detecta bien los colores en mi casa, pero en el salón de clases donde hay mucha luz batallaba y tenia que acomodar los objetos para poderlos detectar, cosa que no sucedía con las computadoras de dos de mis compañeros donde decidí poner en marcha este código y detectando los colores a la primera, una de esas computadoras tenía una cámara de alta definición y otro traía su propia cámara. Al correr los códigos cuando cargo la laptop veo que presentar menores tiempos de delay al ser sometido a los tres colores, en cambio si uso la laptop con poca pila los delays incrementan. Por todo lo anterior destaco que para ser funcional este código debe ajustarse conforme la computadora donde se ejecute y el entorno en que se ejecute pues quizás si hubiese considerado colores más oscuros hubiese logrado una mayor precisión en el salón de clases.
Compartir