首页 > 教程攻略 > ai教程 >机器人的camera入门基础知识

机器人的camera入门基础知识

来源:互联网 时间:2026-06-20 07:37:38

说到相机在ROS中的位置和姿态,第一个绕不开的问题就是坐标系怎么定义。

在ROS系统里,相机坐标系走得是一套自己的规矩:X轴向右,Y轴向下,Z轴向前。这个标准必须记清楚。但有意思的是,大多数相机传感器(CMOS或者CCD)本身,人家物理像素的排列方式决定了一套自然的坐标系——X轴沿像素列增加方向(从左到右),Y轴沿像素行增加方向(从上到下),Z轴沿光轴方向(从镜头指向场景)。这就麻烦了,两套坐标系不统一,所以在ROS里,必须给相机的坐标系做一次tf坐标系的转换。

具体怎么做?两步旋转搞定:

  • 先把坐标系绕Z轴旋转-90°。这一步是为了调整X、Y轴的方向——原来X轴是朝前的,转完之后X轴指向右;Y轴原本朝左,转完指向前面。注意Z轴这时候没动,还是朝上的。
  • 然后绕X轴再转-90°,目的是让Z轴从朝上变成朝前。转完之后,X轴不变还是朝右,Y轴从朝前变成朝下,Z轴则从朝上改为向前。

获取相机的数据信息(V4L2)

V4L2,全称Video for Linux 2,是Linux内核专门为视频设备提供的一套标准驱动框架。它的设计目标非常清晰:给不同硬件的Camera设备——比如Sensor、ISP、马达这些——提供统一的用户空间接口,同时把内核驱动的开发流程变得简单。整个框架分成三层:「用户空间」「内核空间」「硬件模块」,各层干各层的活,职责清楚,交互明确。

说白了,就是在Linux里,你不需要去折腾各种相机驱动的配置和代码移植,直接调用对应的API接口就能操控相机了。

关键声明与API接口解读

1. 结构体video_device:用户与内核的“交互桥梁”

这个结构体抽象出来,代表一个用户空间可以访问的视频设备实例——比如你看到的/dev/video0,背后就对应着一个video_device。它的核心作用是为应用层提供统一的文件操作接口,把底层硬件的差异通通屏蔽掉。

来拆解一下几个关键成员:

  • fops:对接用户空间的各种文件操作,比如你去open一个设备,背后调的就是里面挂载的my_video_open函数。
  • capabilities:这个字段直接告诉应用层,你这个设备到底能干啥——是支持视频采集,还是支持流媒体?一目了然。
  • ioctl_ops:这是控制命令处理函数集,专门处理应用层发来的指令,比如调整分辨率、帧率什么的。

2. 结构体v4l2_device:视频设备的“大管家”

这个结构体代表的是一个完整的视频设备集合,可能包含了Sensor、ISP、马达等多个子设备。它就像一个大管家,负责管理所有子设备,协调资源分配,处理跨子设备的事件通知。

关键成员:

  • subdevs:通过链表把所有的子设备串起来,不管是Sensor子设备还是ISP子设备,统一管理,方便遍历和调用。
  • mutex:互斥锁,确保在多线程或多进程同时访问设备时,不会出乱子。
  • ctrl_handler:全局参数控制中心,分辨率、曝光、白平衡这些参数,都归它管。这样做的好处是不用每个子设备都自己重复实现参数逻辑。

3. 结构体v4l2_subdev:硬件子设备的“抽象代表”

这个结构体抽象出来,代表Camera系统里的单个硬件组件,比如说Sensor、ISP或者音圈马达。它让不同硬件的驱动逻辑模块化,每个子设备都能独立控制。

关键成员:

  • list:链表节点,把子设备挂到v4l2_device的链表上,实现统一管理。
  • ops:这是子设备的具体控制逻辑——启动视频流、调整亮度,这些操作都由它来实现。
  • v4l2_dev:指明这个子设备属于谁,确保控制命令能正确传递到该去的地方。

ioctl命令的“调用链路”

应用层通过ioctl发送控制命令——比如“开启视频流”或者“设置对比度”——这个调用流程是V4L2框架的核心逻辑,一共分4步:

  1. 用户空间发起请求。

    应用程序通过/dev/videoX节点调用ioctl,传入命令码。比如VIDIOC_STREAMON,意思就是说,我要开启视频流了。
  2. 内核层接收请求。

    内核通过video_devicefops成员找到unlocked_ioctl函数——通常就是V4L2核心层的video_ioctl2,然后把这个请求转发过去。
  3. 核心层解析命令并匹配子设备。

    video_ioctl2根据命令码,从video_deviceioctl_ops里找到对应的处理函数。同时,它会去遍历v4l2_devicesubdevs链表,找到需要控制的v4l2_subdev——比如你要控制Sensor,那就去找Sensor子设备。
  4. 驱动层执行硬件控制。

    最后,调用目标v4l2_subdevv4l2_subdev_ops里的对应函数——比如s_stream负责开启视频流——最终通过硬件接口(比如I2C)控制硬件完成操作。

V4L2的命令码:记住这几个核心

V4L2的命令码确实比较长,但核心的就那么几个,需要额外记一下。其实命名是有规律的,VIDIOC就是Video Device IO Control的缩写。后面跟着的都是缩写组合:

  • QUERYCAP:Query Capability,查询能力。
  • ENUM_FMT:Enumerate Format,列出支持的格式。
  • G_FMT:Get Format,获取当前的格式。
  • S_FMT:Set Format,设置格式。
  • REQBUFS:Request Buffers,申请缓冲区。
  • QUERYBUF:Query Buffer,查看单个缓冲区的信息。
  • QBUF:Enqueue Buffer,把缓冲区放进队列。
  • DQBUF:Dequeue Buffer,从队列取出缓冲区。
  • STREAMON:Stream On,开流。
  • STREAMOFF:Stream Off,关流。

下面细细说说每一个:

1. VIDIOC_QUERYCAP


这是所有V4L2操作的第一步。打开设备后,必须先调用它来查查设备的基础能力——比如到底支不支持视频采集,是不是V4L2兼容设备。不查清楚,后面的事儿都没法干。

2. VIDIOC_ENUM_FMT


在设置格式之前,最好先问问设备都支持哪些像素格式——YUYV、MJPEG、H.264,设备能支持哪些你需要心里有数,免得设置了一个不支持的格式导致失败。

3. VIDIOC_G_FMT / VIDIOC_S_FMT


一个拿当前格式,一个设当前格式。设置格式是采集流程的核心步骤之一——你需要告诉设备,我要什么分辨率、什么像素格式。需要注意的是,如果你设的参数硬件不支持,驱动会自动帮你调整成最接近的参数,然后返回实际生效的值给你。

5. VIDIOC_REQBUFS


格式设置好了,接下来得向驱动申请内核缓冲区,用来存放采集的视频帧数据。数量一般申请3到5个,类型常用MMAP(内存映射)。

6. VIDIOC_QUERYBUF


申请完缓冲区之后,挨个查询每个缓冲区的信息——内核地址偏移、长度这些,为后面的mmap映射做准备。

7. VIDIOC_QBUF


在采集循环里,处理完一帧数据之后,必须调用这个命令,把空闲的缓冲区重新放回队列。只有这样,驱动才能继续用它来接收下一帧数据。

8. VIDIOC_DQBUF


这是采集循环的核心步骤。从驱动队列里取出已经填好数据的缓冲区,拿到数据后,读取、处理,然后再交给QBUF放回去。

9. VIDIOC_STREAMON / VIDIOC_STREAMOFF


所有准备工作做好了,调用STREAMON通知驱动开始干活。采集结束,调用STREAMOFF停掉流,然后释放缓冲区、关闭设备。

其他还有几个命令——G_PARM/S_PARM调整帧率,G_STD/S_STD处理视频标准,ENUMINPUT枚举输入源——这些在普通USB摄像头场景下用得不多,了解一下即可。

完整采集流程命令调用顺序

一个标准的USB摄像头采集程序,命令调用的顺序是这样的:

  1. open("/dev/video0")
  2. VIDIOC_QUERYCAP
  3. VIDIOC_ENUM_FMT(可选,先查查支持哪些格式)
  4. VIDIOC_S_FMT(设置你想要的格式)
  5. VIDIOC_REQBUFS(申请缓冲区)
  6. VIDIOC_QUERYBUF + mmap(把缓冲区映射到用户空间)
  7. VIDIOC_QBUF(把所有缓冲区入队)
  8. VIDIOC_STREAMON(启动采集)
  9. 循环干活:VIDIOC_DQBUF → 处理数据 → VIDIOC_QBUF
  10. VIDIOC_STREAMOFF(停止采集)
  11. munmap 解除映射 → close(fd)

V4L2应用视角

ROS中相机的图像采集处理

整个流程走下来,大致是这样的链路:物理摄像头输出MJPEG压缩数据 → V4L2内核缓冲区(内核DMA处理) → mmap映射到用户空间 → 分频处理 → 共享内存写入 → 订阅者读取并解码。

1. 硬件处理层面

要注意一点,DMA是硬件层面的事情:相机数据通过DMA直接写入内核缓冲区,代码中不需要你显式操作。相机硬件直接输出MJPEG压缩数据,而不是原始的RGB数据。硬件压缩的好处是数据量可以减到原来的十分之一到二十分之一,带宽压力小很多。而且这是帧内压缩,单帧独立解码,很适合实时场景。

2. V4L2驱动与内核缓冲区

缓冲区的核心机制是队列。驱动会申请多个环形缓冲区,通常是3到4个。相机硬件通过DMA直接写入内核缓冲区——注意,这种设计实现了零拷贝,数据不需要从内核到用户空间再拷贝一遍。

3. mmap虚拟映射操作

mmap()的作用是创建内存映射。传统的方式,数据需要在内核和用户空间之间来回拷贝——内核缓冲区拷贝到用户缓冲区,再拷贝到目标位置,两次拷贝,效率低下。而mmap则通过把内核缓冲区的物理地址直接映射到用户空间的虚拟地址,用户程序可以直接访问,相当于一次拷贝就够了——这才是真正的零拷贝。

4. 数据读取与缓冲区管理

实际读取的时候,用select来监测文件描述符的就绪状态。使用了双缓冲队列机制:一边是驱动队列,放的是等待硬件填充的空缓冲区;另一边是就绪队列,放的是已经填充数据、等着用户读取的缓冲区。用户程序从就绪队列取出数据,处理完再放回驱动队列,周而复始。

5. 分频处理

分频是在用户空间实现的——简单来说,就是跳过某些帧来降低有效的帧率。比如你拍照频率是30帧,但后端算法(比如YOLO)跑不过来,那你可以只取每2帧或者每3帧来处理。

面试官可能会问:“分频是在采集端做还是处理端做更好?为什么?”

  • 采集端分频:好处是减少了数据传输量,降低了带宽压力。
  • 处理端分频:保留了完整的数据,灵活性更高。

我们在项目中选择了在采集端分频,主要是为了节省共享内存的带宽。

6. 共享内存发布

最后,通过共享内存发布数据。共享内存是进程间通信的一种方式——发布者写入数据,订阅者直接读取,完全避免了ROS TCP带来的序列化和网络开销。整个数据流对比非常明显:传统ROS TCP需要序列化、网络传输、再反序列化,而共享内存只是直接写入、直接读取。当然,共享内存只能在同一台主机上使用,跨主机还是得靠TCP。

sensor_msgs 功能介绍

sensor_msgs是ROS的一个功能包,提供了一系列标准化的消息类型,用于各种传感器数据的通信和交换。

在项目中,它被用在了几个地方:发布雷达话题消息时用它,订阅雷达话题消息时也用它;处理压缩图像信息时,用sensor_msgs::CompressedImage;传输原始图像信息时,又用到了sensor_msgs::Image

相机ROS节点构建流程

最后,把整个节点搭起来,流程很清晰:

  1. 创建包,依赖rclcpp、sensor_msgs、cv_bridge、tf2。
  2. 写V4L2采集逻辑——open、set_fmt、reqbufs、mmap、streamon,走一遍标准流程。
  3. 写ROS发布逻辑——定时器触发,读帧,做坐标系变换,发布图像。
  4. 写TF坐标系发布——把相机坐标变换到base_link。
  5. 创建launch文件,管理camera参数。
  6. 编写CMakeLists.txt。
  7. 编译,运行。

相关阅读