Merge pull request #26837 from warped-rudi:zoom

Zoom functionality for Android native camera capture #26837

### 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
- [x ] 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
This commit is contained in:
Rüdiger Ihle 2025-01-25 07:29:00 +01:00 committed by GitHub
parent 9bb01e799f
commit a2dd4ddbb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 45 deletions

View File

@ -168,7 +168,7 @@ enum VideoCaptureProperties {
CAP_PROP_TRIGGER =24, CAP_PROP_TRIGGER =24,
CAP_PROP_TRIGGER_DELAY =25, CAP_PROP_TRIGGER_DELAY =25,
CAP_PROP_WHITE_BALANCE_RED_V =26, CAP_PROP_WHITE_BALANCE_RED_V =26,
CAP_PROP_ZOOM =27, CAP_PROP_ZOOM =27, //!< Android: May switch physical cameras/lenses. Factor and range are hardware-dependent.
CAP_PROP_FOCUS =28, CAP_PROP_FOCUS =28,
CAP_PROP_GUID =29, CAP_PROP_GUID =29,
CAP_PROP_ISO_SPEED =30, CAP_PROP_ISO_SPEED =30,

View File

@ -80,7 +80,7 @@ static double elapsedTimeFrom(std::chrono::time_point<std::chrono::system_clock>
class AndroidCameraCapture : public IVideoCapture class AndroidCameraCapture : public IVideoCapture
{ {
int cachedIndex; int deviceIndex;
AObjPtr<ACameraManager> cameraManager { nullptr, ACameraManager_delete }; AObjPtr<ACameraManager> cameraManager { nullptr, ACameraManager_delete };
AObjPtr<ACameraDevice> cameraDevice { nullptr, ACameraDevice_close }; AObjPtr<ACameraDevice> cameraDevice { nullptr, ACameraDevice_close };
AObjPtr<AImageReader> imageReader { nullptr, AImageReader_delete }; AObjPtr<AImageReader> imageReader { nullptr, AImageReader_delete };
@ -100,16 +100,17 @@ class AndroidCameraCapture : public IVideoCapture
bool targetAdded = false; bool targetAdded = false;
// properties // properties
uint32_t fourCC = FOURCC_UNKNOWN; uint32_t fourCC = FOURCC_UNKNOWN;
bool settingWidth = false; int32_t desiredWidth = 640;
bool settingHeight = false; int32_t desiredHeight = 480;
int desiredWidth = 640; enum SetupState { setupDone = 0, setupWidth = 0x01, setupHeight = 0x02 } widthHeightState = setupDone;
int desiredHeight = 480;
uint8_t flashMode = ACAMERA_FLASH_MODE_OFF; uint8_t flashMode = ACAMERA_FLASH_MODE_OFF;
uint8_t aeMode = ACAMERA_CONTROL_AE_MODE_ON; uint8_t aeMode = ACAMERA_CONTROL_AE_MODE_ON;
int64_t exposureTime = 0; int64_t exposureTime = 0;
RangeValue<int64_t> exposureRange; RangeValue<int64_t> exposureRange;
int32_t sensitivity = 0; int32_t sensitivity = 0;
RangeValue<int32_t> sensitivityRange; RangeValue<int32_t> sensitivityRange;
float zoomRatio = 1.0f;
RangeValue<float> zoomRange;
ACameraDevice_stateCallbacks deviceCallbacks = {}; ACameraDevice_stateCallbacks deviceCallbacks = {};
ACameraCaptureSession_stateCallbacks sessionCallbacks = {}; ACameraCaptureSession_stateCallbacks sessionCallbacks = {};
@ -136,7 +137,8 @@ class AndroidCameraCapture : public IVideoCapture
std::condition_variable condition; std::condition_variable condition;
public: public:
AndroidCameraCapture(const VideoCaptureParameters& params) AndroidCameraCapture(int index, const VideoCaptureParameters& params)
: deviceIndex(index)
{ {
deviceCallbacks.context = this; deviceCallbacks.context = this;
deviceCallbacks.onError = OnDeviceError; deviceCallbacks.onError = OnDeviceError;
@ -151,8 +153,8 @@ public:
captureCallbacks.onCaptureCompleted = OnCaptureCompleted; captureCallbacks.onCaptureCompleted = OnCaptureCompleted;
captureCallbacks.onCaptureFailed = OnCaptureFailed; captureCallbacks.onCaptureFailed = OnCaptureFailed;
desiredWidth = params.get<int>(CAP_PROP_FRAME_WIDTH, desiredWidth); desiredWidth = params.get<int32_t>(CAP_PROP_FRAME_WIDTH, desiredWidth);
desiredHeight = params.get<int>(CAP_PROP_FRAME_HEIGHT, desiredHeight); desiredHeight = params.get<int32_t>(CAP_PROP_FRAME_HEIGHT, desiredHeight);
static const struct { static const struct {
int propId; int propId;
@ -277,7 +279,7 @@ public:
return false; return false;
} }
if (colorFormat == COLOR_FormatYUV420Planar) { if (colorFormat == COLOR_FormatYUV420Planar) {
Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data()); const Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data());
switch (fourCC) { switch (fourCC) {
case FOURCC_BGRA: case FOURCC_BGRA:
cvtColor(yuv, out, COLOR_YUV2BGRA_YV12); cvtColor(yuv, out, COLOR_YUV2BGRA_YV12);
@ -299,33 +301,32 @@ public:
break; break;
default: default:
LOGE("Unexpected FOURCC value: %d", fourCC); LOGE("Unexpected FOURCC value: %d", fourCC);
break; return false;
} }
} else if (colorFormat == COLOR_FormatYUV420SemiPlanar) { } else if (colorFormat == COLOR_FormatYUV420SemiPlanar) {
Mat yuv(frameHeight + frameHeight/2, frameStride, CV_8UC1, buffer.data()); const Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data(), frameStride);
Mat tmp = (frameWidth == frameStride) ? yuv : yuv(Rect(0, 0, frameWidth, frameHeight + frameHeight / 2));
switch (fourCC) { switch (fourCC) {
case FOURCC_BGRA: case FOURCC_BGRA:
cvtColor(tmp, out, COLOR_YUV2BGRA_NV21); cvtColor(yuv, out, COLOR_YUV2BGRA_NV21);
break; break;
case FOURCC_RGBA: case FOURCC_RGBA:
cvtColor(tmp, out, COLOR_YUV2RGBA_NV21); cvtColor(yuv, out, COLOR_YUV2RGBA_NV21);
break; break;
case FOURCC_BGR: case FOURCC_BGR:
cvtColor(tmp, out, COLOR_YUV2BGR_NV21); cvtColor(yuv, out, COLOR_YUV2BGR_NV21);
break; break;
case FOURCC_RGB: case FOURCC_RGB:
cvtColor(tmp, out, COLOR_YUV2RGB_NV21); cvtColor(yuv, out, COLOR_YUV2RGB_NV21);
break; break;
case FOURCC_GRAY: case FOURCC_GRAY:
cvtColor(tmp, out, COLOR_YUV2GRAY_NV21); cvtColor(yuv, out, COLOR_YUV2GRAY_NV21);
break; break;
case FOURCC_NV21: case FOURCC_NV21:
tmp.copyTo(out); yuv.copyTo(out);
break; break;
default: default:
LOGE("Unexpected FOURCC value: %d", fourCC); LOGE("Unexpected FOURCC value: %d", fourCC);
break; return false;
} }
} else { } else {
LOGE("Unsupported video format: %d", colorFormat); LOGE("Unsupported video format: %d", colorFormat);
@ -351,6 +352,8 @@ public:
return fourCC; return fourCC;
case CAP_PROP_ANDROID_DEVICE_TORCH: case CAP_PROP_ANDROID_DEVICE_TORCH:
return (flashMode == ACAMERA_FLASH_MODE_TORCH) ? 1 : 0; return (flashMode == ACAMERA_FLASH_MODE_TORCH) ? 1 : 0;
case CAP_PROP_ZOOM:
return zoomRange.isValid() ? zoomRatio : -1;
default: default:
break; break;
} }
@ -362,22 +365,12 @@ public:
{ {
switch (property_id) { switch (property_id) {
case CAP_PROP_FRAME_WIDTH: case CAP_PROP_FRAME_WIDTH:
desiredWidth = value; desiredWidth = static_cast<int32_t>(value);
settingWidth = true; setWidthHeight(setupWidth);
if (settingWidth && settingHeight) {
setWidthHeight();
settingWidth = false;
settingHeight = false;
}
return true; return true;
case CAP_PROP_FRAME_HEIGHT: case CAP_PROP_FRAME_HEIGHT:
desiredHeight = value; desiredHeight = static_cast<int32_t>(value);
settingHeight = true; setWidthHeight(setupHeight);
if (settingWidth && settingHeight) {
setWidthHeight();
settingWidth = false;
settingHeight = false;
}
return true; return true;
case CAP_PROP_FOURCC: case CAP_PROP_FOURCC:
{ {
@ -437,6 +430,12 @@ public:
return submitRequest(ACaptureRequest_setEntry_i32, ACAMERA_SENSOR_SENSITIVITY, sensitivity); return submitRequest(ACaptureRequest_setEntry_i32, ACAMERA_SENSOR_SENSITIVITY, sensitivity);
} }
return false; return false;
case CAP_PROP_ZOOM:
if (isOpened() && zoomRange.isValid()) {
zoomRatio = zoomRange.clamp(static_cast<float>(value));
return submitRequest(ACaptureRequest_setEntry_float, ACAMERA_CONTROL_ZOOM_RATIO, zoomRatio);
}
return true;
case CAP_PROP_ANDROID_DEVICE_TORCH: case CAP_PROP_ANDROID_DEVICE_TORCH:
flashMode = (value != 0) ? ACAMERA_FLASH_MODE_TORCH : ACAMERA_FLASH_MODE_OFF; flashMode = (value != 0) ? ACAMERA_FLASH_MODE_TORCH : ACAMERA_FLASH_MODE_OFF;
if (isOpened()) { if (isOpened()) {
@ -449,9 +448,8 @@ public:
return false; return false;
} }
bool initCapture(int index) bool initCapture()
{ {
cachedIndex = index;
cameraManager.reset(ACameraManager_create()); cameraManager.reset(ACameraManager_create());
if (!cameraManager) { if (!cameraManager) {
LOGE("Cannot create camera manager!"); LOGE("Cannot create camera manager!");
@ -464,11 +462,11 @@ public:
return false; return false;
} }
AObjPtr<ACameraIdList> cameraIdList(cameraIds, ACameraManager_deleteCameraIdList); AObjPtr<ACameraIdList> cameraIdList(cameraIds, ACameraManager_deleteCameraIdList);
if (index < 0 || index >= cameraIds->numCameras) { if (deviceIndex < 0 || deviceIndex >= cameraIds->numCameras) {
LOGE("Camera index out of range %d (Number of cameras: %d)", index, cameraIds->numCameras); LOGE("Camera index out of range %d (Number of cameras: %d)", deviceIndex, cameraIds->numCameras);
return false; return false;
} }
const char *cameraId = cameraIdList.get()->cameraIds[index]; const char *cameraId = cameraIdList.get()->cameraIds[deviceIndex];
ACameraDevice* camera; ACameraDevice* camera;
cStatus = ACameraManager_openCamera(cameraManager.get(), cameraId, &deviceCallbacks, &camera); cStatus = ACameraManager_openCamera(cameraManager.get(), cameraId, &deviceCallbacks, &camera);
@ -568,10 +566,13 @@ public:
captureSession.reset(session); captureSession.reset(session);
ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode); ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode);
ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity);
if (aeMode != ACAMERA_CONTROL_AE_MODE_ON) { if (aeMode != ACAMERA_CONTROL_AE_MODE_ON) {
ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity);
ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime); ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime);
} }
if (zoomRange.isValid()) {
ACaptureRequest_setEntry_float(captureRequest.get(), ACAMERA_CONTROL_ZOOM_RATIO, 1, &zoomRatio);
}
ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_FLASH_MODE, 1, &flashMode); ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_FLASH_MODE, 1, &flashMode);
cStatus = ACameraCaptureSession_setRepeatingRequest(captureSession.get(), cStatus = ACameraCaptureSession_setRepeatingRequest(captureSession.get(),
@ -610,6 +611,17 @@ private:
sensitivityRange.min = sensitivityRange.max = 0; sensitivityRange.min = sensitivityRange.max = 0;
sensitivity = 0; sensitivity = 0;
} }
cStatus = ACameraMetadata_getConstEntry(metadata, ACAMERA_CONTROL_ZOOM_RATIO_RANGE, &val);
if (cStatus == ACAMERA_OK){
zoomRange.min = val.data.f[0];
zoomRange.max = val.data.f[1];
zoomRatio = zoomRange.clamp(zoomRatio);
} else {
LOGW("Unsupported ACAMERA_CONTROL_ZOOM_RATIO_RANGE");
zoomRange.min = zoomRange.max = 0;
zoomRatio = 1.0f;
}
} }
// calculate a score based on how well the width and height match the desired width and height // calculate a score based on how well the width and height match the desired width and height
@ -658,9 +670,13 @@ private:
} }
} }
void setWidthHeight() { void setWidthHeight(SetupState newState) {
cleanUp(); if ((widthHeightState | newState) == (setupWidth | setupHeight)) {
initCapture(cachedIndex); cleanUp();
initCapture();
newState = setupDone;
}
widthHeightState = newState;
} }
void cleanUp() { void cleanUp() {
@ -786,8 +802,8 @@ void AndroidCameraCapture::OnCaptureFailed(void* context,
/****************** Implementation of interface functions ********************/ /****************** Implementation of interface functions ********************/
Ptr<IVideoCapture> cv::createAndroidCapture_cam(int index, const VideoCaptureParameters& params) { Ptr<IVideoCapture> cv::createAndroidCapture_cam(int index, const VideoCaptureParameters& params) {
Ptr<AndroidCameraCapture> res = makePtr<AndroidCameraCapture>(params); Ptr<AndroidCameraCapture> res = makePtr<AndroidCameraCapture>(index, params);
if (res && res->initCapture(index)) if (res && res->initCapture())
return res; return res;
return Ptr<IVideoCapture>(); return Ptr<IVideoCapture>();
} }