Utilisation du code généré dans un programme de robot

GRIP génère une classe qui peut être ajoutée à un programme FRC® qui fonctionne sur un roboRIO et sans beaucoup de code supplémentaire, permet de conduire le robot en fonction de la sortie.

Voici un exemple de programme complet qui utilise un pipeline GRIP qui dirige un robot vers un morceau de matériau rétroréfléchissant.

Ce programme est conçu pour illustrer le fonctionnement du code de vision et ne représente pas nécessairement la meilleure technique pour écrire votre programme de robot. Lorsque vous écrivez votre propre programme, tenez compte des considérations suivantes:

  1. L’utilisation de la sortie de la caméra pour diriger le robot peut être problématique. Dans cet exemple, le code de la caméra qui capture et traite les images s’exécute à un rythme beaucoup trop lent de ce qui est souhaitable pour diriger le robot à l’aide d’une boucle d’asservissement. Une meilleure solution, légèrement plus complexe, consiste à obtenir la direction (Headings) relatif au robot à partir de la position de la caméra sur le robot. Ensuite, rafraîchir cette direction à la fréquence de traitement d’image. Finalement, utiliser un gyroscope comme senseur pour compléter la boucle de contrôle.

  2. Gardez le code de vision dans la classe qui enveloppe le pipeline. Une meilleure façon d’écrire du code orienté objet est de sous-classer ou d’instancier la classe de pipeline générée et d’y traiter les résultats OpenCV plutôt que dans le programme du robot. Dans cet exemple, le code du robot détermine la direction ou cap du robot en manipulant les contours OpenCV résultants. En ayant le code OpenCV éparpillé tout au long du programme de robot, il est difficile de changer l’algorithme de vision si vous en avez un meilleur.

Définitions de programme itératives

package org.usfirst.frc.team190.robot;

import org.usfirst.frc.team190.grip.MyVisionPipeline;

import org.opencv.core.Rect;
import org.opencv.imgproc.Imgproc;

import edu.wpi.cscore.UsbCamera;
import edu.wpi.first.cameraserver.CameraServer;
import edu.wpi.first.wpilibj.RobotDrive;
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.vision.VisionRunner;
import edu.wpi.first.wpilibj.vision.VisionThread;

public class Robot extends TimedRobot {

    private static final int IMG_WIDTH = 320;
    private static final int IMG_HEIGHT = 240;

    private VisionThread visionThread;
    private double centerX = 0.0;
    private RobotDrive drive;

    private final Object imgLock = new Object();

Dans cette première partie du programme, vous pouvez voir toutes les instructions d’importation pour les classes WPILib utilisées pour ce programme.

  • La largeur et la hauteur de l’image sont définies comme 320x240 pixels.

  • Le VisionThread est une classe WPILib qui facilite le traitement de votre caméra dans un thread séparé du reste du programme du robot.

  • centerX value sera la valeur X centrale calculée de la cible détectée.

  • RobotDrive encapsule les 4 moteurs de ce robot et permet une conduite simplifiée.

  • imgLock est une variable pour synchroniser l’accès aux données mises à jour simultanément avec chaque passe d’acquisition d’image et le code qui traite les coordonnées et dirige le robot.

@Override
public void robotInit() {
    UsbCamera camera = CameraServer.getInstance().startAutomaticCapture();
    camera.setResolution(IMG_WIDTH, IMG_HEIGHT);

    visionThread = new VisionThread(camera, new MyVisionPipeline(), pipeline -> {
        if (!pipeline.filterContoursOutput().isEmpty()) {
            Rect r = Imgproc.boundingRect(pipeline.filterContoursOutput().get(0));
            synchronized (imgLock) {
                centerX = r.x + (r.width / 2);
            }
        }
    });
    visionThread.start();

    drive = new RobotDrive(1, 2);
}

La méthode robotInit() est appelée une fois au démarrage du programme. Il crée une instance de CameraServer qui commence à capturer des images à la résolution demandée (IMG_WIDTH par IMG_HEIGHT).

Ensuite, une instance de la classe VisionThread est créée. VisionThread commence à capturer des images de la caméra de manière asynchrone dans un thread séparé. Après le traitement de chaque image, le pipeline calculé boîte englobante autour de la cible est récupéré et sa valeur centre X est calculée. Cette valeur centerX sera la valeur x pixels du centre du rectangle dans l’image.

Le VisionThread prend également une instance VisionPipeline (ici, nous avons une sous-classe MyVisionPipeline générée par GRIP) ainsi qu’un rappel que nous utilisons pour gérer la sortie du pipeline. Dans cet exemple, le pipeline génère une liste de contours (contours de zones dans une image) qui marquent des objectifs ou des cibles d’une certaine sorte. Le rappel trouve la boîte englobante du premier contour afin de trouver son centre, puis enregistre cette valeur dans la variable « centerX ». Notez le bloc synchronisé autour de l’affectation: cela garantit que le thread du robot principal aura toujours la valeur à jour de la variable, tant qu’il utilise également des blocs synchronisés pour lire la variable.

@Override
public void autonomousPeriodic() {
    double centerX;
    synchronized (imgLock) {
        centerX = this.centerX;
    }
    double turn = centerX - (IMG_WIDTH / 2);
    drive.arcadeDrive(-0.6, turn * 0.005);
}

Ceci, la dernière partie du programme, est appelé à plusieurs reprises pendant la période autonome du match. Il obtient la valeur centerX pixel de la cible et soustrait la moitié de la largeur de l’image pour la changer en une valeur qui est zéro lorsque le rectangle est centré dans l’image et positif ou négatif lorsque le centre de la cible est sur le côté gauche ou droit du cadre. Cette valeur est utilisée pour diriger le robot vers la cible.

Notez le bloc synchronisé au début. Ce dernier conserve la valeur la plus récente de centerX trouvée par VisionThread.