9

Detección de códigos QR en Python con OpenCV y ZBar

¡Saludos, homo fabers! Hoy voy a enseñaros cómo detectar códigos QR en un vídeo en tiempo real en Python utilizando la librería OpenCV y ZBar. Empezaré con un programa sencillo que sólo leerá el mensaje del código QR y lo escribirá por Terminal, y después lo ampliaremos poco a poco para que también encuentre la posición del centro del código QR sobre la imagen, pinte sus esquinas y calcule su rotación. Si no os interesa la explicación paso a paso, al final también encontraréis el código completo ^-^

Bien, ¡manos a la obra! Hoy vamos a trabajar a ritmo de Saint-Saëns(1835-1921), con una pieza llamada “El Carnaval de los Animales“.


Preparativos

Para este tutorial necesitaremos la librería OpenCV para Python, la librería ZBar y Numpy.

También necesitaréis una webcam y algunos códigos QR impresos en papel que lleven algun tipo de mensaje. Hay muchas webs que permiten generar códigos QR gratis y guardarlos como imagen. Los que he usado para este tutorial los he generado desde aquí, y después los he imprimido y recortado.

Mis códigos QR impresos, con una moneda para comparar el tamaño

Es importante que no hagáis los códigos QR muy pequeños porque a la cámara le costará detectarlos.


1- Detectar un código y leer el mensaje

Para este primer ejemplo, veréis cómo detectar códigos QR en un vídeo y leer su mensaje para mostrarlo por la Consola.

Empezamos por cargar las librerías e inicializar la cámara:


import zbar
import numpy as np
import cv2

#Inicializar la camara
capture = cv2.VideoCapture(0)

 

Dentro del bucle principal, leemos un frame de la cámara, lo convertimos a escala de grises y guardamos sus dimensiones dentro de un array:

 

while 1:
	#Capturar un frame
	val, frame = capture.read()

	#Hay que comprobar que el frame sea valido
	if val:
		#Capturar un frame con la camara y guardar sus dimensiones
		frame_gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
		dimensiones = frame_gris.shape #'dimensiones' sera un array que contendra el alto, el ancho y los canales de la imagen en este orden.

 

Convertimos la imagen a un formato que ZBar pueda interpretar. Los dos primeros parámetros son las dimensiones de la imagen. ‘Y800’ es el formato. El último parámetro indica que hay que convertir cada píxel de la imagen a un carácter, para facilitar después la búsqueda de códigos QR.

		#Convertir la imagen de OpenCV a una imagen que la libreria ZBAR pueda entender
		imagen_zbar = zbar.Image(dimensiones[1], dimensiones[0], 'Y800', frame_gris.tobytes())

 

Creamos un objeto de tipo scanner y escaneamos la imagen:

		#Construir un objeto de tipo scaner, que permitira escanear la imagen en busca de codigos QR
		escaner = zbar.ImageScanner()

		#Escanear la imagen y guardar todos los codigos QR que se encuentren
		escaner.scan(imagen_zbar)

 

Extraemos el texto de cada uno de los códigos QR que se han detectado en la imagen y los escribimos por la Consola:

		for codigo_qr in imagen_zbar:
			dat = codigo_qr.data[:-2] #Guardar el mensaje del codigo QR. Los ultimos dos caracteres son saltos de linea que hay que eliminar
			print(dat)

 

Acabamos mostrando la imagen de la cámara, añadiendo una condición para salir del bucle y destruyendo la ventana que muestra el vídeo.

		#Mostrar la imagen
		cv2.imshow('Imagen', frame)

	#Salir con 'ESC'
	k = cv2.waitKey(5) & 0xFF
	if k == 27:
		break



cv2.destroyAllWindows()

 

El código debería lucir como este:

import zbar
import numpy as np
import cv2

#Inicializar la camara
capture = cv2.VideoCapture(0)

while 1:
	#Capturar un frame
	val, frame = capture.read()

	if val:
		#Capturar un frame con la camara y guardar sus dimensiones
		frame_gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
		dimensiones = frame_gris.shape #'dimensiones' sera un array que contendra el alto, el ancho y los canales de la imagen en este orden.

		#Convertir la imagen de OpenCV a una imagen que la libreria ZBAR pueda entender
		imagen_zbar = zbar.Image(dimensiones[1], dimensiones[0], 'Y800', frame_gris.tobytes())

		#Construir un objeto de tipo scaner, que permitira escanear la imagen en busca de codigos QR
		escaner = zbar.ImageScanner()

		#Escanear la imagen y guardar todos los codigos QR que se encuentren
		escaner.scan(imagen_zbar)

		for codigo_qr in imagen_zbar:
			dat = codigo_qr.data[:-2] #Guardar el mensaje del codigo QR. Los ultimos dos caracteres son saltos de linea que hay que eliminar
			print(dat)

		#Mostrar la imagen
		cv2.imshow('Imagen', frame)

	#Salir con 'ESC'
	k = cv2.waitKey(5) & 0xFF
	if k == 27:
		break



cv2.destroyAllWindows()

2- Contorno y centro

Ahora vamos a ampliar este código para que dibuje el contorno del código QR y el mensaje sobre la imagen. Partiendo del código del apartado anterior, habrá que hacerle algunos cambios.

Primero, justo después de inicializar la cámara, habrá que importar la fuente con la que escribiremos el mensaje del código QR:

import zbar
import numpy as np
import cv2

#Inicializar la camara
capture = cv2.VideoCapture(0)

#Cargar la fuente
font = cv2.FONT_HERSHEY_SIMPLEX

 

Además de guardar el mensaje, también guardaremos las coordenadas de las cuatro esquinas del código QR sobre la imagen y las convertiremos a un array de Numpy. Después dibujaremos el contorno con la función cv2.polylines(). También he eliminado el print(dat), puesto que ahora el mensaje se mostrará sobre la imagen.


		for codigo_qr in imagen_zbar:
			loc = codigo_qr.location #Guardar las coordenadas de las esquinas
			dat = codigo_qr.data[:-2] #Guardar el mensaje del codigo QR. Los ultimos dos caracteres son saltos de linea que hay que eliminar
			#Convertir las coordenadas de las cuatro esquinas a un array de numpy
			#Asi, lo podremos pasar como parametro a la funcion cv2.polylines para dibujar el contorno del codigo QR
			localizacion = np.array(loc, np.int32)

			#Dibujar el contorno del codigo QR en azul sobre la imagen
			cv2.polylines(frame, [localizacion], True, (255,0,0), 2)

Recordad que el array ‘loc’ siempre guardará las coordenadas respetando su orden: primero la esquina superior izquierda (0), esquina inferior izquierda (1), esquina inferior derecha (2) y esquina superior derecha (3).

Esto será de mucha utilidad más adelante, cuando haya que calcular la rotación del código QR.

 

Encontramos el centro calculando el punto medio de las esquinas, y escribimos el mensaje sobre este centro:

			#Buscar el centro del rectangulo del codigo QR
			cx = (loc[0][0]+loc[2][0])/2
			cy = (loc[0][1]+loc[2][1])/2

			#Escribir el mensaje del codigo QR.
			cv2.putText(frame,dat,(cx,cy), font, 0.7,(255,255,255),2)

 

El resto es igual. Al final, éste código mejorado debería quedar así:

import zbar
import numpy as np
import cv2

#Inicializar la camara
capture = cv2.VideoCapture(0)

#Cargar la fuente
font = cv2.FONT_HERSHEY_SIMPLEX

while 1:
	#Capturar un frame
	val, frame = capture.read()

	if val:
		#Capturar un frame con la camara y guardar sus dimensiones
		frame_gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
		dimensiones = frame_gris.shape #'dimensiones' sera un array que contendra el alto, el ancho y los canales de la imagen en este orden.

		#Convertir la imagen de OpenCV a una imagen que la libreria ZBAR pueda entender
		imagen_zbar = zbar.Image(dimensiones[1], dimensiones[0], 'Y800', frame_gris.tobytes())

		#Construir un objeto de tipo scaner, que permitira escanear la imagen en busca de codigos QR
		escaner = zbar.ImageScanner()

		#Escanear la imagen y guardar todos los codigos QR que se encuentren
		escaner.scan(imagen_zbar)

		for codigo_qr in imagen_zbar:
			loc = codigo_qr.location #Guardar las coordenadas de las esquinas
			dat = codigo_qr.data[:-2] #Guardar el mensaje del codigo QR. Los ultimos dos caracteres son saltos de linea que hay que eliminar
			#Convertir las coordenadas de las cuatro esquinas a un array de numpy
			#Asi, lo podremos pasar como parametro a la funcion cv2.polylines para dibujar el contorno del codigo QR
			localizacion = np.array(loc, np.int32)

			#Dibujar el contorno del codigo QR en azul sobre la imagen
			cv2.polylines(frame, [localizacion], True, (255,0,0), 2)

			#Buscar el centro del rectangulo del codigo QR
			cx = (loc[0][0]+loc[2][0])/2
			cy = (loc[0][1]+loc[2][1])/2

			#Escribir el mensaje del codigo QR.
			cv2.putText(frame,dat,(cx,cy), font, 0.7,(255,255,255),2)

		#Mostrar la imagen
		cv2.imshow('Imagen', frame)

	#Salir con 'ESC'
	k = cv2.waitKey(5) & 0xFF
	if k == 27:
		break


cv2.destroyAllWindows()


3- Pintar las cuatro esquinas

Además de dibujar el contorno, también podemos dibujar las cuatro esquinas del código QR, cada una de un color diferente para distingirlas:

Para hacerlo sólo tenéis que añadir este fragmento de código justo después de llamar a la función cv2.polylines():

			#Dibujar las cuatro esquinas del codigo QR
			cv2.circle(frame, loc[0], 3, (0,0,255), -1) #Rojo - esquina superior izquierda
			cv2.circle(frame, loc[1], 3, (0,255,255), -1) #Amarillo - esquina inferior izquierda
			cv2.circle(frame, loc[2], 3, (255,100,255), -1) #Rosa -esquina inferior derecha
			cv2.circle(frame, loc[3], 3, (0,255,0), -1) #Verde - esquina superior derecha

El código completo quedará así:


import zbar
import numpy as np
import cv2

#Inicializar la camara
capture = cv2.VideoCapture(0)

#Cargar la fuente
font = cv2.FONT_HERSHEY_SIMPLEX

while 1:
	#Capturar un frame
	val, frame = capture.read()

	if val:
		#Capturar un frame con la camara y guardar sus dimensiones
		frame_gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
		dimensiones = frame_gris.shape #'dimensiones' sera un array que contendra el alto, el ancho y los canales de la imagen en este orden.

		#Convertir la imagen de OpenCV a una imagen que la libreria ZBAR pueda entender
		imagen_zbar = zbar.Image(dimensiones[1], dimensiones[0], 'Y800', frame_gris.tobytes())

		#Construir un objeto de tipo scaner, que permitira escanear la imagen en busca de codigos QR
		escaner = zbar.ImageScanner()

		#Escanear la imagen y guardar todos los codigos QR que se encuentren
		escaner.scan(imagen_zbar)

		for codigo_qr in imagen_zbar:
			loc = codigo_qr.location #Guardar las coordenadas de las esquinas
			dat = codigo_qr.data[:-2] #Guardar el mensaje del codigo QR. Los ultimos dos caracteres son saltos de linea que hay que eliminar
			#Convertir las coordenadas de las cuatro esquinas a un array de numpy
			#Asi, lo podremos pasar como parametro a la funcion cv2.polylines para dibujar el contorno del codigo QR
			localizacion = np.array(loc, np.int32)

			#Dibujar el contorno del codigo QR en azul sobre la imagen
			cv2.polylines(frame, [localizacion], True, (255,0,0), 2)

			#Dibujar las cuatro esquinas del codigo QR
			cv2.circle(frame, loc[0], 3, (0,0,255), -1) #Rojo - esquina superior izquierda
			cv2.circle(frame, loc[1], 3, (0,255,255), -1) #Amarillo - esquina inferior izquierda
			cv2.circle(frame, loc[2], 3, (255,100,255), -1) #Rosa -esquina inferior derecha
			cv2.circle(frame, loc[3], 3, (0,255,0), -1) #Verde - esquina superior derecha

			#Buscar el centro del rectangulo del codigo QR
			cx = (loc[0][0]+loc[2][0])/2
			cy = (loc[0][1]+loc[2][1])/2

			#Escribir el mensaje del codigo QR.
			cv2.putText(frame,dat,(cx,cy), font, 0.7,(255,255,255),2)

		#Mostrar la imagen
		cv2.imshow('Imagen', frame)

	#Salir con 'ESC'
	k = cv2.waitKey(5) & 0xFF
	if k == 27:
		break


cv2.destroyAllWindows()


4- Rotación

Para algunos proyectos puede ser interesante conocer la orientación del código QR. Supondremos que el ángulo de rotación del código QR viene dado por la pendiente de la recta que une la esquina superior izquierda con la esquina superior derecha.

Si sabemos el vector pendiente de esta recta, tenemos dos coordenadas que se corresponden con los dos catetos de un triángulo. Por tanto, con la fórmula de la tangente puede encontrarse el ángulo de inclinación de la recta. ¿Fácil, verdad?

Pero tenemos un problema. Los píxeles de la pantalla no están ordenados de la misma forma que los puntos en el plano. El eje Y está invertido: cuanto más arriba esté un píxel, menor es el valor de su coordenada Y, y cuánto más abajo, mayor.

Por tanto habrá que aplicar una pequeña conversión a la rotación: restar 360 al ángulo y multiplicarlo por -1. Esto hará que el ángulo quede de la forma correcta.

Justo después de escribir el mensaje del código QR con cv2.putText() calcularemos el vector director de la recta y su inclinación, aplicando las correcciones pertinentes:

			#Calcular el angulo de rotacion del codigo QR. Supondremos que el angulo es la pendiente de la recta que une el vertice loc[0] (rojo) con loc[3] (verde)
			vector_director = [loc[3][0]-loc[0][0], loc[3][1]-loc[0][1]]
			angulo = (np.arctan2(float(vector_director[1]),vector_director[0])*57.29)%360 #Calculo de la tangente y conversion de radianes a grados
			#Correccion debida al orden de las coordenadas en la pantalla
			angulo += -360
			angulo *= -1

Por último escrivimos el ángulo debajo del texto del mensaje:

			#Escribir el angulo sobre la imagen con dos decimales
			cv2.putText(frame,str("%.2f" % angulo),(cx,cy+30), font, 0.7,(255,255,255),2)

CÓDIGO FINAL

Por tanto, habiendo calculado la rotación, el código definitivo quedará así:

"""
	Programa en Python para leer codigos QR.
	Muestra sus dimensiones en la pantalla, su mensaje y su rotacion en grados.

	Escrito por Glare,
	www.robologs.net
"""

import zbar
import numpy as np
import cv2

#Inicializar la camara
capture = cv2.VideoCapture(0)

#Cargar la fuente
font = cv2.FONT_HERSHEY_SIMPLEX


while 1:
	#Capturar un frame
	val, frame = capture.read()

	#Hay que comprobar que el frame sea valido
	if val:
		#Capturar un frame con la camara y guardar sus dimensiones
		frame_gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
		dimensiones = frame_gris.shape #'dimensiones' sera un array que contendra el alto, el ancho y los canales de la imagen en este orden.

		#Convertir la imagen de OpenCV a una imagen que la libreria ZBAR pueda entender
		imagen_zbar = zbar.Image(dimensiones[1], dimensiones[0], 'Y800', frame_gris.tobytes())

		#Construir un objeto de tipo scaner, que permitira escanear la imagen en busca de codigos QR
		escaner = zbar.ImageScanner()

		#Escanear la imagen y guardar todos los codigos QR que se encuentren
		escaner.scan(imagen_zbar)


		for codigo_qr in imagen_zbar:
			loc = codigo_qr.location #Guardar las coordenadas de las esquinas
			dat = codigo_qr.data[:-2] #Guardar el mensaje del codigo QR. Los ultimos dos caracteres son saltos de linea que hay que eliminar

			#Convertir las coordenadas de las cuatro esquinas a un array de numpy
			#Asi, lo podremos pasar como parametro a la funcion cv2.polylines para dibujar el contorno del codigo QR
			localizacion = np.array(loc, np.int32)

			#Dibujar el contorno del codigo QR en azul sobre la imagen
			cv2.polylines(frame, [localizacion], True, (255,0,0), 2)

			#Dibujar las cuatro esquinas del codigo QR
			cv2.circle(frame, loc[0], 3, (0,0,255), -1) #Rojo - esquina superior izquierda
			cv2.circle(frame, loc[1], 3, (0,255,255), -1) #Amarillo - esquina inferior izquierda
			cv2.circle(frame, loc[2], 3, (255,100,255), -1) #Rosa -esquina inferior derecha
			cv2.circle(frame, loc[3], 3, (0,255,0), -1) #Verde - esquina superior derecha


			#Buscar el centro del rectangulo del codigo QR
			cx = (loc[0][0]+loc[2][0])/2
			cy = (loc[0][1]+loc[2][1])/2

			#Escribir el mensaje del codigo QR.
			cv2.putText(frame,dat,(cx,cy), font, 0.7,(255,255,255),2)


			#Calcular el angulo de rotacion del codigo QR. Supondremos que el angulo es la pendiente de la recta que une el vertice loc[0] (rojo) con loc[3] (verde)
			vector_director = [loc[3][0]-loc[0][0], loc[3][1]-loc[0][1]]
			angulo = (np.arctan2(float(vector_director[1]),vector_director[0])*57.29)%360 #Calculo de la tangente y conversion de radianes a grados
			#Correccion debida al orden de las coordenadas en la pantalla
			angulo += -360
			angulo *= -1

			#Escribir el angulo sobre la imagen con dos decimales
			cv2.putText(frame,str("%.2f" % angulo),(cx,cy+30), font, 0.7,(255,255,255),2)

		#Mostrar la imagen
		cv2.imshow('Imagen', frame)

	#Salir con 'ESC'
	k = cv2.waitKey(5) & 0xFF
	if k == 27:
		break

cv2.destroyAllWindows()

 

¡Espero que este tutorial os haya sido de ayuda! Como siempre, si tenéis dudas, os ha surgido algún problema o queréis que clarifique cualquier cosa del tutorial podéis dejarme un comentario o escribirme un correo electrónico a contacto@robologs.net. ¡Hasta la próxima! 😉

Gl4r3

Brillante, luminosa y cegadora a veces, Glare es tan artista como técnica. Le encanta dar rienda suelta a sus módulos de imaginación y desdibujar los ya de por si delgados límites que separan el mundo de la electrónica y el arte. Su mayor creación hasta la fecha es un instrumento capaz de convertir los colores y la luz en música. Cuándo sus circuitos no están trabajando en una nueva obra electrónica, le gusta dedicar sus ciclos a la lectura o a documentar sus invenciones para beneficio de los humanos. Sus artilugios favoritos son aquellos que combinan una funcionalidad práctica con un diseño elegante y artístico.

Antes de comentar, por favor, lee las Normas

9 Comentarios en "Detección de códigos QR en Python con OpenCV y ZBar"

avatar
Ordenar por:   más nuevos primero | más antiguos primero
97SRZ00
Humano

Hola, no he sido capaz de instalar la libreria de zbar. Donde la puedo instalar facilmente. Gracias

P323
Humano

Hola, me es imposible ejecutar el codigo ya que me da error al importar zbar, he intentado con la version 2.5, 2.6 y 2.7, alguien tiene idea de porque no consigo hacerlo funcionar? Un saludo

Daniel
Humano

hola buenas, tengo est error.

Traceback (most recent call last):
File “/home/pi/Desktop/Prollectos 2018/Brazo robótico/Python/15-6-17 QR.py”, line 21, in
imagen_zbar = zbar.Image(dimensiones[1], dimensiones[0], ‘Y800’, frame_gris.tobytes())
AttributeError: ‘numpy.ndarray’ object has no attribute ‘tobytes’
>>>

carlos reyes
Humano

buen día, quisiera preguntar si es posible implementar esta aplicación pero con código de barras 2D pdf 417 en vez de código QR

El primero
Humano

Felicitaciones, un post increible!!

wpDiscuz