From 472a2aa692d46d3c876adcca64f3a168b781b565 Mon Sep 17 00:00:00 2001 From: Will Stuckey Date: Sun, 25 Oct 2026 17:17:46 -0400 Subject: [PATCH 1/3] fix autoexposure for some v4l2 cameras --- src/driver/opencvdriver.cpp | 31 +++++++++++++++++++++++++------ src/driver/opencvdriver.h | 4 ++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/driver/opencvdriver.cpp b/src/driver/opencvdriver.cpp index 1692403..6d78736 100644 --- a/src/driver/opencvdriver.cpp +++ b/src/driver/opencvdriver.cpp @@ -33,6 +33,12 @@ std::shared_ptr OpenCVDriver::readImage() { if(!capture.read(mat)) return nullptr; + // V4L2 streams start on the first read; some UVC cameras ignore exposure set before that. + if(!exposureApplied) { + applyExposure(); + exposureApplied = true; + } + return image; } @@ -82,13 +88,26 @@ float OpenCVDriver::getExposure() { } void OpenCVDriver::setExposure(float exposure) { - if(exposure == 0.0f) { - capture.set(cv::CAP_PROP_AUTO_EXPOSURE, 1.0); + this->exposure = exposure; + applyExposure(); +} + +void OpenCVDriver::applyExposure() { + // The V4L2 backend passes CAP_PROP_AUTO_EXPOSURE through as V4L2_CID_EXPOSURE_AUTO, a menu where UVC + // cameras offer 1 (manual) and 3 (aperture priority, i.e. automatic). Other backends keep OpenCV's + // 0.25/0.75 convention. + const bool v4l2 = capture.getBackendName() == "V4L2"; + const double autoMode = v4l2 ? 3.0 : 0.75; + const double manualMode = v4l2 ? 1.0 : 0.25; + + capture.set(cv::CAP_PROP_AUTO_EXPOSURE, autoMode); + if(exposure == 0.0f) return; - } else { - capture.set(cv::CAP_PROP_AUTO_EXPOSURE, 0.0); - capture.set(cv::CAP_PROP_EXPOSURE, exposure * 1000.0); - } + + // Passing through automatic first: some UVC cameras (e.g. the SunplusIT UC70) keep their last + // exposure and ignore exposure_time_absolute until they switch from automatic to manual. + capture.set(cv::CAP_PROP_AUTO_EXPOSURE, manualMode); + capture.set(cv::CAP_PROP_EXPOSURE, exposure * 1000.0); } float OpenCVDriver::getGain() { diff --git a/src/driver/opencvdriver.h b/src/driver/opencvdriver.h index 1dcde09..8abd398 100644 --- a/src/driver/opencvdriver.h +++ b/src/driver/opencvdriver.h @@ -50,7 +50,11 @@ class OpenCVDriver : public CameraDriver { void setWhiteBalance(WhiteBalanceType type, float blue, float red) override; private: + void applyExposure(); + cv::VideoCapture capture; std::shared_ptr image = nullptr; std::string name; + float exposure = 0.0f; + bool exposureApplied = false; }; From 5a2e9f87031e54cd58366ed8cc6d1bdad51e99de Mon Sep 17 00:00:00 2001 From: Will Stuckey Date: Sat, 7 Nov 2026 14:09:05 -0500 Subject: [PATCH 2/3] guard failure to open camera fh --- src/driver/opencvdriver.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/driver/opencvdriver.cpp b/src/driver/opencvdriver.cpp index 6d78736..51c72e7 100644 --- a/src/driver/opencvdriver.cpp +++ b/src/driver/opencvdriver.cpp @@ -15,11 +15,16 @@ */ #include "opencvdriver.h" #include "driver/cameradriver.h" +#include "log.h" #include OpenCVDriver::OpenCVDriver(const CameraConfig& config): capture(config.path, cv::CAP_ANY, {cv::CAP_PROP_HW_ACCELERATION, cv::VIDEO_ACCELERATION_ANY}), name(config.path) { std::replace(name.begin(), name.end(), '/', '_'); + // Usually a wrong path or a camera busy in another program; later calls would throw. + if(!capture.isOpened()) + FATAL("Could not open camera " << config.path << ": no OpenCV backend can open it. Check the path, and whether another program is using the camera (fuser -v " << config.path << ")."); + // Use compressed data stream to unlock the highest resolution - framerate combination on USB2 cameras capture.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G')); } From d6f38e65fbf408a19adb2d05426715f5d32ea92c Mon Sep 17 00:00:00 2001 From: Will Stuckey Date: Sat, 7 Nov 2026 14:13:18 -0500 Subject: [PATCH 3/3] rewrite cv/v4l exposure mode constants to use header defines --- src/driver/opencvdriver.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/driver/opencvdriver.cpp b/src/driver/opencvdriver.cpp index 51c72e7..427bff8 100644 --- a/src/driver/opencvdriver.cpp +++ b/src/driver/opencvdriver.cpp @@ -16,8 +16,13 @@ #include "opencvdriver.h" #include "driver/cameradriver.h" #include "log.h" +#include #include +// CAP_PROP_AUTO_EXPOSURE on backends other than V4L2: an OpenCV convention no header defines. +static constexpr double OPENCV_AUTO_EXPOSURE_ON = 0.75; +static constexpr double OPENCV_AUTO_EXPOSURE_OFF = 0.25; + OpenCVDriver::OpenCVDriver(const CameraConfig& config): capture(config.path, cv::CAP_ANY, {cv::CAP_PROP_HW_ACCELERATION, cv::VIDEO_ACCELERATION_ANY}), name(config.path) { std::replace(name.begin(), name.end(), '/', '_'); @@ -98,12 +103,15 @@ void OpenCVDriver::setExposure(float exposure) { } void OpenCVDriver::applyExposure() { - // The V4L2 backend passes CAP_PROP_AUTO_EXPOSURE through as V4L2_CID_EXPOSURE_AUTO, a menu where UVC - // cameras offer 1 (manual) and 3 (aperture priority, i.e. automatic). Other backends keep OpenCV's - // 0.25/0.75 convention. - const bool v4l2 = capture.getBackendName() == "V4L2"; - const double autoMode = v4l2 ? 3.0 : 0.75; - const double manualMode = v4l2 ? 1.0 : 0.25; + double autoMode = OPENCV_AUTO_EXPOSURE_ON; + double manualMode = OPENCV_AUTO_EXPOSURE_OFF; + + // V4L2 passes the value straight to V4L2_CID_EXPOSURE_AUTO; UVC cameras offer manual and aperture + // priority (automatic). + if(capture.getBackendName() == "V4L2") { + autoMode = V4L2_EXPOSURE_APERTURE_PRIORITY; + manualMode = V4L2_EXPOSURE_MANUAL; + } capture.set(cv::CAP_PROP_AUTO_EXPOSURE, autoMode); if(exposure == 0.0f)