19f02ba8305e1.png

来源:第四届 3D 视觉创新应用竞赛 | 华南理工大学 | 毛一非 项目名称:基于奥比中光 3D 相机的识别取物机器人

在智能制造、自动化分拣和移动机器人场景中,机器人的关键能力正在从“能动”走向“会看、会判断、会执行”。华南理工大学团队基于奥比中光 Gemini 2 双目结构光 3D 相机、NVIDIA Jetson Orin Nano 开发板和自研运动控制系统,搭建了一套面向颜色识别、距离测量与目标抓取的精准智能抓取系统。

这套系统能够通过 RGB-D 视觉数据识别红、蓝目标物体,并结合深度信息估算目标距离,再把识别结果转化为底盘移动、取球、升降、投放等动作。它不是单纯的视觉识别 Demo,而是一套从感知、计算到控制闭环都较完整的中大型机器人方案。

一、项目概述

随着工业自动化和智能制造快速发展,机器人已经成为提升生产效率、稳定产品质量和减少重复劳动的重要工具。尤其在电子组装、食品分拣、制药、资源回收等场景中,系统不仅需要“移动到位”,还需要准确识别目标、判断距离,并稳定完成抓取或搬运。

本项目面向这一类需求,开发了一套基于 3D 视觉的识别取物机器人。它通过双目结构光相机获取彩色图像和深度图像,利用颜色差分与局部深度采样算法识别目标球体,再由 Jetson Orin Nano 完成视觉处理,并通过 STM32 控制底盘、电机、舵机等执行机构。

相关资源

🔗 开源代码:RobotV_Identify-and-retrieve-items

📖奥比中光开发文档:Orbbec SDK for Python 文档

二、系统架构:视觉、计算与控制协同

整个系统采用分层架构:前端由 3D 相机完成环境感知,中间由 Jetson 进行图像处理和目标判断,底层由 STM32、CAN 总线、电机和舵机完成运动控制。这样的架构让视觉算法和执行控制相对解耦,也便于后续替换算法或扩展机械结构。

19f02befb18c3.png

硬件组成

模块 设备 / 方案 作用
感知层 奥比中光 Gemini 2 双目结构光 3D 相机 同步获取彩色图像与深度图像,支持目标识别和距离测量
计算层 NVIDIA Jetson Orin Nano 运行 Orbbec SDK for Python 与 OpenCV 视觉处理程序
控制层 STM32F407 + FreeRTOS 负责电机控制、任务调度、串口通信和执行机构控制
执行层 全向舵轮底盘 实现前后、横移、斜向移动和原地旋转
执行层 3508 电机、2006 电机、DS3230 舵机 驱动底盘、取球机构和放球机构
反馈层 编码器、光电门等传感器 提供电机状态、舵轮角度和机构位置反馈

三、核心算法:RGB-D 融合目标识别

视觉识别部分的核心思路很直接:先用 RGB 图像进行颜色筛选,定位最可能的目标区域;再利用深度图在目标中心附近取样,得到更稳定的距离估计。这样既减少了全图深度处理的计算量,也避免只依赖颜色导致距离信息缺失。

19f02c226e566.png

算法主流程

原来的“算法流程表”更适合改成流程代码块。读者可以一眼看到系统每一轮识别如何运行:

while robot_is_running:
    1. 从 Gemini 2 获取一帧 RGB 图像和一帧深度图
    2. 将深度图从原始 Y16 数据转换为毫米单位
    3. 过滤无效深度值,并裁剪掉边缘干扰区域
    4. 把 1280 x 720 的 RGB 图像切成 5 x 4 个候选块
    5. 计算每个候选块的 B、G、R 平均值
    6. 通过 R-B 和 B-R 差分找出红球、蓝球候选块
    7. 在候选块中心附近取 3 x 3 深度网格
    8. 输出目标颜色、图像位置和平均深度

1. 深度数据获取

getCurrentFrame() 负责从 Gemini 2 相机中获取彩色帧和深度帧。程序使用 Orbbec SDK 的 Pipeline 模式启用彩色流和深度流,其中深度流采用 Y16 格式,并通过 get_depth_scale() 将原始数据转换为毫米级深度值。

下面是根据项目脚本整理后的核心逻辑。为了便于阅读,省略了部分异常处理和重复判断:

def getCurrentFrame():
    pipeline = Pipeline()
    config = Config()

    # 1. 启用彩色流
    color_profiles = pipeline.get_stream_profile_list(OBSensorType.COLOR_SENSOR)
    color_profile = color_profiles.get_default_video_stream_profile()
    config.enable_stream(color_profile)

    # 2. 启用深度流:1280 宽、Y16 格式、30 fps
    depth_profiles = pipeline.get_stream_profile_list(OBSensorType.DEPTH_SENSOR)
    depth_profile = depth_profiles.get_video_stream_profile(
        1280, 0, OBFormat.Y16, 30
    )
    config.enable_stream(depth_profile)

    pipeline.start(config)

    frames = pipeline.wait_for_frames(100)
    depth_frame = frames.get_depth_frame()
    color_frame = frames.get_color_frame()

    # 3. 将深度数据转换成真实距离
    width = depth_frame.get_width()
    height = depth_frame.get_height()
    scale = depth_frame.get_depth_scale()

    depth_data = np.frombuffer(depth_frame.get_data(), dtype=np.uint16)
    depth_data = depth_data.reshape((height, width))
    depth_data = depth_data.astype(np.float32) * scale

    # 4. 过滤过近、过远和无效数据
    depth_data = np.where(
        (depth_data > MIN_DEPTH) & (depth_data < MAX_DEPTH),
        depth_data,
        0
    ).astype(np.uint16)

    # 5. 保存彩色帧,供颜色识别函数读取
    img = frame_to_bgr_image(color_frame)
    cv2.imwrite("color.jpg", img)

    # 6. 裁剪深度图上下边缘,减少无效区域干扰
    return depth_data[40:760][:]

这段逻辑的关键价值在于:它不是只拿 RGB 图片做颜色识别,而是同步保留了深度图,让后续算法能够知道目标“在哪里”和“离相机多远”。

2. 颜色识别:差分峰值判断

系统将彩色图像划分为 20 个区域,并计算每个区域的 BGR 平均值。相比直接与固定色卡做绝对匹配,项目采用红蓝通道的相对差值进行判断,例如红色区域更关注 R-B 的差异,蓝色区域更关注 B-R 的差异。

19f02c6e0640a.png
def split_image_to_grid(img):
    """将 1280 x 720 图像切成 5 x 4 个区域,每块 256 x 180 像素。"""
    regions = []
    for row in range(4):
        for col in range(5):
            x1, x2 = col * 256, (col + 1) * 256
            y1, y2 = row * 180, (row + 1) * 180
            regions.append(img[y1:y2, x1:x2])
    return regions


def calc_mean_bgr(region):
    """计算候选区域的平均 B、G、R 值。"""
    b, g, r = cv2.mean(region)[:3]
    return round(b), round(g), round(r)

红色与蓝色并不是靠“某个固定阈值”一刀切,而是在所有候选区域中寻找最明显的颜色差异:

max_red = {"idx": None, "score": -1}
max_blue = {"idx": None, "score": -1}

for idx, region in enumerate(regions):
    b, g, r = calc_mean_bgr(region)

    # 红色候选:红色通道足够高,或红蓝差值足够明显
    red_like = (
        (r >= 130 and g < 100 and b < 100)
        or r >= 200
        or (r - b) >= 80
    )
    if red_like and (r - b) > max_red["score"]:
        max_red = {"idx": idx, "score": r - b}

    # 蓝色候选:蓝色通道占优,并排除灰白区域误判
    gray_like = (
        abs(b - g) <= 5
        and abs(g - r) <= 5
        and abs(b - r) <= 5
    )
    blue_like = (
        (b >= 100 and r <= 150 and g <= 140)
        or b > r
    )
    if blue_like and not gray_like and (b - r) > max_blue["score"]:
        max_blue = {"idx": idx, "score": b - r}

这段算法的优势在于:现实环境中的光照并不稳定,绝对 RGB 值容易漂移,而通道之间的相对差值通常更能反映目标颜色本身。换句话说,系统关注的不是“这个像素是不是标准红色”,而是“它是不是当前画面里最像红色目标的区域”。

3. 深度采样:3 x 3 局部均值

识别到目标区域后,程序会根据区域编号计算中心点,并在中心点附近取 3 x 3 的小窗口进行深度采样。只要该窗口中存在有效深度点,就计算有效点的平均值;如果全部无效,则返回错误码,提醒上层逻辑该位置深度不可靠。

def get_center_pos(idx):
    """根据 5 x 4 网格编号,计算区域中心点坐标。"""
    row = idx // 5
    col = idx % 5
    pos_y = row * 180 + 90
    pos_x = col * 256 + 128
    return pos_y, pos_x


def get_depth(depth_data, pos_y, pos_x):
    """在中心点附近取 3 x 3 有效深度均值。"""
    valid_depths = []

    for y in range(pos_y - 1, pos_y + 2):
        for x in range(pos_x - 1, pos_x + 2):
            depth = depth_data[y][x]
            if depth != 0:
                valid_depths.append(depth)

    if not valid_depths:
        return -110000

    return sum(valid_depths) / len(valid_depths)

从实现上看,3 x 3 采样虽然很小,但它同时解决了三个问题:过滤单点噪声、跳过无效深度值、避免全图深度遍历带来的算力浪费。这也是系统能在边缘计算平台上保持实时性的关键之一。

四、运动控制:全向移动与闭环执行

识别目标只是第一步,机器人还需要稳定移动到目标位置,并完成取球、升降和投放动作。项目的控制部分基于 STM32F407、FreeRTOS、CAN 总线和多电机 PID 控制模型构建,整体强调实时性和稳定性。

19f02ca628e26.png

全向舵轮底盘

传统轮式底盘通常需要先转向再前进,而全向舵轮底盘可以在水平面内实现任意方向移动。项目中的 motorctrl.c 根据 Vx、Vy 和 omega 计算四个舵轮模块的目标角度与速度,并通过方向优化减少舵轮旋转幅度。

用伪代码可以概括为:

void ctrlmotor(double Vx, double Vy, double omega) {
    // 1. 设置死区,避免摇杆轻微抖动造成底盘漂移
    if (fabs(Vx) < 100) Vx = 0;
    if (fabs(Vy) < 100) Vy = 0;
    if (fabs(omega) < 100) omega = 0;

    // 2. 根据底盘平动速度与旋转速度,计算四个舵轮目标角度
    wheel[0].target_angle = atan2(Vy - omega * cos(alpha),
                                  Vx - omega * sin(alpha));
    wheel[1].target_angle = atan2(Vy - omega * cos(alpha),
                                  Vx + omega * sin(alpha));
    wheel[2].target_angle = atan2(Vy + omega * cos(alpha),
                                  Vx + omega * sin(alpha));
    wheel[3].target_angle = atan2(Vy + omega * cos(alpha),
                                  Vx - omega * sin(alpha));

    // 3. 选择最短转向路径,必要时反转轮速,减少舵轮旋转幅度
    for (int i = 0; i < 4; i++) {
        optimize_steering_direction(&wheel[i]);
    }

    // 4. 将目标速度与目标角度写入 PID 控制模型
    update_pid_targets(wheel);
}

这段控制逻辑让机器人具备前后移动、左右横移、斜向移动和原地旋转能力。对于抓取任务来说,横移和斜移尤其重要,因为机器人可以在不大幅改变朝向的情况下完成精细对位。

PID 闭环控制

项目使用由 MATLAB Simulink 生成的 PID 控制模型,覆盖 3508 与 2006 电机的速度环和角度环。控制模型中加入了输出限幅、滤波、死区判断和积分处理,能够在复杂机械负载下保持更稳定的响应。

下面的伪代码展示了速度环控制的主要思想:

float speed_pid_step(float target_rpm, float current_rpm) {
    float error = target_rpm - current_rpm;

    // 比例项:快速响应当前误差
    float p_out = Kp * error;

    // 积分项:消除长期稳态误差
    integral += Ki * error * dt;

    // 微分项:抑制变化过快带来的震荡
    float derivative = (error - last_error) / dt;
    float d_out = Kd * derivative;

    float output = p_out + integral + d_out;

    // 输出限幅,保护电机和驱动器
    output = clamp(output, -16384, 16384);

    last_error = error;
    return output;
}

在实际工程中,PID 输出并不会停留在软件内部,而是会被写入 CAN 控制指令,最终驱动电机:

void assign_output(void) {
    for (int i = 0; i < 16; i++) {
        output[i] = (rtU.yaw_status[i] == VEL)
            ? rtY.yaw_SPD_OUT[i]
            : rtY.yaw_ANG_OUT[i];
    }

    CAN1_cmd_chassis(output[0], output[1], output[2], output[3],
                     output[4], output[5], output[6], output[7]);

    CAN2_cmd_chassis(output[8], output[9], output[10], output[11],
                     output[12], output[13], output[14], output[15]);
}

FreeRTOS 任务调度

FreeRTOS 中创建了默认任务、取球任务和升降任务。默认任务持续调整舵轮状态;取球任务根据按键或控制条件启动取球动作;升降任务结合位置反馈控制上升、投放和复位。

void MX_FREERTOS_Init(void) {
    defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
    ThrowBallHandle   = osThreadNew(Throw,            NULL, &ThrowBall_attributes);
    RiseHandle        = osThreadNew(RiseBall,         NULL, &Rise_attributes);
}

void Throw(void *argument) {
    for (;;) {
        if (condition[0] == 1) {
            set_getball(3390);  // 启动取球电机
            movement_take();    // 执行取球机构动作
            motor_off();        // 动作完成后关闭电机
        }
    }
}

void RiseBall(void *argument) {
    for (;;) {
        if (condition[1] == 1) {
            rtU.yaw_target4 = -1500;  // 升降机构上升

            if (rise_position_is_ready()) {
                throw_ball();         // 到位后投放
                rtU.yaw_target4 = 2000;
            }
        }
        osDelay(1);
    }
}

定时中断则承担“心跳”角色:更新遥控输入、刷新看门狗、读取电机反馈、计算 PID、发送 CAN 控制量。它把视觉决策和执行动作连接成连续闭环。

TIM10 interrupt every control cycle:
    read joystick values -> Vx / Vy / omega
    if no signal for too long -> stop chassis
    refresh watchdog
    read encoder and motor feedback
    set speed / angle mode for each motor
    calculate wheel targets
    run pidctl_step()
    send CAN outputs
    update motor state

五、开发与测试过程

项目部署在 NVIDIA Jetson Orin Nano 上,系统环境为 Linux 20.04,视觉程序主要使用 Python、OpenCV 和 Orbbec SDK for Python。相机调试阶段通过 Orbbec Viewer 观察彩色流、深度流和红外流,再将官方示例迁移到项目识别程序中。

19f02cf3b46c3.png

Jetson 与相机部署流程

部署步骤也比表格更适合用命令块展示。下面这段整理自项目中的 Orin 启动说明:

# 1. 进入 Orbbec Python SDK 目录
cd pyorbbecsdk/pyorbbecsdk

# 2. 设置 PYTHONPATH,让 Python 能找到 SDK 库文件
export PYTHONPATH=$PYTHONPATH:$(pwd)/install/lib/

# 3. 安装并刷新 udev 规则,让系统正确识别深度相机
sudo bash ./scripts/install_udev_rules.sh
sudo udevadm control --reload-rules && sudo udevadm trigger

# 4. 运行 RGB-D 识别程序
python3 /home/orin/pyorbbecsdk/RGBD_detection.py

# 5. 如需单独测试图像采集,可运行测试脚本
python3 /home/orin/pyorbbecsdk/imgtest.py

在实际调试中,团队还通过 arp -a 查找 Jetson IP,使用 SSH、串口或 MobaXterm 登录开发板,并借助 Orbbec Viewer 查看彩色流、深度流和红外流,逐步确认相机、网络与 Python 环境都处于可用状态。

关键技术难点与应对

1. 光照与背景噪声
   challenge: 复杂光照会改变颜色表现,导致误判。
   solution : 使用 R-B、B-R 这样的通道差分,而不是只依赖绝对 RGB 值。

2. 实时数据处理
   challenge: 高分辨率 RGB-D 数据会带来较大计算压力。
   solution : 先用 RGB 区域粗筛,再只在目标中心附近采样深度。

3. 深度数据可靠性
   challenge: 遮挡、反光或目标表面不均会影响深度。
   solution : 过滤深度为 0 的无效点,并对 3 x 3 局部区域求均值。

4. 仿真场景建设
   challenge: 需要在 Isaac Sim 中复现机器人和使用场地。
   solution : 使用 USD、STEP、Blend 等模型资源构建仿真环境。

5. 机械取放球
   challenge: 成功率依赖机械结构、传感器反馈和控制参数协同。
   solution : 结合 PID、光电门、编码器和任务动作序列联合优化。

六、项目优势与应用前景

项目的价值不只在于“识别一个颜色”,而在于把 3D 视觉、实时计算、底盘运动和机械执行连接成完整系统。相比单一传感器或单一视觉算法,这种多传感器方案更适合面向真实场景迭代。

核心优势

优势 体现
识别稳定 颜色差分算法降低光照变化影响
定位更完整 RGB 识别与深度测量结合,既知道“是什么”,也知道“有多远”
运动灵活 全向舵轮底盘支持横移、斜移和原地旋转
控制可靠 Simulink 生成 PID 模型,结合编码器与 CAN 控制
易于调试 Orbbec Viewer、Python 示例和模块化工程便于开发验证
成本可控 相比部分激光雷达方案,双目结构光相机兼具视觉输出与距离感知

应用场景

应用方向 典型场景 系统价值
工业制造 自动化生产线分拣、色彩质量检测、物料搬运 提升分拣效率与一致性
资源回收 按颜色或类别识别可回收物 提高回收分类效率
环境监测 识别水质、植被或污染事件中的颜色变化 辅助快速巡检与数据采集
教育科研 机器人视觉、运动控制、嵌入式开发实验 适合作为综合实践平台
交互展示 识别彩色物体并触发机械动作 增强展陈互动性

七、技术栈总结

类型 技术 / 工具
3D 视觉 奥比中光 Gemini 2、Orbbec SDK for Python、RGB-D 数据采集
图像处理 Python、OpenCV、BGR 均值统计、颜色差分判断
边缘计算 NVIDIA Jetson Orin Nano、Linux 20.04
嵌入式控制 STM32F407、FreeRTOS、HAL、DMA、UART
电机控制 CAN 总线、3508 电机、2006 电机、PID 速度环与角度环
机械执行 全向舵轮底盘、升降机构、取球机构、舵机投放机构
建模仿真 Isaac Sim、USD、STEP、Blender

结语

这套识别取物机器人展示了 3D 视觉在机器人系统中的实际价值:相机负责感知世界,算法负责理解目标,控制系统负责把判断转化为动作。通过 RGB-D 融合识别、局部深度采样、全向移动和闭环 PID 控制,项目为面向真实场景的智能抓取机器人提供了一条清晰、可复用的实现路径。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注