1、准备工作
因为本人使用VirtualBox虚拟机运行的ROS2,所以首先要让摄像头可以在虚拟机中运行
1.1 安装VirtualBox扩展包
1)下载地址:https://www.virtualbox.org/wiki/Downloads,注意扩展包的版本要和虚拟机的版本匹配
2)安装
打开虚拟机 ——》工具 ——》扩展 ——》安装
3)安装成功
1.2 在虚拟机中添加摄像头
1.3 测试摄像头
1)查看摄像头节点
~$ ls /dev/video*
/dev/video0 /dev/video1
2)使用ffplay测试
ffplay /dev/video0
2、安装usb_cam
安装命令如下,注意本人的Ubuntu版本是22.04,对应ROS2版本为humble
sudo apt install ros-humble-usb-cam
3、命令测试
1)启动摄像头及显示
~$ ros2 launch usb_cam demo_launch.py
[INFO] [launch]: All log files can be found below /home/laoer/.ros/log/2023-06-09-10-03-47-100527-laoer-VirtualBox-8264
[INFO] [launch]: Default logging verbosity is set to INFO
/opt/ros/humble/share/usb_cam/config/params.yaml
……
从打印信息可以看出,摄像头相关参数的配置文件路径:/opt/ros/humble/share/usb_cam/config/params.yaml
2)只启动摄像头节点
ros2 run usb_cam usb_cam_node_exe --ros-args --params-file /opt/ros/humble/share/usb_cam/config/params.yaml
3)使用rqt_image_view显示图像
下载,注意版本,本人的ROS2版本为humble
sudo apt install ros-humble-rqt-image-view
4、编程测试
4.1 python编程测试
1)进入功能包源码目录(根据自己的环境创建)
cd ~/ros/eg/src/py
2)创建功能包
ros2 pkg create --build-type ament_python camera
3)编辑源码
cd camera
vi topic_camera_sub.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from sensor_msgs.msg import Image # 图像消息类型
from cv_bridge import CvBridge # ROS与OpenCV图像转换类
import cv2 # Opencv图像处理库
class ImageSubscriber(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.sub = self.create_subscription(
Image, 'image_raw', self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)
self.cv_bridge = CvBridge() # 创建一个图像转换对象,用于OpenCV图像与ROS的图像消息的互相转换
def show(self, image):
cv2.imshow("camera", image) # 使用OpenCV显示图像效果
cv2.waitKey(10)
def listener_callback(self, data):
self.get_logger().info('Receiving video frame') # 输出日志信息,提示已进入回调函数
image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8') # 将ROS的图像消息转化成OpenCV图像
self.show(image)
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = ImageSubscriber("topic_webcam_sub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
4)修改配置
修改setup.py,添加入口
在entry_points中添加
'topic_camera_sub= camera.topic_camera_sub:main',
完整的setup.py如下
~/ros/eg/src/py/camera$ cat setup.py
from setuptools import setup
package_name = 'camera'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='laoer',
maintainer_email='laoer@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'topic_camera_sub= camera.topic_camera_sub:main',
],
},
)
5)编译
注意:在项目根目录中编译
cd ~/ros/eg
colcon build
6)运行
在终端1中启动摄像头
ros2 run usb_cam usb_cam_node_exe --ros-args --params-file /opt/ros/humble/share/usb_cam/config/params.yaml
在终端2中启动测试程序
cd ~/ros/eg
source install/setup.sh
ros2 run camera topic_camera_sub
4.2 c++编程测试
1)进入功能包源码目录(根据自己的环境创建)
cd ~/ros/eg/src/cpp
2)创建功能包
ros2 pkg create --build-type ament_cmake cpp_camera --dependencies rclcpp OpenCV sensor_msgs cv_bridge
3)编辑源码
cd cpp_camera/src
vi camera_sub.cpp
#include <memory>
#include <opencv2/opencv.hpp>
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "cv_bridge/cv_bridge.h"
// std::bind占位符引用
using std::placeholders::_1;
class CameraSub : public rclcpp::Node
{
public:
CameraSub()
: Node("cpp_camera_subscriber")
{
# a) 初始化订阅者,使用消息类型Image、主题名称image_raw */
subscription_ = this->create_subscription<sensor_msgs::msg::Image>(
"image_raw", 10, std::bind(&CameraSub::topic_callback, this, _1));
}
private:
void topic_callback(const sensor_msgs::msg::Image & img) const
{
RCLCPP_INFO(this->get_logger(), "Receiving video frame");
# b)cv_bridge::toCvCopy:Image 话题消息转 opencv 的 cv::Mat
cv::imshow("camera", cv_bridge::toCvCopy(img, sensor_msgs::image_encodings::BGR8).get()->image);
cv::waitKey(10);
}
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
// 创建并运行
rclcpp::spin(std::make_shared<CameraSub>());
rclcpp::shutdown();
return 0;
}
4)配置
修改CMakeLists.txt
添加引用
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(OpenCV REQUIRED)
find_package(cv_bridge REQUIRED)
生成可执行文件(使用对应的源码文件和依赖)
add_executable(camera_sub src/camera_sub.cpp)
ament_target_dependencies(camera_sub rclcpp sensor_msgs OpenCV cv_bridge)
添加安装规则
install(TARGETS
camera_sub
DESTINATION lib/${PROJECT_NAME})
完整CM艾克List.txt如下
cmake_minimum_required(VERSION 3.8)
project(cpp_camera)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(OpenCV REQUIRED)
find_package(cv_bridge REQUIRED)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
set(ament_cmake_copyright_FOUND TRUE)
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
add_executable(camera_sub src/camera_sub.cpp)
ament_target_dependencies(camera_sub rclcpp sensor_msgs OpenCV cv_bridge)
install(TARGETS
camera_sub
DESTINATION lib/${PROJECT_NAME})
ament_package()
修改package.xml,添加依赖
<depend>rclcpp</depend>
<depend>OpenCV</depend>
<depend>sensor_msgs</depend>
<depend>cv_bridge</depend>
5)编译
注意:在项目根目录中编译
cd ~/ros/eg
colcon build
6)运行
在终端1中启动摄像头
ros2 run usb_cam usb_cam_node_exe --ros-args --params-file /opt/ros/humble/share/usb_cam/config/params.yaml
在终端2中启动测试程序
cd ~/ros/eg
source install/setup.sh
ros2 run cpp_camera camera_sub
5、话题消息
5.1 原始未压缩图像 ImageRaw
1)python数据描述
class Image_
_fields_and_field_types = {
'header': 'std_msgs/Header', // 话题消息队列头
'height': 'uint32', // 图像高(单位像素)
'width': 'uint32', // 图像宽(单位像素)
'encoding': 'string', // 像素格式,如“bgr8”
'is_bigendian': 'uint8', // 数据大小端字节序
'step': 'uint32', // 步长(图像宽,单位字节)
'data': 'sequence<uint8>', // 图像数据
}
2)C++中的描述
C++中encoding(像素格式)定义在名字空间 sensor_msgs::image_encodings 中,如:
namespace sensor_msgs
{
namespace image_encodings
{
const char RGB8[] = "rgb8";
const char RGBA8[] = "rgba8";
const char RGB16[] = "rgb16";
……
sensor_msgs::msg::Image_
this->header = const std_msgs::msg::Header_……
this->height = 0ul;
this->width = 0ul;
this->encoding = "";
this->is_bigendian = 0;
this->step = 0ul;
this->data = std::vector<uint8_t……
5.2 压缩图像 CompressedImage
压缩格式支持:jpeg、png、tiff
1)python
_fields_and_field_types = {
'header': 'std_msgs/Header',
'format': 'string',
'data': 'sequence<uint8>',
}
2)C++
this->header = const std_msgs::msg::Header_……
this->format = ""; // 可选值:jpeg、png、tiff
this->data = std::vector<uint8_t……