CREANDO APLICACIÓN PARA LEER CÓDIGOS «QR» CON LA CÁMARA WEB.

Bienvenidos una semana más a vuestro blog sobre programación en Python. Como recordarán, en semanas anteriores vimos el modo en que podíamos efectuar la lectura de códigos «QR«, en python, (aunque en esta ocasión haremos uso de la librería «pyzbar«). A su vez, hace dos semanas, vimos como podíamos hacer uso de la cámara web, usando «opencv«. Hoy vamos a combinar los conocimientos que adquirimos en esas ocasiones para crear un sencillo programa (a cuyo código integro podéis acceder a través del enlace que dejo al final), mediante el cual podremos efectuar la lectura de dichos códigos de un modo sencillo:

La aplicación, como se ve, consistirá en una ventana, con un espacio en el que se mostrará la entrada recogida por la cámara (cuando esta sea activada mediante el botón «INICIAR CAPTURA POR CAMARA«), de modo que una vez iniciada la cámara, simplemente tendremos que poner el código a leer, delante de la misma, hasta que su información aparezca en el espacio de texto inferior. A su vez, (sin necesidad de usar la cámara) nuestra aplicación nos dará la opción de leer códigos, a partir de archivos «jpg» o «png» almacenados en nuestro ordenador, con el botón «CARGAR ARCHIVO» (esta opción también nos permitirá detectar códigos de barras). Finalmente, podremos leer, directamente, códigos que pueda haber en pantalla, usando el botón «DETECTAR EN PANTALLA» (para lo que nos ayudaremos de la librería «pyautogui«).

Visto someramente en que va a consistir nuestra aplicación, comenzaremos a escribir nuestro código. Para lo cual, naturalmente, empezaremos importando las librerías y recursos que va a emplear nuestro programa para realizar su función:

Como se ve, son varios los recursos y librerías que vamos a emplear: «tkinter» para la elaboración de la ventana, con sus elementos (canvas, botones y caja de texto), «pyzbar» para la lectura de los códigos «QR» (y eventualmente, también, de barras), «pyautogui» para car capturas de pantalla, «numpy«, para trabajar con los arrays de los frames generados por la cámara, «opencv» («cv2») fundamentalmente, pare el uso de la cámara web y «PIL» («pillow») para mostrar los frames generados por la cámara, en el visor. Hemos de señalar también que algunas de estas librerías («opencv«, «pyautogui«, «numpy«, «PIL» y «pyzbar«) deberán haber sido instaladas previamente en nuestro equipo (para lo que podremos usar, en «windows», el comando «pip install «) para poder usarlas. Una vez importados los recursos, pasaremos a iniciar la clase (a la que llamaremos «App«) que contendrá los elementos y funciones de nuestro programa:

La clase App (que iniciaremos con la función «def __init__()«) empezará creando los elementos visuales de nuestra aplicación: Empezando por la ventana misma (que crearemos con «self.ventana=Tk()«) en la que insertaremos los tres botones (definidos en los objetos «self.btnLoad«, «self.btnCamera» y «self.btnScreen«) el espacio de texto en el que se mostrará la información leída (con «self.display») y el objeto canvas, en el que se mostrará la imagen, captada por la cámara web («self.canvas«). A su vez, usaremos «mainloop()» para mostrar la ventana abierta.

Ejecutando lo hecho hasta ahora, obtendremos nuestra interfaz tal y como se muestra en la imagen sobre estas líneas. No obstante, nuestro programa aún no puede hacer nada (aparte de mostrar la referida ventana) siendo necesario incluir las distintas funciones encargadas de leer los códigos «QR» y mostrar el resultado de la lectura en el «display» inferior.

Empezaremos por la función encargada de leer información, a partir de un archivo «jpg» o «png», almacenada en nuestro equipo (esta se ejecuta con el botón «self.btnLoad«, «CARGAR ARCHIVO«). Dicha función llevará por nombre «abrir()«:

Esta función usará el método «askopenfilename()» para que, navegando por nuestro sistema de carpetas, podamos escoger el código «QR» en formato «png» o «jpg» que queramos leer. De modo que si por dicho procedimiento seleccionamos un archivo («if ruta != «»«) este (que será un archivo de imagen) será leído por nuestro programa (con «cv2.imread(ruta)«) siendo almacenada dicha lectura en la variable «archivo«. Sobre este archivo es el que vamos a aplicar la función de lectura «decode()«, que solo devolverá (en la variable «info«) un resultado, si en el archivo de imagen, se detecta un código «QR» (en cuyo caso después de borrar el contenido que pueda haber en la caja de texto («self.display.delete(‘1.0’.END)«) insertaremos el texto correspondiente a la información leída («self.display.insert(END,self.info[0][0])«). Si con tal operación, no se detecta ningún «QR» mostraremos la correspondiente ventana de aviso («messagebox.showwarning()«):

La segunda opción de lectura es la que aplicaremos seleccionando el botón «DETECTAR EN PANTALLA» (elemento «btnScreen» en nuestro script) mediante una nueva función de nombre «screen_shot()«:

Esta función usa el método «.screenshot()» de la librería «pyautogui» para realizar una captura de pantalla, en la que se supone que se encuentra el código «QR» a leer. Esta captura crea un archivo de imagen «.jpg» que, de modo similar al caso anterior, será tomado como argumento de la función «decode()» de modo que se si esta consigue detectar el «QR» mostrará su información en el cuadro de texto inferior. En caso contrario, mostrará la correspondiente ventana de código no encontrado.

La tercera (y última) vía de lectura, que ofrecerá nuestro programa es, también, la principal, ya que es la que va a hacer uso de la cámara web de nuestro dispositivo para la lectura del «QR«. Dicha vía de lectura comenzará con la ejecución de la función «active_cam()«:

El cometido de esta función no es otro que el activar o desactivar la cámara web, en función del estado actual de la variable «active_camera» (que definimos al crear la ventana, con un valor inicial de «False» («self.active_camera=False«)). De modo que si el estado actual de dicha variable es «False» («if self.active_camera==False:«), en primer lugar cambiará su valor a «True» y pasará a ejecutar las funciones «VideoCaptura()» (para iniciar la cámara) seguida de la función «visor()» ,para mostrar en el componente canvas, el contenido de la entrada de la cámara (ambas funciones las veremos a continuación). Por contra, si el valor de «active_camera» no es «False» (por ser «True«), en primer término cambiará su estado a «False«, finalizará la cámara («self.vid.release()«) y borrará el contenido que haya en el componente canvas («self.canvas.delete(‘all’)«) para dejar la pantalla del visor en negro. A su vez, cambiaremos el texto del botón «btnCamera» a «INICIAR CAPTURA POR CAMARA».

Como hemos dicho, al activar la cámara la primera función que se ejecutará será «VideoCaptura()«:

Una de las circunstancias que debemos tener en cuenta, para que nuestro programa acceda a la cámara, es si esta, se encuentra o no activada. Así para evitar errores derivados del intentar acceder a una cámara no activada, después de crear el objeto encargado de acceder a la misma («self.vid=cv2.VideoCapture()«) asegurarnos de que esta está encendida («self.vid.isOpened()«) de modo que si se da dicha circunstancia («if self.vid.isOpened():«) pasar a adaptar las dimensiones del componente canvas al alto y ancho de la imagen de entrada de la cámara. Por contra, si la cámara no se encuentra activa, para evitar errores en el programa, abriremos una ventana de aviso con el correspondiente mensaje de error, tras lo cual, como se ve en el código , el valor de la variable «active_camera» volverá a su estado original («False«).

Una vez que, se ha comprobado que la cámara está activada, y (en caso afirmativo) una vez que las dimensiones del canvas se ha adaptado a las dimensiones de los futuros frames de entrada, pasaremos a mostrar la entrada en el canvas, con la función «visor():

El cometido de esta función es bien sencillo: El de mostrar, en la pantalla de la cámara, con una periodicidad de 0,015 segundos, los frames, obtenidos por la misma, mostrando sucesivamente cada frame en el canvas mediante el método «create_image()«. Dichos frames, a su vez, serán creados por la función «get_frame()» que vemos a continuación:

Esta función será la encargada de ir proporcionado, a la función «visor()», cada uno de los «frames», que, por medio del método «.read()» («self.vid.read()«), irá generando a partir de la información de entrada de la cámara web (el producto de esa lectura se almacenará en la variable «frame«) otra variable que nos proporcionará aquí «self.vid.read()» es «verif«, la cual la cual adoptará un valor «True» o «False» en función de si la lectura puede o no efectuarse (la lectura no podrá realizarse si la cámara, estando activada, está sin embargo, siendo utilizada por otra aplicación). Así, si el valor de «verif» es «True» (o lo que es lo mismo: «if verif:«) la función devolverá el frame a «visor()» para que esta lo muestre en el canvas. De no ser así, («else:«) se mostrará una nueva ventana de aviso, tras lo que el estado de «active_camera» retornará a «False«.

Otra consecuencia que, dentro de la función «get_frame()» tendrá «verif=True», es que, al «frame» generado le aplicaremos la función «capta()«:

Lo que hará esta función es tomar cada uno de los frames generados por la función anterior, e ir aplicando la función «decode()» para decodificar el contenido de códigos «QR«. El resultado de esta operación será una lista (asignada a la variable «info«) con la información de dicho «QR«. No obstante, si en el frame analizado no se detecta ningún «QR» (por no haberse puesto este delante de la cámara), el resultado de dicha operación será una lista vacía («info = []«). Por contra, si en el frame se detecta un código «QR» cuya información puede leerse («if self.info!=[]:«), pasaremos a limpiar el contenido que pueda haber en el display de texto, («display.delete()«) e insertar el texto correspondiente al contenido decodificado del «QR» («self.display.insert(END,self.info[0][0])«). También en este caso procederemos a dibujar sobre el «frame» un rectángulo que enmarque el código detectado/leído, con la función «draw_rectangle()«:

La función «draw_rectangle()» dibujará un recuadro azul que enmarcará el «QR» detectado.

El cometido de «draw_rectangle()«, como puede deducirse por su nombre, es el de dibujar sobre los frames que se muestran en el canvas, un el rectángulo en el que se enmarca el código «QR» detectado. Para la creación de dicho rectángulo (cuyas dimensiones, se actualizarán frame a frame, con el movimiento del «QR» delante de la cámara y que vendrá definido por las variables «x«, «y» (coordenadas de posición) y «w» y «h» (anchura y altura del código detectado) usaremos el método «rectangle()» en el que también definiremos el color de su contorno («(255,0,0)» en «BGR«, correspondiente al azul) y un grosor de 6. Igualmente incorporaremos un texto («QR Code«) en rojo, con el método «putText()«.

Finalmente, incluiremos la función («def __del__(self):«) para finalizar el programa, la cual, al cerrar la ventana del programa, finalice la cámara («self.vid.release()«) en caso de que esta esté siendo usada por el programa al tiempo de cerrarlo («if self.active_camera==True:«).

Con lo visto, tendríamos creada nuestra sencilla aplicación para lectura de códigos «QR» en Python y cuyo script completo podéis ver en el siguiente enlace:

https://github.com/antonioam82/QR_Camera/blob/master/QR_Camera.py

Una segunda versión, de esta aplicación, con visor de cámara desplegable, puede verse en el siguiente enlace:

https://github.com/antonioam82/QR_Camera/blob/master/QRCamera_new.py

Saludos.