Recherche de site Web

Comment transformer des images et créer une vidéo avec OpenCV


Lorsque vous travaillez avec OpenCV, vous travaillez le plus souvent avec des images. Cependant, il peut s'avérer utile de créer une animation à partir de plusieurs images. Il y a de fortes chances que l'affichage d'images en succession rapide vous donne un aperçu différent ou qu'il soit plus facile de visualiser votre travail en introduisant un axe temporel.

Dans cet article, vous verrez comment créer un clip vidéo dans OpenCV. À titre d'exemple, vous apprendrez également quelques techniques de base de manipulation d'images pour créer les images. Vous apprendrez notamment :

  • Comment manipuler des images sous forme de tableau numpy
  • Comment manipuler des images à l'aide des fonctions OpenCV
  • Comment créer un fichier vidéo dans OpenCV

Démarrez votre projet avec mon livre Machine Learning in OpenCV. Il fournit des tutoriels d'auto-apprentissage avec du code fonctionnel.

Aperçu

Cet article est divisé en deux parties ; ils sont:

  • Effet Ken Burns
  • Écrire une vidéo

Effet Ken Burns

Vous allez créer beaucoup d’images en suivant d’autres posts. Il s'agit peut-être de visualiser les progrès de votre projet d'apprentissage automatique ou de montrer comment une technique de vision par ordinateur manipule votre image. Pour simplifier les choses, vous allez effectuer la manipulation la plus simple sur une image d’entrée : le recadrage.

La tâche de cet article est de créer un effet Ken Burns. Il s'agit d'une technique de panoramique et de zoom qui porte le nom du cinéaste Ken Burns :

Au lieu d'afficher une grande photo statique à l'écran, l'effet Ken Burns recadre un détail, puis effectue un panoramique sur l'image.
— Wikipédia, « Effet Ken Burns »

Voyons comment créer l'effet Ken Burns dans le code Python à l'aide d'OpenCV. Nous commençons par une image, par exemple la photo d'oiseau ci-dessous que vous pouvez télécharger sur Wikipédia :

  • https://upload.wikimedia.org/wikipedia/commons/b/b7/Hooded_mountain_tanager_%28Buthraupis_montana_cucullata%29_Caldas.jpg

Cette image est en 4563×3042 pixels. Ouvrir cette image avec OpenCV est simple :

import cv2

imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"

img = cv2.imread(imgfile, cv2.IMREAD_COLOR)
cv2.imshow("bird", img)
cv2.waitKey(0)

L'image lue par OpenCV, img, est bien un tableau numpy de forme (3042, 4563, 3) et dans le type de données uint8 (entier non signé 8 bits) pour c'est une image colorée dont chaque pixel est représenté par des valeurs BGR comprises entre 0 et 255.

L'effet Ken Burns consiste à zoomer et à effectuer un panoramique. Chaque image de la vidéo est un recadrage de l'image originale (puis un zoom pour remplir l'écran). Recadrer l'image à partir d'un tableau numpy est facile, puisque numpy vous a déjà fourni la syntaxe de découpage :

cropped = img[y0:y1, x0:x1]

L'image est un tableau numpy tridimensionnel. Les deux premières dimensions concernent respectivement la hauteur et la largeur (de la même manière que pour définir les coordonnées d'une matrice). Par conséquent, vous pouvez utiliser la syntaxe de découpage numpy pour prendre les pixels $y_0$à $y_1$dans le sens vertical et les pixels $x_0$à $x_1$dans le sens horizontal (rappelez-vous que dans la matrice, les coordonnées sont numérotées de haut en bas et de de gauche à droite).

Recadrer une image signifie prendre une photo de dimension $W\times H$dans une dimension plus petite $W'\times H'$. Pour réaliser une vidéo, vous souhaitez créer des images d'une dimension fixe. La dimension recadrée $W'\times H'$devra être redimensionnée. De plus, pour éviter toute distorsion, l’image recadrée doit également avoir un rapport hauteur/largeur prédéfini.

Pour redimensionner une image, vous pouvez définir un nouveau tableau numpy, puis calculer et remplir les valeurs des pixels une par une. Il existe de nombreuses façons de calculer la valeur d'un pixel, par exemple en utilisant une interpolation linéaire ou simplement en copiant le pixel le plus proche. Si vous essayez d’implémenter l’opération de redimensionnement, vous ne trouverez pas cela difficile mais néanmoins assez fastidieux. Le moyen le plus simple consiste donc à utiliser la fonction native d’OpenCV, comme la suivante :

resized = cv2.resize(cropped, dsize=target_dim, interpolation=cv2.INTER_LINEAR)

La fonction cv2.resize() prend une image et la dimension cible sous forme de tuple de (largeur, hauteur) en taille de pixel et renvoie un nouveau tableau numpy. Vous pouvez spécifier l'algorithme de redimensionnement. Ce qui précède utilise une interpolation linéaire et cela semble bon dans la plupart des cas.

Ce sont essentiellement toutes les façons dont vous pouvez manipuler une image dans OpenCV, à savoir :

  • Manipulez directement le tableau numpy. Cela fonctionne bien pour les tâches simples où vous souhaitez travailler au niveau des pixels.
  • Utilisation des fonctions OpenCV. Ceci est plus adapté aux tâches complexes où vous devez considérer l'image entière ou où il est trop inefficace de manipuler chaque pixel.

Avec ceux-ci, vous pouvez créer votre animation Ken Burns. Le flux est le suivant :

  1. Étant donné une image (de préférence haute résolution), vous souhaitez définir le panoramique en spécifiant les coordonnées de mise au point de début et de fin. Vous souhaitez également définir le taux de zoom de début et de fin.
  2. Vous disposez d'une durée vidéo prédéfinie et du FPS (frame per second). Le nombre total d'images dans la vidéo est la durée multipliée par le FPS.
  3. Pour chaque image, calculez les coordonnées de recadrage. Redimensionnez ensuite l'image recadrée à la résolution cible de la vidéo
  4. Une fois toutes les images préparées, vous écrivez dans le fichier vidéo.

Commençons par les constantes : supposons que nous allons créer une vidéo 720p de deux secondes (résolution 1280×720) à 25 FPS (ce qui est assez faible mais visuellement acceptable). Le panoramique commencera au centre à 40 % de la gauche et à 60 % du haut de l'image, et se terminera au centre à 50 % de la gauche et 50 % du haut de l'image. Le zoom démarrera à partir de 70 % de l'image originale, puis dézoomera jusqu'à 100 %.

imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
video_dim = (1280, 720)
fps = 25
duration = 2.0
start_center = (0.4, 0.6)
end_center = (0.5, 0.5)
start_scale = 0.7
end_scale = 1.0

Vous allez recadrer l'image de nombreuses fois pour créer des frames (justement, il y a 2×25=50 frames). Il est donc avantageux de créer une fonction de recadrage :

def crop(img, x, y, w, h):
    x0, y0 = max(0, x-w//2), max(0, y-h//2)
    x1, y1 = x0+w, y0+h
    return img[y0:y1, x0:x1]

Cette fonction de recadrage prend une image, la position centrale provisoire en coordonnées de pixels, ainsi que la largeur et la hauteur en nombre de pixels. Le recadrage garantira qu'il ne commencera pas au-delà de la bordure de l'image, c'est pourquoi les deux fonctions max() sont utilisées. Le recadrage est effectué à l'aide de la syntaxe de découpage numpy.

Si vous considérez que le point temporel actuel se situe à $\alpha$% de la durée totale, vous pouvez utiliser la transformation affine pour calculer le niveau exact de zoom et la position du panoramique. En termes de position relative du centre du panoramique (en termes de pourcentage de largeur et de hauteur d'origine), la transformée affine donne

rx = end_center[0]*alpha + start_center[0]*(1-alpha)
ry = end_center[1]*alpha + start_center[1]*(1-alpha)

alpha est compris entre 0 et 1. De même, le niveau de zoom est

scale = end_scale*alpha + start_scale*(1-alpha)

Compte tenu de la taille de l'image originale et de l'échelle, vous pouvez calculer la taille de l'image recadrée par multiplication. Mais comme le rapport hauteur/largeur de l’image peut ne pas être le même que celui de la vidéo, vous devez ajuster la dimension recadrée pour l’adapter au rapport hauteur/largeur de la vidéo. Supposons que le tableau numpy d'image soit img et que le niveau de zoom soit scale calculé ci-dessus, la taille recadrée peut être calculée comme :

orig_shape = img.shape[:2]

if orig_shape[1]/orig_shape[0] > video_dim[0]/video_dim[1]:
    h = int(orig_shape[0]*scale)
    w = int(h * video_dim[0] / video_dim[1])
else:
    w = int(orig_shape[1]*scale)
    h = int(w * video_dim[1] / video_dim[0])

Ce qui précède consiste à comparer le rapport hauteur/largeur (largeur divisée par hauteur) entre l'image et la vidéo, et le niveau de zoom est utilisé pour le bord le plus limité et calcule l'autre bord en fonction du rapport hauteur/largeur cible.

Une fois que vous savez de combien d'images vous avez besoin, vous pouvez utiliser une boucle for pour créer chaque image avec un paramètre affine différent alpha, qui peut être obtenu à l'aide d'une fonction numpy linspace(). Le code complet est le suivant :

import cv2
import numpy as np

imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
video_dim = (1280, 720)
fps = 25
duration = 2.0
start_center = (0.4, 0.6)
end_center = (0.5, 0.5)
start_scale = 0.7
end_scale = 1.0

img = cv2.imread(imgfile, cv2.IMREAD_COLOR)
orig_shape = img.shape[:2]

def crop(img, x, y, w, h):
    x0, y0 = max(0, x-w//2), max(0, y-h//2)
    x1, y1 = x0+w, y0+h
    return img[y0:y1, x0:x1]

num_frames = int(fps * duration)
frames = []
for alpha in np.linspace(0, 1, num_frames):
    rx = end_center[0]*alpha + start_center[0]*(1-alpha)
    ry = end_center[1]*alpha + start_center[1]*(1-alpha)
    x = int(orig_shape[1]*rx)
    y = int(orig_shape[0]*ry)
    scale = end_scale*alpha + start_scale*(1-alpha)
    # determined how to crop based on the aspect ratio of width/height
    if orig_shape[1]/orig_shape[0] > video_dim[0]/video_dim[1]:
        h = int(orig_shape[0]*scale)
        w = int(h * video_dim[0] / video_dim[1])
    else:
        w = int(orig_shape[1]*scale)
        h = int(w * video_dim[1] / video_dim[0])
    # crop, scale to video size, and save the frame
    cropped = crop(img, x, y, w, h)
    scaled = cv2.resize(cropped, dsize=video_dim, interpolation=cv2.INTER_LINEAR)
    frames.append(scaled)

# write to MP4 file
vidwriter = cv2.VideoWriter("output.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, video_dim)
for frame in frames:
    vidwriter.write(frame)
vidwriter.release()

Les dernières lignes expliquent comment utiliser OpenCV pour écrire une vidéo. Vous créez un objet VideoWriter avec le FPS et la résolution spécifiés. Ensuite, vous écrivez les images une par une et relâchez l'objet pour fermer le fichier écrit.

La vidéo créée ressemble à celle-ci. Un aperçu est le suivant :

Aperçu de la vidéo créée. La visualisation de ceci nécessite un navigateur pris en charge.

Écrire une vidéo

À partir de l'exemple de la section précédente, vous avez vu comment créer un objet VideoWriter :

vidwriter = cv2.VideoWriter("output.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, video_dim)

Contrairement à la façon dont vous pouvez écrire un fichier image (tel que JPEG ou PNG), le format de la vidéo créée par OpenCV n'est pas déduit du nom de fichier. C'est le deuxième paramètre pour spécifier le format vidéo, à savoir le FourCC, qui est un code de quatre caractères. Vous pouvez trouver le code FourCC et le format vidéo correspondant dans la liste à l'URL suivante :

  • https://fourcc.org/codecs.php

Cependant, tous les codes FourCC ne peuvent pas être utilisés. C'est parce qu'OpenCV crée la vidéo à l'aide de l'outil FFmpeg. Vous pouvez trouver la liste des formats vidéo pris en charge à l'aide de la commande :

ffmpeg -codecs

Assurez-vous que la commande ffmpeg est la même que celle utilisée par OpenCV. Notez également que le résultat de la commande ci-dessus vous indique uniquement le format pris en charge par ffmpeg, pas le code FourCC correspondant. Vous devez rechercher le code ailleurs, par exemple à partir de l'URL mentionnée ci-dessus.

Pour vérifier si vous pouvez utiliser un code FourCC particulier, vous devez l'essayer et voir si OpenCV déclenche une exception :

try:
    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    writer = cv2.VideoWriter('temp.mkv', fourcc, 30, (640, 480))
    assert writer.isOpened()
    print("Supported")
except:
    print("Not supported")

Résumé

Dans cet article, vous avez appris à créer une vidéo dans OpenCV. La vidéo créée est construite à partir d'une séquence d'images (c'est-à-dire sans audio). Chaque image est une image de taille fixe. À titre d'exemple, vous avez appris à appliquer l'effet Ken Burns à une image, que vous avez notamment appliqué :

  • La technique de recadrage d'une image à l'aide de la syntaxe de découpage numpy
  • La technique de redimensionnement d'une image à l'aide des fonctions OpenCV
  • Utiliser la transformation affine pour calculer les paramètres de zoom et de panoramique et créer des images de la vidéo

Et enfin, vous écrivez les images dans un fichier vidéo à l'aide de l'objet VideoWriter dans OpenCV.

Articles connexes