Lire et traiter la vidéo: classe CameraServer

Concepts

Les caméras généralement utilisées en FRC® (les caméras USB et Ethernet de base telles que la caméra Axis) offrent des modes de fonctionnement relativement limités. En général, elles ne fournissent qu’une seule sortie d’image (généralement dans un format compressé RGB tel que JPG) à une seule résolution et taux d’image. Les caméras USB sont particulièrement limitées car une seule application peut accéder à la caméra à la fois.

CameraServer peut prendre en charge plusieurs caméras. Il gère des détails tels que la reconnexion automatique lorsqu’une caméra est déconnectée momentanément, et met également les images de la caméra à la disposition de plusieurs « clients » (par exemple, votre code de robot et le tableau de bord peuvent lire les images de la caméra même simultanément).

Nommer les caméras

Chaque caméra dans CameraServer doit porter un nom unique. C’est également le nom qui apparaît pour la caméra dans le tableau de bord. Certaines variantes des fonctions CameraServer startAutomaticCapture() et addAxisCamera() nommeront automatiquement la caméra (par exemple « USB Camera 0 » ou « Axis Camera »), ou vous pouvez donner à la caméra un nom plus descriptif (par exemple « Cam d’admission »). La seule exigence est que chaque caméra ait un nom unique.

Remarques sur la caméra USB

L’utilisation du processeur

Le CameraServer est conçu pour minimiser l’utilisation du processeur en effectuant uniquement des opérations de compression et de décompression lorsque cela est nécessaire et en désactivant automatiquement le streaming lorsqu’aucun client n’est connecté.

Pour minimiser l’utilisation du processeur, la résolution du tableau de bord doit être réglée sur la même résolution que la caméra; cela permet au CameraServer de ne pas décompresser et recompresser l’image, au lieu de cela, il peut simplement transmettre l’image JPEG reçue de la caméra directement au tableau de bord. Il est important de noter que la modification de la résolution sur le tableau de bord ne change pas la résolution de la caméra; changer la résolution de la caméra peut être fait en appelant setResolution() sur l’objet caméra.

Bande passante USB

Le roboRIO ne peut transmettre et recevoir autant de données à la fois via ses interfaces USB. Les images de la caméra peuvent nécessiter beaucoup de données, et il est donc relativement facile de dépasser cette limite. La cause plus fréquente d’une erreur de bande passante USB est le choix d’un mode vidéo non JPEG ou une résolution trop élevée, en particulier lorsque plusieurs caméras sont connectées.

Architecture

Le CameraServer se compose de deux couches, la classe haut-niveau WPILib CameraServer class et la librairie de bas-niveau cscore library.

Classe CameraServer

La classe CameraServer (qui fait partie de WPILib) fournit une interface de haut niveau pour ajouter des caméras à votre code de robot. Elle est également responsable de la publication d’informations sur les caméras et les serveurs de caméras sur NetworkTables afin que les tableaux de bord de Driver Station tels que LabVIEW Dashboard et Shuffleboard puissent répertorier les caméras et déterminer l’emplacement de leurs flux. La classe utilise un modèle de type « singleton » (possédant un seul objet à la fois) pour maintenir une base de données de toutes les caméras et tous les serveurs créés.

Certaines fonctions importantes de CameraServer:

  • startAutomaticCapture(): ajoute une caméra USB (par exemple Microsoft LifeCam) et lance un serveur pour qu’elle puisse être visualisée à partir du tableau de bord.

  • addAxisCamera(): Ajoute une caméra Axis. Même si vous ne traitez pas les images de la caméra Axis dans votre code de robot, vous pouvez utiliser cette fonction pour que la caméra Axis apparaisse dans la liste déroulante des caméras du tableau de bord. Ceci démarre également un serveur afin que le flux Axis puisse toujours être affiché lorsque votre poste de pilotage est connecté au roboRIO via USB (utile en compétition si vous avez à la fois la caméra Axis et le roboRIO connectés aux deux ports Ethernet radio du robot).

  • getVideo(): Obtenez l’accès OpenCV à une caméra. Cela vous permet d’obtenir des images de la caméra pour le traitement d’images sur le roboRIO (dans votre code de robot).

  • putVideo(): démarrez un serveur sur lequel vous pouvez fournir des OpenCV à ce serveur. Cela vous permet de passer des images traitées et/ou annotées personnalisées au tableau de bord.

Librairie cscore

La librairie cscore fournit l’implémentation de niveau bas pour:

  • Obtenir des images de caméras USB et HTTP (par exemple Axis)

  • Modifier les paramètres de l’appareil photo (par exemple le contraste et la luminosité)

  • Changer les modes vidéo de la caméra (format pixel, résolution et fréquence d’images)

  • Agir en tant que serveur Web et fournir des images en tant que flux MJPEG standard

  • Convertir des images vers/depuis des objets Mat OpenCV pour le traitement d’images

Sources et Récepteurs

L’architecture de base de la bibliothèque cscore est similaire à celle de MJPGStreamer, avec des fonctionnalités réparties entre les sources et les récepteurs. Il peut y avoir plusieurs Sources et plusieurs Récepteurs créés et fonctionnant simultanément.

Un objet qui génère des images est une Source et un objet qui accepte/consomme des images est un Récepteur. La génération/consommation est du point de vue de la librairie. Les caméras sont donc des Sources (elles génèrent des images). Le serveur Web MJPEG est un Récepteur, car il accepte des images à partir du programme (même s’il peut transférer ces images vers un navigateur Web ou un tableau de bord). Les Sources peuvent être connectées à plusieurs Récepteurs, mais les Récepteurs peuvent être connectés à une seule et unique Source. Lorsqu’un Récepteur est connecté à une Source, la librairie cscore se charge de transmettre chaque image de la Source au Récepteur.

  • Les Sources obtiennent des images individuelles (comme celles fournies par une caméra USB) et déclenchent un événement lorsqu’une nouvelle image est disponible. Si aucun Récepteur n’écoute une Source particulière, la librairie peut s’arrêter ou se déconnecter d’une Source pour économiser le processeur et les ressources d’Entrées/Sorties. La librairie gère de manière autonome les déconnexions/reconnexions de la caméra en interrompant simplement et en reprenant le déclenchement des événements (par exemple, une déconnexion arrête l’envoie de nouvelles trames, mais ne génère pas d’erreur).

  • Les Récepteurs écoutent l’événement d’une Source particulière, récupérent la dernière image et la transfère à sa destination dans le format approprié. De même que pour les Sources, si un Récepteur particulier est inactif (par exemple, aucun client n’est connecté à un serveur MJPEG configuré sur HTTP), la librairie peut désactiver des parties de son traitement pour économiser les ressources du processeur.

Le code utilisateur (tel que celui utilisé dans un programme de robot FRC) peut agir soit comme Source (fournissant des images traitées comme s’il s’agissait d’une caméra), soit comme Récepteur (recevant une image pour traitement) via les objets Source et Récepteur OpenCV. Ainsi, un pipeline de traitement d’image qui récupère les images d’une caméra et sert les images traitées ressemble au graphique ci-dessous:

Schéma-bloc montrant qu’un programme peut soit recevoir, soit s’approvisionner à partir d’OpenCV.

Étant donné que les Sources peuvent avoir plusieurs Récepteurs connectés, le pipeline peut se ramifier. Par exemple, l’image d’origine de la caméra peut également être servie en connectant la Source UsbCamera à un deuxième Récepteur MjpegServer en plus du récepteur CvSink, ce qui donne le graphique ci-dessous:

Schéma-bloc de plusieurs récepteurs.

Lorsqu’une nouvelle image est capturée par la caméra, le CvSink et le MjpegServer [1] la reçoivent.

Le graphique ci-dessus est crée par le bout de code CameraServer ci-dessous:

import edu.wpi.first.cameraserver.CameraServer;
import edu.wpi.cscore.CvSink;
import edu.wpi.cscore.CvSource;

// Creates UsbCamera and MjpegServer [1] and connects them
CameraServer.startAutomaticCapture();

// Creates the CvSink and connects it to the UsbCamera
CvSink cvSink = CameraServer.getVideo();

// Creates the CvSource and MjpegServer [2] and connects them
CvSource outputStream = CameraServer.putVideo("Blur", 640, 480);
#include "cameraserver/CameraServer.h"

// Creates UsbCamera and MjpegServer [1] and connects them
frc::CameraServer::StartAutomaticCapture();

// Creates the CvSink and connects it to the UsbCamera
cs::CvSink cvSink = frc::CameraServer::GetVideo();

// Creates the CvSource and MjpegServer [2] and connects them
cs::CvSource outputStream = frc::CameraServer::PutVideo("Blur", 640, 480);

L’implémentation de CameraServer effectue les opérations suivantes au niveau cscore: CameraServer prend en charge de nombreux détails tels que la création de noms uniques pour tous les objets cscore et la sélection automatique des numéros de port. CameraServer conserve également un registre de type « singleton » des objets créés afin qu’ils ne soient pas détruits s’ils sortent de leur cadre, ou champ.

import edu.wpi.cscore.CvSink;
import edu.wpi.cscore.CvSource;
import edu.wpi.cscore.MjpegServer;
import edu.wpi.cscore.UsbCamera;

// Creates UsbCamera and MjpegServer [1] and connects them
UsbCamera usbCamera = new UsbCamera("USB Camera 0", 0);
MjpegServer mjpegServer1 = new MjpegServer("serve_USB Camera 0", 1181);
mjpegServer1.setSource(usbCamera);

// Creates the CvSink and connects it to the UsbCamera
CvSink cvSink = new CvSink("opencv_USB Camera 0");
cvSink.setSource(usbCamera);

// Creates the CvSource and MjpegServer [2] and connects them
CvSource outputStream = new CvSource("Blur", PixelFormat.kMJPEG, 640, 480, 30);
MjpegServer mjpegServer2 = new MjpegServer("serve_Blur", 1182);
mjpegServer2.setSource(outputStream);
#include "cscore_oo.h"

// Creates UsbCamera and MjpegServer [1] and connects them
cs::UsbCamera usbCamera("USB Camera 0", 0);
cs::MjpegServer mjpegServer1("serve_USB Camera 0", 1181);
mjpegServer1.SetSource(usbCamera);

// Creates the CvSink and connects it to the UsbCamera
cs::CvSink cvSink("opencv_USB Camera 0");
cvSink.SetSource(usbCamera);

// Creates the CvSource and MjpegServer [2] and connects them
cs::CvSource outputStream("Blur", cs::PixelFormat::kMJPEG, 640, 480, 30);
cs::MjpegServer mjpegServer2("serve_Blur", 1182);
mjpegServer2.SetSource(outputStream);

Comptage de références

Tous les objets cscore sont comptés en interne. La connexion d’un Récepteur à une Source augmente le compte de références de la Source, il est donc strictement nécessaire de garder le Récepteur dans sa portée. La classe CameraServer conserve un registre de tous les objets créés avec les fonctions CameraServer, de sorte que les Sources et les Récepteurs créés de cette manière ne sortent jamais de leur portée (sauf s’ils sont explicitement supprimés).