EasyOpenCV basics
Open a camera and understand how a custom OpenCV pipeline receives frames
The SDK already includes EasyOpenCV for supported SDK versions (SDK 8.2 and later). For that setup, you do not need to add a separate Maven repository, download a native library, or copy a .so file to the . Those instructions appear in older tutorials and are not the normal setup for a current FTC project.
EasyOpenCV gives your a camera stream and a place to run your own OpenCV code. Choose it when you need a custom pipeline. If the built-in FTC Color Locator or AprilTag processor already answers your question, begin with VisionPortal; it requires less custom processing code.
Camera lifecycle
- Your OpMode finds a camera by the name in Configuration.
- EasyOpenCV opens that camera asynchronously. “Asynchronously” means the open result arrives later in a callback, so the robot loop does not wait for the camera to open.
- You attach an
OpenCvPipelinebefore starting the stream. - EasyOpenCV passes each camera frame to the pipeline's
processFramemethod. - When the OpMode ends, stop the stream and close the camera.

The mask is a learning example from a controlled product photo; tune your pipeline against your live camera image.
The vision illustration shows the stages after a frame reaches your pipeline. EasyOpenCV itself manages camera frames and pipeline calls; it does not choose what object to detect. Your OpenCV code does that.
Webcam configuration
In the configuration, add the webcam and give it a simple name such as Webcam 1. That name is part of your program's hardware configuration. If your code asks for Webcam 1 but the configured device is named Front Camera, hardwareMap cannot find it.
Connect the camera to the Control Hub or Robot Controller device, then use a preview sample to confirm that the image is live and correctly rotated before you write detection logic.
Camera setup
This short setup is the part that belongs in your LinearOpMode. PollenTargetPipeline is the small custom class you will build in Writing Pipelines.
int monitorViewId = hardwareMap.appContext.getResources().getIdentifier(
"cameraMonitorViewId", "id", hardwareMap.appContext.getPackageName());
WebcamName cameraName = hardwareMap.get(WebcamName.class, "Webcam 1");
OpenCvCamera camera = OpenCvCameraFactory.getInstance()
.createWebcam(cameraName, monitorViewId);
PollenTargetPipeline pipeline = new PollenTargetPipeline();
camera.setPipeline(pipeline);val monitorViewId = hardwareMap.appContext.resources.getIdentifier(
"cameraMonitorViewId", "id", hardwareMap.appContext.packageName
)
val cameraName = hardwareMap.get(WebcamName::class.java, "Webcam 1")
val camera = OpenCvCameraFactory.getInstance()
.createWebcam(cameraName, monitorViewId)
val pipeline = PollenTargetPipeline()
camera.setPipeline(pipeline)The first lines find a local preview container on the Robot Controller Android screen. The Camera Stream is a separate stream; it does not require that local view. The hardwareMap.get(...) line connects your variable to the named camera. setPipeline(...) tells EasyOpenCV which class should process the images.
Use the OpenCvCameraFactory and callback signatures from the sample bundled with your installed FTC SDK. EasyOpenCV and SDK APIs have changed over time; the SDK sample is the version-matched reference.
Asynchronous opening
Declare private volatile String cameraError = ""; as a Java OpMode field, or @Volatile private var cameraError = "" in Kotlin. In the OpMode loop, display it with telemetry.addData("Camera error", cameraError). The callback writes it from another thread.
Opening a camera can take time or fail. EasyOpenCV reports success in onOpened() and failure in onError(). Start streaming only after success:
camera.openCameraDeviceAsync(new OpenCvCamera.AsyncCameraOpenListener() {
@Override
public void onOpened() {
camera.startStreaming(640, 480, OpenCvCameraRotation.UPRIGHT);
}
@Override
public void onError(int errorCode) {
// Save the error for telemetry in the OpMode loop.
cameraError = "Camera open failed: " + errorCode;
}
});camera.openCameraDeviceAsync(object : OpenCvCamera.AsyncCameraOpenListener {
override fun onOpened() {
camera.startStreaming(640, 480, OpenCvCameraRotation.UPRIGHT)
}
override fun onError(errorCode: Int) {
// Save the error for telemetry in the OpMode loop.
cameraError = "Camera open failed: $errorCode"
}
})The 640 × 480 size and UPRIGHT rotation are examples, not universal settings. Start with a resolution supported by your camera; more pixels can take more processing time. Do not try to start streaming inside the OpMode before onOpened() runs.
Shutdown
After starting the stream, the OpMode must continue running while you read pipeline results. Keep the result visible in telemetry during the first test. Use try/finally around the loop. With EasyOpenCV 1.7.3, call camera.closeCameraDevice() in finally, then pipeline.release(). This synchronous close stops processing before returning. The pipeline in Writing Pipelines releases its own working Mats; it leaves the camera-owned input alone. This differs from the asynchronous VisionPortal.close() wrapper.
Do not run a based on an untested camera result. First confirm the preview, then confirm that the pipeline result changes as you move a target in front of the camera.
Pixel channel order
EasyOpenCV supplies pipeline frames in RGBA order. Some desktop OpenCV examples use BGR instead. When converting a frame to HSV, use the conversion code that matches the frame you actually receive, such as COLOR_RGB2HSV (which accepts RGB or RGBA input and ignores alpha); copying COLOR_BGR2HSV from a desktop tutorial can produce incorrect colors.
You will apply that conversion one step at a time in Writing Pipelines.
Troubleshooting
- Confirm that the configured camera name exactly matches the name in
hardwareMap.get(...). - Check the cable, adapter, and USB connection.
- Confirm that the camera is not already in use by another OpMode or process.
- Read
onErrortelemetry and the Robot Controller log. - Try the version-matched FTC/EasyOpenCV sample before changing resolution or adding image processing.