La stabilisation vidéo est une technique qui réduit les mouvements et les tremblements indésirables dans les séquences vidéo. Les prises de vue à la main, les vibrations et les mouvements peuvent tous provoquer des mouvements instables de la caméra. La stabilisation vidéo permet d’obtenir une vidéo plus fluide.



L’objectif principal de la stabilisation vidéo est d’estimer le mouvement de la caméra entre des images consécutives. Le processus peut ensuite appliquer les transformations appropriées pour aligner les images. Cela permet de minimiser le mouvement perçu.


Configuration de l’environnement

Commencez par créer un environnement virtuel pour vous assurer que les paquets que vous installez pour exécuter le programme n’entrent pas en conflit avec les paquets existants. Exécutez ensuite cette commande de terminal pour installer les bibliothèques nécessaires :

 pip install opencv-python numpy

Cette commande installe les bibliothèques NumPy et OpenCV. NumPy fournit des outils pour les tâches numériques tandis qu’OpenCV s’occupe des tâches de vision par ordinateur.

Le code source complet est disponible dans un dépôt GitHub.


Importation des bibliothèques nécessaires et définition de trois fonctions cruciales

Créez un nouveau fichier Python et donnez-lui un nom de votre choix. Importez les bibliothèques NumPy et OpenCV au début du script.

 import numpy as np
import cv2

L’importation de ces bibliothèques vous permettra d’utiliser leurs fonctions dans votre code.

Définissez ensuite trois fonctions qui seront cruciales pour le processus de stabilisation.

La fonction calculate_moving_average

Créer une fonction et la nommer calculer_la_moyenne_mouvementale. Cette fonction calcule la moyenne mobile d’une courbe donnée en utilisant le rayon que vous spécifiez. Elle utilise une opération de convolution avec une taille de fenêtre spécifiée et un noyau uniforme. Cette moyenne mobile permet d’atténuer les fluctuations de la trajectoire.

 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 fonction renvoie une courbe lisse. Elle permet de réduire le bruit et les fluctuations de la courbe. Pour ce faire, elle calcule la moyenne des valeurs à l’intérieur de la fenêtre coulissante.

La fonction smooth_trajectory

Créez une autre fonction et nommez-la trajectoire_lisse. Cette fonction applique la moyenne mobile à chaque dimension de la trajectoire. Pour ce faire, elle crée une copie lissée de la trajectoire originale. Cela améliorera encore la stabilité de la vidéo.

 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

Le trajectoire_lisse renvoie une trajectoire lissée.

La fonction fix_border

Créer une fonction finale et la nommer fix_border. Cette fonction fixe la bordure du cadre en appliquant une rotation et une mise à l’échelle. Elle prend le cadre en entrée, calcule sa forme, construit une matrice de transformation et applique la transformation au cadre. Enfin, elle renvoie le cadre fixé.

 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 fonction fix_border permet de s’assurer que les images stabilisées ne présentent pas d’artefacts de bordure causés par le processus de stabilisation.


Initialisation de la stabilisation vidéo et prise en compte de l’entrée

Commencez par définir le rayon que la fonction de lissage de la trajectoire utilisera.

 SMOOTHING_RADIUS = 50

Ensuite, passez le chemin vidéo de la vidéo tremblante que vous souhaitez stabiliser.

 # Open the input video file
# Replace the path with 0 to use your webcam
cap = cv2.VideoCapture('inputvid.mp4')

Obtenez les propriétés de la vidéo tremblante :

 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)

Définir le format de sortie. Il s’agit du format dans lequel le programme enregistrera la vidéo stabilisée. Vous pouvez utiliser n’importe quel format vidéo courant.

 fourcc = cv2.VideoWriter_fourcc(*'mp4v')

Enfin, initialisez le graveur vidéo :

 out = cv2.VideoWriter('video_out.mp4', fourcc, fps, (2 * width, height)) 

L’extension du nom de fichier que vous transmettez au graveur vidéo doit être la même que celle que vous avez définie dans le format de sortie.


Lecture et traitement des images

La première étape du traitement de la vidéo tremblante commence ici. Elle consiste à lire les images de la vidéo d’entrée, à calculer les transformations et à remplir le tableau des transformations.

Commencez par lire la première image.

 _, prev_frame = cap.read()
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)

Initialiser ensuite le tableau de transformation. Il stockera des informations pour chaque image.

 transforms = np.zeros((num_frames - 1, 3), np.float32)

Enfin, vous devez calculer le flux optique entre les images consécutives. Ensuite, il faut estimer la transformation affine entre les points.

 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

La boucle itère sur chaque image (sauf la dernière) pour calculer les transformations. Elle calcule le flux optique entre les images consécutives à l’aide de la méthode Lucas-Kanade. cv2.goodFeaturesToTrack détecte les points caractéristiques dans l’image précédente prev_gray. Ensuite, cv2.calcOpticalFlowPyrLK suit ces points dans le cadre actuel curr_gray.

Seuls les points ayant un statut de 1 (indiquant un suivi réussi) aident à estimer une matrice de transformation affine. Le code met à jour la matrice de transformation affine. prev_gray variable contenant l’image en niveaux de gris actuelle pour la prochaine itération.


Lissage de la trajectoire

Il est nécessaire de lisser la trajectoire obtenue par les transformations pour obtenir un résultat stable.

 # 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

Le code ci-dessus calcule la trajectoire du mouvement de la caméra et la lisse.


Stabilisation et écriture d’images

L’étape finale consiste à stabiliser les images et à écrire la vidéo stabilisée dans un fichier de sortie.

Commencez par réinitialiser la capture vidéo. Cela permet de s’assurer que les opérations futures liront à partir du début de la vidéo.

 cap.set(cv2.CAP_PROP_POS_FRAMES, 0)

Stabilisez ensuite la vidéo en traitant chaque image.

 # 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)

Le code ci-dessus stabilise chaque image en utilisant les transformations calculées, y compris les ajustements de translation et de rotation. Il combine ensuite les images stabilisées avec les images originales pour établir une comparaison.


Libération de la capture et de l’écriture vidéo

Finalisez le programme en libérant les objets de capture vidéo et d’écriture.

 # Release the video capture and writer, and close any open windows
cap.release()
out.release()
cv2.destroyAllWindows()

Ce code ferme également toutes les fenêtres ouvertes.


Sortie du programme final

La sortie du programme ressemblera à ceci :

Sortie de la stabilisation vidéo dans l'IDE Pycharm

Et voici un exemple de vidéo stabilisée :


La sortie montre la comparaison entre la vidéo tremblante et la vidéo stabilisée.


Explorer les capacités d’OpenCV

OpenCV peut être utilisé dans de nombreux domaines impliquant la vision par ordinateur. En effet, il offre un large éventail de fonctionnalités. Vous devriez explorer ses capacités en travaillant sur d’autres projets impliquant la vision par ordinateur. Cela vous permettra de découvrir de nouveaux concepts et de nouveaux domaines de recherche.