Un paquete de reproductor de video Python creado para la investigación de visión por computadora

Imagen del autor

Al desarrollar algoritmos de visión por computadora, el viaje desde el concepto hasta la implementación funcional a menudo implica innumerables iteraciones de visualización, análisis y depuración de cuadros de video. A medida que profundizaba en los proyectos de visión por computadora, me encontré escribiendo repetidamente el mismo código repetitivo para la visualización y depuración de videos.

En algún momento, decidí que ya era suficiente, así que creé Reproductor de vídeo CVun paquete de reproductor de vídeo de código abierto basado en Python, diseñado específicamente para profesionales de la visión por computadora que resolverá este problema de una vez por todas.

Reproductor de vídeo CV “Modo de doble cuadro” con visualizaciones adicionales y atajos de teclado. Imagen del autor

Si alguna vez desarrolló un algoritmo para análisis de video, probablemente haya escrito alguna versión del siguiente código para ayudarlo a visualizarlo y depurarlo:

import cv2

cap = cv2.VideoCapture(<video_path>)
ret = True
while ret:
ret, frame = cap.read()
algo_output = some_video_analsys_algorithm(frame)
frame_to_display = visualizer(frame, algo_output)
cv2.imshow(frame_to_display)
cv2.waitKey()

Pero en casi todos los proyectos en los que he trabajado, este código rara vez fue suficiente. A medida que avanzaba el proyecto, me encontré agregando más y más funciones para ayudarme a comprender lo que estaba pasando.

Por ejemplo:

  • Navegación por el vídeo de ida y vuelta fotograma a fotograma.
  • La capacidad de grabar la salida en un archivo.
  • Admite fuentes distintas a un simple archivo de vídeo (carpeta de fotogramas, transmisión, almacenamiento remoto, etc.)

Pero lo que más me molestó fue la falta de interactividad.. Al utilizar este tipo de código, la visualización se crea antes de renderizarse y no puede cambiar una vez que se muestra. Y, si bien esto está bien para algoritmos simples, para los más complejos, simplemente se necesita demasiada información para cada cuadro. Y sin la capacidad de decidir sobre la marcha qué desea mostrar, se encontrará ejecutando el mismo vídeo una y otra vez, cada vez con diferentes parámetros de visualización.

Este proceso fue tedioso y agotador.

Imagen del autor

Reproductor de vídeo CV nació de la necesidad de una solución sencilla y personalizable para renderizar vídeos y fotogramas de forma interactiva. Permite cualquier cantidad de superposiciones, barras laterales o cualquier otra edición de fotogramas, cada una de las cuales el usuario puede activar y desactivar fácilmente durante el tiempo de ejecución. veamos un ejemplo de cómo se hace esto:

Instalación

Comenzamos instalando el paquete usando pip install cvvideoplayer

Reproduciendo video vainilla

Luego podemos importar el reproductor de video y ejecutar un video sin editar con el siguiente código:

from cvvideoplayer import create_video_player

VIDEO_OR_FRAME_FOLDER_PATH = "<add local path here>"

video_player = create_video_player(video_source=VIDEO_OR_FRAME_FOLDER_PATH)
video_player.run()

Esto abrirá el reproductor de video y le permitirá reproducirlo con la barra espaciadora o usando las flechas, también agregará algunas funciones predeterminadas integradas. frame-edit-callbacks que desarrollaremos en el siguiente apartado.

Imagen del autor

Para agregar una visualización personalizada al video, podemos usar el frame_edit_callbacks argumento de la create_video_player función constructora así:

from cvvideoplayer import VideoPlayer

VIDEO_OR_FRAME_FOLDER_PATH = "<add local path here>"

video_player = create_video_player(
video_source=VIDEO_OR_FRAME_FOLDER_PATH,
frame_edit_callbacks=[
FitFrameToScreen(),
FrameInfoOverlay(),
KeyMapOverlay(),
]
)
video_player.run()

Cuando no se especifica, la lista predeterminada será exactamente la del ejemplo anterior.

Devoluciones de llamada integradas

Hay un montón de devoluciones de llamada integradas para usar, como por ejemplo:

  • FitFrameToScreen — Cambia automáticamente el tamaño del marco para que se ajuste al tamaño de la pantalla.
  • FrameInfoOverlay — Imprime el número de fotograma y la resolución del fotograma original en la esquina superior izquierda.
  • KeyMapOverlay — Detecta e imprime automáticamente todos los atajos de teclado disponibles (también los agregados por el usuario).
  • DetectionCsvPlotter — Traza cuadros delimitadores especificados en un CSV con el siguiente encabezado: frame_id, etiqueta, x1, y1, ancho, alto, puntuación
  • FrameNormlizer — Permite al usuario ajustar el rango dinámico de la imagen.
  • HistogramEqulizer – se explica por sí mismo

Y se agregan más con cada versión.

Creando una devolución de llamada personalizada

Aquí es donde brilla la utilidad del paquete. Para agregar su propia visualización personalizada, cree una nueva clase que herede BaseFrameEditCallback e implementa el edit_frame método, por ejemplo:

class MyCallback(BaseFrameEditCallback):
def __init__(
self,
enable_by_default: bool = True,
enable_disable_key: Optional[str] = None,
additional_keyboard_shortcuts: Optional[List[KeyFunction]] = None
**any_other_needed_params
):
super().__init__(
enable_by_default,
enable_disable_key,
additional_keyboard_shortcuts
)

def edit_frame(
self,
video_player: "VideoPlayer",
frame: np.ndarray,
frame_num: int,
original_frame: np.ndarray,
) -> np.ndarray:
"""
This function receives the displayed frame and should return it
after it has been altered in any way desirable by the user

Args:
video_player: an instance fo VideoPlayer
frame (): the frame to be edited and displayed
frame_num ():
original_frame () the frame before any alterations

Returns: the edited frame
"""
frame = add_any_visalizations(frame)
return frame

Además, puede agregar métodos de configuración y desmontaje anulando estos métodos en la clase principal:

class MyCallback(BaseFrameEditCallback):
...
def setup(self, video_player: "VideoPlayer", frame) -> None:
"""
Optionally configure more parameters according to the
first incoming frame
"""

def teardown(self) -> None:
"""
Optionally define how the callback should close when the
video player is closed
"""

Para cada devolución de llamada, CV Video Player le permite agregar atajos de teclado personalizados que pueden cambiar la visualización que realiza en tiempo de ejecución.

El atajo más básico es habilitar/deshabilitar la devolución de llamada y se crea usando el enable_disable_key parámetro así:

my_callback = MyCallback(
enable_disable_key="ctrl+a"
)

La cadena pasada aquí puede ser cualquier combinación de modificadores (ctrl, alt y shift) con una letra o un número, por ejemplo: “crtl+alt+s”, «gramo», “mayús+v”, “control+1” etcétera.

Para agregar atajos que cambien la visualización en sí, puede anular eladditional_keyboard_shortcuts propiedad que devuelve una lista de la clase de datosKeyFunction .

from cvvideoplayer import KeyFunction

class MyCallback(BaseFrameEditCallback):
...
@property
def additional_keyboard_shortcuts(self) -> List[KeyFunction]:
[
KeyFunction(
key="alt+r",
function=self.a_function_to_modify_the_visualiztion,
description="what this does"
)
]

A KeyFunction se construye utilizando tres argumentos:

  • El key argumento – Lo mismo que para enable_disable_key La cadena pasada aquí puede ser cualquier combinación de modificadores (ctrl, alt y shift) con una letra o un número, por ejemplo: “crtl+alt+s”, «gramo», “mayús+v”, “control+1”
  • El description argumento: es utilizado por el KeyMapOverlay devolución de llamada para imprimir todos los accesos directos disponibles en la pantalla.
  • El function argumento: tiene que ser una función que no acepte argumentos.

En muchos casos, KeyFunction recibirá una función que alterna algún atributo booleano de la devolución de llamada, lo que cambiará algo que el edit_frameEl método lo hace. Entonces algo como:

from cvvideoplayer import KeyFunction

class MyCallback(BaseFrameEditCallback):
...
@property
def additional_keyboard_shortcuts(self) -> List[KeyFunction]:
[
KeyFunction(
key="alt+r",
function=self.a_function_to_modify_the_visualiztion,
description="what this does"
)
]
def a_function_to_modify_the_visualiztion():
self._draw_something = bool(1 - self._draw_somthing)

Muchas veces me encontré queriendo comparar dos visualizaciones diferentes una al lado de la otra. Por ejemplo, comparar dos detectores o la salida de un algoritmo con el marco original sin modificaciones, etc.

Para hacer eso agregué double_frame_mode que puede activarse mediante:

video_player = create_video_player(
...
double_frame_mode=True
)

El vídeo al principio de este blog es un ejemplo de cómo se ve este modo.

En este modo, puede utilizar “ctrl+1” y “ctrl+2″ para decidir qué visualización de fotograma desea controlar con el teclado.

De forma predeterminada, ambos marcos tendrán las mismas devoluciones de llamada disponibles, pero si desea devoluciones de llamada diferentes para el marco derecho, puede usar el right_frame_callback argumento para darle al marco derecho un conjunto diferente de devoluciones de llamada (el marco izquierdo tendrá las pasadas al frame_edit_callback argumento):

video_player = create_video_player(
...
double_frame_mode=True
right_frame_callbacks = [callback1, callback2, ...]
)

Espero que esta herramienta sea útil para todos ustedes. Si tiene alguna idea sobre cómo mejorarlo, hágamelo saber en la pestaña de problemas en la página del proyecto. página de GitHuby no olvides dejar una estrella mientras lo haces :)…