Cómo implementar la estabilización de video en tiempo real usando OpenCV
Utilice esta técnica para aplicar algunas matemáticas inteligentes a sus videos y reducir las sacudidas.
La estabilización de video es una técnica que reduce los movimientos no deseados y las sacudidas en las secuencias de video. Los disparos manuales, la vibración y el movimiento pueden provocar movimientos inestables de la cámara. La estabilización de video produce un video de apariencia más fluida.
El objetivo principal de la estabilización de vídeo es estimar el movimiento de la cámara entre fotogramas consecutivos. Luego, el proceso puede aplicar transformaciones apropiadas para alinear los marcos. Esto minimiza el movimiento percibido.
Configurando su entorno
Comience creando un entorno virtual para asegurarse de que los paquetes que instale para ejecutar el programa no entren en conflicto con los existentes. Luego ejecute este comando de terminal para instalar las bibliotecas necesarias:
pip install opencv-python numpy
Este comando instala las bibliotecas NumPy y OpenCV. NumPy proporciona herramientas para tareas numéricas, mientras que OpenCV se ocupa de tareas de visión por computadora.
El código fuente completo está disponible en un repositorio de GitHub.
Importar las bibliotecas necesarias y definir tres funciones cruciales
Crea un nuevo archivo Python y dale un nombre de tu agrado. Importe las bibliotecas NumPy y OpenCV al comienzo del script.
import numpy as np
import cv2
Importar estas bibliotecas le permitirá utilizar sus funciones en su código.
A continuación, defina tres funciones que serán cruciales para el proceso de estabilización.
La función calcular_moving_average
Cree una función y asígnele el nombre calcular_moving_average. Esta función calculará el promedio móvil de una curva determinada utilizando el radio que especifique. Emplea una operación de convolución con un tamaño de ventana específico y un núcleo uniforme. Esta media móvil ayuda a suavizar las fluctuaciones en la trayectoria.
def calculate_moving_average(curve, radius):
# Calculate the moving average of a curve using a given radius
window_size = 2 * radius + 1
kernel = np.ones(window_size) / window_size
curve_padded = np.lib.pad(curve, (radius, radius), 'edge')
smoothed_curve = np.convolve(curve_padded, kernel, mode='same')
smoothed_curve = smoothed_curve[radius:-radius]
return smoothed_curve
La función devuelve una curva suave. Ayuda a reducir el ruido y las fluctuaciones en la curva. Lo hace promediando los valores dentro de la ventana deslizante.
La función smooth_trajectory
Crea otra función y llámala smooth_trajectory. Esta función aplicará la media móvil en cada dimensión de la trayectoria. Lo logrará creando una copia suavizada de la trayectoria original. Esto mejorará aún más la estabilidad del vídeo.
def smooth_trajectory(trajectory):
# Smooth the trajectory using moving average on each dimension
smoothed_trajectory = np.copy(trajectory)
for i in range(3):
smoothed_trajectory[:, i] = calculate_moving_average(
trajectory[:, i],
radius=SMOOTHING_RADIUS
)
return smoothed_trajectory
La función smooth_trajectory devuelve una trayectoria suavizada.
La función fix_border
Cree una función final y asígnele el nombre fix_border. Esta función arreglará el borde del marco aplicando una transformación de rotación y escala. Toma el marco de entrada, calcula su forma, construye una matriz de transformación y aplica la transformación al marco. Finalmente, devuelve el marco fijo.
def fix_border(frame):
# Fix the frame border by applying rotation and scaling transformation
frame_shape = frame.shape
matrix = cv2.getRotationMatrix2D(
(frame_shape[1] / 2, frame_shape[0] / 2),
0,
1.04
)
frame = cv2.warpAffine(frame, matrix, (frame_shape[1], frame_shape[0]))
return frame
La función fix_border garantiza que los fotogramas estabilizados no tengan ningún artefacto en el borde causado por el proceso de estabilización.
Inicializando la estabilización de video y tomando la entrada
Comience estableciendo el radio que utilizará la función de suavizado de trayectoria.
SMOOTHING_RADIUS = 50
Luego, pase la ruta del video inestable que desea estabilizar.
# Open the input video file
# Replace the path with 0 to use your webcam
cap = cv2.VideoCapture('inputvid.mp4')
Obtenga las propiedades del video inestable:
num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
Establezca el formato de salida. Este es el formato en el que el programa guardará el vídeo estabilizado. Puede utilizar cualquier formato de vídeo común que desee.
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
Finalmente, inicialice el escritor de video:
out = cv2.VideoWriter('video_out.mp4', fourcc, fps, (2 * width, height))
La extensión del nombre del archivo que le pase al escritor del video debe ser la misma que configuró en el formato de salida.
Marcos de lectura y procesamiento
El primer paso para procesar el vídeo tembloroso comienza aquí. Implica leer fotogramas del vídeo de entrada, calcular transformaciones y completar la matriz de transformaciones.
Comience leyendo el primer fotograma.
_, prev_frame = cap.read()
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
Luego inicialice la matriz de transformación. Almacenará información para cada cuadro.
transforms = np.zeros((num_frames - 1, 3), np.float32)
Finalmente, es necesario calcular el flujo óptico entre fotogramas consecutivos. Luego, estime la transformación afín entre los puntos.
for i in range(num_frames - 2):
# Calculate optical flow between consecutive frames
prev_points = cv2.goodFeaturesToTrack(
prev_gray,
maxCorners=200,
qualityLevel=0.01,
minDistance=30,
blockSize=3
)
success, curr_frame = cap.read()
if not success:
break
curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)
curr_points, status, err = cv2.calcOpticalFlowPyrLK(
prev_gray,
curr_gray,
prev_points,
None
)
assert prev_points.shape == curr_points.shape
idx = np.where(status == 1)[0]
prev_points = prev_points[idx]
curr_points = curr_points[idx]
# Estimate affine transformation between the points
matrix, _ = cv2.estimateAffine2D(prev_points, curr_points)
translation_x = matrix[0, 2]
translation_y = matrix[1, 2]
rotation_angle = np.arctan2(matrix[1, 0], matrix[0, 0])
transforms[i] = [translation_x, translation_y, rotation_angle]
prev_gray = curr_gray
El bucle itera sobre cada fotograma (excepto el último fotograma) para calcular las transformaciones. Calcula el flujo óptico entre fotogramas consecutivos utilizando el método de Lucas-Kanade. cv2.goodFeaturesToTrack detecta puntos característicos en el fotograma anterior prev_gray. Luego, cv2.calcOpticalFlowPyrLK rastrea estos puntos en el cuadro actual curr_gray.
Sólo los puntos con un estado de 1 (que indica un seguimiento exitoso) ayudan a estimar una matriz de transformación afín. El código actualiza la variable prev_gray con el marco de escala de grises actual para la siguiente iteración.
Suavizando la trayectoria
Es necesario suavizar la trayectoria obtenida de las transformaciones para lograr un resultado estable.
# Calculate the trajectory by cumulatively summing the transformations
trajectory = np.cumsum(transforms, axis=0)
# Smooth the trajectory using moving average
smoothed_trajectory = smooth_trajectory(trajectory)
# Calculate the difference between the smoothed and original trajectory
difference = smoothed_trajectory - trajectory
# Add the difference back to the original transformations to obtain smooth
# transformations
transforms_smooth = transforms + difference
El código anterior calcula la trayectoria del movimiento de la cámara y la suaviza.
Marcos estabilizadores y de escritura.
El último paso es estabilizar los fotogramas y escribir el vídeo estabilizado en un archivo de salida.
Comience restableciendo la captura de video. Esto garantiza que las operaciones futuras se lean desde el inicio del vídeo.
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
Luego estabilice el video procesando cada cuadro.
# Process each frame and stabilize the video
for i in range(num_frames - 2):
success, frame = cap.read()
if not success:
break
translation_x = transforms_smooth[i, 0]
translation_y = transforms_smooth[i, 1]
rotation_angle = transforms_smooth[i, 2]
# Create the transformation matrix for stabilization
transformation_matrix = np.zeros((2, 3), np.float32)
transformation_matrix[0, 0] = np.cos(rotation_angle)
transformation_matrix[0, 1] = -np.sin(rotation_angle)
transformation_matrix[1, 0] = np.sin(rotation_angle)
transformation_matrix[1, 1] = np.cos(rotation_angle)
transformation_matrix[0, 2] = translation_x
transformation_matrix[1, 2] = translation_y
# Apply the transformation to stabilize the frame
frame_stabilized = cv2.warpAffine(
frame,
transformation_matrix,
(width, height)
)
# Fix the border of the stabilized frame
frame_stabilized = fix_border(frame_stabilized)
# Concatenate the original and stabilized frames side by side
frame_out = cv2.hconcat([frame, frame_stabilized])
# Resize the frame if its width exceeds 1920 pixels
if frame_out.shape[1] > 1920:
frame_out = cv2.resize(
frame_out,
(frame_out.shape[1] // 2, frame_out.shape[0] // 2)
)
# Display the before and after frames
cv2.imshow("Before and After", frame_out)
cv2.waitKey(10)
# Write the frame to the output video file
out.write(frame_out)
El código anterior estabiliza cada cuadro utilizando las transformaciones calculadas, incluidos los ajustes de traslación y rotación. Luego combina los marcos estabilizados con los originales para realizar una comparación.
Liberación de la captura de video y el escritor
Finalice el programa liberando los objetos de captura de video y escritor.
# Release the video capture and writer, and close any open windows
cap.release()
out.release()
cv2.destroyAllWindows()
Este código también cierra cualquier ventana abierta.
Resultado final del programa
La salida del programa se verá así:
Y aquí hay un ejemplo del video estabilizado:
El resultado muestra la comparación entre el vídeo inestable y el estabilizado.
Explora las capacidades de OpenCV
Puede aplicar OpenCV en muchos campos que involucran la visión por computadora. Esto se debe a que ofrece una amplia gama de funcionalidades. Debería explorar sus capacidades trabajando en más proyectos que involucren visión por computadora. Esto le presentará nuevos conceptos y le brindará nuevas áreas para investigar.