Merge pull request #27230 from sirudoi:4.x

videoio: add Orbbec Gemini 330 camera support #27230

### 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
- [x] The feature is well documented and sample code can be built with the project CMake

### Description of Changes
#### motivated:
- Orbbec has launched a new RGB-D camera — the Gemini 330. To fully leverage the capabilities of the Gemini 330, Orbbec simultaneously released version 2 of the open-source OrbbecSDK. This PR adapts the support for the Gemini 330 series cameras to better meet and respond to users’ application requirements.
#### change:
- Add support for the Orbbec Gemini330 camera.
- Fixed an issue with Femto Mega on Windows 10/11; for details, see [issue](https://github.com/opencv/opencv/pull/23237#issuecomment-2242347295).
- When enabling `HAVE_OBSENSOR_ORBBEC_SDK`, the build now fetches version 2 of the OrbbecSDK, and the sample API calls have been updated to the v2 format.

### Testing
|     OS     |                Compiler                 |      Camera       | Result |
|:----------:|:---------------------------------------:|:-----------------:|:------:|
| Windows 11 | (VS2022) MSVC runtime library version 14.40       | Gemini 335/336L   | Pass   |
| Windows 11 | (VS2022) MSVC runtime library version 14.19       | Gemini 335/336L   | Pass   |
| Ubuntu22.04| GCC 11.4                               | Gemini 335/336L   | Pass   |
| Ubuntu18.04| GCC 7.5                                | Gemini 335/336L   | Pass   |

### Acknowledgements
Thank you to the OpenCV team for the continuous support and for creating such a robust open source project. I appreciate the valuable feedback from the community and reviewers, which has helped improve the quality of this contribution!
This commit is contained in:
sirudoi 2025-04-25 16:04:19 +08:00 committed by GitHub
parent 829495355d
commit 485c7d5be7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 118 additions and 27 deletions

View File

@ -123,4 +123,5 @@ This tutorial code's is shown lines below. You can also download it from
![BGR And DEPTH And DepthToColor frame](images/orbbec_uvc_cpp.jpg)
### Note
Mac users need sudo privileges to execute the code.
- Mac users need sudo privileges to execute the code.
- **Firmware**: If youre using an Orbbec UVC 3D camera, please ensure your cameras firmware is updated to the latest version to avoid potential compatibility issues. For more details, see [Orbbecs Release Notes](https://github.com/orbbec/OrbbecSDK_v2/releases).

View File

@ -128,7 +128,7 @@ enum VideoCaptureAPIs {
CAP_INTEL_MFX = 2300, //!< Intel MediaSDK
CAP_XINE = 2400, //!< XINE engine (Linux)
CAP_UEYE = 2500, //!< uEye Camera API
CAP_OBSENSOR = 2600, //!< For Orbbec 3D-Sensor device/module (Astra+, Femto, Astra2, Gemini2, Gemini2L, Gemini2XL, Femto Mega) attention: Astra2 cameras currently only support Windows and Linux kernel versions no higher than 4.15, and higher versions of Linux kernel may have exceptions.
CAP_OBSENSOR = 2600, //!< For Orbbec 3D-Sensor device/module (Astra+, Femto, Astra2, Gemini2, Gemini2L, Gemini2XL, Gemini330, Femto Mega) attention: Astra2 cameras currently only support Windows and Linux kernel versions no higher than 4.15, and higher versions of Linux kernel may have exceptions.
};

View File

@ -39,6 +39,21 @@ namespace obsensor {
#define OBSENSOR_FEMTO_MEGA_PID 0x0669 // pid of Orbbec Femto Mega Camera
#define OBSENSOR_GEMINI2L_PID 0x0673 // pid of Orbbec Gemini 2 L Camera
#define OBSENSOR_GEMINI2XL_PID 0x0671 // pid of Orbbec Gemini 2 XL Camera
#define OBSENSOR_GEMINI335_PID 0x0800 // pid of Orbbec Gemini 335 Camera
#define OBSENSOR_GEMINI330_PID 0x0801 // pid of Orbbec Gemini 330 Camera
#define OBSENSOR_GEMINI336_PID 0x0803 // pid of Orbbec Gemini 336 Camera
#define OBSENSOR_GEMINI335L_PID 0x0804 // pid of Orbbec Gemini 335L Camera
#define OBSENSOR_GEMINI330L_PID 0x0805 // pid of Orbbec Gemini 330L Camera
#define OBSENSOR_GEMINI336L_PID 0x0807 // pid of Orbbec Gemini 336L Camera
#define IS_OBSENSOR_GEMINI330_SHORT_PID(pid) \
((pid) == OBSENSOR_GEMINI335_PID || (pid) == OBSENSOR_GEMINI330_PID || (pid) == OBSENSOR_GEMINI336_PID)
#define IS_OBSENSOR_GEMINI330_LONG_PID(pid) \
((pid) == OBSENSOR_GEMINI335L_PID || (pid) == OBSENSOR_GEMINI330L_PID || (pid) == OBSENSOR_GEMINI336L_PID)
#define IS_OBSENSOR_GEMINI330_PID(pid) \
(IS_OBSENSOR_GEMINI330_SHORT_PID(pid) || IS_OBSENSOR_GEMINI330_LONG_PID(pid))
enum StreamType
{

View File

@ -228,8 +228,8 @@ MSMFStreamChannel::MSMFStreamChannel(const UvcDeviceInfo& devInfo) :
})
delete[] buffer;
HR_FAILED_RETURN(MFCreateDeviceSource(deviceAttrs_.Get(), &deviceSource_));
HR_FAILED_RETURN(deviceSource_->QueryInterface(__uuidof(IAMCameraControl), reinterpret_cast<void**>(&cameraControl_)));
HR_FAILED_RETURN(deviceSource_->QueryInterface(__uuidof(IAMVideoProcAmp), reinterpret_cast<void**>(&videoProcAmp_)));
HR_FAILED_LOG(deviceSource_->QueryInterface(__uuidof(IAMCameraControl), reinterpret_cast<void**>(&cameraControl_)));
HR_FAILED_LOG(deviceSource_->QueryInterface(__uuidof(IAMVideoProcAmp), reinterpret_cast<void**>(&videoProcAmp_)));
HR_FAILED_RETURN(MFCreateAttributes(&readerAttrs_, 3));
HR_FAILED_RETURN(readerAttrs_->SetUINT32(MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, false));
@ -314,7 +314,7 @@ void MSMFStreamChannel::start(const StreamProfile& profile, FrameCallback frameC
currentProfile_ = profile;
currentStreamIndex_ = -1;
for (uint8_t index = 0; index <= 5; index++)
for (uint8_t index = 0; index < 5; index++)
{
for (uint32_t k = 0;; k++)
{
@ -341,6 +341,12 @@ void MSMFStreamChannel::start(const StreamProfile& profile, FrameCallback frameC
fps == profile.fps &&
frameFourccToFormat(device_fourcc) == profile.format)
{
for (uint8_t i = 0; i < 5; ++i) {
if (index == i)
continue;
streamReader_->SetStreamSelection(i, FALSE);
}
HR_FAILED_RETURN(streamReader_->SetCurrentMediaType(index, nullptr, mediaType.Get()));
HR_FAILED_RETURN(streamReader_->SetStreamSelection(index, true));
streamReader_->ReadSample(index, 0, nullptr, nullptr, nullptr, nullptr);
@ -393,7 +399,7 @@ bool MSMFStreamChannel::setXu(uint8_t ctrl, const uint8_t* data, uint32_t len)
KSP_NODE node;
memset(&node, 0, sizeof(KSP_NODE));
node.Property.Set = { 0xA55751A1, 0xF3C5, 0x4A5E, {0x8D, 0x5A, 0x68, 0x54, 0xB8, 0xFA, 0x27, 0x16} };
node.Property.Set = reinterpret_cast<const GUID &>(xuUnit_.id);
node.Property.Id = ctrl;
node.Property.Flags = KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_TOPOLOGY;
node.NodeId = xuNodeId_;
@ -412,7 +418,7 @@ bool MSMFStreamChannel::getXu(uint8_t ctrl, uint8_t** data, uint32_t* len)
}
KSP_NODE node;
memset(&node, 0, sizeof(KSP_NODE));
node.Property.Set = { 0xA55751A1, 0xF3C5, 0x4A5E, {0x8D, 0x5A, 0x68, 0x54, 0xB8, 0xFA, 0x27, 0x16} };
node.Property.Set = reinterpret_cast<const GUID&>(xuUnit_.id);
node.Property.Id = ctrl;
node.Property.Flags = KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_TOPOLOGY;
node.NodeId = xuNodeId_;

View File

@ -314,7 +314,7 @@ bool V4L2StreamChannel::setXu(uint8_t ctrl, const uint8_t* data, uint32_t len)
}
memcpy(xuSendBuf_.data(), data, len);
struct uvc_xu_control_query xu_ctrl_query = {
.unit = XU_UNIT_ID,
.unit = xuUnit_.unit,
.selector = ctrl,
.query = UVC_SET_CUR,
.size = (__u16)(ctrl == 1 ? 512 : (ctrl == 2 ? 64 : 1024)),
@ -333,7 +333,7 @@ bool V4L2StreamChannel::getXu(uint8_t ctrl, uint8_t** data, uint32_t* len)
xuRecvBuf_.resize(XU_MAX_DATA_LENGTH);
}
struct uvc_xu_control_query xu_ctrl_query = {
.unit = XU_UNIT_ID,
.unit = xuUnit_.unit,
.selector = ctrl,
.query = UVC_GET_CUR,
.size = (__u16)(ctrl == 1 ? 512 : (ctrl == 2 ? 64 : 1024)),

View File

@ -35,6 +35,9 @@
namespace cv {
namespace obsensor {
const ObExtensionUnit OBSENSOR_COMMON_XU_UNIT = { XU_UNIT_ID_COMMON, { 0xA55751A1, 0xF3C5, 0x4A5E, { 0x8D, 0x5A, 0x68, 0x54, 0xB8, 0xFA, 0x27, 0x16 } } };
const ObExtensionUnit OBSENSOR_G330_XU_UNIT = { XU_UNIT_ID_G330, { 0xC9606CCB, 0x594C, 0x4D25, { 0xaf, 0x47, 0xcc, 0xc4, 0x96, 0x43, 0x59, 0x95 } } };
const uint8_t OB_EXT_CMD0[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0x52, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 };
const uint8_t OB_EXT_CMD1[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0x54, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const uint8_t OB_EXT_CMD2[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0x56, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 };
@ -48,6 +51,7 @@ const uint8_t OB_EXT_CMD9[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfa, 0x13
const uint8_t OB_EXT_CMD11[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfe, 0x13, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const uint8_t OB_EXT_CMD12[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfe, 0x13, 0x3f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 };
const uint8_t OB_EXT_CMD13[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfa, 0x13, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const uint8_t OB_EXT_CMD14[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfa, 0x14, 0xd3, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 };
#if defined(HAVE_OBSENSOR_V4L2)
#define fourCc2Int(a, b, c, d) \
@ -62,6 +66,7 @@ const std::map<uint32_t, FrameFormat> fourccToOBFormat = {
{fourCc2Int('M', 'J', 'P', 'G'), FRAME_FORMAT_MJPG},
{fourCc2Int('Y', '1', '6', ' '), FRAME_FORMAT_Y16},
{fourCc2Int('Y', '1', '4', ' '), FRAME_FORMAT_Y14},
{fourCc2Int('Z', '1', '6', ' '), FRAME_FORMAT_Y16}
};
StreamType parseUvcDeviceNameToStreamType(const std::string& devName)
@ -204,7 +209,6 @@ DepthFrameUnpacker::~DepthFrameUnpacker() {
delete[] outputDataBuf_;
}
#define ON_BITS(count) ((1 << count) - 1)
#define CREATE_MASK(count, offset) (ON_BITS(count) << offset)
#define TAKE_BITS(source, count, offset) ((source & CREATE_MASK(count, offset)) >> offset)
@ -245,6 +249,7 @@ void DepthFrameUnpacker::process(Frame *frame){
IUvcStreamChannel::IUvcStreamChannel(const UvcDeviceInfo& devInfo) :
devInfo_(devInfo),
xuUnit_(IS_OBSENSOR_GEMINI330_PID(devInfo.pid) ? OBSENSOR_G330_XU_UNIT : OBSENSOR_COMMON_XU_UNIT),
streamType_(parseUvcDeviceNameToStreamType(devInfo_.name))
{
@ -286,6 +291,11 @@ bool IUvcStreamChannel::setProperty(int propId, const uint8_t* /*data*/, uint32_
rst &= getXu(2, &rcvData, &rcvLen);
rst &= setXu(2, OB_EXT_CMD6, sizeof(OB_EXT_CMD6));
rst &= getXu(2, &rcvData, &rcvLen);
}else if(IS_OBSENSOR_GEMINI330_PID(devInfo_.pid)) {
rst &= setXu(2, OB_EXT_CMD6, sizeof(OB_EXT_CMD6));
rst &= getXu(2, &rcvData, &rcvLen);
rst &= setXu(2, OB_EXT_CMD14, sizeof(OB_EXT_CMD14));
rst &= getXu(2, &rcvData, &rcvLen);
}else{
rst &= setXu(2, OB_EXT_CMD0, sizeof(OB_EXT_CMD0));
rst &= getXu(2, &rcvData, &rcvLen);
@ -400,6 +410,42 @@ bool IUvcStreamChannel::getProperty(int propId, uint8_t* recvData, uint32_t* rec
*recvDataSize = sizeof(CameraParam);
memcpy(recvData, &param, *recvDataSize);
}
else if(IS_OBSENSOR_GEMINI330_SHORT_PID(devInfo_.pid)){
// return default param
CameraParam param;
param.p0[0] = 460.656f;
param.p0[1] = 460.782f;
param.p0[2] = 320.985f;
param.p0[3] = 233.921f;
param.p1[0] = 460.656f;
param.p1[1] = 460.782f;
param.p1[2] = 320.985f;
param.p1[3] = 233.921f;
param.p6[0] = 640;
param.p6[1] = 480;
param.p7[0] = 640;
param.p7[1] = 480;
*recvDataSize = sizeof(CameraParam);
memcpy(recvData, &param, *recvDataSize);
}
else if(IS_OBSENSOR_GEMINI330_LONG_PID(devInfo_.pid)){
// return default param
CameraParam param;
param.p0[0] = 366.751f;
param.p0[1] = 365.782f;
param.p0[2] = 319.893f;
param.p0[3] = 243.415f;
param.p1[0] = 366.751f;
param.p1[1] = 365.782f;
param.p1[2] = 319.893f;
param.p1[3] = 243.415f;
param.p6[0] = 640;
param.p6[1] = 480;
param.p7[0] = 640;
param.p7[1] = 480;
*recvDataSize = sizeof(CameraParam);
memcpy(recvData, &param, *recvDataSize);
}
else{
rst &= setXu(2, OB_EXT_CMD5, sizeof(OB_EXT_CMD5));
rst &= getXu(2, &rcvData, &rcvLen);
@ -453,7 +499,15 @@ bool IUvcStreamChannel::initDepthFrameProcessor()
setXu(2, OB_EXT_CMD13, sizeof(OB_EXT_CMD13));
getXu(2, &rcvData, &rcvLen);
return true;
}
else if(IS_OBSENSOR_GEMINI330_PID(devInfo_.pid))
{
uint8_t* rcvData;
uint32_t rcvLen;
setXu(2, OB_EXT_CMD7, sizeof(OB_EXT_CMD7));
getXu(2, &rcvData, &rcvLen);
return true;
}
else if(streamType_ == OBSENSOR_STREAM_DEPTH && setXu(2, OB_EXT_CMD4, sizeof(OB_EXT_CMD4)))

View File

@ -27,7 +27,8 @@
namespace cv {
namespace obsensor {
#define XU_MAX_DATA_LENGTH 1024
#define XU_UNIT_ID 4
#define XU_UNIT_ID_COMMON 4
#define XU_UNIT_ID_G330 3
struct UvcDeviceInfo
{
@ -46,6 +47,16 @@ enum StreamState
STREAM_STARTED = 2,
STREAM_STOPPING = 3,
};
struct Guid {
uint32_t data1;
uint16_t data2, data3;
uint8_t data4[8];
};
struct ObExtensionUnit {
uint8_t unit;
Guid id;
};
StreamType parseUvcDeviceNameToStreamType(const std::string& devName);
FrameFormat frameFourccToFormat(uint32_t fourcc);
@ -104,6 +115,7 @@ protected:
protected:
const UvcDeviceInfo devInfo_;
const ObExtensionUnit xuUnit_;
StreamType streamType_;
Ptr<IFrameProcessor> depthFrameProcessor_;
};

View File

@ -80,11 +80,9 @@ VideoCapture_obsensor::VideoCapture_obsensor(int index) : isOpened_(false)
obsensor::StreamProfile profile = depthProfile;
if(OBSENSOR_GEMINI2_PID == channel->getPid()){
profile = gemini2DepthProfile;
}
else if(OBSENSOR_ASTRA2_PID == channel->getPid()){
}else if(OBSENSOR_ASTRA2_PID == channel->getPid()){
profile = astra2DepthProfile;
}
else if(OBSENSOR_FEMTO_MEGA_PID == channel->getPid()){
}else if(OBSENSOR_FEMTO_MEGA_PID == channel->getPid()){
profile = megaDepthProfile;
}else if(OBSENSOR_GEMINI2L_PID == channel->getPid()){
profile = gemini2lDepthProfile;
@ -164,6 +162,11 @@ bool VideoCapture_obsensor::retrieveFrame(int outputType, OutputArray frame)
grabbedDepthFrame_(rect).copyTo(frame);
}else if(OBSENSOR_GEMINI2XL_PID == streamChannelGroup_.front()->getPid()){
grabbedDepthFrame_.copyTo(frame);
}else if(IS_OBSENSOR_GEMINI330_PID(streamChannelGroup_.front()->getPid())){
const double DepthValueScaleG300 = 1.0;
grabbedDepthFrame_ = grabbedDepthFrame_*DepthValueScaleG300;
Rect rect(0, 0, 640, 480);
grabbedDepthFrame_(rect).copyTo(frame);
}else{
grabbedDepthFrame_.copyTo(frame);
}