ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

HIKROBOT工业相机ROS驱动开发:从MVS SDK到话题发布

HIKROBOT工业相机ROS驱动开发:从MVS SDK到话题发布 简介这份源码面向工业自动化与机器视觉方向的开发者提供基于MVS SDK与ROS框架的海康机器人工业相机驱动程序用于解决相机在ROS系统中的集成、参数配置与图像数据采集问题适合具备一定C与ROS基础的中高级开发者参考使用。资源包共25个文件约64KB包含7个YAML配置文件与7个launch启动文件分别用于相机参数标定与节点启动配置2个C源文件与头文件实现底层通信逻辑另有cfg动态参数、rviz可视化配置、package.xml及许可证等辅助文件目录结构清晰便于按模块查阅。目前已有444人学习下载。通过这份源码读者可以快速将海康工业相机接入ROS系统理解曝光、分辨率、帧率等参数配置方式并参考室内外多场景启动配置与标定文件完成图像采集与传输功能的定制开发。1. 从一台 HIKROBOT 相机接进 ROS 开始说起手里有一台 HIKROBOT 工业相机想让它出现在 ROS 的 topic 列表里这件事听起来简单实际动手会发现中间隔着一整套 SDK 和一层驱动封装。MVS SDK 是海康机器人官方提供的相机访问接口负责枚举设备、取流、解析图像ROS 负责把这些图像变成标准话题让下游的标定、检测、建图节点能直接订阅。两者之间的桥就是我们要写的驱动程序。这篇笔记面向的是已经装好 ROS、手头有 HIKROBOT 相机、想把图像接进自己机器人系统的工程师。不管你是做 ROS 小车自主导航仿真还是做机械臂视觉抓取只要相机是海康机器人系列这套驱动思路都能复用。我会从 SDK 选型讲到节点实现再到参数配置和踩坑记录目标是让你看完能自己写出一份可用的驱动源码而不是只会调别人的包。2. MVS SDK 与 ROS 驱动架构先想清楚桥怎么搭2.1 为什么不能直接在 ROS 节点里调 SDKMVS SDK 提供的是 C 接口和 C 接口底层走的是 USB3.0 或千兆网口取流是阻塞式的。ROS 节点的回调机制是事件驱动的如果直接在回调里调 SDK 的取流函数整个节点会被卡死话题发布频率直接掉到零。常见做法是开一个独立的采集线程SDK 在这个线程里循环取帧取到之后放进一个线程安全的队列ROS 的定时器或另一个线程从队列里取图并发布。这样采集和发布解耦即使下游节点处理慢也不会把相机取流拖垮。另一个原因是生命周期管理。SDK 需要显式地枚举设备、创建句柄、开始取流、停止取流、销毁句柄。ROS 节点的启动和关闭是异步的如果不在析构函数里正确释放 SDK 资源下次启动相机时会出现设备被占用的情况。我一般会把 SDK 的初始化和反初始化封装在一个 RAII 风格的类里构造时枚举并打开设备析构时自动停止取流并销毁句柄。2.2 驱动节点的最小功能集一个能用的 HIKROBOT ROS 驱动至少要做到四件事枚举并打开指定序列号的相机、配置曝光和增益、持续取流并转成 sensor_msgs/Image、支持动态参数调整。枚举和打开是基础曝光和增益决定了图像质量取流转 Image 是核心动态参数让使用者不用重启节点就能调亮度。图像格式方面MVS SDK 输出的原始数据可能是 Mono8、BayerRG8、RGB8 等。ROS 的 sensor_msgs/Image 需要指定 encoding如果 encoding 写错下游的 cv_bridge 转换会直接报错或者出绿屏。我一般会在驱动里根据 SDK 返回的像素格式做一次映射Mono8 直接对应 mono8BayerRG8 对应 bayer_rggb8RGB8 对应 rgb8。如果相机输出的是 Bayer 格式可以在驱动里做一次 debayer也可以直接发 Bayer 让下游处理前者省下游算力后者保留原始数据。2.3 采集线程与 ROS 发布线程的同步采集线程和发布线程之间需要一个缓冲区。最简单的做法是用一个固定大小的环形队列采集线程往队尾写发布线程从队头读。队列满的时候采集线程丢弃最旧的一帧保证永远取到的是最新图像。这样做的好处是延迟可控不会因为下游卡顿导致图像越积越多。同步机制用 std::mutex 加 std::condition_variable 就够了。采集线程取到帧后加锁写入队列然后 notify 发布线程。发布线程等待条件变量被唤醒后加锁取帧解锁后再发布话题。注意发布话题的时候不要持锁否则下游回调如果耗时会把采集线程也堵住。// 环形队列的简化实现 class FrameBuffer { public: explicit FrameBuffer(size_t capacity) : capacity_(capacity) {} void push(const cv::Mat frame) { std::unique_lockstd::mutex lock(mutex_); if (queue_.size() capacity_) { queue_.pop(); // 丢弃最旧帧 } queue_.push(frame.clone()); cond_.notify_one(); } bool pop(cv::Mat frame, int timeout_ms) { std::unique_lockstd::mutex lock(mutex_); if (!cond_.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { return !queue_.empty(); })) { return false; } frame queue_.front(); queue_.pop(); return true; } private: size_t capacity_; std::queuecv::Mat queue_; std::mutex mutex_; std::condition_variable cond_; };这段代码的关键参数是 capacity_我一般设成 3 到 5。设太小会导致发布线程偶尔取不到帧设太大则延迟增加。timeout_ms 设成 100 毫秒左右超时后发布线程可以发一帧空图或者直接跳过避免节点卡死。frame.clone() 是必须的因为 SDK 的缓冲区会被复用不 clone 的话下一帧会覆盖当前数据。3. 从零写一个 HIKROBOT ROS 驱动节点3.1 工程结构与依赖配置我一般把驱动做成一个标准的 ROS package目录结构如下include 放头文件src 放源文件launch 放启动文件config 放参数 yaml。CMakeLists.txt 里需要 find_package 找 catkin、roscpp、sensor_msgs、cv_bridge、image_transport还要手动指定 MVS SDK 的头文件和库路径。MVS SDK 在 Linux 下的默认安装路径通常是 /opt/MVS头文件在 /opt/MVS/include库在 /opt/MVS/lib/64。CMakeLists.txt 里用 include_directories 把 SDK 头文件目录加进去用 link_directories 加库目录target_link_libraries 里链接 MvCameraControl。注意 SDK 的库名在不同版本里可能略有差异常见的是 libMvCameraControl.so。如果编译时报 undefined reference先用 ldconfig -p | grep MvCamera 确认库是否在系统路径里。package.xml 里声明依赖roscpp、sensor_msgs、cv_bridge、image_transport、camera_info_manager。如果要做相机标定还要加 camera_calibration_parsers。这些依赖用 rosdep 装就行不用手动编译。3.2 相机枚举与打开的代码实现枚举设备用 MV_CC_EnumDevices 函数传入 MV_GIGE_DEVICE | MV_USB_DEVICE 可以同时枚举网口和 USB 相机。枚举结果是一个 MV_CC_DEVICE_INFO_LIST 结构里面包含设备数量和信息数组。遍历数组用 MV_CC_GetDeviceInfo 或者直接读结构体里的序列号找到目标相机。打开相机分两步先 MV_CC_CreateHandle 创建句柄再 MV_CC_OpenDevice 打开设备。网口相机还需要设置心跳时间和最优包大小USB 相机不用。打开之后设置触发模式为连续采集用 MV_CC_SetEnumValue 设置 TriggerMode 为 0。然后 MV_CC_StartGrabbing 开始取流。// 枚举并打开指定序列号的相机 bool openCameraBySerial(const std::string target_serial) { MV_CC_DEVICE_INFO_LIST device_list; int ret MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, device_list); if (ret ! MV_OK || device_list.nDeviceNum 0) { ROS_ERROR(No HIKROBOT camera found, ret0x%x, ret); return false; } for (unsigned int i 0; i device_list.nDeviceNum; i) { MV_CC_DEVICE_INFO* info device_list.pDeviceInfo[i]; std::string serial; if (info-nTLayerType MV_GIGE_DEVICE) { serial reinterpret_castchar*(info-SpecialInfo.stGigEInfo.chSerialNumber); } else if (info-nTLayerType MV_USB_DEVICE) { serial reinterpret_castchar*(info-SpecialInfo.stUsb3VInfo.chSerialNumber); } if (serial target_serial) { handle_ nullptr; ret MV_CC_CreateHandle(handle_, info); if (ret ! MV_OK) { return false; } ret MV_CC_OpenDevice(handle_); if (ret ! MV_OK) { return false; } // 设置连续采集模式 MV_CC_SetEnumValue(handle_, TriggerMode, 0); MV_CC_StartGrabbing(handle_); return true; } } return false; }这段代码里 target_serial 从 ROS 参数服务器读取默认值可以留空留空时打开第一个枚举到的相机。MV_CC_EnumDevices 的第二个参数是设备类型掩码如果只接 USB 相机可以只传 MV_USB_DEVICE减少枚举时间。打开网口相机时如果网络环境复杂建议先设置 MV_CC_SetIntValue 调整包大小否则可能出现丢包导致图像花屏。3.3 取流线程与图像格式转换取流线程是一个 while 循环每轮调用 MV_CC_GetImageBuffer 获取一帧拿到之后根据像素格式转成 cv::Mat推入 FrameBuffer然后调用 MV_CC_FreeImageBuffer 释放 SDK 缓冲区。循环的退出条件是一个 atomic 标志析构时置为 false 并 join 线程。像素格式转换是容易翻车的地方。MVS SDK 返回的 MV_FRAME_OUT_INFO_EX 结构里有 enPixelType 字段常见值有 PixelType_Gvsp_Mono8、PixelType_Gvsp_BayerRG8、PixelType_Gvsp_RGB8_Packed。Mono8 直接构造 cv::Mat 就行BayerRG8 需要用 cv::cvtColor 做 debayerRGB8 直接拷贝。如果格式不认识打警告并跳过这一帧不要硬转。void grabLoop() { MV_FRAME_OUT_INFO_EX frame_info; unsigned char* data new unsigned char[width_ * height_ * 3]; while (running_) { int ret MV_CC_GetImageBuffer(handle_, frame_info, 1000); if (ret ! MV_OK) { ROS_WARN_THROTTLE(1.0, GetImageBuffer failed, ret0x%x, ret); continue; } cv::Mat image; if (frame_info.enPixelType PixelType_Gvsp_Mono8) { image cv::Mat(frame_info.nHeight, frame_info.nWidth, CV_8UC1, frame_info.pBufAddr).clone(); } else if (frame_info.enPixelType PixelType_Gvsp_BayerRG8) { cv::Mat bayer(frame_info.nHeight, frame_info.nWidth, CV_8UC1, frame_info.pBufAddr); cv::cvtColor(bayer, image, cv::COLOR_BayerRG2BGR); } else if (frame_info.enPixelType PixelType_Gvsp_RGB8_Packed) { cv::Mat rgb(frame_info.nHeight, frame_info.nWidth, CV_8UC3, frame_info.pBufAddr); cv::cvtColor(rgb, image, cv::COLOR_RGB2BGR); } else { MV_CC_FreeImageBuffer(handle_, frame_info); continue; } buffer_.push(image); MV_CC_FreeImageBuffer(handle_, frame_info); } delete[] data; }MV_CC_GetImageBuffer 的超时参数设成 1000 毫秒超时后返回非 MV_OK打一条节流警告继续循环。注意 MV_CC_FreeImageBuffer 必须在 clone 之后调用否则 SDK 缓冲区不会被释放取几帧之后就会卡住。Bayer 转 BGR 用 COLOR_BayerRG2BGR如果相机实际是 BayerGB 或 BayerBG转出来的颜色会偏需要根据相机型号调整。3.4 曝光增益的动态参数配置曝光和增益通过 dynamic_reconfigure 暴露给用户。在 cfg 文件里定义 exposure_time 和 gain 两个参数范围根据相机型号设定一般曝光时间 100 到 100000 微秒增益 0 到 20 分贝。回调函数里用 MV_CC_SetFloatValue 设置 ExposureTime用 MV_CC_SetFloatValue 设置 Gain。注意设置曝光时间之前要先把 ExposureAuto 设为 Off否则手动值会被自动曝光覆盖。增益同理GainAuto 设为 Off。设置完之后可以读回来确认MV_CC_GetFloatValue 获取当前值如果和设定值差太多说明相机不支持这个范围需要调整 cfg 里的边界。#!/usr/bin/env python # dynamic_reconfigure 的 cfg 文件示例 PACKAGE hikrobot_camera from dynamic_reconfigure.parameter_generator_catkin import * gen ParameterGenerator() gen.add(exposure_time, double_t, 0, Exposure time in microseconds, 5000.0, 100.0, 100000.0) gen.add(gain, double_t, 0, Gain in dB, 0.0, 0.0, 20.0) exit(gen.generate(PACKAGE, hikrobot_camera, Camera))这个 cfg 文件生成之后ROS 会自动创建参数服务器接口和 rqt_reconfigure 界面。回调里先关自动模式再设值顺序不能反。如果相机正在取流设置曝光时间会立即生效不需要停止取流。但有些型号的相机在取流过程中修改增益会导致一两帧图像异常这是正常现象不用紧张。4. 避坑与排查那些让我熬夜的相机驱动问题4.1 现象节点启动后话题有发布但图像全黑原因通常是曝光时间设得太短或者镜头光圈没打开。MVS SDK 默认曝光可能是自动模式但驱动里如果手动设了一个很小的值比如 100 微秒在室内光线下就是全黑。另一个可能是像素格式映射错了比如把 Mono8 当成了 BayerRG8 转转出来就是全黑或者全绿。解决方法是先用 MVS 客户端连上相机确认能正常出图记下当时的曝光时间和像素格式。然后在驱动里把曝光时间设成同一个值像素格式映射也改成一致的。如果 MVS 客户端也黑那就是硬件问题检查镜头盖和光圈。4.2 现象取流几十秒后卡死话题停止发布这是 SDK 缓冲区没有释放的典型症状。MV_CC_GetImageBuffer 拿到的帧必须用 MV_CC_FreeImageBuffer 释放如果中间某条分支 return 之前忘了释放缓冲区很快耗尽取流就卡住了。我见过有人在格式转换失败时直接 continue没有调 FreeImageBuffer跑十几秒就挂。解决方法是把 FreeImageBuffer 放在所有分支都会走到的地方或者用 RAII 包装一下。最简单的做法是在 continue 之前也调一次 FreeImageBuffer确保每一帧都被释放。另外检查 clone 是否真的做了如果直接把 SDK 缓冲区指针包成 cv::Mat 而不 clone下一帧会覆盖当前数据下游看到的就是花屏。4.3 现象网口相机丢包图像下半部分花屏千兆网口相机在带宽不足或者网卡没开巨帧的时候容易丢包。图像上半部分正常下半部分花屏或者绿屏基本就是丢包。MVS SDK 有重传机制但驱动里如果包大小设得太大重传也救不回来。解决办法是在打开相机之后设置最优包大小。用 MV_CC_GetOptimalPacketSize 获取推荐值然后用 MV_CC_SetIntValue 设置 GevSCPSPacketSize。同时确认网卡 MTU 设成了 9000 以上交换机也支持巨帧。如果还是丢把相机的帧率降下来或者换 USB 相机。4.4 现象编译时报找不到 MvCameraControl.hMVS SDK 安装之后头文件在 /opt/MVS/include但 CMakeLists.txt 里没有把这个路径加进去。或者 SDK 装在了非默认路径比如 /home/user/MVS那就需要手动指定。解决方法是先在终端确认头文件位置find / -name MvCameraControl.h 2/dev/null。然后在 CMakeLists.txt 里用 include_directories 加上对应目录link_directories 加上库目录。如果用的是 catkin 的 package记得把 SDK 路径写成绝对路径不要用相对路径否则编译目录变了就找不到。4.5 现象动态参数调了没反应dynamic_reconfigure 的回调函数里设置了曝光时间但图像亮度没变化。原因可能是 ExposureAuto 没有关掉自动曝光把手动值覆盖了。或者回调函数里用的句柄是空的因为相机还没打开。解决方法是先在回调里检查句柄是否有效无效就直接返回。然后在设置曝光之前先调 MV_CC_SetEnumValue 把 ExposureAuto 设为 Off再设 ExposureTime。设置完之后用 MV_CC_GetFloatValue 读回来如果读回来的值和设定值差很多说明相机不支持这个范围需要调整 cfg 文件的边界。5. 进阶技巧让驱动更稳、更好用5.1 用 camera_info_manager 管理标定参数驱动发布图像的同时最好也发布 camera_info。camera_info_manager 可以从 yaml 文件加载标定参数也可以从参数服务器读。我一般会在 launch 文件里指定 camera_info_url指向一个标定好的 yaml。这样下游的 image_proc 节点可以直接用不用再手动传参数。标定用 camera_calibration 包就行跑一次生成 yaml然后放到 config 目录。驱动启动时用 camera_info_manager 加载发布到 camera_info 话题。注意图像话题和 camera_info 话题的命名要对应比如 /camera/image_raw 对应 /camera/camera_info。5.2 多相机同步的简单做法如果系统里有两台以上的 HIKROBOT 相机可以用 MV_CC_SetEnumValue 设置 TriggerMode 为 1触发源设为软触发或硬触发。软触发用 MV_CC_SetCommandValue 发触发命令硬触发需要接线。ROS 这边可以用 message_filters 做时间同步把多路图像按时间戳对齐。我一般会先让每台相机独立跑起来确认单台稳定之后再上同步。同步的时候注意时间戳来源最好都用 ROS 的 ros::Time::now()不要用相机内部时间戳否则不同相机的时间基准不一致同步会失败。5.3 性能调优的几个参数取流线程的优先级可以调高用 pthread_setschedparam 设成 SCHED_FIFO避免被其他线程抢占。FrameBuffer 的容量设成 3 左右太大延迟高太小丢帧。图像发布用 image_transport 的 raw 模式就行如果需要压缩再开 compressed 插件。如果 CPU 占用太高可以在驱动里做一次降采样或者把 debayer 放到 GPU 上做。我一般会先看 top 里节点的 CPU 占用如果超过单核 80%就要考虑优化。最简单的优化是减少 clone 次数能移动就不要拷贝。5.4 一个我常用的启动文件模板launch node namehikrobot_camera pkghikrobot_camera typehikrobot_camera_node outputscreen param nameserial_number valueDA1234567 / param namepixel_format valueBayerRG8 / param nameframe_rate value30 / param namecamera_info_url valuefile://$(find hikrobot_camera)/config/camera.yaml / /node /launch这个模板里 serial_number 留空就打开第一个相机pixel_format 根据实际相机填frame_rate 设成相机支持的值。camera_info_url 用 file:// 前缀指向 yaml 文件。启动之后用 rostopic hz 看发布频率用 rqt_image_view 看图像是否正常。写驱动这件事最深的教训就是不要相信“默认值”。曝光、增益、包大小、像素格式每一个默认值都可能让你调半天。我现在的习惯是拿到一台新相机先用 MVS 客户端把所有参数看一遍记下来然后在驱动里显式设置不依赖 SDK 的默认行为。这样即使换一台同型号的相机驱动也能直接跑起来。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表