? MIDIENDO la DISTANCIA entre 2 objetos | Python – OpenCV
Si necesitáramos medir una distancia pequeña entre dos objetos (círculos verdes), podríamos simplemente tomar una regla y usar esta para determinar dicha distancia, pero en el tutorial de hoy me he planteado medir la distancia en centímetros que separa a dos objetos en el eje x, mediante técnicas de visión por computador, para ello como en previos tutoriales usaremos OpenCV con Python.
Para este proceso usaremos un área de referencia dada por una hoja blanca A4, por lo que para alinearla usaremos el código de los post anteriores: Transformación de perspectiva | OpenCV con Python y ? Escáner de documentos + ? reconocimiento de texto (OCR) ?| OpenCV con Python.
CONTENIDO
- Extraer la región de interés (hoja A4)
- Detección de los círculos verdes
- Calculando la distancia entre dos objetos
- Pruebas de funcionamiento
Extraer la región de interés (hoja A4)
Dado que vamos a tomar como referencia a una imagen correspondiente a una hoja blanca A4, procederemos a extraerla mediante transformación de perspectiva. Para ello usaremos parte del código el tutorial anterior, por lo que te recomiendo darle un vistazo.
Creamos un script llamado distancia_dos_objetos.py, y continuamos con el código:
import cv2 import numpy as np import imutils def ordenar_puntos(puntos): n_puntos = np.concatenate([puntos[0], puntos[1], puntos[2], puntos[3]]).tolist() y_order = sorted(n_puntos, key=lambda n_puntos: n_puntos[1]) x1_order = y_order[:2] x1_order = sorted(x1_order, key=lambda x1_order: x1_order[0]) x2_order = y_order[2:4] x2_order = sorted(x2_order, key=lambda x2_order: x2_order[0]) return [x1_order[0], x1_order[1], x2_order[0], x2_order[1]] def roi(image, ancho, alto): imagen_alineada = None gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, th = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) cnts = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:1] for c in cnts: epsilon = 0.01*cv2.arcLength(c,True) approx = cv2.approxPolyDP(c,epsilon,True) if len(approx) == 4: puntos = ordenar_puntos(approx) pts1 = np.float32(puntos) pts2 = np.float32([[0,0], [ancho,0], [0,alto], [ancho,alto]]) M = cv2.getPerspectiveTransform(pts1, pts2) imagen_alineada = cv2.warpPerspective(image, M, (ancho,alto)) return imagen_alineada
Línea 1 a 3: Importamos los paquetes necesarios openCV, numpy e imutils.
Línea 5 a 16: Esta función es la misma que habíamos usado en el tutorial anterior (http://omes-va.com/escaner-de-documentos-reconocimiento-de-texto/). Nos servirá para ordenar los 4 puntos de la hoja en: vértice superior izquierdo, superior derecho, inferior izquierdo e inferior derecho.
Línea 18 a 37: La segunda función que vamos a usar es una llamada roi, que nos servirá para obtener nuestra región de interés, en este caso la imagen correspondiente a la hoja A4 alineada. Este proceso también lo usamos en el tutorial anterior.
Los parámetros de esta función son: imagen de entrada, así como el ancho y alto que tendrá la imagen de salida.
Para discriminar la hoja A4 dentro de la imagen de entrada, transformaremos esta en escala de grises, y luego aplicaremos umbralización siemple, de este modo podremos obtener una imagen binaria (th
) en la que el área en blanco repesentará a la hoja A4. A continuación aplicaremos cv2.findContours
y luego seleccionamos el contorno más grande (aquí estoy asumiendo que el área más grande corresponderá a la hoja A4).
Finalmente controlamos que el contorno tenga 4 vértices y aplicamos la transformación de perspectiva, de tal modo que obtendremos una nueva imagen con un ancho y alto especificados por los parámetros que pide la función.
Antes de pasar con la siguiente parte del programa, debemos tener en cuenta el ancho y alto en centímetros de la hoja A4 . Esta posee 29,7cm (297mm) de alto y 21 cm (210mm) de ancho.
Si calculamos su relación de aspecto (aspect ratio), obtendríamos lo siguiente:
El resultado de la relación de aspecto nos servirá mucho para establecer las medidas de la hoja A4 pero en pixeles. Por ejemplo quisiera obtener una imagen resultante con 720 pixeles de ancho, podría tomar el aspect ratio y obtener el alto correspondiente. ¡Hagamos los cálculos!.
De este modo podemos establecer que la imagen de la hoja A4 tendrá 720 pixeles de ancho y 509 de alto. Entonces continuemos con con la programación:
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) #cap = cv2.VideoCapture('Video.mp4') while True: ret, frame = cap.read() if ret == False: break #frame = imutils.resize(frame, width=720) imagen_A4 = roi(frame, ancho=1080, alto=509)
Línea 40 y 41: Con la línea 40 realizaremos un videostreaming, mientras que con la línea 41 podremos leer un video en específico.
Línea 45 a 49: Leemos los fotogramas y llamamos a la función roi previamente creada, en ella especificamos como primer argumento a frame, luego un ancho de 720 y alto de 509, como habíamos obtenidos en los cálculos anteriores.
Cabe aclarar que he establecido el valor de 720 y a través de la relación de aspecto pude establecer como alto a 509 pixeles, pero podrías establecer otros.
NOTA: La línea 48 puedes descomentarla en caso de que el video que estés leyendo sea muy grande. Claro que también podrías omitir esta línea o cambiar el valor de redimensionamiento.
Detección de los círculos verdes
if imagen_A4 is not None: puntos = [] imagenHSV = cv2.cvtColor(imagen_A4, cv2.COLOR_BGR2HSV) verdeBajo = np.array([36, 14, 0], np.uint8) verdeAlto = np.array([56, 120, 255], np.uint8) maskVerde = cv2.inRange(imagenHSV, verdeBajo, verdeAlto) cnts = cv2.findContours(maskVerde, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:2] for c in cnts: x, y, w, h = cv2.boundingRect(c) cv2.rectangle(imagen_A4, (x, y), (x+w, y+h), (255, 0, 0), 2) puntos.append([x, y, w, h])
Línea 51 a 56: Si existe una imagen almacenada en imagen_A4
, entonces continuamos con la condición.
Declaramos un array vacío llamado puntos
en el que más tarde almacenaremos los puntos correspondientes a cada círculo verde. Luego transformamos imagen_A4
de BGR a HSV, establecemos los límites bajo y alto del color verde en el formato de color HSV y luego en la línea 56 obtenemos una imagen binaria cuya región en blanco representará la presencia del color verde, mientras en negro la no presencia del mismo.
Línea 58 y 59: Buscamos los contornos presentes en maskVerde
y luego seleccionamos los dos contornos más grandes (estoy asumiendo que estos contornos corresponden a los círculos verces).
Línea 61 a 64: Continuamos analizando los dos contornos seleccionados, y de cada uno de ellos extraemos las coordenadas x, y ancho y alto del contorno con ayuda de cv2.boundingRect
. Estos datos nos permitirán dibujar un rectángulo que rodeará a cada contorno. En la línea 64 almacenaremos los puntos encontrados (x, y ancho y alto).
Calculando la distancia entre dos objetos
if len(puntos) == 2: x1, y1, w1, h1 = puntos[0] x2, y2, w2, h2 = puntos[1] if x1 < x2: distancia_pixeles = abs(x2 - (x1+w1)) distancia_cm = (distancia_pixeles*29.7)/720 cv2.putText(imagen_A4, "{:.2f} cm".format(distancia_cm), (x1+w1+distancia_pixeles//2, y1-30), 2, 0.8, (0,0,255), 1, cv2.LINE_AA) cv2.line(imagen_A4,(x1+w1,y1-20),(x2, y1-20),(0, 0, 255),2) cv2.line(imagen_A4,(x1+w1,y1-30),(x1+w1, y1-10),(0, 0, 255),2) cv2.line(imagen_A4,(x2,y1-30),(x2, y1-10),(0, 0, 255),2) else: distancia_pixeles = abs(x1 - (x2+w2)) distancia_cm = (distancia_pixeles*29.7)/720 cv2.putText(imagen_A4, "{:.2f} cm".format(distancia_cm), (x2+w2+distancia_pixeles//2, y2-30), 2, 0.8, (0,0,255), 1, cv2.LINE_AA) cv2.line(imagen_A4,(x2+w2,y2-20),(x1, y2-20),(0, 0, 255),2) cv2.line(imagen_A4,(x2+w2,y2-30),(x2+w2, y2-10),(0, 0, 255),2) cv2.line(imagen_A4,(x1,y2-30),(x1, y2-10),(0, 0, 255),2)
Línea 66 a 68: Vamos a verificar que tengamos 2 arrays con la información de los dos círculos verdes, si es así desempaquetamos estos puntos.
Para poder obtener la distancia en pixeles y luego trasformarla en distancia en centímetros vamos a necesitar conocer el objeto que se encuentre a la izquierda y el que esté a la derecha, para de este modo saber cuanto los separa.
Ya que no sabemos cual de ellos está a la izquierda o derecha, vamos a comparar sus coordenadas en el eje x.
Línea 70 a 76: En esta condición tenemos que el contorno con x1 es menor, es decir está a la izquierda, mientras que a x2 a la derecha. Entonces procedemos a aplicar la diferencia entre el punto más alejado (x2) y el punto del primer contorno (x1+w1).
En la línea 72 aplicaremos una regla de 3 simple, con la información del ancho de la hoja real y el de la imagen, además de la distancia en x obtenida en distancia_pixeles
.
De la línea 73 a 76 visualizaremos la distancia en centímetros obtenida. Además dibujaremos una línea horizontal que cubre dicha distancia, y dos líneas verticales al inicio y fin de la línea.
Línes 77 a 83: Realizamos el mismo procedimiento de las líneas 70 a 76, con la diferencia que ahora x2 será menor a x1, es decir que x2 corresponde al contorno de la izquierda y x1 al de la derecha.
cv2.imshow('imagen_A4',imagen_A4) cv2.imshow('frame',frame) k = cv2.waitKey(1) & 0xFF if k == 27: break cap.release() cv2.destroyAllWindows()
Línea 85 y 87: Visualizamos las imánges contenidas en imagen_A4
y frame
.
Línea 87 a 92: Si se preciona ESC, el proceso se romperá y terminará la captura del video.
Pruebas de funcionamiento
En un principio realicé pruebas con una webcam, que posee cierto efecto ojo de pez, entonces obtuve los siguientes resultados al medir a 1, 5, 10, 15 y 20 cm de distancia:
También hice pruebas con mi teléfono, en este los fotogramas no poseen este efecto ojo de pez, por lo que los resultados al medir las distancias fueron:
Como ves usando el teléfono, al no tener este efecto ojo de pez se pudo obtener mejores resultados, mientras que con la webcam que estaba usando en un principio el error al medir es más grande.
Cabe destacar que siempre vamos a tener cierto porcentaje de error al medir, no solo usando una cámara y visión articial como en este caso, sino incluso con una regla ya sea por la construcción de la misma o por error humano, por lo que debemos apuntar a obtener un error lo más bajo posible.
Hola, he visto casi todos tus videos en youtube y ahora estoy repasando todos los tutoriales. Tengo un par de proyectos en mente que me gustaría poner en práctica, quisiera saber si hay alguna manera de cómo poder extraer medidas de objetos mediante imágenes o video?
Hola Carlos, muchas gracias por ver el contenido que he realizado, me alegra mucho que te sea de utilidad. Emm… creo que podrías realizarlo a través de la detección del objeto y teniendo como datos de entrada la distancia de la cámara al objeto, así como su ancho o alto.
hola, estoy interesado en lo mismo, he intentado calcular el área con la función que trae opencv por defecto, pero no he tenido suerte, hay alguna forma de que se pueda detectar el ancho y largo? agradecido de antemano por tu ayuda
Hola, no se podría modificar este script para que se tome la distancia entre puntos (vértices de un objeto) y de esa manera poder calcular el área ocupada? Tengo la idea pero no se como llevarla a cabo
Hola Jesus, lo que yo haría es determinar donde está presente el objeto que se desee medir, ya sea aplicando umbralización simple, detección de color, de objetos, etc. Luego lo encerraría en un recuadro delimitador con cv2.boundingRect. Una vez teniendo la información del alto y ancho en pixeles del objeto, tendría que hacer una regla de 3 con los valores de la hoja A4 por ejemplo o con cualquier otro objeto que tenga de referencia y que sepa sus medidas en centímetros.
Hola. Como podría estandarizar la distancia a la cual tomar la foto de un objeto, de manera tal que esta escala siempre se mantenga?
No tengo idea de tu proyecto, pero tengo uno similar en mente, soy ingeniero agrónomo y necesito realizar levantamientos topográficos y creo que con una simple cámara y una serie de puntos montados en un tractor puedo realizarlos, ya se que existen drones para realizar este tipo de trabajos pero me saldría costoso comprar uno de esos y también gastaría mucho solicitando el servicio, estamos en algo similar
Me encanta tu Blog, eres muy organizada en el código y además aportas buena información, felicitaciones
Muchas gracias Claudia. ?
hola excelente contenido disculpa una pregunta ¿como se podria calcular la distancia de mas objetos en un video?
Hola Jeferson, puedes usar la misma técnica, tomando un valor conocido como lo es la hoja A4 por ejemplo, pero en ese caso se mediría el ancho y alto.
hola buenas tardes, como se podria hacer la medicion de dos objetos en una foto normal.
Hola Henrry, cómo en una foto normal?
Hola:) yo estoy en un proyecto de mi Universidad de detectar la distancia de los áboles a las líneas de tensión de cableado, ahí se que sabría siempre a que altura están ubicadas las líneas de tensión y el largo y ancho tienen las líneas de tensión, mi duda es si puedo tomar eso de base en una foto para poder medir la distancia de un árbol a las líneas de tensión
Hola Katherine, puede que este tutorial te pueda ayudar. https://www.pyimagesearch.com/2015/01/19/find-distance-camera-objectmarker-using-python-opencv/
Buenas tardes, primero muchas gracias por toda la información aportada. Estoy en frente de este problema, busco ayuda:
La siguiente imagen es una foto de la fachada de una casa.
Figura 1: Foto de ejemplo (en perspectiva).
Como dato de entrada, sabemos que el tamaño del marco de la puerta es de 2.10m de
alto por 0.73m de ancho. En base a estas medidas hacer un programa que permita medir el
largo de cosas en el plano de la fachada.
-Capturar una imagen de una fachada con perspectiva y hacer un programa que permita:
–encontrar la transformación perspectiva entre los 4 vértices del marco y un rectángulo
con la misma relación de aspecto que la puerta real;
–aplicar dicha transformación a la imagen y mostrarla en una ventana;
sobre esta ventana permitir que el usuario haga dos clicks y mostrar la distancia en
metros entre dichos puntos;
Desde ya muchas gracias por la ayuda…
Hola Jesica, muchas gracias. 🙂 Me gustaría poder ayudarte pero no puedo ver la imagen que tienes como entrada. 🙁
hola buenas, es posible modificar el programa para que me de la distancia de 3 pares de puntos? en vez de solo 2
Hola Jose, si lo podrías hacer. Tendrías que calcular las distancias del primer punto al segundo punto, y de este al tercero. También podrías obtener la distancia entre el primer y tercer punto.
Buenos días Gaby primero que todo agradezco mucho el contenido de tus videos son bastante completos y muy bien explicados. Gaby tengo un problema al momento de visualizar la imagen_A4 alineada no me aparece solo me aparece el th y el frame intente organizar bien el código retorne imagen alineada y no me funciona agradezco tu ayuda y tu respuesta que tengas un excelente día y muchas bendiciones
Hola Omar, muchas gracias. Puedes comentar el código? Puede que te haya falta cv2.imshow de la imagen que deseas visualizar.
Hola! Me han servido mucho tus videos, ya que estoy aprendiendo a usar Python.
Este código en especial, he tratado de adaptarlo para que mida la distancia entre dos puntos verdes pero no en un video, sino en una imagen y aun no se como, podrías ayudarme con eso?
Hola Abril muchas gracias :). Obtienes algún error?
Hola, excelente video pero tengo una pregunta espero puedas responder, a que distancia aproximadamente tienes la camara del celular de la hoja con la que estas expllicando el video,
Buen día,
Espero estés muy bien, tus videos son super buenos, te felicito por tus contenidos son de mucha ayuda. Estoy intentando usar este video como referencia para un proyecto parecido, solo que en mi caso debo medir la posición del objeto en la hoja(determinar su posición dando sus coordenadas en la hoja en cm ) en vez de medir la distancia entre dos objetos. Como también medir el tamaño del objeto, crees que sea posible hacer algo así?
Hola Nestor, si podrías hacerlo realizando un procedimiento similar. Te recomendaría que a los objetos los encierres en un rectángulo, con ello ya tendrías los píxeles del alto y ancho de los objetos y podrías calcularlos en centímetros, como lo hacemos en este tutorial.
Hola Gaby.
Me podrías guiar tengo el siguiente problema, tengo 3 círculos 2 de color rosa y uno celeste ahora cada círculo se mueve el celeste puede ir hacia los rosas o viceversa como puedo calcular la distancia de cada círculo.
Imagen test: https://imgur.com/VDYVdnG
Al círculo celeste le agrego un contorno al igual que el rosa y de ahí saco las coordenadas
Contours1, _ = cv2.findContours(Mask1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Contours2, _ = cv2.findContours(Mask2.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
x1 = int(M1[«m10»]/M1[«m00»])
y1 = int(M1[‘m01’]/M1[‘m00’])
Convierto las coordenadas en una distancia, el problema es que se combinan las coordenadas que obtengo Contours2 (de los 2 círculos rosas) no hay una forma de agregarle a cada círculo que genere el Contours2 una id o algo así?
def CalculateDistance(X1, Y1, X2, Y2):
dist = math.sqrt((X2 – X1)**2 + (Y2 – Y1)**2)
return dist
Mi conocimiento de python-opencv es de lo mas bajo.
Algo que me olvide de agregar, o cuando un contorno toque otro contorno que me devuelva un true lo que sea mas fácil.
Hola Gennaro, y si añades un control. Por ejemplo, si los círculos están dispuestos de izquierda a derecha, puedes controlar el orden de ellos tomando sus coordenadas en x, y si están verticales puedes controlarlos en y. De este modo podrías saber el orden en que están dispuestos.
¡Hola! Muy buen video.
Pero tenia una consulta.
Como se puede hacer para cambiar a otros colores. Algo distinto al verde. He intentado cambiar los números pero no me funciona. Incluso me metí a páginas que me dan los valores BGR y nada.
Honestamente no entiendo muy bien el porqué de eso números para el VerdeAlto y bajo.
Soy nuevo en el OpenCV pero admiro tu forma de explicar.
Buen día Gaby,
Es Impresionante lo que haces,
Ayúdame por favor, como se puede calcular que un objeto este en el medio de la escena y luego se valide true y si esta mas a la derecha o izquierda sea false.
saludos,
Un Omesito
Hola Gaby, está muy bueno el tutorial. Me surge una duda:
Se puede hacer medición de forma vertical?
Gracias
Muchas gracias por tus aportes, FELICIDADES!!!