vendredi, décembre 1, 2023

Architecture de pipeline de vision par ordinateur : un didacticiel


La vidéo et toutes ses pièces mobiles peuvent être très difficiles à gérer pour un développeur. Un développeur expert compréhension approfondie des structures de donnéesles techniques d’encodage et le traitement de l’image et du signal jouent un rôle majeur dans les résultats de tâches de traitement vidéo quotidiennes prétendument simples, telles que compression ou édition.

Pour travailler efficacement avec du contenu vidéo, vous devez comprendre les propriétés et les distinctions entre ses formats de fichiers principaux (par exemple, .mp4, .mov, .wmv, .avi) et leurs codecs spécifiques à la version (par exemple, H.264, H.265). ,VP8,VP9). Les outils nécessaires à un traitement vidéo efficace sont rarement soigneusement regroupés sous forme de bibliothèques complètes, ce qui laisse au développeur le soin de naviguer dans le vaste et complexe écosystème d’outils open source pour fournir des applications de vision par ordinateur attrayantes.

Applications de vision par ordinateur expliquées

Les applications de vision par ordinateur reposent sur la mise en œuvre d’un large éventail de techniques, depuis les simples heuristiques jusqu’aux réseaux neuronaux complexes– par lequel nous transmettons une image ou une vidéo à un ordinateur en entrée et produisons une sortie significative, telle que :

  • Fonctionnalités de reconnaissance faciale dans les appareils photo des smartphones, utiles pour organiser et rechercher des albums photos et pour identifier des individus dans les applications de réseaux sociaux.
  • Détection des marquages ​​routiers, telle que mise en œuvre dans les voitures autonomes se déplaçant à grande vitesse.
  • Technologie de reconnaissance optique de caractères qui permet aux applications de recherche visuelle (comme Google Lens) de reconnaître la forme des caractères de texte dans les photos.

Les exemples précédents sont aussi différents que possible, chacun présentant une fonction entièrement distincte, mais partageant un simple point commun : Les images sont leur principal apport. Chaque application transforme des images ou des cadres non structurés, parfois chaotiques, en données intelligibles et ordonnées, ce qui profite aux utilisateurs finaux.

La taille compte : les défis courants liés au travail avec la vidéo

Un utilisateur final qui visionne une vidéo peut la considérer comme une seule entité. Mais un développeur doit l’aborder comme un ensemble de cadres individuels et séquentiels. Par exemple, avant qu’un ingénieur n’écrive un programme pour détecter les modèles de trafic en temps réel dans une vidéo de véhicules en mouvement, il doit d’abord extraire des images individuelles de cette vidéo, puis appliquer un algorithme qui détecte les voitures sur la route.

À l’état brut, un fichier vidéo est d’une taille énorme, ce qui le rend trop volumineux pour être stocké dans la mémoire d’un ordinateur, difficile à gérer pour le développeur, difficile à partager et coûteux à stocker. Une seule minute de vidéo brute non compressée à 60 images par seconde (ips) nécessite plus de 22 Go d’espace de stockage, par exemple :

60 secondes * 1080 px (hauteur) * 1920 px (largeur) * 3 octets par pixel * 60 fps = 22h39 FR

La vidéo est donc systématiquement compressée avant d’être traitée. Mais rien ne garantit qu’une image vidéo compressée individuelle affichera une image dans son intégralité. En effet, les paramètres appliqués au moment de la compression définissent la qualité et les détails que l’image individuelle d’une vidéo conservera. Même si la vidéo compressée, dans son ensemble, peut être suffisamment lue pour offrir une expérience visuelle formidable, ce n’est pas la même chose que les images individuelles qui la composent peuvent être interprétées comme des images complètes.

Dans ce didacticiel, nous utiliserons des outils de vision par ordinateur open source populaires pour résoudre certains défis fondamentaux du traitement vidéo. Cette expérience vous permettra de personnaliser un pipeline de vision par ordinateur en fonction de vos cas d’utilisation précis. (Pour simplifier les choses, nous ne décrirons pas les composants audio de la vidéo dans cet article.)

Un didacticiel simple sur une application de vision par ordinateur : calcul de la luminosité

Pour fournir une application de vision par ordinateur, un équipe d’ingénierie développe et met en œuvre un pipeline de vision par ordinateur efficace et puissant dont l’architecture comprend, au minimum :

Étape 1 : Acquisition d’images

Les images ou vidéos peuvent être acquises à partir d’un large éventail de sources, notamment des caméras ou des capteurs, des vidéos numériques enregistrées sur disque ou des vidéos diffusées sur Internet.

Étape 2 : Prétraitement de l’image

Le développeur choisit des opérations de prétraitement, telles que le débruitage, le redimensionnement ou la conversion dans un format plus accessible. Ceux-ci sont destinés à rendre les images plus faciles à utiliser ou à analyser.

Étape 3 : Extraction des fonctionnalités

Lors de l’étape de représentation ou d’extraction, les informations contenues dans les images ou trames prétraitées sont capturées. Ces informations peuvent consister par exemple en des bords, des coins ou des formes.

Étape 4 : Interprétation, analyse ou résultat

Dans la dernière étape, nous accomplissons la tâche à accomplir.

Imaginons que vous ayez été embauché pour créer un outil qui calcule la luminosité des images individuelles d’une vidéo. Nous alignerons l’architecture du pipeline du projet pour qu’elle corresponde au modèle simple de vision par ordinateur partagé ci-dessus.

Le programme que nous allons produire dans ce tutoriel a été inclus en tant que exemple au sein d’Hypetrigger, une bibliothèque Rust open source que j’ai développée. Hypetrigger comprend tout ce dont vous avez besoin pour exécuter un pipeline de vision par ordinateur sur du streaming vidéo à partir d’Internet : TensorFlow liaisons pour la reconnaissance d’images, Tesseract pour la reconnaissance optique des caractères et la prise en charge de l’utilisation du décodage vidéo accéléré par GPU pour une vitesse multipliée par 10. Pour installer, clonez le Dépôt Hypetrigger et exécutez la commande cargo add hypetrigger.

Pour maximiser l’apprentissage et l’expérience à acquérir, nous allons construire un pipeline de vision par ordinateur à partir de zéro dans ce didacticiel, plutôt que d’implémenter Hypetrigger convivial.

Notre pile technologique

Pour notre projet, nous utiliserons :

Outil

Description

Présenté comme l’un des meilleurs outils pour travailler avec la vidéo, FFmpeg— le couteau suisse de la vidéo — est une bibliothèque open source écrite en C et utilisée pour l’encodage, le décodage, la conversion et le streaming. Il est utilisé dans les logiciels d’entreprise comme Google Chrome, VLC Media Playeret Logiciel de diffusion ouvert (OBS), entre autres. FFmpeg est disponible en téléchargement en tant qu’outil de ligne de commande exécutable ou bibliothèque de code source, et peut être utilisé avec n’importe quel langage pouvant générer des processus enfants.

Une force majeure de Rust est sa capacité à détecter les erreurs de mémoire (par exemple, les pointeurs nuls, les erreurs de segmentation, les références pendantes) au moment de la compilation. Rust offre des performances élevées avec une sécurité de mémoire garantie, et est également très performant, ce qui en fait un bon choix pour le traitement vidéo.

Étape 1 : Acquisition d’images

Dans ce scénario, un acquis précédemment exemple de vidéo animée est prêt à être traité.

Étape 2 : Prétraitement de l’image

Pour ce projet, le prétraitement de l’image consiste à convertir la vidéo de son format encodé H.264 en format brut. RVBun format beaucoup plus facile à utiliser.

Décompressons notre vidéo à l’aide de l’outil de ligne de commande portable et exécutable de FFmpeg depuis un programme Rust. Le programme Rust ouvrira et convertira notre exemple de vidéo en RVB. Pour des résultats optimaux, nous ajouterons le Syntaxe FFmpeg au ffmpeg commande:

Argument*

Description

Cas d’utilisation

-i

Indique le nom de fichier ou l’URL de la vidéo source.

-f

Définit le format de sortie.

Le rawvideo format pour obtenir des images vidéo brutes

-pix_fmt

Définit le format des pixels.

rgb24 pour produire des canaux de couleur RVB avec huit bits par canal

-r

Définit la fréquence d’images de sortie.

1 produire une image par seconde

<output>

Indique à FFmpeg où envoyer la sortie ; c’est un argument final obligatoire.

*Pour une liste complète des arguments, entrez ffmpeg -help.

Ces arguments combinés en ligne de commande ou au terminal nous donnent ffmpeg -i input_video.mp4 -f rawvideo -pix_fmt rgb24 pipe:1 et nous servent de point de départ pour traiter les images de la vidéo :

use std::{
    io::{BufReader, Read},
    process::{Command, Stdio},
};

fn main() {
    // Test video provided by https://gist.github.com/jsturgis/3b19447b304616f18657.
    let test_video =
        "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";


    // Video is in RGB format; 3 bytes per pixel (1 red, 1 blue, 1 green).
    let bytes_per_pixel = 3;

    let video_width = 1280;
    let video_height = 720;

    // Create an FFmpeg command with the specified arguments.
    let mut ffmpeg = Command::new("ffmpeg")
        .arg("-i")
        .arg(test_video) // Specify the input video
        .arg("-f") // Specify the output format (raw RGB pixels)
        .arg("rawvideo")
        .arg("-pix_fmt")
        .arg("rgb24") // Specify the pixel format (RGB, 8 bits per channel)
        .arg("-r")
        .arg("1") // Request rate of 1 frame per second
        .arg("pipe:1") // Send output to the stdout pipe
        .stderr(Stdio::null())
        .stdout(Stdio::piped())
        .spawn() // Spawn the command process
        .unwrap(); // Unwrap the result (i.e., panic and exit if there was an error)
}

Notre programme recevra une image vidéo à la fois, chacune décodée en RVB brut. Pour éviter d’accumuler d’énormes volumes de données, allouons un tampon de la taille d’une image qui libérera de la mémoire à la fin du traitement de chaque image. Ajoutons également une boucle qui remplit le tampon avec les données du canal de sortie standard de FFmpeg :

fn main() {
    // …

    // Read the video output into a buffer.
    let stdout = ffmpeg.stdout.take().unwrap();
    let buf_size = video_width * video_height * bytes_per_pixel;
    let mut reader = BufReader::new(stdout);
    let mut buffer = vec![0u8; buf_size];
    let mut frame_num = 0;

    while let Ok(()) = reader.read_exact(buffer.as_mut_slice()) {
        // Retrieve each video frame as a vector of raw RGB pixels.
        let raw_rgb = buffer.clone();
    }
}

Notez que le while la boucle contient une référence à raw_rgbune variable qui contient une image RVB complète.

Pour calculer la luminosité moyenne de chaque image prétraitée à l’étape 2, ajoutons la fonction suivante à notre programme (soit avant, soit après la main méthode):

/// Calculate the average brightness of an image,
/// returned as a float between 0 and 1.
fn average_brightness(raw_rgb: Vec<u8>) -> f64 {
    let mut sum = 0.0;
    for (i, _) in raw_rgb.iter().enumerate().step_by(3) {
        let r = raw_rgb[i] as f64;
        let g = raw_rgb[i + 1] as f64;
        let b = raw_rgb[i + 2] as f64;
        let pixel_brightness = (r / 255.0 + g / 255.0 + b / 255.0) / 3.0;
        sum += pixel_brightness;
    }
    sum / (raw_rgb.len() as f64 / 3.0)
}

Puis, à la fin du while en boucle, nous pouvons calculer et imprimer la luminosité des cadres sur la console :

fn main() {
    // …

    while let Ok(()) = reader.read_exact(buffer.as_mut_slice()) {
        // Retrieve each video frame as a vector of raw RGB pixels.
        let raw_rgb = buffer.clone();

        // Calculate the average brightness of the frame.
        let brightness = average_brightness(raw_rgb);
        println!("frame {frame_num} has brightness {brightness}");
        frame_num += 1;
    }
}

Le code, à ce stade, correspondra à ceci fichier exemple.

Et maintenant, nous exécutons le programme sur notre exemple de vidéo pour produire le résultat suivant :

frame 0 has brightness 0.055048076377046
frame 1 has brightness 0.467577447011064
frame 2 has brightness 0.878193112575386
frame 3 has brightness 0.859071674156269
frame 4 has brightness 0.820603467400872
frame 5 has brightness 0.766673757205845
frame 6 has brightness 0.717223347005918
frame 7 has brightness 0.674823835783496
frame 8 has brightness 0.656084418402863
frame 9 has brightness 0.656437488652946
[500+ more frames omitted]

Étape 4 : Interprétation

Voici une représentation graphique de ces nombres :

Une représentation graphique des niveaux de luminosité de notre exemple de vidéo de 0 à 10 minutes.
Niveau de luminosité moyen au fil du temps

Dans le graphique précédent, notez la ligne tracée qui représente la luminosité de notre vidéo. Ses pics et vallées nets représentent les transitions spectaculaires de luminosité qui se produisent entre les images consécutives. La luminosité de l’image 0, représentée à l’extrême gauche du graphique, mesure 5 % (c’est-à-dire assez sombre) et culmine brusquement à 87 % (c’est-à-dire remarquablement lumineux), seulement deux images plus tard. Des transitions tout aussi importantes se produisent vers 5h00, 8h00 et 9h40 après le début de la vidéo. Dans ce cas, de telles variations intenses de luminosité représentent des transitions normales de scènes de film, comme le montre la vidéo.

Cas d’utilisation réels pour calculer la luminosité

Dans le monde réel, nous continuerions probablement à analyser les niveaux de luminosité détectés et, sous condition, déclencherions une action. Dans un véritable traitement de postproduction, le cinéaste, le vidéaste ou le monteur vidéo analyserait ces données et conserverait toutes les images dont les valeurs de luminosité se situent dans la plage convenue du projet. Alternativement, un professionnel peut extraire et examiner les images dont les valeurs de luminosité sont incertaines et peut finalement approuver, restituer ou exclure des images individuelles de la sortie finale de la vidéo.

Un autre cas d’utilisation intéressant pour analyser la luminosité d’un cadre peut être illustré en considérant un scénario impliquant des images de caméras de sécurité provenant d’un immeuble de bureaux. En comparant les niveaux de luminosité des cadres aux enregistrements d’entrées/sorties du bâtiment, nous pouvons déterminer si la dernière personne à partir éteint réellement les lumières comme elle est censée le faire. Si notre analyse indique que les lumières restent allumées une fois que tout le monde est parti pour la journée, nous pourrions envoyer des rappels encourageant les gens à éteindre les lumières lorsqu’ils partent afin d’économiser de l’énergie.

Ce didacticiel détaille certains traitements de base de la vision par ordinateur et jette les bases de techniques plus avancées, telles que la représentation graphique de plusieurs caractéristiques de la vidéo d’entrée pour les corréler en utilisant davantage de données. mesures statistiques avancées. Une telle analyse marque un passage du monde de la vidéo au domaine de l’inférence statistique et de l’apprentissage automatique, l’essence de la vision par ordinateur.

En suivant les étapes décrites dans ce didacticiel et en tirant parti des outils présentés, vous pouvez minimiser les obstacles (fichiers volumineux ou formats vidéo compliqués) que nous associons généralement à la décompression vidéo et à l’interprétation des pixels RVB. Et lorsque vous aurez simplifié le travail avec la vidéo et la vision par ordinateur, vous pourrez mieux vous concentrer sur ce qui compte : fournir des solutions intelligentes et robustes. capacités vidéo dans vos candidatures.


L’équipe éditoriale du Toptal Engineering Blog exprime sa gratitude à Martin Goldberg pour examiner les exemples de code et autres contenus techniques présentés dans cet article.

Related Articles

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici

Latest Articles