四驱四转移动机器人 web移动端显示

在进行四驱四转机器人控制过程中,由于使用手柄进行进行控制,为了更加清晰的观测到摄像头采集的数据,经过查阅资料 ,以及古月居泡泡论坛的提问,本人将使用手机作为移动端,使用手机Web端查看USB摄像头图像,搭配手柄支架,将手机固定的方式。

硬件如下

Xbox手柄一个,手机一个,移动机器人(ros2 foxy版本)

硬件准备

步骤如下:

1、摄像头画面获取

在ROS 2中使用Python开发摄像头获取包可以让我们轻松地获取摄像头数据,并将其用于后续处理或传输到其他节点。下面是创建一个ROS 2摄像头获取包的步骤:

步骤 1:创建ROS 2功能包

首先,我们需要创建一个ROS 2功能包来容纳我们的摄像头获取代码。打开终端并执行以下命令:

bashCopy code
ros2 pkg create camera_capture_py

这将在当前目录下创建一个名为camera_capture_py的ROS 2功能包。

步骤 2:创建摄像头获取节点

在刚创建的功能包中,我们需要创建一个ROS 2节点来获取摄像头数据。创建一个名为camera_capture_node.py的Python文件,并在其中编写以下代码:

pythonCopy codeimport rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2

class CameraCaptureNode(Node):
    def __init__(self):
        super().__init__('camera_capture_node')
        self.publisher_ = self.create_publisher(Image, 'camera_topic', 10)
        self.timer_ = self.create_timer(0.1, self.capture_image)
        self.bridge_ = CvBridge()

    def capture_image(self):
        # 获取摄像头图像
        # 这里使用OpenCV来获取摄像头数据
        cap = cv2.VideoCapture(0)
        ret, frame = cap.read()
        cap.release()

        if ret:
            # 将图像转换为ROS 2图像消息
            msg = self.bridge_.cv2_to_imgmsg(frame)
            self.publisher_.publish(msg)

def main(args=None):
    rclpy.init(args=args)
    node = CameraCaptureNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

以上代码创建了一个名为CameraCaptureNode的ROS 2节点,它会定时获取摄像头图像,并将其发布到名为camera_image的话题上。请根据需要调整摄像头的索引和图像发布频率。

步骤 3:构建和运行摄像头获取包

完成节点的编写后,我们需要将功能包构建并运行节点。执行以下步骤:

  1. 打开终端并进入功能包的根目录。
bashCopy code
cd camera_capture_py
  1. 在终端中构建功能包。
bashCopy code
colcon build
  1. 在同一终端中,使用以下命令运行摄像头获取节点。
bashCopy code
ros2 run camera_capture_py camera_capture_node.py

现在,ROS 2摄像头获取包已经成功创建,并且节点正在运行以获取摄像头图像并发布到ROS 2话题上。

2、使用WebSocket将图像流传输到Web端

使用Python和ROS2创建的功能包,用于从ROS2图像话题接收图像数据,并通过WebSocket将图像流传输到Web端。下面逐行解释代码,并介绍如何创建ROS2功能包、编译和运行。

代码解释

  1. 导入所需的Python库和ROS2相关库。
import asyncio
import cv2
import websockets
from cv_bridge import CvBridge
from sensor_msgs.msg import Image
import rclpy
from rclpy.node import Node
  1. 创建一个名为CameraSubscriber的ROS2节点类,继承自Node类。该节点类用于接收图像消息,并处理WebSocket连接。
pythonCopy codeclass CameraSubscriber(Node):
    def __init__(self):
        super().__init__('camera_subscriber')
        self.bridge = CvBridge()
        self.websocket = None
  1. 在CameraSubscriber类中定义了handle_websocket方法,用于处理WebSocket连接。
pythonCopy code    async def handle_websocket(self, websocket, path):
        self.websocket = websocket
        print("WebSocket connection established.")

        while True:
            # 接收来自WebSocket客户端的消息
            message = await websocket.recv()
            print(f"Received message from client: {message}")
  1. 定义了send_image方法,用于将图像帧数据发送给WebSocket客户端。
pythonCopy code    async def send_image(self, image):
        if self.websocket:
            try:
                # 将帧转换为JPEG格式
                _, encoded_frame = cv2.imencode('.jpg', image)

                # 发送帧数据给WebSocket客户端
                await self.websocket.send(encoded_frame.tobytes())
            except Exception as e:
                print(f"Error sending image: {str(e)}")
  1. 定义了image_callback方法,用于接收ROS2图像消息,并将其转发给send_image方法发送给WebSocket客户端。
pythonCopy code    def image_callback(self, msg):
        if self.websocket:
            try:
                # 将ROS图像消息转换为OpenCV图像格式
                frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')

                # 在后台发送图像数据
                asyncio.create_task(self.send_image(frame))
            except Exception as e:
                print(f"Error sending image: {str(e)}")
  1. 在main函数中初始化ROS2节点和订阅图像话题,并创建一个WebSocket服务器来处理WebSocket连接。
pythonCopy codedef main(args=None):
    rclpy.init(args=args)
    camera_subscriber = CameraSubscriber()
    image_subscriber = camera_subscriber.create_subscription(
        Image,
        'camera_topic',
        camera_subscriber.image_callback,
        10
    )

    async def run():
        # 启动WebSocket服务器
        start_server = websockets.serve(camera_subscriber.handle_websocket, 'localhost', 8001)

        try:
            async with start_server:
                print("WebSocket server started.")
                await asyncio.Future()  # 保持主循环运行,直到键盘中断
        except KeyboardInterrupt:
            pass

        camera_subscriber.destroy_node()
        rclpy.shutdown()

    asyncio.run(run())

if __name__ == '__main__':
    main()

创建ROS2功能包、编译和运行

下面是创建ROS2功能包、编译和运行的步骤:

  1. 创建ROS2功能包:使用以下命令创建一个ROS2功能包,用于包含上述代码。
bashCopy code
ros2 pkg create my_package
  1. 将上述代码复制到my_package目录中的camera_subscriber.py文件中。
  2. my_package目录中创建一个名为launch的文件夹,并在其中创建一个名为camera_subscriber.launch.py的文件。
  3. 将以下内容添加到camera_subscriber.launch.py文件中,用于启动ROS2节点。
pythonCopy codeimport launch
from launch_ros.actions import Node

def generate_launch_description():
    return launch.LaunchDescription([
        Node(
            package='my_package',
            executable='camera_subscriber',
            name='camera_subscriber',
            output='screen'
        )
    ])
  1. 在终端中,进入ROS2工作空间根目录,并编译功能包。
bashCopy codecd ~/ros2_ws
colcon build --packages-select my_package
  1. 运行ROS2节点。
bashCopy code
ros2 launch my_package camera_subscriber.launch.py

现在,您的ROS2节点将接收图像消息,并通过WebSocket服务器将图像流传输到Web端。

web端接收视频流

在Web开发中,使用WebSocket可以实现实时的双向通信。结合HTML5的video元素,我们可以在Web端接收并显示视频流。下面是在Web端接收视频流的步骤:

步骤 1:建立WebSocket连接

首先,我们需要在Web端建立WebSocket连接以接收视频流。在JavaScript中,可以使用WebSocket API来创建WebSocket对象,并指定要连接的服务器地址。以下是建立WebSocket连接的示例代码:

javascriptCopy code
const websocket = new WebSocket('ws://localhost:8001');

将上述代码放置在Web页面的JavaScript脚本中,并将服务器地址替换为实际的WebSocket服务器地址。

步骤 2:接收和显示视频流

接下来,我们需要编写JavaScript代码来接收和显示视频流。通过WebSocket连接,我们可以接收来自服务器的视频帧数据,并使用HTML5的video元素显示这些帧。以下是接收和显示视频流的示例代码:

javascriptCopy codeconst videoElement = document.getElementById('video');

websocket.onmessage = function(event) {
  // 接收到视频帧数据
  const frameData = event.data;

  // 将帧数据转换为Blob对象
  const blob = new Blob([frameData], { type: 'image/jpeg' });

  // 创建URL对象
  const url = URL.createObjectURL(blob);

  // 设置<video>元素的源为接收到的视频帧
  videoElement.src = url;
};

websocket.onclose = function() {
  // WebSocket连接关闭
  console.log('WebSocket connection closed.');
};

在上述代码中,我们通过WebSocket的onmessage事件处理程序接收到视频帧数据。然后,我们将帧数据转换为Blob对象,并使用URL.createObjectURL()方法创建URL对象。最后,我们将URL对象设置为video元素的源,从而显示接收到的视频帧。

确保在HTML文件中定义了一个video元素,并为其分配一个唯一的ID,例如:

htmlCopy code
<video id="video" autoplay></video>

这将在Web页面中显示一个video元素,并使其自动播放视频。

问题补充

当使用Python的WebSocket进行视频流传输到Web端时,有时可能会遇到一些问题。

问题1: Failed to open a WebSocket connection: invalid Connection header: keep-alive.

当浏览器尝试直接连接WebSocket服务器时,会出现”Failed to open a WebSocket connection: invalid Connection header: keep-alive.”错误。这是因为您需要一个WebSocket客户端来连接WebSocket服务器,而不能直接在浏览器中访问WebSocket服务器。

解决方法: 确保您在Web端使用WebSocket客户端连接WebSocket服务器。

javascriptCopy code
const websocket = new WebSocket('ws://localhost:8001');

问题2: 没有接收到视频帧

如果WebSocket连接成功,但未接收到视频帧,可能存在以下问题。

  • 验证视频帧是否发送成功

  • 检查WebSocket服务器的代码,确保它正确发送视频帧。可以添加调试语句或记录来验证视频帧是否按预期发送。

  • 确认视频帧是否正确编码和发送

  • 确保在将视频帧发送到WebSocket连接之前,正确对其进行编码(如JPEG或PNG)。在服务器和客户端双方的编码和解码步骤上仔细检查,确保正确性。

  • 检查WebSocket客户端代码

  • 检查运行在浏览器WebSocket客户端中的JavaScript代码。确保它正确处理接收到的视频帧,并使用HTML的<video>元素更新接收到的帧。请仔细检查代码是否存在错误或缺失功能。

  • 验证HTML <video> 元素的兼容性

  • 确保在Web页面中的HTML <video> 元素正确设置以显示接收到的视频帧。检查元素是否具有所需的属性,如srcautoplay,并正确设置样式。

  • 检查浏览器控制台

结果现象

演示1

演示2