From d369cf6d50d6b5ddd9bdf438ebe63f3f0e341ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ihle?= Date: Wed, 18 Dec 2024 10:16:50 +0100 Subject: [PATCH] Merge pull request #26627 from warped-rudi:torch Android camera feature enhancements #26627 Closes https://github.com/opencv/opencv/issues/24687 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/videoio/include/opencv2/videoio.hpp | 12 ++ modules/videoio/src/cap_android_camera.cpp | 142 ++++++++++++------- modules/videoio/src/cap_android_mediandk.cpp | 2 +- modules/videoio/src/cap_interface.hpp | 4 +- 4 files changed, 109 insertions(+), 51 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index 52857fabab..92fa5d39b7 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -580,6 +580,18 @@ enum { CAP_PROP_ARAVIS_AUTOTRIGGER = 600 //!< Autom //! @} ARAVIS + +/** @name Android + @{ +*/ + +//! Properties of cameras available through NDK Camera API backend +enum { CAP_PROP_ANDROID_DEVICE_TORCH = 8001, + }; + +//! @} Android + + /** @name AVFoundation framework for iOS @{ */ diff --git a/modules/videoio/src/cap_android_camera.cpp b/modules/videoio/src/cap_android_camera.cpp index 52f741c444..d0bcfb367b 100644 --- a/modules/videoio/src/cap_android_camera.cpp +++ b/modules/videoio/src/cap_android_camera.cpp @@ -33,23 +33,27 @@ using namespace cv; #define FOURCC_BGR CV_FOURCC_MACRO('B','G','R','3') #define FOURCC_RGB CV_FOURCC_MACRO('R','G','B','3') +#define FOURCC_BGRA CV_FOURCC_MACRO('B','G','R','4') +#define FOURCC_RGBA CV_FOURCC_MACRO('R','G','B','4') #define FOURCC_GRAY CV_FOURCC_MACRO('G','R','E','Y') #define FOURCC_NV21 CV_FOURCC_MACRO('N','V','2','1') #define FOURCC_YV12 CV_FOURCC_MACRO('Y','V','1','2') #define FOURCC_UNKNOWN 0xFFFFFFFF -template class RangeValue { -public: +template struct RangeValue { T min, max; /** * return absolute value from relative value * * value: in percent (50 for 50%) * */ T value(int percent) { - return static_cast(min + (max - min) * percent / 100); + return static_cast(min + ((max - min) * percent) / 100); + } + RangeValue(T minv = 0, T maxv = 0) : min(minv), max(maxv) {} + bool Supported() const { return (min != max); } + T clamp( T value ) const { + return (value > max) ? max : ((value < min) ? min : value); } - RangeValue() { min = max = static_cast(0); } - bool Supported(void) const { return (min != max); } }; static inline void deleter_ACameraManager(ACameraManager *cameraManager) { @@ -165,8 +169,7 @@ void OnCaptureFailed(void* context, * on camera. For this sample purpose, clamp to a range showing visible * video on preview: 100000ns ~ 250000000ns */ -static const long kMinExposureTime = 1000000L; -static const long kMaxExposureTime = 250000000L; +static const RangeValue exposureTimeLimits = { 1000000, 250000000 }; static double elapsedTimeFrom(std::chrono::time_point start) { return std::chrono::duration(std::chrono::system_clock::now() - start).count(); @@ -188,7 +191,7 @@ class AndroidCameraCapture : public IVideoCapture int32_t frameWidth = 0; int32_t frameStride = 0; int32_t frameHeight = 0; - int32_t colorFormat; + int32_t colorFormat = COLOR_FormatUnknown; std::vector buffer; bool sessionOutputAdded = false; bool targetAdded = false; @@ -198,8 +201,9 @@ class AndroidCameraCapture : public IVideoCapture bool settingHeight = false; int desiredWidth = 640; int desiredHeight = 480; - bool autoExposure = true; - int64_t exposureTime = 0L; + uint8_t flashMode = ACAMERA_FLASH_MODE_OFF; + uint8_t aeMode = ACAMERA_CONTROL_AE_MODE_ON; + int64_t exposureTime = 0; RangeValue exposureRange; int32_t sensitivity = 0; RangeValue sensitivityRange; @@ -212,7 +216,24 @@ public: std::condition_variable condition; public: - AndroidCameraCapture() {} + AndroidCameraCapture(const VideoCaptureParameters& params) + { + desiredWidth = params.get(CAP_PROP_FRAME_WIDTH, desiredWidth); + desiredHeight = params.get(CAP_PROP_FRAME_HEIGHT, desiredHeight); + + static const struct { + int propId; + uint32_t defaultValue; + } items[] = { + { CAP_PROP_AUTO_EXPOSURE, 1 }, + { CAP_PROP_FOURCC, FOURCC_UNKNOWN }, + { CAP_PROP_ANDROID_DEVICE_TORCH, 0 } + }; + + for (auto it = std::begin(items); it != std::end(items); ++it) { + setProperty(it->propId, params.get(it->propId, it->defaultValue)); + } + } ~AndroidCameraCapture() { cleanUp(); } @@ -357,14 +378,20 @@ public: if (colorFormat == COLOR_FormatYUV420Planar) { Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data()); switch (fourCC) { + case FOURCC_BGRA: + cvtColor(yuv, out, COLOR_YUV2BGRA_YV12); + break; + case FOURCC_RGBA: + cvtColor(yuv, out, COLOR_YUV2RGBA_YV12); + break; case FOURCC_BGR: - cv::cvtColor(yuv, out, cv::COLOR_YUV2BGR_YV12); + cvtColor(yuv, out, COLOR_YUV2BGR_YV12); break; case FOURCC_RGB: - cv::cvtColor(yuv, out, cv::COLOR_YUV2RGB_YV12); + cvtColor(yuv, out, COLOR_YUV2RGB_YV12); break; case FOURCC_GRAY: - cv::cvtColor(yuv, out, cv::COLOR_YUV2GRAY_YV12); + cvtColor(yuv, out, COLOR_YUV2GRAY_YV12); break; case FOURCC_YV12: yuv.copyTo(out); @@ -377,14 +404,20 @@ public: Mat yuv(frameHeight + frameHeight/2, frameStride, CV_8UC1, buffer.data()); Mat tmp = (frameWidth == frameStride) ? yuv : yuv(Rect(0, 0, frameWidth, frameHeight + frameHeight / 2)); switch (fourCC) { + case FOURCC_BGRA: + cvtColor(tmp, out, COLOR_YUV2BGRA_NV21); + break; + case FOURCC_RGBA: + cvtColor(tmp, out, COLOR_YUV2RGBA_NV21); + break; case FOURCC_BGR: - cv::cvtColor(tmp, out, cv::COLOR_YUV2BGR_NV21); + cvtColor(tmp, out, COLOR_YUV2BGR_NV21); break; case FOURCC_RGB: - cv::cvtColor(tmp, out, cv::COLOR_YUV2RGB_NV21); + cvtColor(tmp, out, COLOR_YUV2RGB_NV21); break; case FOURCC_GRAY: - cv::cvtColor(tmp, out, cv::COLOR_YUV2GRAY_NV21); + cvtColor(tmp, out, COLOR_YUV2GRAY_NV21); break; case FOURCC_NV21: tmp.copyTo(out); @@ -408,13 +441,15 @@ public: case CAP_PROP_FRAME_HEIGHT: return isOpened() ? frameHeight : desiredHeight; case CAP_PROP_AUTO_EXPOSURE: - return autoExposure ? 1 : 0; + return (aeMode == ACAMERA_CONTROL_AE_MODE_ON) ? 1 : 0; case CAP_PROP_EXPOSURE: return exposureTime; case CAP_PROP_ISO_SPEED: return sensitivity; case CAP_PROP_FOURCC: return fourCC; + case CAP_PROP_ANDROID_DEVICE_TORCH: + return (flashMode == ACAMERA_FLASH_MODE_TORCH) ? 1 : 0; default: break; } @@ -452,6 +487,8 @@ public: switch (newFourCC) { case FOURCC_BGR: case FOURCC_RGB: + case FOURCC_BGRA: + case FOURCC_RGBA: case FOURCC_GRAY: fourCC = newFourCC; return true; @@ -478,29 +515,31 @@ public: } } case CAP_PROP_AUTO_EXPOSURE: - autoExposure = (value != 0); + aeMode = (value != 0) ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; if (isOpened()) { - uint8_t aeMode = autoExposure ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; - camera_status_t status = ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_u8, ACAMERA_CONTROL_AE_MODE, aeMode); } return true; case CAP_PROP_EXPOSURE: if (isOpened() && exposureRange.Supported()) { - exposureTime = (int64_t)value; + exposureTime = exposureRange.clamp(static_cast(value)); LOGI("Setting CAP_PROP_EXPOSURE will have no effect unless CAP_PROP_AUTO_EXPOSURE is off"); - camera_status_t status = ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_i64, ACAMERA_SENSOR_EXPOSURE_TIME, exposureTime); } return false; case CAP_PROP_ISO_SPEED: if (isOpened() && sensitivityRange.Supported()) { - sensitivity = (int32_t)value; + sensitivity = sensitivityRange.clamp(static_cast(value)); LOGI("Setting CAP_PROP_ISO_SPEED will have no effect unless CAP_PROP_AUTO_EXPOSURE is off"); - camera_status_t status = ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_i32, ACAMERA_SENSOR_SENSITIVITY, sensitivity); } return false; + case CAP_PROP_ANDROID_DEVICE_TORCH: + flashMode = (value != 0) ? ACAMERA_FLASH_MODE_TORCH : ACAMERA_FLASH_MODE_OFF; + if (isOpened()) { + return submitRequest(ACaptureRequest_setEntry_u8, ACAMERA_FLASH_MODE, flashMode); + } + return true; default: break; } @@ -561,7 +600,7 @@ public: return false; } std::shared_ptr cameraMetadata = std::shared_ptr(metadata, deleter_ACameraMetadata); - ACameraMetadata_const_entry entry; + ACameraMetadata_const_entry entry = {}; ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); double bestScore = std::numeric_limits::max(); @@ -594,25 +633,19 @@ public: } LOGI("Best resolution match: %dx%d", bestMatchWidth, bestMatchHeight); - ACameraMetadata_const_entry val = { 0, }; - camera_status_t status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val); - if (status == ACAMERA_OK) { - exposureRange.min = val.data.i64[0]; - if (exposureRange.min < kMinExposureTime) { - exposureRange.min = kMinExposureTime; - } - exposureRange.max = val.data.i64[1]; - if (exposureRange.max > kMaxExposureTime) { - exposureRange.max = kMaxExposureTime; - } + ACameraMetadata_const_entry val; + cStatus = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val); + if (cStatus == ACAMERA_OK) { + exposureRange.min = exposureTimeLimits.clamp(val.data.i64[0]); + exposureRange.max = exposureTimeLimits.clamp(val.data.i64[1]); exposureTime = exposureRange.value(2); } else { LOGW("Unsupported ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE"); - exposureRange.min = exposureRange.max = 0l; - exposureTime = 0l; + exposureRange.min = exposureRange.max = 0; + exposureTime = 0; } - status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE, &val); - if (status == ACAMERA_OK){ + cStatus = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE, &val); + if (cStatus == ACAMERA_OK){ sensitivityRange.min = val.data.i32[0]; sensitivityRange.max = val.data.i32[1]; sensitivity = sensitivityRange.value(2); @@ -693,12 +726,13 @@ public: return false; } captureSession = std::shared_ptr(session, deleter_ACameraCaptureSession); - uint8_t aeMode = autoExposure ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; + ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode); ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity); - if (!autoExposure) { + if (aeMode != ACAMERA_CONTROL_AE_MODE_ON) { ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime); } + ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_FLASH_MODE, 1, &flashMode); cStatus = ACameraCaptureSession_setRepeatingRequest(captureSession.get(), GetCaptureCallback(), 1, &request, nullptr); if (cStatus != ACAMERA_OK) { @@ -732,6 +766,18 @@ public: cameraManager = nullptr; imageReader = nullptr; } + + template + bool submitRequest(FuncT setFn, uint32_t tag, const T &data) + { + ACaptureRequest *request = captureRequest.get(); + + return request && + setFn(request, tag, 1, &data) == ACAMERA_OK && + ACameraCaptureSession_setRepeatingRequest(captureSession.get(), + GetCaptureCallback(), + 1, &request, nullptr) == ACAMERA_OK; + } }; /******************************** Session management *******************************/ @@ -788,8 +834,8 @@ void OnCaptureFailed(void* context, /****************** Implementation of interface functions ********************/ -Ptr cv::createAndroidCapture_cam( int index ) { - Ptr res = makePtr(); +Ptr cv::createAndroidCapture_cam(int index, const VideoCaptureParameters& params) { + Ptr res = makePtr(params); if (res && res->initCapture(index)) return res; return Ptr(); diff --git a/modules/videoio/src/cap_android_mediandk.cpp b/modules/videoio/src/cap_android_mediandk.cpp index 293dcc91bf..98f9eea3f9 100644 --- a/modules/videoio/src/cap_android_mediandk.cpp +++ b/modules/videoio/src/cap_android_mediandk.cpp @@ -668,7 +668,7 @@ const AndroidMediaNdkVideoWriter::FourCCInfo AndroidMediaNdkVideoWriter::FOURCC_ /****************** Implementation of interface functions ********************/ -Ptr cv::createAndroidCapture_file(const std::string &filename) { +Ptr cv::createAndroidCapture_file(const std::string &filename, const VideoCaptureParameters& ) { Ptr res = makePtr(); if (res && res->initCapture(filename.c_str())) return res; diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index a57564b11e..a8e3e49dd0 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -391,8 +391,8 @@ Ptr createGPhoto2Capture(const std::string& deviceName); Ptr createXINECapture(const std::string &filename); -Ptr createAndroidCapture_cam( int index ); -Ptr createAndroidCapture_file(const std::string &filename); +Ptr createAndroidCapture_cam(int index, const VideoCaptureParameters& params); +Ptr createAndroidCapture_file(const std::string &filename, const VideoCaptureParameters& params); Ptr createAndroidVideoWriter(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params);