? Reconocimiento de matrículas vehiculares | OpenCV, Pytesseract (OCR) – Python

Por Administrador

Ya se han tratado varios temas en este blog sobre visión por computador empleando OpenCV, por lo que me he sentido impulsada (basándome en el contenido previo de las publicaciones/videos) a crear este post relacionado con la detección de placas de autos y a identificar los caracteres que aparecen en ellas.

Debo decir que lo que quisiera mostrarte hoy es una aplicación que engloba detección de bordes, detección de figuras geométricas, entre otros, que no necesita por ahora de machine learning (que es algo que espero desarrollar en un futuro). Y claro, como no tiene un proceso de aprendizaje puede que esta aplicación únicamente se pueda aplicar bajo cierta iluminación o ángulo y distancia de la cámara al auto, entre otros. Sin embargo, si se te permitiera controlar estas u otras condiciones, puede que tengas muy buenos resultados.

En gran medida en este programa se usará contenido que ya hemos visto, por lo tanto cuando sea necesario lo citaré para que puedas profundizar algún tema (si es que fuera necesario), de igual manera lo haré si se presenta algún tema nuevo en el blog. ¡Así que abre tu editor de código favorito y empecemos a realizar la detección de placas vehiculares junto con sus caracteres!.

CONTENIDO

  • ¿Qué es el Reconocimiento Automático de Matrículas (ANPR)?
  • ¿Qué es el Reconocimiento Óptico de Caracteres (OCR)?
    • ¿Cómo usar OCR en Python?
  1. Empezando la aplicación
    1. Leyendo la imagen y transformarla a binaria
    2. Encontrando contornos
      1. Determinando el área de los contornos
      2. Encontrar el rectángulo de la matrícula
      3. Discriminando contornos y obteniendo el aspect ratio
    3. Una vez obtenida la placa… ¡Es hora de aplicar OCR con pytesseract!
    4. Finalmente, ¡visualización!
  • Programa completo
  • Resultados

¿Qué es el Reconocimiento Automático de Matrículas (ANPR)?

Figura 1: Reconocimiento de matrículas (Fuente).

ANPR pos sus siglas en inglés Automatic number plate recognition, también llamado Licence Plate Recognition (LPR), es un método de vigilancia en masa que permite detectar dentro de una imagen a la placa de un auto y posteriormente utilizando Reconocimiento Óptico de Caracteres (OCR) determinar cada uno de los alfanuméricos que componen a dicha matrícula, de tal modo que esta información pueda ser usada con algún fin.

Si buscas información sobre reconocimiento de matrículas en internet, podrás encontrar varios videos en donde se puede apreciar el registro de entrada y salida a ciertos lugares como estacionamientos, unidades educativas, residenciales o el uso de esta tecnológica en peajes, entre otros. Incluso como a mí me pasó al buscar información para desarrollar este tutorial, te podrías topar con varias empresas como esta, que ofrecen servicios y/o productos que permiten reconocer placas con gran precisión y velocidad, sin importar el país o la cámara que se use.

Por ello si estás desarrollando alguna aplicación sobre este tema, te recomiendo que analices muy bien las características que encuentres en cada uno de los servicios que ofertan distintas empresas, o estudios desarrollados sobre este reconocimiento. De tal modo que tomes dichas características como referencia, y a su vez, que estas puedan guiarte a agregar robustez al programa que estés desarrollando.

¿Qué es el Reconocimiento Óptico de Caracteres (OCR)?

Figura 2: Reconocimiento óptico de caracteres (Fuente).

Optical Character Recognition (OCR), es un proceso automático mediante el cual se extrae texto de una imagen y se transforma a un formato digital, almacenando caracteres, números y símbolos en forma de datos, a los cuales podremos acceder y manipular.

Si pensamos en alguna aplicación, podríamos por ejemplo usar OCR para digitalizar el contenido de un documento, de este modo nos ahorraríamos el pasar manualmente todo ese contenido. Podríamos también extraer los caracteres y dígitos de placas de autos, certificados, registros, entre otros. Cabe destacar que la imagen de entrada puede contener no solo texto impreso sino también escrito a mano.

¿Cómo usar OCR en python?

El motor de reconocimiento óptico de caracteres que emplearemos es tesseract, cuyo desarrollo ha sido financiado por Google desde el 2006, es de código abierto y considerado como uno de los más precisos. Si deseas más información puedes acceder a su manual de usuario, en donde encontrarás su documentación, los idiomas con los que trabaja según la versión que instales o como mejorar la extracción de caracteres, entre otros.

Para acceder a tesseract usando Python necesitamos del paquete pytesseract. Si aún no lo has instalado te recomiendo seguir los pasos de este post de Pyimagesearch. También puedes encontrar información sobre su instalación en su repo en github.

1. Empezando la aplicación

La imagen que escogí para poder realizar la detección de placas y sus caracteres contiene un auto el cual está visto de frente, donde se puede apreciar claramente su placa. Esta no es una imagen tomada de la realidad, sino que es una imagen tratada de un spot publicitario. Sin embargo al final haré pruebas del código con imágenes descargadas de internet que si corresponden a un entorno real.

La imagen con la que vamos a trabajar es la siguiente:

Figura 3: Imagen de entrada.

1.1 Leyendo la imagen y transformarla a binaria

import cv2
import pytesseract

placa = []

image = cv2.imread('auto001.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.blur(gray,(3,3))
canny = cv2.Canny(gray,150,200)
canny = cv2.dilate(canny,None,iterations=1)

Línea 1: Importamos openCV.

NOTA: Si usas Windows, es posible que necesites añadir pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract' en la línea 3.

Línea 2: Importamos pytesseract que será el paquete que nos ayudará a extraer los caracteres de la placa.

Línea 4: Declaramos placa un array vacío que nos permitirá almacenar la imagen a la matrícula encontrada. Esto lo veremos luego.

Línea 6: Leemos la imagen de entrada que corresponde a la figura 3.

Linea 7 y 8: Transformamos la imagen de BGR a escala de grises, mientras que en la siguiente línea aplicamos cv2.blur para disminuir el ruido que pueda presentar la imagen, puedes asignar otros valores dependiendo de la imagen sobre la que estés trabajando (¿más información sobre cv2.blur?, aquí te dejo la documentación de OpenCV).

Línea 9 y 10: Aplicamos cv2.Canny a la imagen a escala de grises para poder encontrar los bordes que presente la imagen. He establecido 150 y 200 como primer y segundo umbral, luego de algunas pruebas (aquí tienes otro tutorial en donde apliqué canny). En la línea 10 aplicamos dilatación (doc de OpenCV) a la imagen, esto permitirá engrosar las líneas blancas que obtuvimos al aplicar la detección de bordes, lo que nos servirá para cerrar el contorno correspondiente a la placa en caso de que haya estado abierto.

Hasta aquí debemos asegurarnos que exista área en blanco que esté rodeando a la placa, con ello ya podremos trabajar en los siguientes pasos. 

Si visualizamos la imagen, tendríamos:

Figura 4: Imagen obtenida de la variable canny.

1.2 Encontrarndo contornos

En un principio vamos a aplicar la función cv2.findContours y visualizaremos los contornos, luego procederemos  a determinar la matrícula.

#_,cnts,_ = cv2.findContours(canny,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)#OpenCV 3
cnts,_ = cv2.findContours(canny,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)#OpenCV 4
cv2.drawContours(image,cnts,-1,(0,255,0),2)

Línea 12 y 13: Procedemos a encontrar los contornos de canny que es la imagen binaria que habíamos conseguido antes. Puedes emplear la línea 12 o 13 dependiendo de la versión de OpenCV que tengas instalado. Esta vez he usado cv2.RETR_LIST para obtener todos los contornos de la imagen, si tienes dudas sobre este argumento y en sí sobre cv2.findContours puedes visitar este post: ?‍? CONTORNOS y como DIBUJARLOS en OpenCV y Python.

Línea 14: Para dibujar todos los contornos encontrados vamos a ayudarnos dela función cv2.drawContours. Si visualizamos obtendremos lo siguiente: 

Figura 5: Visualización de los contornos encontrados.

Como podemos apreciar en la figura 5, se han dibujado un motón de contornos, y eso puede preocuparnos porque el único que realmente necesitábamos era el que rodea la placa.

¿Qué podemos hacer?, no te preocupes, en la figura 5 podemos apreciar que sí se ha encerrado el contorno correspondiente a la placa, lo que necesitamos ahora es desechar aquellos contornos no deseados Para ello podemos diferenciarlos basándonos en su área, intentando de encontrar la forma rectangular dela matrícula, entre otros. Veamos.

1.2.1 Determinando el área de los contornos 

for c in cnts:
   area = cv2.contourArea(c)

Línea 16: Como necesitamos analizar  cada contorno almacenado en cnts, nos ayudamos de un for.

Línea 17: Para determinar el área de un contorno usaremos la función cv2.contourArea. Te recomiendo imprimir en cada momento la variable area, para que puedas darte cuenta en que rango está la correspondiente a la matrícula. 

NOTA: Puedes determinar un área mínima y/o máxima para que el contorno sea considerado como una placa. La cantidad que especifiques para ello corresponderá también a la distancia a la que esté ubicada la matrícula de la cámara, dicho de otro modo, mientas más lejos esté la cámara del auto, más pequeña se va a apreciar la matrícula, mientras que si se acerca, la matrícula se visualizará más grande.

Incluso podrías tomar esos datos y establecer a qué distancia de la cámara-auto puede funcionar tu aplicación. Así eliminaras también aquellos contornos pequeños que no corresponden al área de interés.

1.2.2 Encontrar el rectángulo de la matrícula

¿Recuerdas el post en el que hablamos  de como detectar figuras geométricas simples?, Si tu respuesta es no o un «más  o menos», te dejo este post: Detectando FIGURAS GEOMÉTRICAS (??⬛) con OpenCV – Python. En este habíamos hablado de como detectar un rectángulo en una imagen y eso es precisamente lo que vamos a realizar en esta aplicación.

x,y,w,h = cv2.boundingRect(c)
epsilon = 0.09*cv2.arcLength(c,True)
approx = cv2.approxPolyDP(c,epsilon,True)

Línea 19: Obtenemos los puntos x, y, w y h que rodean a cada contorno, esto nos ayudará para luego determinar el aspect ratio del contorno y determinar que se trate de un rectángulo.

Línea 20 y 21: Obtenemos approx, que es en donde se almacenará el número de vértices del contorno, para ello usamos la función cv2.approxPolyDP que a su vez usa epsilonde la línea 20. Aquí el porcentaje que he usado es de 9%, pero tu podrías cambiarlo para experimentar. Si estás batallando un poco para entender lo que hacen estas líneas te recomiendo este tutorial.

1.2.3 Discriminando contornos y obteniendo el aspect ratio

if len(approx)==4 and area>9000:
  print('area=',area)
  #cv2.drawContours(image,[approx],0,(0,255,0),3)

Línea 23: Ahora vamos a usar un condicional. Si el número de vértices es 4 y el contorno tiene un área mayor a 9000 es potencialmente una placa.

Línea 24: Puedes imprimir el área de los contornos que estan siendo potencialmente considerados como una placa, para modificar la condición de la línea 23 y adaptarlo a tu aplicación.

Línea 25: Si descomentas esta línea, podrás dibujar el contorno que está siendo considerado como placa bajo la condición de la línea 23. Obtendrías algo así:

Figura 6: Contorno dibujado luego de aplicar las condiciones de la línea 23.

Hasta ahora todo va de maravilla, pero podríamos añadir más criterios para detectar la placa, como por ejemplo el aspect ratio del contorno para determinar que sea un rectángulo, pero además… también podrías averiguar el alto y ancho de las placas vehiculares de tu país y trabajar también con su aspect ratio.

Figura 7: Medidas de las placas de autos de Ecuador (fuente)
.

En la figura 7 por ejemplo tenemos las medidas de las placas de Ecuador, entonces podríamos determinar su aspect ratio. ¿Recuerdas cómo obtener el aspect ratio de un contorno?.

Entonces el código sería:

aspect_ratio = float(w)/h

Bien, si calculamos el aspect ratio de la placa es decír 404/154 obtendríamos 2.62, entonces podríamos comparar con un valor cercano a este. Para dejar un margen de error, concretamente usaré para este tutorial 2.4.

if aspect_ratio > 2.4:

NOTA: El criterio de aplicar el aspect ratio según las medidas de la placa vehicular es una sugerencia que podrías o no aplicar. Si deseas no aplicar esto, únicamente debes comparar con que el aspect ratio sea mucho mayor a 1, para que el contorno sea considerado un rectángulo.

1.3 Una vez obtenida la placa… ¡Es hora de aplicar OCR con pytesseract!

Ahora que establecimos condiciones para poder encontrar el área en donde se encuentra la placa, deberemos trabajar sobre ese área para obtener sus caracteres y dígitos.

placa = gray[y:y+h,x:x+w]
text = pytesseract.image_to_string(placa,config='--psm 11')
print('PLACA: ',text)

Línea 29: Almacenamos en placa el área donde está presente la matrícula, de la imagen en escala de grises. 

Debo decir que realicé pruebas tanto con imágenes binarias de placas, así como en grises, y con la segunda obtuve un mejor reconocimiento de caracteres (en este caso), es por ello que en este tutorial tomo el área de la placa en la imagen en escala de grises.

Línea 30: Como ya tenemos la región de interés lista aplicaremos pytesseract.image_to_string, como primer argumento estará placa, mientras que el segundo corresponde al modo de segmentación de página config='--psm 11'. ¿Qué quiere decir esto?, te dejo a continuación una lista de los modos de segmentación de página que soporta tesseract (Si pruebas con otros, el resultado de reconocimiento de caracteres puede cambiar).

Lista de modos de segmentación de página compatibles con tesseract (Fuente)

Línea 31: Imprimimos la informacion obtenida luego de aplicar tesseract a la placa.

1.4 Finalmente, ¡visualización!

      cv2.imshow('PLACA',placa)
      cv2.moveWindow('PLACA',780,10)
      cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),3)
      cv2.putText(image,text,(x-20,y-10),1,2.2,(0,255,0),3)
      
cv2.imshow('Image',image)
cv2.moveWindow('Image',45,10)
cv2.waitKey(0)

Línea 33: Visualizamos la placa detectada.

Línea 34: Con cv2.moveWindow, indicamos que la ventana PLACA se visualice en la posición (780,10) de la pantalla.

Línea 35: Dibujamos un rectángulo que rodeará a la placa, para ello usamos la información obtenida en la línea 19.

Línea 36: Visualizamos la información obtenida de la línea 30.

Línea 38 a 40: Visualizamos la imagen contenida en image, luego hacemos que la ventana correspondiente a esta imagen se ubique en la posición (45,10) de la pantalla, y finamente usamos cv2.waitKey(0) para visualizar hasta que cualquier tecla sea presionada.

Programa completo

import cv2
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract'
placa = []

image = cv2.imread('auto001.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.blur(gray,(3,3))
canny = cv2.Canny(gray,150,200)
canny = cv2.dilate(canny,None,iterations=1)

#_,cnts,_ = cv2.findContours(canny,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
cnts,_ = cv2.findContours(canny,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
#cv2.drawContours(image,cnts,-1,(0,255,0),2)

for c in cnts:
  area = cv2.contourArea(c)

  x,y,w,h = cv2.boundingRect(c)
  epsilon = 0.09*cv2.arcLength(c,True)
  approx = cv2.approxPolyDP(c,epsilon,True)
  
  if len(approx)==4 and area>9000:
    print('area=',area)
    #cv2.drawContours(image,[approx],0,(0,255,0),3)

    aspect_ratio = float(w)/h
    if aspect_ratio>2.4:
      placa = gray[y:y+h,x:x+w]
      text = pytesseract.image_to_string(placa,config='--psm 11')
      print('PLACA: ',text)

      cv2.imshow('PLACA',placa)
      cv2.moveWindow('PLACA',780,10)
      cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),3)
      cv2.putText(image,text,(x-20,y-10),1,2.2,(0,255,0),3)
      
cv2.imshow('Image',image)
cv2.moveWindow('Image',45,10)
cv2.waitKey(0)

Resultados

Luego de todo este procedimiento, obtenemos lo siguiente:

Figura 8: Identificación de la placa vehicular, junto a la imagen de la placa

En la figura 8 muestra la imagen de salida, en la que podemos ver como se ha encontrado exitosamente la placa, además de los caracteres que aparecene en ella.

He probado también con otras imágenes que encontré navegando por internet, y esta vez son imágenes reales, veamos los resultados:

Figura 9: Resultados al aplicar el programa sobre imágenes reales.

En los autos de las imagenes de la parte superior de la figura 9 hemos obtenido realmente buenos resultados, sin embargo si nos fijamos en los autos de la parte inferior en especial en la ‘Q’, tenemos que el carro de la izquierda si se pudo identificar, sin embago en el auto de la derecha en vez de «Q» hemos obtenido «0», además si notas el último dígito es 0, pero dado a que tiene un vacío en la parte superior derecha ha sido reconocido como un ‘6’.

En otras también probadas, a pesar de que se detectaban muy bien las placas, al aplicar OCR no obtenía ningún caracter u obtenía caracteres totalmente diferentes. Te recomiendo realizar muchas pruebas de funcionamiento ya que el reconocimiento de caracteres podría fallar.

Dado que este programa no está realizado con machine learning, es más propenso a que falle ante cambios de iluminación, ángulo de vista, entre otros a menos que, puedas controlar el ambiente donde trabaje. Y bien, espero que este tutorial sirva para repasar el contenido visto en anteriores publicaciones y también espero que te hayas divertido mucho. Nos vemos en un siguiente tutorial de visión por computador.