从一帧图像到实时视频:启元 Q1 的视觉数据架构
- 写在最前面
- 1 按需抓拍与连续视频怎样分工
- 2 一次抓拍怎样交付图像和采集信息
- 3 连续视频怎样变成算法可用的图像
- 3.1 RTSP 入口与客户端视频后端
- 3.2 网络媒体流与本地录像的表示不同
- 4 取流与模型推理怎样各自推进
- 5 怎样读懂帧率和时延
- 6 沿数据通路定位问题与扩展能力
- 参考资料
- 启元机器人开发者社区
写在最前面
版权声明:本文为原创技术架构解读,遵循 CC 4.0 BY-SA 协议。转载请注明出处。
文章依据启元 Q1 SDK v1.0.0.0 公开接口说明与 Python 示例梳理,重点是单帧 JPEG 抓拍和 RTSP 连续视频两条视觉数据通路。
同样使用机器人摄像头,拍一张桌面照片、持续显示现场画面、让视觉模型定期识别物体,对数据的要求并不相同。抓拍需要一次完整响应,预览希望不断得到新画面,模型推理则需要与自身处理速度相适应的输入。把它们接到一起之前,首先要看清图像经过了哪些模块。
启元 Q1 的公开 SDK 给出了两条视觉入口:通过 ROS 2 服务获取单帧 JPEG,以及通过 RTSP 读取连续视频。沿着这两条通路,可以进一步理解图像采集、网络传输、客户端解码与应用消费之间的分工。本文以 Q1 SDK v1.0.0.0 的接口和示例为依据,分析一个视觉应用如何从“拿到画面”发展为有清楚职责划分的数据系统。[1] [2]
图1 启元 Q1 的两条视觉数据通路。抓拍服务的响应直接携带 JPEG;连续视频由 RTSP 入口和客户端视频后端衔接。依据 SDK 接口与示例绘制,表示公开访问关系。[2]
1 按需抓拍与连续视频怎样分工
Q1 的视觉采集管理章节列出了交互相机,标识为 head_monocular_centra。同一相机可以面向不同的使用节奏提供数据:程序偶尔需要一张图时,调用 CaptureJpegImage;应用需要连续画面时,访问相应的 RTSP 地址。[2]
抓拍服务围绕一次请求工作。调用者指定相机,服务等待图像并把 JPEG 数据连同元信息返回。连续视频则建立持续读取通路,由客户端的视频后端接收和解码,应用循环取得图像帧。图1把两种交互方式放在一起,入口的差别也对应着后续程序组织方式的差别。
接口说明建议单帧抓拍按约 1 fps 的节奏使用,并在一次响应结束后再发起下一次调用;更高频的连续图像需求转向 RTSP。这里的 1 fps 是文档使用建议,适合用来选择入口。对开发者而言,一张按需获取的照片与一段持续更新的视频,可以各自承担清楚的职责。[2]
例如,资料采集程序可以在用户需要时保存一张 JPEG;远程观察程序可以持续读流;视觉分析模块则从连续帧中选择适合自己的输入。这种分工使取图方式与应用需求对应起来,也为后续的预览、推理和录像提供了不同的组织空间。
2 一次抓拍怎样交付图像和采集信息
单帧入口的完整服务名是 /aima/hal/camera/CaptureJpegImage。请求包含 camera_id、timeout_ms 和公共请求头;响应包含公共响应信息与 JpegImage 包装体。图像直接位于响应中,因此,这个服务同时完成请求交互和图像数据交付。[2]
JpegImage 提供的不只是图片内容。camera_id 标明来源,width 和 height 描述尺寸,exposure、gain 等字段保留采集条件,image 则采用 sensor_msgs/CompressedImage 承载压缩图像。把这些信息随图片一并保存,后续才能解释同一识别程序为什么可能收到尺寸、亮度或来源不同的输入。[2]
表1 一次 JPEG 响应中各组信息的职责
| 字段或内容 | 对应含义 | 应用中的用途 |
|---|---|---|
| camera_id、device | 相机来源与设备信息 | 将图片和正确的数据源关联 |
| width、height | 图像尺寸 | 安排解码后的缩放与模型输入处理 |
| framerate、exposure、gain | 返回的采集元信息 | 记录与解释采集条件 |
| image.format、image.data | 压缩格式与 JPEG 字节 | 保存图片,或解码为像素后交给算法 |
字段依据 JpegImage.msg 与接口说明;右列为相应应用用途。元信息字段用于描述此次返回的数据。[2]
官方 get_jpg.py 示例展示了一个很直接的数据交接:从 response.jpeg.image.data 取得字节,检查内容非空,再写入 JPEG 文件。保存模块面对的是完整的 JPEG 载荷;如果后续模块接收的是像素数组,还需要在接口之间加入 JPEG 解码和模型要求的预处理。[2]
超时也有各自负责的阶段。服务请求中的 timeout_ms 控制等待新帧的最长时间,设为 0 时,接口文档给出的服务端默认值是 1000 ms。客户端等待整个调用返回还要覆盖通信和响应处理,所以示例另设了调用等待时间。把这两个时间分开,才容易判断问题发生在等待图像,还是等待服务响应的过程中。[2]
这条通路适合把“获取一张图片”封装成一个独立能力。上层只需要选择相机和等待策略,图像服务负责返回图片,保存或识别模块继续处理自己需要的数据形态。曝光和增益等信息在这里作为返回值使用;需要调整采集参数时,应继续查找对应的控制接口。
3 连续视频怎样变成算法可用的图像
3.1 RTSP 入口与客户端视频后端
连续视频使用 RTSP 地址访问,SDK 示例的地址模板为 rtsp://<IP>:2554/live_<camera_id>。其中 IP 选择机器人设备,camera_id 选择相机,2554 是文档和示例给出的默认端口。网络连接和地址准备好后,客户端直接打开这条视频流。[2]
官方 get_video_stream.py 使用 OpenCV 的 VideoCapture,先尝试 FFmpeg 后端,失败后再尝试默认后端。后端负责处理视频输入和解码等细节,应用通过 open() 打开输入,通过 read() 逐帧取得已经解码的图像。OpenCV 对 read() 的定义也明确包含取帧、解码和返回图像这几个动作。[2] [3]
因此,在这份示例中,模型的接入位置十分具体:read() 返回有效的 frame 后,程序把它交给 _handle_frame()。源码在这一位置留下了视觉算法处理入口。应用可以把图像缩放、通道处理和模型推理接在这里,机器人视觉入口与具体识别算法由此通过图像帧衔接起来。[2]
3.2 网络媒体流与本地录像的表示不同
同一段视频在不同位置有不同的表示。经过网络入口进入客户端的是媒体流;VideoCapture 交给 Python 程序的是解码后的图像数组;示例随后调用 VideoWriter,把这些图像重新编码并写入本地 MP4。图2按数据所处位置展示了这几个阶段。[2] [3] [4]
图2 从视频输入到像素帧与本地录像。VideoCapture 完成取流与解码,视觉算法消费图像帧,VideoWriter 负责本地录像输出。示例中的 mp4v 属于输出端配置。[2–4]
这个表示变化会影响对参数的理解。示例中的 mp4v 用于配置 VideoWriter 的输出编码,它说明本地文件怎样写入。分析网络传输方式时,则应读取视频输入端的实际媒体信息。文件封装格式、输出编码和摄像头输入流的编码,需要按各自所在的模块解释。
类似地,示例日志累加 frame.nbytes。NumPy 将 nbytes 定义为数组元素占用的字节总数,所以这里统计的是解码后像素数据的规模,适合帮助分析内存读写和处理量;网络带宽需要在媒体流或网络层另外统计。把像素字节数除以时间,得到的也是对应的像素数据处理量。[2] [5]
4 取流与模型推理怎样各自推进
官方视频示例采用顺序循环:读一帧、处理一帧、写入录像,再读下一帧。这个结构便于展示能力接入,但把耗时模型直接放进处理函数后,后续 read() 也会等待模型返回。随着处理时间增加,接收、解码、推理或写盘中的某一环可能成为整条链路的瓶颈。[2]
面向需要持续观察现场的应用,可以在公开取流入口之上,把取流解码与模型推理安排到不同工作单元中。取流模块持续更新可用图像;模型准备好处理下一次输入时,再取出适合当前任务的帧。图3给出一种应用组织方式,预览、推理和录像分别采用符合自身用途的数据消费策略。
图3 取流、预览、推理与录像的应用组织示意。最新帧缓存服务于持续画面理解,录像使用有界顺序队列;这是依据公开读流入口提出的应用设计。
对于单帧目标识别、实时预览这类强调及时性的任务,可以保留一个容量有限的最新帧缓存。模型处理当前帧时,新到的图像更新待处理输入;模型空闲后读取较新的帧。这样可以限制应用内部的积压,让较慢的模型按自身能力使用视频。需要连续动作片段或严格时序信息的算法,则应保留相应时间窗口,不能直接沿用只取最新帧的策略。
这类取舍发生在已经解码的图像层。输入媒体流的处理仍交给视频后端,应用按用途选择哪些像素帧交给后续模块。实现共享缓存时,还应保证模型正在读取的图像不会被写入方同时修改,可以通过只读引用、受控交接或必要的复制安排数据所有权。
录像通常更关注过程的顺序和覆盖范围,适合使用独立的有界队列与写入模块。需要保护取流节奏时,录像分支可采用非阻塞入队,并在队列满时记录丢帧或停止本次录制。若要求完整保留过程,就要保证写入吞吐,并明确是否允许阻塞上游。预览则倾向于尽快显示当前画面。不同消费者由此获得符合各自用途的缓存与调度策略。
集中读取并分发帧的设计,也把管理责任集中到了接入模块:它负责维护视频连接、解释读帧错误并释放资源。发生断流后,如果应用需要重连,应由这一模块有节奏地重新建立读取通路,同时更新连接代次,使消费者能够区分断流前后的图像。官方示例提供了读帧失败后的退出和资源释放,持续重连需要应用继续实现。[2]
5 怎样读懂帧率和时延
视觉应用中出现的多个帧率,描述的对象可能不同。视频后端返回的流属性、程序每秒处理的帧数、模型每秒完成的推理数,以及录像文件采用的帧率,都需要分别记录。表2将这些数值放回相应模块,便于理解性能瓶颈发生在哪里。
表2 视觉数据链路中的指标应在对应位置解释
| 指标 | 观察位置 | 说明的事情 |
|---|---|---|
| 流帧率属性 | VideoCapture 的 CAP_PROP_FPS | 后端提供的输入属性,需要结合后端支持情况解释 |
| 应用处理帧率 | 应用收到并处理帧的数量与时间 | 当前读流和处理流程的实际推进速度 |
| 推理耗时与排队时间 | 帧进入模型前后 | 模型处理速度与应用内部等待 |
| 录像输出帧率 | VideoWriter 的 fps 参数 | 写出视频的播放时间基准 |
| 像素数据量 | frame.nbytes 及其累计值 | 解码后图像数组的数据规模 |
字段与程序行为依据官方视频示例;OpenCV 与 NumPy 文档用于解释属性语义。[2–5]
例如,示例会读取 CAP_PROP_FPS;读取值不可用时,采用 25.0 作为本地录像的回退帧率。这个常量属于保存模块的参数选择。要了解程序实际处理速度,应结合帧计数与经过的时间;要了解模型速度,则另外记录每次推理的耗时。OpenCV 也提示,读取属性的结果会受到后端和设备实现影响。[2] [3]
主动抽帧或丢帧后,若仍按原来的固定帧率写出,录像中的时长可能随之改变。需要保留真实时间关系时,应记录原帧时间,并选择适合的输出帧率或支持时间戳的录制方式。[4]
时间记录同样要明确起点。示例从第一帧交给应用后开始,用 time.monotonic() 统计经过的时间。应用可以在帧进入缓存、开始推理和结束推理时继续记录本地时间,由此得到排队与处理耗时。相机曝光到模型输入的完整时延,还需要可靠的源端采集时间及相应的时间基准;仅有客户端计时,只能描述客户端能够观察到的这段过程。[2]
为了让识别结果可以回到正确的画面,应用还可以在接入时附加帧序号、连接代次和接收时间,并让推理结果保留这些信息。界面据此选择对应帧叠加识别结果;日志也能定位是哪一张图、在哪一次连接中产生了输出。这些关联属于视觉数据管理,可以独立于模型的具体类别和大小实现。
6 沿数据通路定位问题与扩展能力
有了前面的职责划分,排查可以从最近一个正常交接点继续。抓拍服务失败时,先查看相机标识、服务响应和等待新帧的状态;RTSP 无法打开时,先检查地址、网络可达性和视频后端。输入能打开却读不到有效帧时,再观察媒体接收与解码情况。这样,排查顺序就对应到真实的数据路径。
如果画面能够持续读取,但推理输入越来越旧,应继续比较读帧节奏、缓存长度和模型耗时;如果预览正常而录像异常,则把注意力放到输出编码、帧尺寸、录像帧率和写盘上。启元 PrimeConsole 提供实时视频监控入口,可以辅助观察机器人画面;自己的应用仍需要保留上述分层信息,才能定位其中的差异。[1]
从扩展角度看,只接入一次取流并在应用内部共享解码帧,可以减少同一进程中重复读取和解码的机会;把消费者放到不同进程或设备上,则需要继续处理传输、复制和资源分配。两种组织方式各有成本,选择应围绕实际的延迟、吞吐与部署需求。
回到一开始的拍照、预览和识别需求,启元 Q1 的视觉接口提供了不同的数据入口,也给出了清楚的算法接入位置。抓拍服务交付一次图像,RTSP 支撑连续输入,客户端后端把媒体数据变成像素,应用再按各模块的需求分配这些帧。读懂这些交接关系,开发者就能明确该在哪一层接入模型、保存记录,以及处理数据跟不上的问题。
参考资料
启元接口与示例按 Q1 SDK v1.0.0.0 核对,资料查阅日期为 2026 年 10 月 19 日。下列 SDK 资料包包含正文所述消息定义与 Python 示例。
[1]启元开发者社区 PrimeConsole 与资料下载
Q1 SDK v1.0.0.0 的下载入口,以及 PrimeConsole 实时视频监控功能说明。
[2]Q1 SDK v1.0.0.0 官方资料包
《接口说明》第 2.3.2.1 节视觉采集管理;相机请求与消息定义:
aimdk_msgs/hal/camera/srv/CaptureJpegImage.srv
aimdk_msgs/hal/camera/msg/JpegImage.msg
示例:examples/python/get_jpg.py;examples/python/get_video_stream.py。
[3]OpenCV VideoCapture 类参考
用于解释 open、read、视频后端和属性读取的职责。
[4]OpenCV VideoWriter 类参考
用于解释本地视频输出的编码、帧率与图像尺寸参数。
[5]NumPy ndarray.nbytes 字段说明
用于解释示例中解码后图像数组的元素字节数。
启元机器人开发者社区
想进一步查看启元 Q1 的开发文档、SDK 与视觉应用实践,可以访问启元机器人开发者社区,结合相关开放资料继续探索机器人的视觉感知与应用开发。
hello,这里是 晓雨的笔记本。如果你喜欢我的文章,欢迎点赞、关注和评论,我会持续分享具身智能与机器人开发的技术实践。
欢迎大家添加好友交流。