3. 相机选型与驱动:RGB相机 vs 深度相机、USB相机驱动(v4l2)、ROS2相机节点

做机械臂视觉抓取,第一步不是写代码,而是选相机。我见过不少团队,算法调得挺漂亮,结果相机选错了,项目直接卡在硬件环节。说白了,相机就是机械臂的「眼睛」,眼睛不好使,后面全是白搭。

这一章,我们聊聊怎么选相机、怎么驱动它、怎么把它接入ROS2。我会结合自己踩过的坑,给你一些实在的建议。

3.1 RGB相机 vs 深度相机:到底怎么选?

先问个问题:你的机械臂需要知道物体的三维位置吗?

如果只是做平面抓取(比如传送带上的工件),一个普通的RGB相机就够了。但如果是无序抓取、货架取物,或者需要避开障碍物,那深度相机几乎是必须的。

我整理了一个对比表,方便你快速决策:

特性 RGB相机 深度相机
输出数据 彩色图像(2D) 彩色图像 + 深度图(3D)
典型分辨率 1920×1080 或更高 1280×720(深度图通常更低)
帧率 30~60 FPS 15~30 FPS(受深度计算限制)
工作距离 无限制(取决于镜头) 0.3~5m(视具体技术)
光照敏感度 依赖环境光 结构光/ToF受强光干扰大
成本 低(几十到几百元) 中高(几百到几千元)
典型场景 二维码定位、平面检测 无序抓取、避障、SLAM

我的建议:如果预算允许,直接上深度相机。你想想看,后期加装深度传感器,结构改动和标定工作量远大于一开始就选对。我在一个仓储项目中吃过这个亏——先用RGB相机做,后来发现深度信息全靠估,精度根本不够,最后返工换了Intel RealSense D435,多花了三周时间。

目前主流的深度相机技术有三种:

  • 结构光(如Kinect v1、RealSense SR300):精度高,但室外强光下基本废掉。
  • 飞行时间ToF(如Kinect v2、RealSense L515):抗干扰稍好,但分辨率偏低。
  • 双目立体视觉(如RealSense D400系列、ZED):依赖纹理,弱光下表现差,但成本可控。

我个人最常用的是RealSense D435。为什么?因为它兼顾了室内精度和室外可用性,而且ROS2驱动非常成熟。嗯,这里要注意:如果你要在强光下工作,双目方案可能比ToF更靠谱。

3.2 USB相机驱动:v4l2实战

不管选什么相机,最终都要通过驱动把图像数据读出来。对于USB相机,Linux下最通用的接口就是v4l2(Video for Linux 2)。

先检查你的相机是否被识别:

ls /dev/video*
# 通常输出 /dev/video0  /dev/video1  ...

然后用 v4l2-ctl 查看相机能力:

v4l2-ctl --list-devices
v4l2-ctl -d /dev/video0 --list-formats-ext

你会看到类似这样的输出:

ioctl: VIDIOC_ENUM_FMT
    Index       : 0
    Type        : Video Capture
    Pixel Format: 'MJPG' (compressed)
    Name        : Motion-JPEG
        Size: Discrete 1920x1080
            Interval: Discrete 0.033s (30.000 fps)
        Size: Discrete 1280x720
            Interval: Discrete 0.016s (60.000 fps)

小技巧:很多USB相机默认输出MJPG压缩格式,CPU解码压力小,但画质有损。如果你做目标检测,建议优先选YUYV或RAW格式,虽然带宽大,但避免了压缩伪影对检测精度的影响。我曾经因为用了MJPG格式,导致小目标检测率下降了5%,排查了两天才发现是压缩噪声的问题。

下面是一个用v4l2读取单帧图像的C++示例:

#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <sys/mman.h>

int fd = open("/dev/video0", O_RDWR);
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
ioctl(fd, VIDIOC_S_FMT, &fmt);

// 请求缓冲区并映射
struct v4l2_requestbuffers req = {0};
req.count = 1;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);

// ... 省略队列、出队、读取数据等步骤
close(fd);

这段代码只是示意,实际项目中你还需要处理缓冲区队列、帧同步、错误重试等逻辑。不过别担心,我们后面会用ROS2封装好这些细节。

3.3 ROS2相机节点:从驱动到话题

在ROS2里,我们通常不会直接操作v4l2。ROS2提供了 image_transport 和 camera_info_manager 这些工具,帮你把相机数据发布成标准话题。

最省事的办法是用 v4l2_camera 这个现成包:

sudo apt install ros-humble-v4l2-camera
ros2 run v4l2_camera v4l2_camera_node --ros-args \
    -p video_device:="/dev/video0" \
    -p pixel_format:="yuyv" \
    -p image_size:="[640,480]"

启动后,你会看到这些话题:

  • /image_raw — 原始图像数据
  • /camera_info — 相机内参和畸变参数

但如果你用的是深度相机(比如RealSense),官方提供了 realsense2_camera 包:

sudo apt install ros-humble-realsense2-camera
ros2 launch realsense2_camera rs_launch.py \
    depth_module.profile:=1280x720x30 \
    rgb_camera.profile:=1920x1080x30 \
    pointcloud.enable:=true

这会同时发布:

  • /camera/color/image_raw — RGB图
  • /camera/depth/image_rect_raw — 深度图
  • /camera/depth/color/points — 点云

注意:深度相机的帧率通常比RGB低。如果你做实时检测,建议把RGB和深度图的时间戳对齐。ROS2的 message_filters::sync::ApproximateTime 可以帮你做近似时间同步。我曾经没做同步,结果检测框和深度值差了100ms,抓取位置直接偏了2cm。

下面是一个简单的ROS2节点,订阅RGB图像并调用OpenCV显示:

#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/highgui/highgui.hpp>

class ImageSubscriber : public rclcpp::Node
{
public:
    ImageSubscriber() : Node("image_subscriber")
    {
        sub_ = this->create_subscription<sensor_msgs::msg::Image>(
            "/image_raw", 10,
            std::bind(&ImageSubscriber::callback, this, std::placeholders::_1));
    }

private:
    void callback(const sensor_msgs::msg::Image::SharedPtr msg)
    {
        cv::Mat frame = cv_bridge::toCvCopy(msg, "bgr8")->image;
        cv::imshow("Camera Feed", frame);
        cv::waitKey(1);
    }
    rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
};

3.4 知识体系总览

为了让你对整个流程有个全局印象,我画了一张结构图:

相机选型与驱动知识体系 相机选型决策 RGB相机 深度相机 驱动层:v4l2 / UVC / 厂商SDK ROS2相机节点:v4l2_camera / realsense2_camera /image_raw /camera_info /depth/image_raw 💡 核心流程:选型 → 驱动 → ROS2节点 → 话题输出 → 下游检测/抓取节点 关键点:分辨率/帧率匹配、时间戳同步、相机内参标定

3.5 避坑指南

最后,分享几个我亲身踩过的坑:

  1. USB带宽不足:同时跑RGB和深度相机时,USB 3.0是底线。我曾经用USB 2.0接RealSense D435,结果深度图只有5 FPS,根本没法用。解决办法:换USB 3.0口,或者降低分辨率。
  2. 相机内参必须标定:别偷懒用出厂参数。每个相机都有个体差异,尤其是畸变参数。我见过一个项目,直接用默认内参做手眼标定,结果抓取误差达到1cm。用ROS2的 camera_calibration 包,十分钟就能标好。
  3. ROS2节点命名冲突:如果你同时接多个相机,记得给每个节点设置不同的命名空间。否则话题会互相覆盖,你都不知道看到的是哪个相机的画面。