OpenCV 如何接入 Orbbec UVC 3D 相机读取深度与彩色图像?
本文解决的任务是:在 OpenCV 中接入基于 UVC 协议的 Orbbec 3D 相机,持续读取相机的彩色(BGR)图像与深度图(单位毫米)。与依赖 OpenNI 的旧款 Orbbec 相机不同,UVC 协议相机不需要安装 Orbbec SDK 即可通过 cv::VideoCapture(C++ 中为 cv::VideoCapture)访问,用法与访问普通 USB 相机类似;深度图与彩色图像的标定和对齐由 OpenCV 内部完成。
适用前提(来自官方教程 orbbec_uvc.markdown):
- 兼容性要求 OpenCV >= 4.10;
- 旧款依赖 OpenNI 的 Orbbec 相机(如 Astra)不在本文范围内,需使用 OpenNI2 SDK 构建的 OpenCV,参见 orbbec_astra_openni.markdown;
- Mac 用户运行示例代码需要
sudo权限; - 请确保相机固件已更新到最新版本,以避免潜在的兼容性问题。
构建:什么平台需要加 Orbbec SDK 编译开关
先判断你的平台是否需要重新编译 OpenCV:
- Windows / Linux:默认构建的 OpenCV 即可通过系统接口(Windows 上为 MSMF,Linux 上为 V4L2)访问 Orbbec UVC 相机,直接使用官方安装即可,教程建议参考 OpenCV 官方的 Get Started 安装方式;
- macOS(OpenCV 4.11 及以上):必须从源码编译 OpenCV,并打开
-DOBSENSOR_USE_ORBBEC_SDK=ON开关:
cmake -DOBSENSOR_USE_ORBBEC_SDK=ON ..
make
sudo make install
关于这个开关的两个关键事实(均来自教程):
- 默认启用
-DOBSENSOR_USE_ORBBEC_SDK=ON时,使用的是 OrbbecSDK v2(即ORBBEC_SDK_VERSION默认为2),支持整个 Orbbec Gemini 330 系列; - 如果需要 Femto、Gemini2XL、Astra+ 这类旧款相机,则切换到 OrbbecSDK v1:
cmake -DOBSENSOR_USE_ORBBEC_SDK=ON -DORBBEC_SDK_VERSION=1 ..
make -j
sudo make install
构建脚本 detect_obsensor.cmake 印证了上述平台差异:未启用该开关时,Windows 需要 mfapi.h、vidcap.h(MSMF 路径),Unix 需要 linux/videodev2.h(V4L2 路径);启用开关时则通过 3rdparty/orbbecsdk/orbbecsdk.cmake 下载并链接 OrbbecSDK。
Python 最短路径:打开相机并读取 BGR 与深度图
官方示例位于 videocapture_obsensor.py,完整代码如下:
#!/usr/bin/env python
import numpy as np
import sys
import cv2 as cv
def main():
# Open Orbbec depth sensor
orbbec_cap = cv.VideoCapture(0, cv.CAP_OBSENSOR)
if orbbec_cap.isOpened() == False:
sys.exit("Fail to open camera.")
while True:
# Grab data from the camera
if orbbec_cap.grab():
# RGB data
ret_bgr, bgr_image = orbbec_cap.retrieve(None, cv.CAP_OBSENSOR_BGR_IMAGE)
if ret_bgr:
cv.imshow("BGR", bgr_image)
# depth data
ret_depth, depth_map = orbbec_cap.retrieve(None, cv.CAP_OBSENSOR_DEPTH_MAP)
if ret_depth:
color_depth_map = cv.normalize(depth_map, None, 0, 255, cv.NORM_MINMAX, cv.CV_8UC1)
color_depth_map = cv.applyColorMap(color_depth_map, cv.COLORMAP_JET)
cv.imshow("DEPTH", color_depth_map)
else:
print("Fail to grab data from the camera.")
if cv.pollKey() >= 0:
break
orbbec_cap.release()
if __name__ == '__main__':
main()
按教程对代码的说明,各步骤的作用与判断条件是:
cv.VideoCapture(0, cv.CAP_OBSENSOR)打开第一个 Orbbec 深度传感器设备;isOpened()返回False时程序退出并显示Fail to open camera.,这是设备打开失败的直接判据;- 主循环中先调用
orbbec_cap.grab()抓帧;grab()成功返回True才继续读取数据,失败则打印Fail to grab data from the camera.; orbbec_cap.retrieve(None, cv.CAP_OBSENSOR_BGR_IMAGE)取 BGR 彩色图像,成功显示在BGR窗口;orbbec_cap.retrieve(None, cv.CAP_OBSENSOR_DEPTH_MAP)取深度图。示例先归一化到 0~255,再用cv.applyColorMap(COLORMAP_JET)伪彩色显示在DEPTH窗口;cv.pollKey() >= 0(按下任意键)退出循环,最后orbbec_cap.release()释放相机资源。
注意:彩色流与深度流来自同一次 grab(),两个 retrieve() 在同一个循环体内分别取走各自通道的数据,不需要各调一次 grab()。
C++ 路径:CAP_OBSENSOR、内参读取与深度范围截断
C++ 官方示例位于 videocapture_obsensor.cpp。示例支持 6 个可选命令行参数来指定流参数(均为可选,不带参数时使用默认配置):
| 参数 | 作用 | 对应的属性 |
|---|---|---|
--dw / --dh / --df |
深度流宽度 / 高度 / 帧率 | CAP_PROP_OBSENSOR_DEPTH_WIDTH 等 |
--cw / --ch / --cf |
彩色流宽度 / 高度 / 帧率 | CAP_PROP_FRAME_WIDTH 等 |
核心流程(省略逐像素叠加部分,完整版见上述示例文件):
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace cv;
int main(int argc, char** argv)
{
cv::CommandLineParser parser(argc, argv,
"{help h ? | | help message}"
"{dw | | depth width }"
"{dh | | depth height }"
"{df | | depth fps }"
"{cw | | color width }"
"{ch | | color height }"
"{cf | | depth fps }"
);
if (parser.has("help"))
{
parser.printMessage();
return 0;
}
std::vector<int> params;
if (parser.has("dw"))
{
params.push_back(CAP_PROP_OBSENSOR_DEPTH_WIDTH);
params.push_back(parser.get<int>("dw"));
}
if (parser.has("dh"))
{
params.push_back(CAP_PROP_OBSENSOR_DEPTH_HEIGHT);
params.push_back(parser.get<int>("dh"));
}
if (parser.has("df"))
{
params.push_back(CAP_PROP_OBSENSOR_DEPTH_FPS);
params.push_back(parser.get<int>("df"));
}
if (parser.has("cw"))
{
params.push_back(CAP_PROP_FRAME_WIDTH);
params.push_back(parser.get<int>("cw"));
}
if (parser.has("ch"))
{
params.push_back(CAP_PROP_FRAME_HEIGHT);
params.push_back(parser.get<int>("ch"));
}
if (parser.has("cf"))
{
params.push_back(CAP_PROP_FPS);
params.push_back(parser.get<int>("cf"));
}
VideoCapture obsensorCapture;
if (params.empty())
obsensorCapture.open(0, CAP_OBSENSOR);
else
obsensorCapture.open(0, CAP_OBSENSOR, params);
if(!obsensorCapture.isOpened()) {
std::cerr << "Failed to open obsensor capture! Index out of range or no response from device";
return -1;
}
double fx = obsensorCapture.get(CAP_PROP_OBSENSOR_INTRINSIC_FX);
double fy = obsensorCapture.get(CAP_PROP_OBSENSOR_INTRINSIC_FY);
double cx = obsensorCapture.get(CAP_PROP_OBSENSOR_INTRINSIC_CX);
double cy = obsensorCapture.get(CAP_PROP_OBSENSOR_INTRINSIC_CY);
double k1 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_K1);
double k2 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_K2);
double k3 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_K3);
double k4 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_K4);
double k5 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_K5);
double k6 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_K6);
double p1 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_P1);
double p2 = obsensorCapture.get(CAP_PROP_OBSENSOR_COLOR_DISTORTION_P1);
std::cout << "obsensor camera intrinsic params: fx=" << fx << ", fy=" << fy << ", cx=" << cx << ", cy=" << cy << std::endl;
std::cout << "obsensor camera distortion params: k,p=" << k1 << ", " << k2 << ", " << k3 << ", "
<< k4 << ", " << k5 << ", " << k6 << ", "
<< p1 << ", " << p2 << std::endl;
Mat image;
Mat depthMap;
Mat adjDepthMap;
// Minimum depth value
const double minVal = 300;
// Maximum depth value
const double maxVal = 5000;
while (true)
{
if (obsensorCapture.grab())
{
if (obsensorCapture.retrieve(image, CAP_OBSENSOR_BGR_IMAGE))
{
imshow("RGB", image);
}
if (obsensorCapture.retrieve(depthMap, CAP_OBSENSOR_DEPTH_MAP))
{
depthMap.convertTo(adjDepthMap, CV_8U, 255.0 / (maxVal - minVal), -minVal * 255.0 / (maxVal - minVal));
applyColorMap(adjDepthMap, adjDepthMap, COLORMAP_JET);
imshow("DEPTH", adjDepthMap);
}
// 完整版示例在此处把深度图(alpha=0.6 透明叠加)覆盖到 BGR 图像上,
// 显示在 "DepthToColor" 窗口,逐像素实现见示例文件原文
}
if (pollKey() >= 0)
break;
}
return 0;
}
与 Python 版相比,C++ 示例多了两块内容:
- 内参与畸变系数:打开相机后通过
obsensorCapture.get()读取fx/fy/cx/cy以及k1~k6、p1、p2彩色畸变参数并打印,供后续做投影或校正时直接使用; - 深度范围截断:示例取
minVal = 300、maxVal = 5000,把深度值线性转换到 8 位。按教程解释,取回的真实深度值单位是毫米,这个 300~5000 毫米的固定范围应理解为按深度相机的测距范围做的截断,用于剔除深度图中的无效像素——它不是 API 的硬性限制,只是示例的展示处理方式。
打开失败时的判据是 isOpened() 为 false,此时输出 Failed to open obsensor capture! Index out of range or no response from device。
结果验证
两种语言的验证方式一致:
- 打开阶段:
isOpened()通过(Python 不退出、C++ 不打印打开失败信息); - 运行阶段:屏幕上出现彩色窗口(Python 示例窗口名
BGR,C++ 示例窗口名RGB)和伪彩色深度窗口(DEPTH);C++ 示例还有DepthToColor叠加窗口; - 数据阶段:
retrieve()返回值为真才显示对应通道;深度数据按毫米解释,C++ 示例中经convertTo线性映射为 8 位图再伪彩色化。
教程给出的官方运行截图如下(文档示例),左/上为彩色帧,右/下为深度帧:
按任意键(pollKey() 检测到按键)退出循环,release() 释放相机资源。
排查与限制
按教程明确给出的条件逐项核对:
| 现象 | 文档给出的条件 / 处理 |
|---|---|
打开相机失败(Fail to open camera. / Failed to open obsensor capture!) |
确认 OpenCV 版本 >= 4.10;Mac 4.11+ 确认编译时加了 -DOBSENSOR_USE_ORBBEC_SDK=ON;Mac 上运行代码需要 sudo 权限 |
| 兼容性异常 | 确认相机固件已更新到最新版本(教程要求,具体版本信息以 Orbbec 官方 Release Notes 为准) |
| 旧款相机(Femto、Gemini2XL、Astra+) | 需 -DORBBEC_SDK_VERSION=1 切换到 OrbbecSDK v1;默认 v2 仅保证覆盖 Gemini 330 系列 |
| Astra2 相机 | C++ 示例头部注明:Astra2 目前仅支持 Windows 和内核版本不高于 4.15 的 Linux,更高版本内核可能出现异常 |
| 旧款 OpenNI 协议相机(Astra 等) | 不属于 UVC 路径,需要 OpenNI2 SDK 构建的 OpenCV,见 orbbec_astra_openni.markdown |
另外两个来自 videoio.hpp 头文件的事实,写代码时可以直接引用:
CAP_OBSENSOR = 2600,注释同时列出了支持的设备范围(Astra+、Femto、Astra2、Gemini2、Gemini2L、Gemini2XL、Gemini330、Femto Mega);retrieve()支持三个通道:CAP_OBSENSOR_DEPTH_MAP(深度值,单位 mm,CV_16UC1)、CAP_OBSENSOR_BGR_IMAGE(BGR 流)、CAP_OBSENSOR_IR_IMAGE(红外流,CV_16UC1)。
下一步
- 需要红外通道时,可在同一
grab()循环内增加retrieve(..., CAP_OBSENSOR_IR_IMAGE); - 需要调整出流分辨率/帧率时,使用 C++ 示例的
--dw/--dh/--df(深度)与--cw/--ch/--cf(彩色) 命令行参数; - 相机标定、深度与彩色对齐已由 OpenCV 内部完成,打开相机后直接读取
CAP_PROP_OBSENSOR_INTRINSIC_*属性即可拿到内参,无需自行标定。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

