Skip to content
CrocoDocs
Starter GuideProgrammingEngineeringDesignPortfolio
Documentation

Introduction

IntroductionBIOBUZZ game overview

Getting started

Getting startedBlocksOnBot JavaAndroid StudioYour first OpModeJava basicsMotors and servosNonblocking codeDebugging robot code

Control systems

Control systemsJoystick mappingField-centric mecanum drivePID controlFeedforwardFlywheel velocity controlSystem identificationMotion profilingKalman filterLow-pass filter

Autonomous

AutonomousTimed and encoder movementOdometryLocalization troubleshootingMotion planningReliable autonomous sequencesPure PursuitSensor fusionAuto to TeleOp handoff

Season topics

Season topicsProjectile mathematicsTurret aimingShooting on the move

Code practices

Code practicesNaming conventionsCode organizationLoop timing and bulk readsFormattingComments and documentationTeam collaboration

Libraries

LibrariesNextFTCFTCLibSolversLibPedro PathingRoad RunnerFTC DashboardMercurial v2Mercurial v1PanelsSloth

Community software

Community softwareBlaze FTCOpenLoopHive Vision

Sensors and vision

Vision overviewVision basicsLimelight 3A
VisionPortal
Color blob locator
AprilTag detection
Starter Guide
Engineering
Design
Portfolio
Appearance
VisionPortal

VisionPortal

Open an FTC camera, attach a processor, and check the preview

VisionPortal is the SDK's camera manager. It opens a webcam or supported camera, shows a preview, and sends frames to one or more processors. An AprilTag processor or Color Locator processor examines those frames and returns measurements for your .

A real Driver Hub camera preview in which the FTC AprilTag processor labels two detected BIOBUZZ AprilTag clusters.

FTC Docs example image. The labels are detection overlays; your own OpMode still needs to read and check detection data before the acts.

Setup order

The setup has three jobs, in this order:

  1. Create a processor. Choose what the camera should detect.
  2. Build a VisionPortal. Tell it which configured camera to open, then attach the processor.
  3. Read the processor result. Use the values in your OpMode only after confirming the preview and measurements.

The processor is not the camera, and creating a processor alone does not start a video stream.

Processor setup

For a first AprilTag preview, the FTC SDK offers a convenience method:

AprilTagProcessor aprilTag = AprilTagProcessor.easyCreateWithDefaults();
val aprilTag = AprilTagProcessor.easyCreateWithDefaults()

This makes the processor with its default options. Use the Builder API when you need to configure options such as overlays or a tag library. The matching FTC sample shows the exact options available in your installed SDK.

Portal setup

This example assumes a webcam whose Robot Controller configuration name is Webcam 1:

VisionPortal portal = new VisionPortal.Builder()
    .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"))
    .addProcessor(aprilTag)
    .build();
val portal = VisionPortal.Builder()
    .setCamera(hardwareMap.get(WebcamName::class.java, "Webcam 1"))
    .addProcessor(aprilTag)
    .build()

If you use an Android Robot Controller phone camera or a different webcam name, follow the camera selection in the current SDK sample. A misspelled configured name prevents the camera from opening.

Preview checks

While the OpMode is in , open the camera stream. Check that:

  • the image is live and upright;
  • the target can be seen at the distance where the robot will use it;
  • the processor draws an overlay when a target is recognized;
  • telemetry changes when you move or hide the target.

Keep the robot stationary during this test. When you finish using the camera, close the portal if the OpMode or SDK sample owns its lifecycle. Do not start by steering a robot from unverified vision data.

Processor selection

  • Use AprilTag to detect known printed markers and, when tag metadata and camera calibration permit, estimate pose relative to the camera.
  • Use the FTC Color processor to estimate a predominant color in an image region.
  • Use the FTC Color Locator to find blobs of a selected color and measure their image position and shape.
  • Use EasyOpenCV when your team needs a custom OpenCV pipeline.

The current FTC color processors are part of VisionPortal. AprilTag, color processors, and camera preview may be combined in one portal when needed; each processor still consumes processing time. Start with one processor so it is easier to see which part is not working.

Custom processors

An EasyOpenCV OpenCvPipeline and an FTC VisionProcessor have different camera and frame-processing APIs. You cannot pass an OpenCvPipeline to VisionPortal.Builder.addProcessor().

You can reuse the OpenCV steps from a pipeline by moving them into a class that implements VisionProcessor. The main change is where each part goes:

  1. Move setup that only needs to happen once into init().
  2. Move image operations from OpenCvPipeline.processFrame() into VisionProcessor.processFrame().
  3. Return a small result (or store a thread-safe result) for the OpMode to read. Use onDrawFrame() if you need to draw on VisionPortal's preview.

Here is a small example that finds the center of blue pixels. It follows the same OpenCV pattern used in many EasyOpenCV pipelines: convert to HSV, threshold a color, then measure the mask. The bounds are just starting values; camera lighting and the target material change what works.

import org.firstinspires.ftc.robotcore.internal.camera.calibration.CameraCalibration;
import org.firstinspires.ftc.vision.VisionProcessor;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.imgproc.Moments;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

public class BlueCenterProcessor implements VisionProcessor {
    private final Mat hsv = new Mat();
    private final Mat mask = new Mat();
    private volatile double centerX = Double.NaN;
    private boolean released;

    @Override
    public void init(int width, int height, CameraCalibration calibration) {
        // Runs once when VisionPortal initializes this processor.
    }

    @Override
    public synchronized Object processFrame(Mat frame, long captureTimeNanos) {
        if (released) return null;
        // VisionPortal supplies RGBA frames; RGB2HSV accepts four channels and ignores alpha. HSV makes color thresholds easier to tune.
        Imgproc.cvtColor(frame, hsv, Imgproc.COLOR_RGB2HSV);
        Core.inRange(
            hsv,
            new Scalar(90, 80, 50),   // lower H, S, V
            new Scalar(130, 255, 255), // upper H, S, V
            mask
        );

        Moments moments = Imgproc.moments(mask, true);
        centerX = moments.get_m00() > 0
            ? moments.get_m10() / moments.get_m00()
            : Double.NaN;
        return null; // No preview overlay in this first example.
    }

    @Override
    public void onDrawFrame(
        android.graphics.Canvas canvas,
        int onscreenWidth,
        int onscreenHeight,
        float scaleBmpPxToCanvasPx,
        float scaleCanvasDensity,
        Object userContext
    ) {
        // Optional: draw a box or marker on the preview here.
    }

    public double getCenterX() {
        return centerX;
    }

    public synchronized void release() {
        if (released) return;
        released = true;
        centerX = Double.NaN;
        hsv.release();
        mask.release();
    }
}
import android.graphics.Canvas
import org.firstinspires.ftc.robotcore.internal.camera.calibration.CameraCalibration
import org.firstinspires.ftc.vision.VisionProcessor
import org.opencv.core.Core
import org.opencv.core.Mat
import org.opencv.imgproc.Moments
import org.opencv.core.Scalar
import org.opencv.imgproc.Imgproc

class BlueCenterProcessor : VisionProcessor {
    private val hsv = Mat()
    private val mask = Mat()
    @Volatile private var centerX = Double.NaN
    private var released = false

    override fun init(width: Int, height: Int, calibration: CameraCalibration) {
        // Runs once when VisionPortal initializes this processor.
    }

    @Synchronized
    override fun processFrame(frame: Mat, captureTimeNanos: Long): Any? {
        if (released) return null
        // VisionPortal supplies RGBA frames; RGB2HSV accepts four channels and ignores alpha. HSV makes color thresholds easier to tune.
        Imgproc.cvtColor(frame, hsv, Imgproc.COLOR_RGB2HSV)
        Core.inRange(hsv, Scalar(90.0, 80.0, 50.0), Scalar(130.0, 255.0, 255.0), mask)

        val moments: Moments = Imgproc.moments(mask, true)
        centerX = if (moments.m00 > 0.0) moments.m10 / moments.m00 else Double.NaN
        return null // No preview overlay in this first example.
    }

    override fun onDrawFrame(
        canvas: Canvas,
        onscreenWidth: Int,
        onscreenHeight: Int,
        scaleBmpPxToCanvasPx: Float,
        scaleCanvasDensity: Float,
        userContext: Any?
    ) {
        // Optional: draw a box or marker on the preview here.
    }

    fun getCenterX() = centerX

    @Synchronized
    fun release() {
        if (released) return
        released = true
        centerX = Double.NaN
        hsv.release()
        mask.release()
    }
}

Attach that processor just like an SDK processor, then read its latest result during INIT. This OpMode fragment assumes Webcam 1 is the name in your Robot Controller configuration:

BlueCenterProcessor blueProcessor = new BlueCenterProcessor();
VisionPortal portal = new VisionPortal.Builder()
    .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"))
    .addProcessor(blueProcessor)
    .build();

try {
    while (opModeInInit()) {
        double x = blueProcessor.getCenterX();
        telemetry.addData("Blue center X", Double.isNaN(x) ? "not found" : x);
        telemetry.update();
        idle();
    }
} finally {
    portal.close();
    blueProcessor.release();
}
val blueProcessor = BlueCenterProcessor()
val portal = VisionPortal.Builder()
    .setCamera(hardwareMap.get(WebcamName::class.java, "Webcam 1"))
    .addProcessor(blueProcessor)
    .build()

try {
    while (opModeInInit()) {
        val x = blueProcessor.getCenterX()
        telemetry.addData("Blue center X", if (x.isNaN()) "not found" else x)
        telemetry.update()
        idle()
    }
} finally {
    portal.close()
    blueProcessor.release()
}

In SDK 12, VisionPortal.close() starts asynchronous camera shutdown. Both processFrame() and release() above use the same lock and a terminal released flag: cleanup waits for an active callback, and later callbacks skip the freed Mats. The camera owns the input frame; never release it yourself.

Adapt the processor interface and its three callbacks; most of your OpenCV operations can stay the same. Confirm the frame color order and threshold against the FTC sample for your installed SDK, then tune using the live preview. For a standard color-blob task, the SDK's Color Blob Locator guide may save you from writing this processor yourself. EasyOpenCV uses its own pipeline API; the VisionProcessor interface is the route for VisionPortal.

Calibration and SDK versions

A live preview does not prove that a pose estimate is accurate. AprilTag pose depends on correct tag size and camera calibration for the camera and resolution. Some cameras have built-in calibration; others require calibration. Read the official webcam and calibration guide before using pose for field localization.

FTC SDK 12.0 introduced AprilTag cluster detections. If your SDK is 12.0 or later, see AprilTag Detection before copying older code that assumes every detection has an .id field.

Sources

  • FTC VisionPortal overview
  • FTC Vision processor initialization
  • FTC VisionPortal initialization
  • FTC webcams and camera calibration
  • FTC Color Locator beginner sample
  • EasyOpenCV project

VisionBench

Test an EasyOpenCV pipeline on your computer before using the robot

Color blob locator

Step-by-step introduction to the FTC Color Locator processor

On this page

Setup orderProcessor setupPortal setupPreview checksProcessor selectionCustom processorsCalibration and SDK versionsSources