ELIGIENDO VIDEO DE ENTRADA ? + DETECCIÓN FACIAL ? | GUI con Tkinter y OpenCV en Python
Y empezamos un nuevo post, en el que también estaremos hablando sobre la creación de una GUI con Tkinter, ayudándonos de OpenCV. En esta ocasión vamos a fusionar los dos programas que realizamos en el post anterior: GUI con Tkinter y OpenCV en Python | Videos ?. De tal modo que crearemos una GUI en la cual podamos elegir entre leer un video ya almacenado en nuestro ordenador o leer uno en vivo desde la webcam.
¡Vamos a empezar!.
CONTENIDO
- Construyendo la Interfaz Gráfica de Usuario con Tkinter para elegir un video de entrada leído con OpenCV, en donde se aplicará detección de rostros
- Importando los paquetes necesarios
- Leer el clasificador de rostros con OpenCV
- Creando una función para elegir el video de entrada o video en directo mediante la selección de los radiobuttons
- Función para visualizar un video en la GUI con OpenCV y Tkinter
- Función para la detección de rostros con OpenCV
- Función para finalizar y limpiar la visualización del video, se asignará al botón “Finalizar visualización y limpiar”
- Construcción de la GUI con Tkinter
- Probando la GUI creada con Tkinter
Construyendo la Interfaz Gráfica de Usuario con Tkinter para elegir un video de entrada leído con OpenCV, en donde se aplicará detección de rostros
Antes de describir la GUI que vamos a realizar, tengo que aclarar que este programa está basado en los programas desarrollados en el post anterior, por lo que si quieres profundizar un poquito más sobre ello, te recomiendo hecharle un vistazo a ese post o a su videotutorial.
Ahora sí, vamos a describir la GUI que construiremos.
Como lo podemos apreciar en la figura 1, la GUI tendrá:
- Label «VIDEO DE ENTRADA», que se ubicará en la sección superior de la GUI.
- 2 radio buttons: «Elegir video» y «Video en directo». Estos nos permitirán determinar si se escoge un video ya almacenado en el computador o se va a realizar la captura desde una webcam.
- Label informativo, que se encontrará debajo del radiobutton correspondiente a «Elegir video». Si dicho radiobutton es elegido, entonces se visualizarán los últimos 20 caracteres del path de dicho video.
- Label para el video, que nos permitirá ubicar el video en la GUI.
- Botón «Finalizar visualización y limpiar», que nos permitirá terminar con la visualización.
Importando los paquetes necesarios
Vamos a empezar creando un script de python llamado gui_video.py.
from tkinter import * from tkinter import filedialog from PIL import Image from PIL import ImageTk import cv2 import imutils
Línea 1: Importamos Tkinter que nos servirá para construir la GUI.
Línea 2: filedialog
, nos permitirá más adelante abrir un cuadro de diálogo para elegir el video de entrada.
Línea 3 y 4: Al igual que en el post anterior, usaremos PIL (Python Imaging Library) que nos permitirá relacionar el video leído con OpenCV, con la GUI. Para ello necesitamos Image e ImageTk.
Si no tienes instalada la librería puedes hacerlo con: pip install pillow.
Línea 6 a 6: Importamos OpenCV e imutils.
Leer el clasificador de rostros con OpenCV
faceClassif = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
Línea 8: Vamos a leer el clasificador de rostros entrenado con haar cascades, como lo hemos visto en otros tutoriales. La lectura la realizo en esta línea y no dentro de una función, ya que solo será necesario leer una vez dicho clasificador.
Creando una función para elegir el video de entrada o video en directo mediante la selección de los radiobuttons
Procederemos a darle funcionalidad a los radiobuttons, de tal forma que se pueda elegir si se lee un video almacenado ya en nuestro computador o capturar un videostreaming desde la webcam. A esta función la llamaremos en las líneas 77 y 78.
def video_de_entrada(): global cap if selected.get() == 1: path_video = filedialog.askopenfilename(filetypes = [ ("all video format", ".mp4"), ("all video format", ".avi")]) if len(path_video) > 0: btnEnd.configure(state="active") rad1.configure(state="disabled") rad2.configure(state="disabled") pathInputVideo = "..." + path_video[-20:] lblInfoVideoPath.configure(text=pathInputVideo) cap = cv2.VideoCapture(path_video) visualizar() if selected.get() == 2: btnEnd.configure(state="active") rad1.configure(state="disabled") rad2.configure(state="disabled") lblInfoVideoPath.configure(text="") cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) visualizar()
Línea 10: Declaramos la función video_de_entrada.
Línea 11: Declaramos global cap
ya que vamos a usar dicha variable en distintas funciones dentro del programa.
Línea 12 a 24: En la línea 12 tenemos que si se ha elegido el primer radiobutton es decir si selected.get() == 1
entonces se abrirá un diálogo de archivos para escoger archivos .mp4 y .avi. Si se ha elegido un video, es decir si len(path_video) > 0
, entonces el botón «Finalizar visualización y limpiar» se activará, mientras que los radiobuttos se desactivarán para no interferir con el video que se esté reproduciendo en la GUI en este momento.
En cuanto al label de información relacionado al path del video de entrada, este mostrará tres puntos suspensivos y los últimos 20 caracteres del path del video elegido. Esto se hará únicamente cuando se elija un video, no cuando se realice un el video en directo con la webcam (que corresponde a escoger el radiobutton 2).
Luego en la línea 23 establecemos path_video
como argumento de cv2.VideoCapture
para indicar que ese será el video que se leerá. Mientras que en la línea 24 se llamará a la función visualizar que veremos más adelante.
Línea 25 a 31: Si se ha elegido el segundo radiobutton es decir si selected.get() == 2
, entonces quiere decir que el usuario desea realizar un video en directo con ayuda de la cámara. De este modo en la GUI, se realizará una acción similar a la del otro radiobutton, es decir que el botón «Finalizar visualización y limpiar» se activa, mientras que los radiobuttos se desactivan. El label de información sobre el path del video se limpiará y establecemos cv2.VideoCapture(0, cv2.CAP_DSHOW)
para indicar que el video se realizará mediante el uso de la cámara del computador.
Mientras que en la línea 31, al igual que en el anterior radiobutton, llamará a la función visualizar.
Función para visualizar un video en la GUI con OpenCV y Tkinter
Vamos a crear la función visualizar, que nos permitirá visualizar el video almacenado en el computador o de la webcam, veamos:
def visualizar(): global cap ret, frame = cap.read() if ret == True: frame = imutils.resize(frame, width=640) frame = deteccion_facilal(frame) frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) im = Image.fromarray(frame) img = ImageTk.PhotoImage(image=im) lblVideo.configure(image=img) lblVideo.image = img lblVideo.after(10, visualizar) else: lblVideo.image = "" lblInfoVideoPath.configure(text="") rad1.configure(state="active") rad2.configure(state="active") selected.set(0) btnEnd.configure(state="disabled") cap.release()
Línea 33: Creamos la función visualizar.
Línea 34: Al igual que con la función anterior, declaramos global cap
.
Línea 35 a 45: Procedemos a leer los fotogramas del video, luego los redimensionaremos a un ancho de 640 pixeles y en la línea 38 estaremos aplicando la función deteccion_facial, la cual crearemos más adelante en la línea 55.
Para que se visualice el video con los colores correctos, transformaremos los fotogramas de BGR a RGB con cv2.cvtColor
. Y luego convertimos las imágenes a fomato ImageTk, para en las líneas 43 y 44 insertarlas en la GUI mediante lblVideo
.
Debido a que necesitamos leer constantemente múltiples fotogramas, requerimos un ciclo. Esto lo lograremos con after
que nos permitirá llamar a la función visualizar, cada 10 milisegundos.
Línea 46 a 53: Si por el contrario no hubieran fotogramas a leer, entonces se limpia lblVideo
al igual que lblInfoVideoPath
. Los radiobuttons vuelven a activarse y se los setea en 0 con selected.set(0)
, para que se limpie la selección previa.
Mientras que el botón «Finalizar visualización y limpiar» se desactiva y la captura se libera.
Función para la detección de rostros con OpenCV
En la función visualizar, en la línea 38 llamamos a la función deteccion_facial. Es ahora que vamos a construir esta función.
def deteccion_facilal(frame): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = faceClassif.detectMultiScale(gray, 1.3, 5) for (x, y, w, h) in faces: frame = cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) return frame
Línea 55: Declaramos la función deteccion_facilal, la cual pedirá como argumento a frame
, para sobre este encontrar los rostros presentes.
Línea 56 y 57: Para este programa voy a transformar frame
, de BGR a escala de grises. En la siguiente línea se procede a detectar los rostros presentes en gray
que a su vez de almacenarán en faces
.
NOTA: Para más información sobre como OpenCV realiza la detección facial con haar cascades te recomiendo ir a este tutorial: ? DETECCIÓN DE ROSTROS ? con Haar Cascades Python – OpenCV
Línea 58 y 59: Vamos a desempaquetar las coordenadas, ancho y alto de todos los rostros detectados, y en la línea 59 procedemos a dibujar un rectángulo de tal modo que rodee a cada rostro.
Línea 60: Esta función retornará frame
.
Función para finalizar y limpiar la visualización del video, se asignará al botón «Finalizar visualización y limpiar»
Continuamos con la última función que crearemos, que es finalizar_limpiar. Esta permitirá acabar con la visualización y limpiar la GUI una vez que sea presionado el botón «Finalizar visualización y limpiar».
def finalizar_limpiar(): lblVideo.image = "" lblInfoVideoPath.configure(text="") rad1.configure(state="active") rad2.configure(state="active") selected.set(0) cap.release()
Línea 62: Declaramos la función finalizar_limpiar.
Línea 63 y 64: Limpiamos los labels lblVideo
y lblInfoVideoPath
.
Línea 65 a 67: Activamos los radiobuttons y usamos selected.set(0)
, para limpiar cualquier selección que haya existido.
Línea 68: Liberamos la captura del video.
Construcción de la GUI con Tkinter
Lo único que nos queda por hacer es crear la interfaz gráfica de usuario, de donde estaremos llamando a las funciones que hemos creado hasta ahora.
cap = None root = Tk() lblInfo1 = Label(root, text="VIDEO DE ENTRADA", font="bold") lblInfo1.grid(column=0, row=0, columnspan=2)
Línea 70: Declaramos la variable cap = None
, que es la que usamos en las funciones anteriores.
Línea 71: Creamos la venta principal.
Línea 73 y 74: Creamos un label llamado «VIDEO DE ENTRADA» en negrita, para que se ubique en la parte superior de la GUI. Usamos columnspan=2
para que ocupe 2 columnas.
selected = IntVar() rad1 = Radiobutton(root, text="Elegir video", width=20, value=1, variable=selected, command=video_de_entrada) rad2 = Radiobutton(root, text="Video en directo", width=20, value=2, variable=selected, command=video_de_entrada) rad1.grid(column=0, row=1) rad2.grid(column=1, row=1)
Línea 76: Digitamos selected = IntVar()
, esto nos permitirá manejar diferentes valores enteros para los radio buttons.
Línea 77: Vamos a crear el primer radio button. Entonces digitamos Radiobutton
y especificaremos la ventana en donde se va a visualizar, y el texto será «Elegir video», en este caso tendrá un ancho de 30 y asignamos value=1
y variable=selected
. Cada vez que se seleccione este radio button, selected tomará el valor de value, en este caso 1. También tendremos que añadir command= video_de_entrada
para que se llame a la función video_de_entrada, cada vez que se presione dicho radio button.
Línea 78: Similar a la línea 77, con la diferencia que se mostrará «Video en directo» y value será igual a 2.
Línea 79 y 80: Con ayuda de grid
ubicamos estos radiobuttos en la fila 1 y en la columna 0 y 1 respectivamente.
lblInfoVideoPath = Label(root, text="", width=20) lblInfoVideoPath.grid(column=0, row=2) lblVideo = Label(root) lblVideo.grid(column=0, row=3, columnspan=2) btnEnd = Button(root, text="Finalizar visualización y limpiar", state="disabled", command=finalizar_limpiar) btnEnd.grid(column=0, row=4, columnspan=2, pady=10) root.mainloop()
Línea 82 y 83: Creamos el label lblInfoVideoPath
que será en donde se visualizará el path del video de entrada. Este en un principio estará vacío y tendrá un ancho de 20. Este label se ubicará en la columna 0 fila 2.
Línea 85 y 86: Este es el label en donde se visualizará el video de entrada, ya sea pre grabado o un video en directo. Se ubicará en la columna 0, fila 3 y ocupará 2 columnas.
Línea 88 y 89: Finalmente tenemos el botón «Finalizar visualización y limpiar». Este en un principio estará desactivado (solo se activará cuando se esté visualizando o reproduciendo un video), pero al cambiar de estado llamará a la función finalizar_limpiar.
Este botón se ubicará en la columna 0, fila 4, ocupará 2 columnas y se ha especificado pady=10
para que no se ubique tan cerca de la parte inferior de la GUI.
Línea 91: Con root.mainloop()
creamos un ciclo sin fin para la ventana creada. De este modo la ventana estará disponible hasta que el usuario la cierre.
Probándo la GUI creada con Tkinter
Ahora que ya hemos construido el programa, ¡vamos a probarlo!.
Una vez en la ventana de la figura 2, elegiremos el primer radiobutton «Elegir video» y se nos desplegará un diálogo de archivos.
Como podemos apreciar en la figura 3, en el diálogo de archivos nos aparecen los videos en .mp4 y en .avi para escogerlos. Podremos escoger cualquier video con estas extensiones.
En la figura 4 en la GUI podemos ver que al momento que se está visualizando el video, los radiobuttons se desactivan, se visualiza parte del path del video elegido y el botón se activa.
Si en este caso presionamos el botón «Finalizar visualización y limpiar», obtendríamos lo siguiente:
En caso de que se presione el botón «Finalizar visualización y limpiar» o que ya haya terminado la visualización del video, entonces los radiobuttons se vuelven a activar, además de que se limpia la selección realizada anteriormente sobre ellos. El label correspondiente al path del video elegido se limpia, y el botón antes presionado se vuelve a desactivar.
Ahora procederemos a probar el radiobutton con el «Video en directo».
Similar a la figura 4, ya que una vez que hemos elegido «Video en directo», los radiobuttons se desactivan, se limpia el label del path del video ya que en este caso no tendríamos ningún path y se activa el botón «Finalizar visualización y limpiar».
Y esto ha sido todo por el tutorial de hoy, espero que les haya gustado mucho. ¡Tengan un grandioso día!, nos vemos en un siguiente tutorial/videotutorial. 🙂
Como siempre, muy buen tutorial y muy bien explicado. Con respecto al código, me he permitido incluir un par de mejoras a saber:
– Poner un título a la ventana.
– Llamar al método «visualizar» (que yo llamé «visualizarVideo») al final del método «videoDeEntrada», ya que tanto si se selecciona reproducir un vídeo que esté almacenado en el ordenador o en un dispositivo de almacenamiento externo como si se selecciona reproducir un vídeo en streaming, en ambos casos se va a ejecutar el mismo código.
Aquí tienes el código con las mejoras indicadas:
# -*- coding: utf-8
from tkinter import *
from tkinter import filedialog
from PIL import Image
from PIL import ImageTk
import cv2
import imutils
#Cargamos el reconocedor de caras preentrenado.
clasificador = cv2.CascadeClassifier(cv2.data.haarcascades + ‘haarcascade_frontalface_default.xml’)
#Creamos el método que se va a encargar de reconocer las caras que aparezcan en la imágenes del vídeo.
def reconocimientoFacial(ventana):
gris = cv2.cvtColor(ventana, cv2.COLOR_BGR2GRAY)
caras = clasificador.detectMultiScale(gris, 1.3, 5)
for (x, y, ancho, alto) in caras:
ventana = cv2.rectangle(ventana, (x, y), (x + ancho, y + alto), (0, 255, 0), 2)
return ventana
#Creamos la el método que se va a encargar de cargar los vídeos.
def videoDeEntrada():
global captura
#Detectamos qué botón ha sido seleccionado.
if seleccionado.get() == 1:
»’
Si se ha seleccionado el primer botón, cargamos un vídeo que esté
almacenado en el ordenador o en cualquier dispositivo de almacenamiento
externo.
»’
ruta_video = filedialog.askopenfilename(filetypes=[(«all video format», «.mp4»), («all video format», «.avi»)])
if len(ruta_video) > 0:
#Si se ha seleccionado algún vídeo, activamos el botón de parar la reproducción.
boton.configure(state=»active»)
#Desactivamos los radiobuttons para que no interfieran en la reproducción del vídeo.
btnRadio1.configure(state=»disabled»)
btnRadio2.configure(state=»disabled»)
#Indicamos los últimos 20 caracteres de la ruta del archivo de vídeo y los mostramos.
ruta_video_entrada = «…» + ruta_video[20:]
lblInformacionRutaVideo.configure(text=ruta_video_entrada)
#Iniciamos la captura del vídeo.
captura = cv2.VideoCapture(ruta_video)
if seleccionado.get() == 2:
#Si se ha seleccionado el segundo botón, activamos la webcam.
boton.configure(state=»active»)
btnRadio1.configure(state=»disabled»)
btnRadio2.configure(state=»disabled»)
lblInformacionRutaVideo.configure(text=»»)
captura = cv2.VideoCapture(1, cv2.CAP_DSHOW)
#Visualizamos el vídeo.
visualizarVideo()
def visualizarVideo():
#Creamos el método que se va a encargar de visualizar el vídeo.
global captura
ret, ventana = captura.read()
if ret == True:
ventana = imutils.resize(ventana, width=640)
ventana = reconocimientoFacial(ventana)
ventana = cv2.cvtColor(ventana, cv2.COLOR_BGR2RGB)
im = Image.fromarray(ventana)
img = ImageTk.PhotoImage(image=im)
lblVideo.configure(image=img)
lblVideo.image = img
lblVideo.after(10, visualizarVideo)
else:
lblVideo.image = «»
lblInformacionRutaVideo.configure(text=»»)
btnRadio1.configure(state=»active»)
btnRadio2.configure(state=»active»)
seleccionado.set(0)
boton.configure(state=»disabled»)
captura.release()
def finalizarYLimpiar():
#Creamos el método que se va a encargar de finalizar la reproducción del vídeo y de limpiar la pantalla.
lblVideo.image = «»
lblInformacionRutaVideo.configure(text=»»)
btnRadio1.configure(state=»active»)
btnRadio2.configure(state=»active»)
seleccionado.set(0)
captura.release()
#Creamos la interfaz.
captura = None
root = Tk()
root.title(«Reproductor de vídeo avanzado»)
#Creamos una etiqueta de información.
lblInformacion1 = Label(root, text=»VÍDEO DE ENTRADA», font=»bold»)
lblInformacion1.grid(column=0, row=0, columnspan=2)
#Creamos los radiobuttons, dotándolos de funcionalidad.
seleccionado = IntVar()
btnRadio1 = Radiobutton(root, text=»Elegir vídeo», width=20, value=1, variable=seleccionado, command=videoDeEntrada)
btnRadio2 = Radiobutton(root, text=»Vídeo en directo», width=20, value=2, variable=seleccionado, command=videoDeEntrada)
btnRadio1.grid(column=0, row=1)
btnRadio2.grid(column=1, row=1)
#Creamos otra etiqueta de información, la cual mostrará la ruta del archivo de vídeo.
lblInformacionRutaVideo = Label(root, text=»», width=20)
lblInformacionRutaVideo.grid(column=0, row=2)
#Creamos la etiqueta donde se va a mostrar el vídeo.
lblVideo = Label(root)
lblVideo.grid(column=0, row=3, columnspan=2)
#Creamos un botón en la parte inferior de la interfaz que se encargará de finalizar la reproducción del vídeo.
boton = Button(root, text=»Finalizar visualización y limpiar», state=»disabled», command=finalizarYLimpiar)
boton.grid(column=0, row=4, columnspan=2, pady=10)
#Mantenemos la ventana abierta hasta que el usuario decida cerrarla.
root.mainloop()
P.D: En el siguiente link podrás ver un vídeo que hice para que veas que el programa sigue funcionando igualmente bien con las mejoras que te he mencionado: https://drive.google.com/file/d/1iKz_2j4QzUxK4pUIFaawNSNcf-lmTGs2/view?usp=sharing
Muchas gracias por el aporte Santiago! 🙂
y como seria con fece recognition?
Hola.
¡¡ Felicidades !!
soy bastante nuevo en esto.
Me gustaria integrar en este poroyecto pero en lugar de seleccionar un video de entrada, escribir la url de uan camara ip..
he realizado alguan pruebas pero no me funciona.
saludos y gracias.
Estas aplicaciones de Tkinter cuando a petición del usuario , se le da al botón de expandir la ventana, para que ocupe toda la extensión de la pantalla suele dar malos resultado. Los elementos en la interfaz, no se reposicionan, mantienen su posición anterior, dejando un gran hueco vacío. Personalmente prefiero no dar esa opción. Solo hay que poner esto justo después de crear la ventana principal » en la siguiente linea: root.wm_resizable(False, False)