Réaliser une photo-finish

Un fil X/Twitter de @Astro_Aure nous explique le principe de la photo-finish sportive. J'ai voulu recréer l'expérience avec une caméra et quelques lignes de code.

Je ne vais pas le paraphraser donc vous conseille de lire/aimer/partager ce fil qui m'a motivé à écrire cet article :

J'ai utilisé comme caméra un iPhone SE 2020 qui permet un enregistrement à haute cadence d'images (240fps) avec le mode "ralenti" et sur un trépieds pour avoir un cadre fixe, le tout orienté vers cette ligne d'arrivée simulée par une bande de barnier blanc. Nos athlètes sont symbolisés par 3 rouleaux de barnier 🇫🇷, ça fera bien la farce 🤡.

Traitement de la vidéo captée

Après avoir récupéré le rush, on le trim, sans perte de qualité et en retirant l'audio, pour ne garder que la partie utile.

1ffmpeg -i IMG_6168.MOV -ss 00:00:28 -t 00:00:02 -c copy -an finish.MOV

Quelques caractéristiques de la vidéo

 1% mediainfo finish.MOV
 2General
 3Complete name                            : finish.MOV
 4Format                                   : MPEG-4
 5Format profile                           : QuickTime
 6Codec ID                                 : qt   0000.02 (qt  )
 7File size                                : 9.99 MiB
 8Duration                                 : 2 s 14 ms
 9Overall bit rate                         : 41.6 Mb/s
10Frame rate                               : 240.191 FPS
11Writing application                      : Lavf61.1.100
12
13Video
14ID                                       : 1
15Format                                   : HEVC
16Format/Info                              : High Efficiency Video Coding
17Format profile                           : Main@L5.1@High
18Codec ID                                 : hvc1
19Codec ID/Info                            : High Efficiency Video Coding
20Duration                                 : 2 s 14 ms
21Source duration                          : 1 s 45 ms
22Bit rate                                 : 80.1 Mb/s
23Width                                    : 1 920 pixels
24Height                                   : 1 080 pixels
25Display aspect ratio                     : 16:9
26Frame rate mode                          : Variable
27Frame rate                               : 240.191 FPS
28Minimum frame rate                       : 240.000 FPS
29Maximum frame rate                       : 266.667 FPS
30Color space                              : YUV
31Chroma subsampling                       : 4:2:0
32Bit depth                                : 8 bits
33Bits/(Pixel*Frame)                       : 0.161
34Stream size                              : 9.98 MiB (100%)
35Source stream size                       : 9.98 MiB (100%)
36Title                                    : Core Media Video
37Color range                              : Limited
38Color primaries                          : BT.709
39Transfer characteristics                 : BT.709
40Matrix coefficients                      : BT.709
41mdhd_Duration                            : 1037
42Codec configuration box                  : hvcC

Pour plus de compatibilité (et reduire le débit), on va passer la vidéo de 240fps à 30fps, sans réduire la qualité. Pour celà l'astuce est de l'extraire de son conteneur et de regénérer le PTS.

1ffmpeg -i finish.MOV -map 0:v -c:v copy -bsf:v hevc_mp4toannexb finish.h265
2ffmpeg -fflags +genpts -r 30 -i finish.h265 -c:v copy finish-30.MOV
3rm finish.h265

Et on arrive à la vidéo du dessus.

Conversion en images successives

D'après le processus de génération, on doit traiter des images successives afin d'en extraire une colonne pour chaque.

1ffmpeg -i finish-30.MOV finish-%04d.png

Ce qui nous génère les 249 fichiers suivants :

1finish-0001.png
2finish-0002.png
3finish-0003.png
4...
5finish-0247.png
6finish-0248.png
7finish-0249.png

Chaque image est Full HD (1920x1080), on choisit d'extraire la colonne 955 qui correspond à peu près à la ligne blanche verticale d'arrivée.

Extraction de la colonne choisie

Chaque colonne ainsi extraite va représenter une colonne dans l'image finale. La colonne extraite de finish-0001.png sera la colonne la plus à droite de l'image finale, la colonne extraite de finish-0249.png sera la colonne la plus à gauche de l'image finale.

La captation étant à 240 fps, il y a 4.1667ms entre 2 photos. Chaque colonne de l'image finale représente donc 4.1667ms.

L'assemblage de la photo

Ce mini script de démonstration est écrit en PHP avec sa bibliothèque graphique GD, le principe est bien sûr adaptable avec votre langage préféré.

 1#!/usr/bin/env php
 2<?php
 3
 4$photos = glob(__DIR__ . '/*.png');
 5
 6// la largeur correspond au nombre de photos utiles captées
 7$width = count($photos);
 8$height = imagesy(imagecreatefrompng($photos[0]));
 9// la colonne à garder pour chaque photo
10$col = 955;
11
12// initialisation d'un tableau à 2 dimensions de 249x1080
13$pixels = array_fill(0, $width, array_fill(0, $height, 0));
14
15// sauvegarde dans le tableau des couleurs des pixels des colonnes
16foreach ($photos as $x => $photo) {
17    $current_photo = imagecreatefrompng($photo);
18    for ($y = 0; $y < $height; $y++) {
19        $rgb = imagecolorat($current_photo, $col, $y);
20        $pixels[$width - $x][$y] = $rgb;
21    }
22}
23
24// génération de la photo finale
25$photo_finish = imagecreatetruecolor($width, $height);
26for ($x = 0; $x < $width; $x++) {
27    for ($y = 0; $y < $height; $y++) {
28        $rgb = $pixels[$x][$y];
29        $r = ($rgb >> 16) & 0xFF;
30        $g = ($rgb >>  8) & 0xFF;
31        $b = ($rgb >>  0) & 0xFF;
32        $pixel = imagecolorallocate($photo_finish, $r, $g, $b);
33        imagesetpixel($photo_finish, $x, $y, $pixel);
34    }
35}
36
37imagepng($photo_finish, 'photo-finish.png');
38imagedestroy($photo_finish);

Le résultat

La photo-finish générée

Ressources

comments powered by Disqus