Usar el código generado en un programa de robot

GRIP genera una clase que puede ser añadida a un programa FRC® que se ejecuta en una roboRIO y sin mucho código adicional, conduce el robot según la salida.

Aquí se incluye un programa de muestra completo que utiliza una tubería GRIP que conduce a un robot hacia un trozo de material retrorreflectante.

Este programa está diseñado para ilustrar cómo funciona el código de visión y no representa necesariamente la mejor técnica para escribir su programa de robot. Cuando escriba su propio programa, tenga en cuenta las siguientes consideraciones:

  1. Utilizar la salida de la cámara para dirigir el robot podría ser problemático. El código de la cámara en este ejemplo que captura y procesa las imágenes funciona a una velocidad mucho más lenta que la deseable para un bucle de control para dirigir el robot. Una mejor solución, y sólo ligeramente más compleja, es obtener los encabezados de la cámara y su tasa de procesamiento, y luego tener un bucle de control mucho más rápido para dirigir esos encabezados usando un sensor giroscópico.

  2. Mantenga el código de visión en la clase que envuelve la pipeline/tubería. Una mejor manera de escribir código orientado a objetos es subclasificar o instanciar la clase de tubería generada y procesar los resultados de OpenCV allí en vez de en el programa de robot. En este ejemplo, el código del robot extrae la dirección a conducir manipulando los contornos de OpenCV resultantes. Al tener el código de OpenCV expuesto a lo largo del programa del robot hace difícil cambiar el algoritmo de visión en caso de tener uno mejor.

Definiciones de programas iterativos

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.drive.DifferentialDrive;
import edu.wpi.first.wpilibj.PWMSparkMax;
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.vision.VisionRunner;
import edu.wpi.first.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 DifferentialDrive drive;
    private PWMSparkMax left;
    private PWMSparkMax right;

    private final Object imgLock = new Object();

En esta primera parte del programa se pueden ver todas las declaraciones de importación de las clases WPILib utilizadas para este programa.

  • El ancho y alto de la imagen se definen como 320x240 píxeles.

  • El VisionThread es una clase de WPILib que facilita el procesamiento de la cámara en un hilo separado del resto del programa del robot.

  • El valor centerX será calculado del valor centro X del objetivo detectado.

  • DifferentialDrive encapsula los motores de accionamiento de este robot y permite una conducción simplificada.

  • imgLock es una variable para sincronizar el acceso a los datos que se actualizan simultáneamente con cada pasada de adquisición de imágenes y el código que está procesando las coordenadas y dirigiendo el robot.

@Override
public void robotInit() {
    UsbCamera camera = CameraServer.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();

    left = new PWMSparkMax(0);
    right = new PWMSparkMax(1);
    drive = new DifferentialDrive(left, right);
}

El método robotInit() se llama una vez cuando el programa se inicia. Crea una instancia CameraServer que comienza a capturar imágenes con la resolución solicitada (IMG_WIDTH por IMG_HEIGHT).

A continuación se crea una instancia de la clase VisionThread. VisionThread comienza a capturar imágenes de la cámara asincrónicamente en un hilo separado. Después de procesar cada imagen, se recupera el conducto calculado bounding box alrededor del objetivo y se calcula su valor center X. Este valor centerX será el valor de x píxeles del centro del rectángulo en la imagen.

El VisionThread también toma una instancia de VisionPipeline (aquí, tenemos una subclase MyVisionPipeline generada por el GRIP) así como una llamada de retorno que usamos para manejar la salida de la tubería/pipeline. En este ejemplo, la tubería produce una lista de contornos (contornos de áreas en una imagen) que marcan objetivos o metas de algún tipo. La llamada de retorno encuentra el cuadro delimitador del primer contorno para encontrar su centro, luego guarda ese valor en la variable centerX. Observe el bloque sincronizado alrededor de la asignación: esto asegura que el hilo principal del robot siempre tendrá el valor más actualizado de la variable, siempre y cuando también utilice bloques sincronizados para leer 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);
}

Esta, la parte final del programa, se llama repetidamente durante el período autónomo del partido. Obtiene el valor de centerX píxeles del objetivo y sustrae la mitad del ancho de la imagen para cambiarlo a un valor que es cero cuando el rectángulo está centrado en la imagen y positivo o negativo cuando el centro del objetivo está a la izquierda o a la derecha del cuadro. Ese valor se utiliza para dirigir el robot hacia el objetivo.

Fíjese en el bloque sincronizado al principio. Esto toma una instantánea del valor centerX más reciente encontrado por el VisionThread.