Astra Pro HD Camera 深度相机 Web 可视化完整研究报告

作者: HonestQiao
身份: 嵌入式 + AI + 机器人爱好者,喜欢瞎折腾
日期: 2026年3月24日(一个通宵的成果)
硬件平台: RDK X5 (地平线开发板)
相机型号: Orbbec Astra Pro (奥比中光)


一、项目概述

1.1 研究目标

之前在咸鱼上,收了一个奥比中光的Astra Pro,一直没用上,这次在RDK X5上给用上了。
这次折腾的目标是实现 Astra Pro 深度相机的 Web 端可视化,具体来说我想搞定这些:

  • 同时显示 RGB 彩色图像和深度图像
  • 深度图用伪彩色(heatmap)呈现,近的地方红一点(暖色),远的地方蓝一点(冷色)
  • 支持透明度调节,把深度叠在 RGB 上看效果
  • 额外整点花活:原始灰度深度图、网格距离标注(10×10)
  • 参数能动态调整,比如最远距离、无效像素颜色啥的

1.2 硬件环境

手头上的装备:

开发板: RDK X5 (D-Robotics 地瓜机器人)
相机: Orbbec Astra Pro (USB 2.0,闲鱼淘的二手货)
  - RGB: 1280x720 (标准UVC)
  - Depth: 640x480  (结构化光)
  - 深度范围: 0.4m ~ 8m
OS: Ubuntu 22.04 (Linux 6.1.83)

1.3 软件架构

整个系统的架构图基本如下:


二、研究过程记录(踩坑血泪史)

在AI的配合下,我进行了多番尝试,最终成功完成预期目标。

2.1 设备信息了解

通过v4l2,可以查看摄像头的信息:

$ v4l2-ctl --list-devices
Astra Pro HD Camera: Astra Pro  (usb-xhci-hcd.2.auto-1.2.1):
	/dev/video0
	/dev/video1
	/dev/media0

$ v4l2-ctl -d /dev/video0 --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
	Type: Video Capture

	[0]: 'YUYV' (YUYV 4:2:2)
		Size: Discrete 1280x720
			Interval: Discrete 0.100s (10.000 fps)
			Interval: Discrete 0.200s (5.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 352x288
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 320x240
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 176x144
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 160x120
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
	[1]: 'MJPG' (Motion-JPEG, compressed)
		Size: Discrete 1280x720
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 352x288
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 320x240
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 176x144
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 160x120
			Interval: Discrete 0.033s (30.000 fps)
			Interval: Discrete 0.067s (15.000 fps)

2.2 第一阶段:OpenCV 直接读取?想多了!(失败)

一开始我想得挺美,不就是个 USB 摄像头嘛,OpenCV 直接开干:

cap = cv2.VideoCapture(0)  # 试了 0、2、4、6,都失败了

结果发现 Astra Pro 在系统里其实是两个设备:

$ lsusb | grep Orbbec
Bus 002 Device 003: ID 2bc5:0403 Orbbec Astra Pro  # 深度
Bus 002 Device 004: ID 2bc5:0403 Orbbec Astra Pro  # RGB

原来这货的 RGB 是标准 UVC,能被 OpenCV 读走;但深度流走的是自定义协议(Vendor Specific Class),OpenCV 根本不认识。白忙活了半小时。

USB设备列表


2.3 第二阶段:OpenNI2 安装了,但是运行段错误

OpenCV 不行,那就上 OpenNI2 呗,深度相机通用驱动嘛:

pip install openni

写了个测试脚本,但是一直出现段错误:

python astra_openni_depth.py
============================================================
Astra Pro Depth Camera - OpenNI2 Version
============================================================
[INFO] 初始化 OpenNI2...
     3695 INFO       New log started on 2026-04-01 08:32:32
     3739 INFO       --- Filter Info --- Minimum Severity: VERBOSE
     3817 VERBOSE    No override device in configuration file
     3866 VERBOSE    Configuration has been read from '/etc/openni2/OpenNI.ini'
     3883 VERBOSE    OpenNI 2.2.0 (Build 33)-Linux-generic (Feb  5 2021 19:15:35)
     3896 VERBOSE    Extending the driver path by '/usr/lib/aarch64-linux-gnu/OpenNI2/Drivers', as configured in file '/etc/openni2/OpenNI.ini'
     3910 VERBOSE    Using '/usr/lib/aarch64-linux-gnu/OpenNI2/Drivers' as driver path
     3922 VERBOSE    Looking for drivers in drivers repository '/usr/lib/aarch64-linux-gnu/OpenNI2/Drivers'
    26631 INFO       New log started on 2026-04-01 08:32:32
    26669 INFO       --- Filter Info --- Minimum Severity: VERBOSE
    26692 VERBOSE    Initializing USB...
    39377 INFO       USB is initialized.
    47255 INFO       New log started on 2026-04-01 08:32:32
    47291 INFO       --- Filter Info --- Minimum Severity: VERBOSE
    47313 VERBOSE    Initializing USB...
    62507 INFO       USB is initialized.
    78575 INFO       New log started on 2026-04-01 08:32:32
    78611 INFO       --- Filter Info --- Minimum Severity: VERBOSE
    78632 VERBOSE    Initializing USB...
    94187 INFO       USB is initialized.
    94597 INFO       Device connected: Orbbec Astra (2bc5/0403@1/5)
[INFO] OpenNI2 初始化成功
    95195 VERBOSE    Trying to open device by URI '(NULL)'
    99156 VERBOSE    Initializing device sensor...
    99491 VERBOSE    Configuring module 'Device' from section 'Device' in file '/usr/lib/aarch64-linux-gnu/OpenNI2/Drivers/orbbec.ini'...
   100322 INFO       Setting Device.UsbInterface to 2...
   100343 INFO       Device.UsbInterface value did not change.
   102944 INFO       Module 'Device' configuration was loaded from file.
   102976 VERBOSE    Connecting to USB device...
   102990 VERBOSE    Trying to open sensor '2bc5/0403@1/5'...
   103087 INFO       Usb device opened status 0
   103104 INFO       libusb claim interface 0
   103165 INFO       libusb claim interface status 0
   103178 INFO       USB is high Speed
   103190 VERBOSE    Starting libusb asynch thread...
   103336 WARNING    Failed to set thread priority (1)
   103353 WARNING    USB events thread: Failed to set thread priority to critical. This might cause loss of data...
Warning: USB events thread - failed to set priority. This might cause loss of data...
   103375 VERBOSE    Trying to open endpoint 0x1 for control bulk out...
   103527 VERBOSE    Open endpoint 0x1 failed to USB endpoint not found on device!...
   103544 INFO       Connected to USB device
   114499 VERBOSE    Getting hardware versions...
  5326628 ERROR      Get version failed: USB transfer timeout!
  7326861 VERBOSE    Getting hardware versions...
 12449234 ERROR      Get version failed: USB transfer timeout!
 12449350 VERBOSE    Update supported color video modes from _imageBulkModes
 12449670 VERBOSE    Getting hardware versions...
 17561841 ERROR      Get version failed: USB transfer timeout!
 17561938 VERBOSE    Shutting down USB depth read thread...
 17561997 VERBOSE    Shutting down USB IR read thread...
 17562356 WARNING    USB device handle closed...
 17562425 VERBOSE    Shutting down USB events thread...
 17563569 VERBOSE    Device closed successfully
 17563595 VERBOSE    Shutting down Scheduler thread...
段错误

搞了半天,决定弃用 OpenNI2,专心搞 Astra SDK。


2.4 第三阶段:Astra SDK 安装与 Sample 测试

在正式写代码之前,得先把 Astra SDK 环境搭好,再跑通官方的 Sample 示例,确认硬件和驱动都没问题。

Astra SDK 安装步骤

参考博客:https://blog.csdn.net/m0_72797388/article/details/148432564
(注意:博客是 x86_64 的,我们是 ARM64/aarch64 平台,要下载对应版本)

1. 下载 SDK

去奥比中光开发者官网下载 Astra SDK for Linux ARM64(不是 x86_64):

注意硬件类型:RDK X5 是 ARM64/aarch64 架构,必须下载 ARM64 版本!

# 下载后解压到用户目录
cd ~
tar -xvf AstraSDK-v2.1.3-727c19e7e6-Linux-arm64.tar.gz
# 解压后会得到类似 AstraSDK-v2.1.3-xxx-Linux-arm64 的目录
# 可以重命名为 AstraSDK 方便使用
mv AstraSDK-v2.1.3-xxx-Linux-arm64 AstraSDK

2. 运行安装脚本

SDK 自带了安装脚本,会给 USB 设备添加 udev 规则:

cd ~/AstraSDK
chmod a+x install/install.sh
sudo ./install/install.sh

3. 配置环境变量

参考博客,需要设置 include 和 lib 路径。把以下内容加到 ~/.bashrc

# Astra SDK 环境变量(根据你的实际路径修改)
export ASTRA_SDK=/home/sunrise/AstraSDK
export ASTRA_SDK_INCLUDE=$ASTRA_SDK/include
export ASTRA_SDK_LIB=$ASTRA_SDK/lib
export LD_LIBRARY_PATH=$ASTRA_SDK_LIB:$ASTRA_SDK/lib/Plugins/openni2:$LD_LIBRARY_PATH

然后生效:

source ~/.bashrc

4. 验证安装

检查库文件是否存在:

ls -la $ASTRA_SDK/lib/
# 应该能看到:
# libastra_core.so、libastra.so、libastra_core_api.so 等

Sample 编译与测试

参考博客的编译步骤,但注意我们是 ARM64 平台。

1. 安装依赖

编译 Sample 需要安装以下依赖:

sudo apt update
sudo apt install -y build-essential cmake libsfml-dev

2. 创建必要的目录(关键步骤)

参考博客,SDK 的 CMake 配置有个坑,需要手动创建 tools 目录和空的 CMakeLists.txt:

cd $ASTRA_SDK/samples

# 创建编译目录
mkdir -p build
cd build

# 创建 tools 目录(没有这个目录 cmake 会报错)
mkdir -p ../tools
touch ../CMakeLists.txt

3. 编译 Sample

使用 CMake 编译,指定 -DASTRA_UNIX=TRUE 参数:

cd $ASTRA_SDK/samples/build

# 配置编译
cmake .. -DASTRA_UNIX=TRUE

# 编译(-j5 表示用5个线程,可以根据你的CPU核心数调整)
make -j5

4. 检查编译结果

编译完成后,检查 bin 目录:

ls -la $ASTRA_SDK/samples/build/bin/
# 应该能看到以下可执行文件:
# - SimpleColorViewer-SFML     (查看 RGB 画面)
# - SimpleDepthViewer-SFML     (查看深度画面)
# - SimpleStreamViewer-SFML    (同时查看多个画面)
# - SimpleBodyViewer-SFML      (人体骨架识别)

5. 运行测试

编译成功后,运行示例测试相机:

# 先设置环境变量
    export DISPLAY=:0
    export LD_LIBRARY_PATH=$ASTRA_SDK/lib:$ASTRA_SDK/lib/Plugins/openni2:$LD_LIBRARY_PATH

# 进入 bin 目录
cd $ASTRA_SDK/samples/build/bin/

# 运行深度查看示例
./SimpleDepthViewer-SFML

如果一切正常,会弹出一个窗口显示深度图:

  • 近的地方显示白暖色
  • 远的地方显示暗冷色
  • 黑色表示无效深度(遮挡或超出范围)

也可以测试其他示例:

./SimpleColorViewer-SFML      # 查看 RGB 彩色画面
./SimpleStreamViewer-SFML     # 同时查看深度和彩色

SimpleDepthViewer-SFML 运行截图

常见问题

问题1:cmake 报错找不到 tools 目录

CMake Error: File /home/sunrise/AstraSDK/samples/tools/CMakeLists.txt does not exist.

解决:

# 按照博客的方法,手动创建 tools 目录和空的 CMakeLists.txt
mkdir -p $ASTRA_SDK/samples/tools
touch $ASTRA_SDK/samples/CMakeLists.txt

问题2:编译报错找不到 SFML

Could not find SFML (missing: SFML_GRAPHICS_LIBRARY)

解决:

sudo apt install libsfml-dev

问题3:运行时报错打不开设备

failed to open device: Could not open "2bc5/0403@1/23"

解决:

  1. 检查 USB 连接:lsusb | grep Orbbec
  2. 检查权限:sudo chmod 666 /dev/bus/usb/002/0XX
  3. 检查是否有其他程序占用了相机(如之前的 OpenNI 程序)
  4. 尝试重新插拔 USB

问题3:能打开但没有图像显示

  • 确保 DISPLAY=:0 已设置
  • 如果是 SSH 连接,需要使用 X11 转发:ssh -X rdk_x5.local
  • 或者在本地显示器直接运行

2.5 第四阶段:ctypes 调用 Astra SDK(成功)

Astra SDK 是 C/C++ 的,没有 Python 版,得用 ctypes 硬调。

刚开始各种段错误,各种 NoneType,各种指针类型不匹配。C 语言那套 void*ctypes.byref() 搞得我头皮发麻。

好不容易调通的初始化代码:

import ctypes

# Astra SDK 路径设置
ASTRA_SDK_PATH = '/home/sunrise/AstraSDK'
ASTRA_LIB_PATH = f'{ASTRA_SDK_PATH}/lib'

# 加载库,RTLD_GLOBAL 很重要,不然会崩
ctypes.CDLL(f'{ASTRA_LIB_PATH}/libastra_core.so', ctypes.RTLD_GLOBAL)
astra_core = ctypes.CDLL(f'{ASTRA_LIB_PATH}/libastra_core.so')

# 初始化
rc = astra_core.astra_initialize()
if rc != 0:
    print(f"初始化失败: {rc}")  # 第一次跑的时候这里报了好多遍错

最坑的是:必须在本地终端跑,不能 SSH 挂后台!

我之前用 ssh rdk_x5.local "nohup python3 xxx &",设备死活打不开。后来才发现 Astra SDK 初始化需要 X11 Display,哪怕不显示窗口也要。得先 export DISPLAY=:0

关键成功步骤:

  1. 环境变量设置
export DISPLAY=:0
export LD_LIBRARY_PATH=/home/sunrise/AstraSDK/lib:/home/sunrise/AstraSDK/lib/Plugins/openni2:$LD_LIBRARY_PATH
  1. 相机热插拔
  • 如果相机被异常占用,需要物理重新插拔 USB
  • 运行前确保没有其他程序占用设备

2.6 第五阶段:Web 可视化开发(迭代优化)

最终的代码,请查看:HonestQiao/rdk-x5-Orbbec-Astra-Pro: Astra Pro HD Camera 深度相机 Web 可视化

迭代记录

版本 时间 主要改进 状态
v0.1 3/24 20:00 基础 WebSocket 传输,模拟数据 :white_check_mark: 基础验证
v0.2 3/24 21:00 接入真实深度数据,ctypes 调用 :white_check_mark: 数据获取
v0.3 3/24 22:00 添加 RGB 叠加显示 :warning: 颜色映射错误
v0.4 3/24 22:30 修复颜色映射(JET 色图) :white_check_mark: 颜色正确
v0.5 3/24 23:00 添加灰度图和 5×5 网格 :white_check_mark: 功能完善
v2.0 3/25 00:27 10×10 网格、参数可调、最终版 :white_check_mark: 成功

颜色映射的调试历程

问题:颜色与预期相反

预期:近=红,远=蓝
实际:近=蓝,远=红

原因:OpenCV COLORMAP_JET 的映射方向是反的:

  • 输入 255(白)→ 输出红色
  • 输入 0(黑)→ 输出蓝色

修复:反转归一化方向

# 正确做法:近=min_dist → 255(白) → JET红色
#          远=max_dist → 0(黑)   → JET蓝色
normalized = 255 - np.clip((depth - min_dist) / (max_dist - min_dist) * 255, 0, 255)
depth_colored = cv2.applyColorMap(normalized.astype(np.uint8), cv2.COLORMAP_JET)

三、最终成果

3.1 功能特性

主显示区域(叠加视图)

  • 底层:RGB 彩色摄像头画面
  • 上层:深度热图(JET 伪彩色,红=近,蓝=远)
  • 透明度调节:0% ~ 100% 滑块控制
  • 独立开关:可单独隐藏 RGB 或深度层

主界面截图(RGB + Depth 叠加

原始视图区域

  • :RGB 原图(原始分辨率 640×480)
  • :深度热图(JET 色图)

原始视图区域截图

分析视图区域

  • :灰度深度图(原始深度值可视化)

    • 近(0.3m)= 白色/亮
    • 远(>max_dist)= 黑色/暗
  • :10×10 网格距离图

    • 每个网格中心点显示实际距离(米)
    • 颜色标注:红(<0.6m)、绿(0.6-1.2m)、蓝(>1.2m)

灰度图和网格图截图

3.2 命令行参数

python3 astra_web_rgb_depth_v2.py [选项]

选项:
  --max-dist DIST       最远距离(毫米),默认 8000
                        影响颜色映射和灰度图显示范围

  --invalid-color COLOR 无效深度像素颜色,可选 black/white,默认 black
                        (深度为0的区域,通常是遮挡或超出范围)

示例:
  # 默认跑(8米范围,无效=黑)
  python3 astra_web_rgb_depth_v2.py

  # 只看2米内物体,无效深度显示白色
  python3 astra_web_rgb_depth_v2.py --max-dist 2000 --invalid-color white

  # 大范围扫描模式
  python3 astra_web_rgb_depth_v2.py --max-dist 10000

不同 --max-dist 参数的效果对比(2000 vs 8000)


四、技术实现细节

4.1 Astra Pro 相机封装 - RGB + Depth

class AstraCamera:
    """Astra Pro 相机封装 - RGB + Depth"""
    def __init__(self):
        self.sensor = astra_streamsetconnection_t()
        self.reader = astra_reader_t()
        self.depth_stream = astra_depthstream_t()
        self.color_stream = astra_colorstream_t()
        self.initialized = False
        self.depth_width = 640
        self.depth_height = 480
        self.color_width = 640
        self.color_height = 480
        self.has_color = False
        self.rgb_cap = None
        self.use_opencv_rgb = False

    def initialize(self):
        print("[INFO] 初始化 Astra SDK...")
        rc = astra_core.astra_initialize()
        if rc != 0:
            print(f"[ERROR] astra_initialize failed: {rc}")
            return False
        print("[INFO] Astra SDK 初始化成功")

        rc = astra_core.astra_streamset_open(b"device/default", ctypes.byref(self.sensor))
        if rc != 0:
            print(f"[ERROR] astra_streamset_open failed: {rc}")
            astra_core.astra_terminate()
            return False
        print(f"[INFO] 设备已打开")

        rc = astra_core.astra_reader_create(self.sensor, ctypes.byref(self.reader))
        if rc != 0:
            print(f"[ERROR] astra_reader_create failed: {rc}")
            astra_core.astra_streamset_close(self.sensor)
            astra_core.astra_terminate()
            return False
        print("[INFO] Reader 创建成功")

        # 获取深度流
        rc = astra.astra_reader_get_depthstream(self.reader, ctypes.byref(self.depth_stream))
        if rc != 0:
            print(f"[ERROR] astra_reader_get_depthstream failed: {rc}")
            astra_core.astra_reader_destroy(ctypes.byref(self.reader))
            astra_core.astra_streamset_close(self.sensor)
            astra_core.astra_terminate()
            return False
        print("[INFO] 深度流获取成功")

        # 启动深度流
        rc = astra_core.astra_stream_start(self.depth_stream)
        if rc != 0:
            print(f"[ERROR] astra_stream_start (depth) failed: {rc}")
            astra_core.astra_reader_destroy(ctypes.byref(self.reader))
            astra_core.astra_streamset_close(self.sensor)
            astra_core.astra_terminate()
            return False
        print("[INFO] 深度流已启动")

        # 尝试获取彩色流 (Astra SDK 方式)
        try:
            rc = astra.astra_reader_get_colorstream(self.reader, ctypes.byref(self.color_stream))
            if rc == 0:
                print("[INFO] 彩色流获取成功 (Astra SDK)")
                rc = astra_core.astra_stream_start(self.color_stream)
                if rc == 0:
                    print("[INFO] 彩色流已启动 (Astra SDK)")
                    self.has_color = True
                else:
                    print(f"[WARN] 彩色流启动失败 (Astra SDK): {rc}")
                    self.color_stream = None
            else:
                print(f"[INFO] 彩色流不可用 (Astra SDK): {rc}")
                self.color_stream = None
        except Exception as e:
            print(f"[INFO] 彩色流初始化异常 (Astra SDK): {e}")
            self.color_stream = None

        # 如果 Astra SDK 方式不行,尝试 OpenCV UVC 方式
        if not self.has_color:
            print("[INFO] 尝试通过 OpenCV 打开 RGB 摄像头...")
            for device_id in [4, 2, 0, 6, 8]:
                self.rgb_cap = cv2.VideoCapture(device_id)
                if self.rgb_cap.isOpened():
                    self.rgb_cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
                    self.rgb_cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
                    self.rgb_cap.set(cv2.CAP_PROP_FPS, 30)
                    ret, test_frame = self.rgb_cap.read()
                    if ret and test_frame is not None:
                        print(f"[INFO] RGB 摄像头已打开 (OpenCV /dev/video{device_id})")
                        self.use_opencv_rgb = True
                        self.has_color = True
                        break
                    else:
                        self.rgb_cap.release()
                        self.rgb_cap = None

            if not self.use_opencv_rgb:
                print("[WARN] 无法通过 OpenCV 打开 RGB 摄像头,将只显示深度")

        self.initialized = True
        return True

    def read_frames(self):
        """读取 RGB 和深度帧"""
        if not self.initialized:
            return None, None

        depth_array = None
        color_array = None

        # 如果使用 OpenCV 读取 RGB
        if self.use_opencv_rgb and self.rgb_cap:
            ret, color_array = self.rgb_cap.read()
            if ret and color_array is not None:
                if color_array.shape[:2] != (480, 640):
                    color_array = cv2.resize(color_array, (640, 480))

        # 通过 Astra SDK 读取深度 (和可能的 RGB)
        astra_core.astra_update()
        frame = astra_reader_frame_t()
        rc = astra_core.astra_reader_open_frame(self.reader, 100, ctypes.byref(frame))

        if rc == 0:
            try:
                # 读取深度帧
                depth_frame = astra_depthframe_t()
                rc = astra.astra_frame_get_depthframe(frame, ctypes.byref(depth_frame))
                if rc == 0:
                    depth_length = ctypes.c_uint32()
                    astra.astra_depthframe_get_data_byte_length(depth_frame, ctypes.byref(depth_length))
                    if depth_length.value > 0:
                        num_pixels = depth_length.value // 2
                        depth_buffer = (ctypes.c_int16 * num_pixels)()
                        astra.astra_depthframe_copy_data(depth_frame, depth_buffer)
                        metadata = AstraImageMetadata()
                        astra.astra_depthframe_get_metadata(depth_frame, ctypes.byref(metadata))
                        self.depth_width = metadata.width
                        self.depth_height = metadata.height
                        depth_array = np.ctypeslib.as_array(depth_buffer).copy()
                        depth_array = depth_array.reshape((self.depth_height, self.depth_width))

                # 如果通过 Astra SDK 读取 RGB
                if not self.use_opencv_rgb and self.has_color and self.color_stream:
                    color_frame = astra_colorframe_t()
                    rc = astra.astra_frame_get_colorframe(frame, ctypes.byref(color_frame))
                    if rc == 0:
                        color_length = ctypes.c_uint32()
                        astra.astra_colorframe_get_data_byte_length(color_frame, ctypes.byref(color_length))
                        if color_length.value > 0:
                            num_pixels = color_length.value // 3
                            color_buffer = (ctypes.c_uint8 * color_length.value)()
                            astra.astra_colorframe_copy_data(color_frame, color_buffer)
                            color_metadata = AstraImageMetadata()
                            astra.astra_colorframe_get_metadata(color_frame, ctypes.byref(color_metadata))
                            self.color_width = color_metadata.width
                            self.color_height = color_metadata.height
                            color_array = np.ctypeslib.as_array(color_buffer).copy()
                            color_array = color_array.reshape((self.color_height, self.color_width, 3))
                            color_array = cv2.cvtColor(color_array, cv2.COLOR_RGB2BGR)
            finally:
                astra_core.astra_reader_close_frame(ctypes.byref(frame))

        return color_array, depth_array

    def release(self):
        if not self.initialized:
            return
        print("[INFO] 释放资源...")
        if self.has_color and self.color_stream:
            astra_core.astra_stream_stop(self.color_stream)
        astra_core.astra_stream_stop(self.depth_stream)
        astra_core.astra_reader_destroy(ctypes.byref(self.reader))
        astra_core.astra_streamset_close(self.sensor)
        astra_core.astra_terminate()
        if self.rgb_cap:
            self.rgb_cap.release()
        self.initialized = False
        print("[INFO] 资源已释放")

4.2 深度彩色化算法

def colorize_depth(depth, min_dist=300):
    """
    深度图彩色化 - JET色图: 近=暖色(红), 远=冷色(蓝)

    参数:
        depth: 原始深度图(毫米,numpy数组)
        min_dist: 最近距离(毫米),默认300mm
        MAX_DIST: 全局变量,由 --max-dist 设置

    返回:
        BGR彩色图(OpenCV格式)
    """
    valid_mask = depth > 0
    h, w = depth.shape

    # 输出归一化数组(0-255)
    depth_norm = np.zeros((h, w), dtype=np.uint8)

    if np.any(valid_mask):
        depth_valid = depth[valid_mask].astype(np.float32)

        # 关键:反转映射
        # 近(min_dist) -> 255 -> JET红色
        # 远(MAX_DIST) -> 0   -> JET蓝色
        normalized = 255 - np.clip(
            (depth_valid - min_dist) / (MAX_DIST - min_dist) * 255,
            0, 255
        )
        depth_norm[valid_mask] = normalized.astype(np.uint8)

    # 应用JET色图
    depth_colored = cv2.applyColorMap(depth_norm, cv2.COLORMAP_JET)

    # 无效区域处理
    depth_colored[~valid_mask] = INVALID_COLOR

    return depth_colored

4.3 网格距离计算

def get_grid_distances(depth, cols=10, rows=10):
    """
    计算网格中心点的实际距离

    返回: 长度为 cols*rows 的列表,单位米
    """
    h, w = depth.shape
    distances = []

    for row in range(rows):
        for col in range(cols):
            # 计算网格中心坐标
            cy = int((row + 0.5) * h / rows)
            cx = int((col + 0.5) * w / cols)

            dist_mm = depth[cy, cx]

            if dist_mm > 0:
                dist_m = min(dist_mm / 1000.0, 10.0)
            else:
                dist_m = 0.0  # 无效

            distances.append(dist_m)

    return distances

4.4 前端 Canvas 叠加渲染

// 两层 Canvas 叠加
const rgbCanvas = document.getElementById('rgbCanvas');      // 底层
const depthCanvas = document.getElementById('depthCanvas');  // 上层

// 通过 CSS opacity 控制透明度(0.0 ~ 1.0)
depthCanvas.style.opacity = currentAlpha;

// 绘制流程
rgbCtx.drawImage(rgbImage, 0, 0);
depthCtx.drawImage(depthImage, 0, 0);

// 网格绘制(独立 Canvas)
function drawGrid(distances) {
    const cols = 10, rows = 10;
    // 绘制网格线...
    // 在每个格子中心显示距离数值
}

五、文件清单说明

完整的代码,请查看:HonestQiao/rdk-x5-Orbbec-Astra-Pro: Astra Pro HD Camera 深度相机 Web 可视化

项目目录结构

~/Projects/usb_hobot_stereonet/
├── astra_web_rgb_depth_v2.py  # ⭐ 主程序(最终版本,用这个)
│
├── 📁 开发过程文件(历史版本,留着复盘用)
│   ├── run_astra_ctypes.sh        # ⭐ 测试脚本
│   ├── setup_astra_openni.sh      # OpenNI 安装脚本(备用)
│   ├── astra_web_ctypes.py        # v0.2 - ctypes 基础版
│   ├── astra_web_rgb_depth.py     # v0.5 - 5×5网格版
│   ├── astra_web_opencv.py        # OpenCV 尝试(失败案例)
│   ├── astra_web_overlay.py       # 早期叠加测试
│   ├── astra_web_server.py        # 早期 WebSocket 测试
│   ├── astra_web_visual.py        # 可视化实验
│   ├── astra_ctypes_depth.py      # 纯深度读取测试
│   ├── astra_openni_depth.py      # OpenNI2 版本(冲突案例)
│   ├── test_astra_simple.py       # 简单连接测试
│   ├── astra_depth_ai.py          # AI 检测实验
│   ├── astra_depth_ai_v2.py       # AI 检测 v2
│   ├── astra_depth_only.py        # 仅深度流测试
│   └── run_depth_detection.sh     # 旧运行脚本

历史版本文件

这些文件记录了开发过程中的各个阶段,对复盘和学习有价值:

文件 阶段 说明
astra_web_opencv.py 第1阶段 OpenCV 尝试(失败案例)
astra_openni_depth.py 第2阶段 OpenNI2 版本(冲突案例)
astra_ctypes_depth.py 第4阶段 ctypes 基础(成功关键)
astra_web_ctypes.py 第5阶段 Web 可视化初版
astra_web_rgb_depth.py 第5阶段 v1 版本(5×5网格)
astra_web_rgb_depth_v2.py 第5阶段 最终版(10×10网格)

文件目录截图展示


六、常见问题与解决方案

Q1: 设备无法打开

[ERROR] astra_streamset_open failed: 1

排查步骤:

  1. 检查 USB 连接:lsusb | grep Orbbec
  2. 检查设备权限:ls -la /dev/bus/usb/002/0XX
  3. 尝试热插拔相机
  4. 确保没有另一个程序占用(如 SimpleDepthViewer-SFML)
  5. 检查 X11 环境:echo $DISPLAY 应输出 :0

Q2: WebSocket 连接失败 / TIME_WAIT 过多

bind: Address already in use

解决方案(已集成到系统参数):

# 快速回收 TIME_WAIT 连接
sudo sysctl -w net.ipv4.tcp_tw_reuse=1
sudo sysctl -w net.ipv4.tcp_fin_timeout=10

Q3: 深度图颜色与预期相反

  • 检查归一化公式是否正确(应该是 255 - normalized
  • 确认 JET 色图的映射方向

Q4: 浏览器画面全黑但数据正常

  • 检查 Canvas 的 widthheight 是否设置
  • 确保图像加载完成后再绘制(img.onload 回调)

七、参考资料

官方文档

  1. Astra SDK 文档

  2. RDK X5 官方文档

  3. OpenCV 文档

技术博客与社区

  1. 双目相机深度彩色化原理

  2. 奥比中光Astra Pro在ubuntu22下运行Astra SDK例程

  3. Astra Pro 开发记录

  4. ctypes 调用 C 库教程

相关论文/标准

  1. OpenNI 2.0 规范

    • OpenNI 2 API Reference
  2. 深度相机校准原理

    • Structured Light Depth Sensing
  3. 原始帖子


八、扩展思路(以后可以接着折腾)

可继续研究的方向

  1. AI 目标检测集成

    • astra_depth_ai.py 基础上,结合深度信息进行 3D 目标定位
    • 用 FCOS/YOLO 检测物体,在深度图上标注距离
  2. 点云可视化

    • 把深度图转成 3D 点云(Point Cloud)
    • 用 Three.js 在浏览器里实现 3D 点云展示
  3. 手势识别

    • 基于深度图做手势检测(手掌距离、手指关键点)
  4. SLAM 建图

    • 结合 RDK X5 的 NPU,跑实时 SLAM
  5. 多相机同步

    • 同时接多个 Astra Pro,做全景深度扫描

九、总结

这次折腾从晚上 8 点搞到凌晨 12 点半,历时 4 个多小时。踩了一堆坑,但最后看到浏览器里显示出彩色深度图的时候,还是挺有成就感的。

阶段 结果 关键收获
OpenCV 直接读取 :cross_mark: 失败 Astra Pro 深度流需要专用驱动
OpenNI2 独立安装 :warning: 冲突 SDK 间会抢设备,只能选一个
Astra SDK + ctypes :white_check_mark: 成功 掌握了无官方 Python SDK 时的调用方法
Web 可视化迭代 :white_check_mark: 优化 搞定了颜色映射、透明度、网格等细节

最终成果

  • 一个稳定运行的 Web 可视化工具
  • 完整的 ctypes 调用 Astra SDK 的参考代码
  • 这份踩坑记录(希望能帮到其他人)

给其他开发者的建议

  1. USB 设备问题,先 lsusb 看设备在不在
  2. 多 SDK 冲突时,优先用官方原生 SDK
  3. Web 实时视频,WebSocket + Base64 JPEG 最简单(适合内网)
  4. 颜色映射注意方向,先打印归一化值验证

研究开发就是这样,有时候资料少、坑多、调试麻烦。但当你把一个个问题解决掉,看到一切按你的想法动起来的时候,那种快乐是不可多得的。

3 个赞