ROS2实战案例–调用摄像头并使用opencv库处理

硬件基础(在以下设备中成功部署)

  • ubuntu22.04的ros2(humble)中 (双系统)
  • Jatson orin nano:ubuntu20.04,ros2 foxy
  • 树莓派B4(ubuntu22.04)

环境搭建

安装ros2

这里只推荐鱼香ros的一键安装 ,终端输入即可

1
wget http://fishros.com/install -O fishros && . fishros`  

更多请查阅鱼香ros

安装opencv

建议使用Python的pip工具进行安装,如果喜欢用c++进行开发,需要在opencv官网下载源码自行编译,还是推荐用Python,在后续视觉开发中会方便很多

在终端输入:

1
pip3 install opencv-python     ##opencv-python==4.4.6.40 

或安装指定版本

安装Vscode(强推)

注意安装code的时候勾选添加到上下文选项中,方便打开文件夹

因为ros2实际是分布式通信框架,由很多个节点组成,所以非常推荐使用VScode进行ros2的开发,众所周知VScode是宇宙最强编辑器,用过都说好
下面放一张图
不要在意报错,这是我的windows系统。😓
这是图片

ROS2工作空间及准备

创建工作空间

1
mkdir -p ros2_ws/src && cd ros2_ws/src

创建功能包

这一步在src文件夹下创建

1
ros2 pkg create opencv_tools --build-type ament_python --dependencies rclpy `   

$ ros2 pkg create –build-type

输入 tree 可查看当前工作区状态

创建节点

完成本案例需要创建两个节点,一个获取摄像头的图像信息并发布话题,一个订阅图像信息并进行处理。当然其实一个很简单的python程序可以完成这两项,但是既然要用ros,就要用多节点。

直接在终端创建,用自带的或vim文本编辑器(不推荐

1
2
3
4
cd ros2_ws/src/opencv_tools/opencv_tools
gedit img_publisher.py
gedit img_subscriber.py
chmod +x img_publisher.py img_subscriber.py

在vscode中创建

在工作区文件夹中(ros2_ws)右键用code打开,这一步在安装的时候需要勾选,或者用终端打开文件夹并输入 code ./在vscode中打开工作区
然后在src/opencv_tools/opencv_tools里,注意到有一个_init_.py的空文件,不用管他,右键创建一个img_publisher.py的新文件,这样就是一个节点,一个节点实际上就是一个py文件,再创建一个img_subscriber.py的文件,这个文件用来订阅图像处理的话题并进行处理

在setup文件中添加启动项

打开src/opencv_tools下的setup.py文件,在 ‘console_scripts’中添加新的节点,格式如下:(注意文件路径和文件名一定不要写错,等号左边是节点的名称,可以随便写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='chan',
maintainer_email='[email protected]',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points= {;
'console_scripts': [
'img_publisher = opencv_tools.img_publisher:main',
'img_subscriber = opencv_tools.img_subscriber:main', #添加新的节点
#’nodename=path.file:main'
],
},
)

编译(回到工作区文件夹下!)

1
2
cd ros2_ws
colcon build

不出意外的话到这里准备工作就完成了,接下来就是写节点

创建节点并发布话题

img_publisher.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
import cv2
from cv_bridge import CvBridge
from gi.repository import Gst
Gst.init(None) # Initialize GStreamer

import time

class ImagePublisher(Node):

def __init__(self):
super().__init__('image_publisher')
self.publisher_ = self.create_publisher(Image, 'video_frames', 10)
timer_period = 0.01 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.cap = cv2.VideoCapture("nvarguscamerasrc ! video/x-raw(memory:NVMM), width=640, height=480, format=(string)NV12, framerate=(fraction)60/1 ! nvvidconv flip-method=0 ! video/x-raw, width=640, height=480, format=(string)BGRx ! videoconvert ! video/x-raw, format=(string)BGR ! appsink", cv2.CAP_GSTREAMER)
self.cap = cv2.VideoCapture(0) #usb camera
self.bridge = CvBridge()

def timer_callback(self):
ret, frame = self.cap.read()
if ret:
self.publisher_.publish(self.bridge.cv2_to_imgmsg(frame, "bgr8"))

def destroy_node(self):
self.cap.release()
super().destroy_node()

def main(args=None):
rclpy.init(args=args)

image_publisher = ImagePublisher()

try:
rclpy.spin(image_publisher)
except KeyboardInterrupt:
pass
finally:
image_publisher.cap.release()
image_publisher.destroy_node()
rclpy.shutdown()
time.sleep(1)

if __name__ == '__main__':
main()

img_subscriber.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
import numpy as np

class ImageSubscriber(Node):
"""
Create an ImageSubscriber class, which is a subclass of the Node class.
"""
def __init__(self):
"""
Class constructor to set up the node
"""
# Initiate the Node class's constructor and give it a name
super().__init__('image_subscriber')

# Create the subscriber. This subscriber will receive an Image
# from the video_frames topic. The queue size is 10 messages.
self.subscription = self.create_subscription(
Image,
'video_frames',
self.listener_callback,
10)
self.subscription # prevent unused variable warning

# Used to convert between ROS and OpenCV images
self.br = CvBridge()

def listener_callback(self, data):
"""
Callback function.
"""
# Display the message on the console
self.get_logger().info('Receiving video frame')

# Convert ROS Image message to OpenCV image
current_frame = self.br.imgmsg_to_cv2(data)

# Display image
cv2.imshow("camera", current_frame)

cv2.waitKey(1)

def main(args=None):

# Initialize the rclpy library
rclpy.init(args=args)

# Create the node
image_subscriber = ImageSubscriber()

# Spin the node so the callback function is called.
rclpy.spin(image_subscriber)

# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
image_subscriber.destroy_node()

# Shutdown the ROS client library for Python
rclpy.shutdown()

if __name__ == '__main__':
main()

启动节点

记得先编译
colcon build

创建完两个节点后,打开两个终端分别启动

1
ros2 run opencv_tools img_publisher
1
ros2 run opencv_tools img_subscriber

这样就成功启动两个节点并获取摄像头的图像信息了,是不是非常简单~