PYTHON
ANALYSE D'UNE VIDEO

Défi à relever

La vidéo ci-dessous montre un saut effectué avec un skateboard. On souhaite déterminer la hauteur de ce saut. Elle est égale à l'altitude maximale atteinte par la roue arrière, la roue avant étant levée en premier.

Le défi consiste à écrire un programme en Python qui analyse automatiquement la vidéo. Il s'agit de tracer une courbe donnant la position de la roue arrière en fonction du temps, permettant ainsi de connaître la hauteur du saut. Le travail à effectuer s'appuie sur les données suivantes :

Mise en œuvre

Le programme en Python exploitera les bibliothèques :

Retour vers le futur

En fait, nous verrons que la vidéo se décompose en 76 photos, nommées "image_0.png" à "image_75.png". La roue arrière reste constamment éclairée par le soleil, au moins en partie, sauf pour la trente-septième photo où elle passe complètement dans l'ombre.


image_35.png

image_36.png

image_37.png

La photo "image_36.png" est donc problématique car la roue passe dans l'ombre. Sa couleur change et se distingue difficilement de celle des feuilles d'arbres, en haut à gauche de la vidéo. La position de la roue arrière pour cette photo en particulier est difficile à identifier. Des solutions pourraient être mises en œuvre, mais elles complexifieraient le programme.

Test d'OpenCV

OpenCV inverse les couleurs, en indiquant d'abord la quantité de bleu, puis la quantité de vert, enfin la quantité de rouge. La mise en œuvre d'une image avec OpenCV puis son affichage, directement avec Matplotlib, engendre une inversion des couleurs rouge et bleu. Il faut donc enregistrer l'image puis la rouvrir pour l'afficher correctement.

Exemple de code :

#!/usr/bin/python3

import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

# Image noire de 4 pixels en largeur par 3 pixels en hauteur
img=np.zeros((3,4,3))

# Image entièrement verte
for x in range(0,4):
  for y in range(0,3):
    img[y][x]=[0,255,0]

# Ajout de trois points rouges
img[0][0]=[0,0,255]
img[1][3]=[0,0,255]
img[2][1]=[0,0,255]

# Enregistrement de l'image
cv2.imwrite("dessin.png",img)

# Visualisation de l'image
voir=Image.open("dessin.png")
plt.imshow(voir)
plt.show()

Résultat :

Test de numpy

Le Python étant un langage interprété, il est relativement lent. Faire des boucles sur les trois couleurs primaires de chaque pixel pour toutes les photos de la vidéo prendrait un temps considérable. On exploite donc les possibilités de la bibliothèque numpy pour éviter ces boucles.

En reprenant l'image précédente :

#!/usr/bin/python3

import cv2
import numpy as np

image=cv2.imread('dessin.png')
couleur_cherchee = np.where((image[...,0] == 0) & (image[...,1] == 0) & (image[...,2] == 255) )

print(image.shape)
print()
print(image)
print()
print(couleur_cherchee)

Résultat :

(3, 4, 3)

[[[  0   0 255]
  [  0 255   0]
  [  0 255   0]
  [  0 255   0]]

 [[  0 255   0]
  [  0 255   0]
  [  0 255   0]
  [  0   0 255]]

 [[  0 255   0]
  [  0   0 255]
  [  0 255   0]
  [  0 255   0]]]

(array([0, 1, 2]), array([0, 3, 1]))

Code final, résultat

Le code ci-dessous doit être placé dans un dossier comportant :

#!/usr/bin/python3

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Vidéo à traiter
source="source.mp4"

# Intervalles des couleurs pour le vert fluo
rouge_M=234 ; rouge_m=120 ; vert_M=234 ; vert_m=170 ; bleu_M=46 ; bleu_m=12

# 1 cm pour 10 px
echelle=10.0

# Caractéristiques de la vidéo
video=cv2.VideoCapture(source)
nb_im=int(video.get(cv2.CAP_PROP_FRAME_COUNT))
h=int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
l=int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
fps=int(video.get(cv2.CAP_PROP_FPS))
duree=float(nb_im)/float(fps)

# Images de la vidéo placées dans le dossier dossier_images
for num_img in range(nb_im):
  ret,frame=video.read()
  nom_image="dossier_images/image_"+str(num_img)+".png"
  cv2.imwrite(nom_image, frame)
video.release()

# Traitement des images
tableau_hauteur_pixels=[]
for num_img in range(nb_im) :
  image=cv2.imread('dossier_images/image_'+str(num_img)+'.png')
  couleur_cherchee = np.where((image[...,2]<=rouge_M) & (image[...,2]>=rouge_m) & (image[...,1]<=vert_M) & (image[...,1]>=vert_m) & (image[...,0]<=bleu_M) & (image[...,0]>=bleu_m))
  hauteur_pixel_mini=h
  for i in range(len(couleur_cherchee[0])) :
    if (couleur_cherchee[0][i]<hauteur_pixel_mini) :
      hauteur_pixel_mini=couleur_cherchee[0][i]
  tableau_hauteur_pixels.append(hauteur_pixel_mini)

# Tracé de la courbe
x=np.linspace(0,duree,nb_im)
y=(h-np.array(tableau_hauteur_pixels))/echelle
plt.figure("Altitude de la roue en fonction du temps")
plt.xlabel("Temps (s)")
plt.ylabel("Hauteur (cm)")
plt.plot(x, y, marker='o', color='#00ff00')
plt.show()

Résultat :

Conclusion

La courbe obtenue comporte trois phases :

La mesure serait plus facile et plus précise si on fixait sur le skateboard une diode électroluminescente, rouge par exemple :