一、项目概述

随着科技的飞速发展,巡逻机器人作为智能化、自动化的代表,正在逐渐改变传统的安防模式。该项目通过搭载高清相机,实时采集环境图像,并利用先进的 BLIP 大模型进行图像分析,从而识别异常事件或目标物体。本项目旨在开发一个基于 ROS2 框架的智能巡航机器人系统,该系统能够在 Intel 哪吒开发板上高效运行图像推理任务。

Intel 哪吒开发板采用高性能的 Intel N97 处理器(Alder Lake-N),具备四核 SoC,主频高达 3.60GHz,并内置 Intel UHD Graphics Gen12 GPU,支持多达 24 个执行单元,适用于 AI 推理任务。开发板尺寸为 85mm x 56mm,配备 8GB LPDDR5 内存和 64GB eMMC 存储,内置 TPM 2.0 模块和 40 针 GPIO 连接器,提供丰富的外设接口,非常适合嵌入式机器人应用。

ROS2(Robot Operating System 2)采用了 DDS(Data Distribution Service,数据分布服务)作为其底层通信机制的核心部分。DDS 是一种面向实时系统的开放标准,特别适合于构建大规模分布式系统。ROS2 通过 DDS 提供了高效的系统集成能力和实时通信支持。

BLIP(Bridging Language and Perception)是一个多模态预训练模型,专门用于连接语言和视觉信息处理。它是一个强大的多模态预训练模型,能够处理诸如图像描述生成、视觉问答等多种任务。该模型由三部分组成:

  • Vision Model:图像表示的编码器,负责提取图像特征。
  • Text Encoder:输入查询的编码器,主要用于处理视觉问答和文本到图像检索任务中的文本输入。
  • Text Decoder:输出答案的解码器,负责生成最终的答案或描述。

通过使用 OpenVINO(Open Visual Inference & Neural Network Optimization)工具套件,可以直接将 PyTorch 模型转换为 OpenVINO 中间表示(IR)格式。这一转换允许模型在 Intel 哪吒开发板上加速推理过程,从而显著提升处理速度。这使得在边缘设备上运行复杂的视觉任务成为可能,同时保持高性能和低延迟,非常适合实时机器人应用。

ROS2 的环境中,BLIP 可以被用来处理机器人感知系统中的视觉语言任务。例如,在一个机器人导航任务中,BLIP 可以用于理解环境中的图像,并生成相应的描述或回答相关问题。通过结合 ROS2 的强大通信能力,BLIP 模型可以在不同节点之间高效地交换信息,从而实现复杂的机器人行为。

使用 OpenVINO 对 BLIP 模型进行优化和加速,不仅可以提高模型的运行效率,还可以降低功耗,这对于在资源受限的嵌入式平台上部署机器学习模型尤为重要。这样可以确保机器人系统能够快速响应环境变化,同时保持系统的稳定性和可靠性。

二、系统设计

1、硬件平台:

  •  英特尔开发者套件-哪吒是一款高性能AI开发板,搭载Intel N97处理器,配备8GB LPDDR5内存和64GB eMMC存储空间,支持Windows和Linux操作系统,适用于自动化、物联网网关、数字标牌和机器人等场景。它具有高性能与低功耗特性,内置Intel UHD Graphics Gen12 GPU,支持高分辨率显示,并提供丰富的外设接口。此外,哪吒开发套件目前暂不支持蓝牙和WiFi连接,只能通过外置网卡的方案解决某些对无线连接需求较高的应用场景。如图1所示。
  • 外置WIFI网卡,如rtl18821cu的驱动程序,可下载并进行安装。

git clone https://github.com/brektrou/rtl8821CU.git
cd rtl8821CU/
sudo ./dkms-install.sh

  • Intel GPU驱动,安装哪吒开发板的驱动程序和必要的软件包,可额外安装来提高推理速度。

sudo apt update
sudo apt install -y gpg-agent wget
wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | sudo gpg --yes --dearmor --output /usr/share/keyrings/intel-graphics.gpg
echo "deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu jammy client" | sudo tee /etc/apt/sources.list.d/intel-gpu-jammy.list
sudo apt update
sudo apt install -y intel-opencl-icd intel-level-zero-gpu level-zero

1 英特尔-哪吒开发者套介绍

2、环境搭建

2.1 Ubuntu 22.04 LTS

2.2 安装ROS2 Humble

  • 更新系统包列表与编码设置

sudo apt update && sudo apt upgrade -y
sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8

  • 添加ROS2仓库

sudo apt install curl gnupg lsb-release
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

  • 安装ROS2基础包

sudo apt update
sudo apt upgrade
sudo apt install ros-humble-desktop

  • 环境配置

echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc # 所有终端均生效
source ~/.bashrc

2.3 安装Miniconda

# 下载 Miniconda 安装脚本
wget https://repo.anaconda.com/miniconda/Miniconda3-py310_24.7.1-0-Linux-x86_64.sh
# 运行 Miniconda 安装脚本
bash Miniconda3-py310_24.7.1-0-Linux-x86_64.sh
# 初次安装需要激活 base 环境
source ~/.bashrc
# 改换国内源
conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/
conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/
conda config --set show_channel_urls yes

(2)创建并激活ros2虚拟环境

conda create -n ros2 python=3.10
echo "conda activate ros2" >> ~/.bashrc
source ~/.bashrc

2.4 安装OpenVINO 2024.3.0

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/
pip install openvino-genai==2024.3.0

2.5 下载BLIP Model

pip install -U huggingface_hub
echo "export HF_ENDPOINT=https://hf-mirror.com" >> ~/.bashrc
source ~/.bashrc
pip install git-lfs
git clone https://hf-mirror.com/Salesforce/blip-image-captioning-large

  •  安装BLIP Model依赖

pip install torch>=2.1.0 torchvision transformers>=4.26.0 gradio>=4.19
pip install datasets>=2.14.6 nncf>=2.8.1 tqdm matplotlib==3.6
pip install timm==0.4.12 fairscale==0.4.4

三、ROS2节点设计

1. 创建ROS2工作空间

首先,创建一个ROS2工作空间,并使用rosdep工具自动安装相关依赖。

mkdir -p ~/intel_nezha_ws/src/
cd ~/intel_nezha_ws/src
sudo apt install -y python3-pip
sudo pip3 install rosdepc
sudo rosdepc init
rosdepc update
cd ..
rosdepc install -i --from-path src --rosdistro humble -y

2. 编译工作空间

依赖安装完成后,需要下载colcon对工作空间进行编译。

sudo apt-get install python3-colcon-common-extensions
cd ~/ros2_nazha_ws/
colcon build

3.  设置环境变量

编译成功后,为了让系统能够找到我们的功能包和可执行文件,还需要设置环境变量:

source install/local_setup.sh # 仅在当前终端生效
echo " source ~/intel_nezha_ws/install/local_setup.sh" >> ~/.bashrc # 所有终端均生效

4. 创建客户端/服务器模型ROS2功能包以及服务接口。

cd src
ros2 pkg create nintel --build-type ament_python
ros2 pkg create nezha_ros2_interfaces --build-type ament_cmake --dependencies rosidl_default_generators

5. 创建接口定义文件

sudo vim ~/ros2_nezha_ws/nezha_ros2_interfaces/srv/ImageInference.srv
bool get
---
String s

6. USB-CAM节点实现

USB相机与哪吒开发板连接,并安装必要的驱动程序。可通过 以下指令查看当前设备连接状况。

lsusb

这将列出所有连接的USB设备。找到相机相对应的条目。常用的usb相机驱动一般都是通用的,ROS中也集成了usb相机的标准驱动,可以直接使用ROS中的相机驱动节点。

7. 安装usb-cam相机驱动

sudo apt install ros-humble-usb-cam

如果摄像头被正确识别,它应该会被自动加载相应的驱动。可以通过lsmod命令来检查是否已加载了相关驱动:

lsmod | grep uvc

为了确保摄像头能够正常工作,需要安装一些基本的视频捕获工具。例如,v4l-utilsffmpeg等工具可以帮助您测试摄像头:

sudo apt install -y v4l-utils ffmpeg
v4l2-ctl --list-devices
ffmpeg -f v4l2 -i /dev/video0 -c:v libx264 -preset ultrafast -f mpegts pipe:1 > video.mpg

ROS2中创建一个相机驱动节点,该节点负责从相机获取图像数据,并将其作为ROS2消息发布。

为了确保您的摄像头能够正常工作,需要安装ROS2的图像传输工具包:

sudo apt install -y ros-humble-image-transport-plugins

8. 运行usb相机图像采集节点

ros2 run usb_cam usb_cam_node_exe

9. 服务端节点实现

主要功能:订阅USB图像数据,接收客户端图像分析请求,通过openvino实现对blip大模型进行推理,提高推理速度。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# service_object_server.py

import json
import rclpy                                           # ROS2 Python接口库
from rclpy.node import Node                            # ROS2 节点类
from sensor_msgs.msg import Image                      # 图像消息类型
import numpy as np                                     # Python数值计算库
from cv_bridge import CvBridge                         # ROS与OpenCV图像转换类
import cv2                                             # OpenCV图像处理库
from nezha_ros2_interface.srv import GetObjectInformation  # 自定义的服务接口
import sys
sys.path.append('/home/iven/miniconda3/envs/ros2/lib/python3.10/site-packages/')  # 添加依赖库路径
sys.path.append('src/blip-vqa-base/')                 # 添加项目源码路径
from transformers import BlipProcessor, BlipForConditionalGeneration, BlipConfig
import openvino as ov
from pathlib import Path
from blip_model import OVBlipModel, text_decoder_forward
from functools import partial
from utils import visualize_results


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的图像消息的互相转换
        
        self.srv = self.create_service(GetObjectInformation,               # 创建服务器对象(接口类型、服务名、服务器回调函数)
                                       'get_target_information',
                                       self.object_information_callback)      
        
        self.image = None                                                # 用于存储接收到的图像

        # 加载配置文件
        config_path = "src/blip-vqa-base/config.json"
        self.config = BlipConfig.from_pretrained(config_path)            # 从预训练配置文件加载配置

        # 打印配置信息,确保其包含所需的属性
        self.get_logger().info(f"Loaded Config: {self.config.to_json_string()}")  # 打印配置信息
        if not hasattr(self.config.text_config, 'decoder_start_token_id'):
            # 如果text_config中没有设置decoder_start_token_id,
            # 尝试从vision_config中获取
            if hasattr(self.config.vision_config, 'decoder_start_token_id'):
                self.config.text_config.decoder_start_token_id = self.config.vision_config.decoder_start_token_id
            else:
                # 如果vision_config中也没有设置,则手动设置一个默认值
                self.config.text_config.decoder_start_token_id = 31884  # 替换为实际值

        # 获取正确的decoder_start_token_id
        self.decoder_start_token_id = self.config.text_config.decoder_start_token_id
        self.get_logger().info(f"Loaded Decoder start token ID: {self.decoder_start_token_id}")

        # 加载模型
        model_path = "src/blip-vqa-base"
        self.model = BlipForConditionalGeneration.from_pretrained(model_path, config=self.config)  # 从预训练模型加载模型
        self.processor = BlipProcessor.from_pretrained(model_path)                                # 从预训练模型加载处理器

        # 初始化OVBlipModel
        self.initialize_ov_model()

    def initialize_ov_model(self):
        # 加载OpenVINO IR模型
        VISION_MODEL_OV = Path("src/blip-vqa-base/blip_vision_model.xml")
        TEXT_ENCODE_OV = Path("src/blip-vqa-base/blip_text_encoder.xml")
        TEXT_DECODE_OV = Path("src/blip-vqa-base/blip_text_decoder_with_past.xml")
        self.get_logger().info(f"Loading models from paths: {VISION_MODEL_OV}, {TEXT_ENCODE_OV}, {TEXT_DECODE_OV}")  # 日志记录
        core = ov.Core()
        try:
            ov_vision_model = core.compile_model(model=VISION_MODEL_OV, device_name="GPU")  # 编译视觉模型
            ov_text_encoder = core.compile_model(model=TEXT_ENCODE_OV, device_name="GPU")  # 编译文本编码器
            ov_text_decoder_with_past = core.compile_model(model=TEXT_DECODE_OV, device_name="GPU")  # 编译带过去状态的文本解码器
            self.get_logger().info(f"Models loaded successfully. decoder_start_token_id: {self.decoder_start_token_id}")  # 日志记录
            text_decoder = self.model.text_decoder
            text_decoder.eval()  # 设置评估模式
            text_decoder.forward = partial(text_decoder_forward, ov_text_decoder_with_past=ov_text_decoder_with_past)  # 替换forward方法
            self.ov_model = OVBlipModel(self.config, self.decoder_start_token_id, ov_vision_model, ov_text_encoder, text_decoder)  # 初始化OVBlipModel
        except Exception as e:
            self.get_logger().error(f"Failed to load models: {e}")  # 日志记录失败信息

    def object_detect(self, image):
        self.image = image                                                  # 存储接收到的图像
        cv2.imshow("object", image)                                         # 使用OpenCV显示处理后的图像效果
        cv2.waitKey(50)                                                     # 等待一段时间以显示图像

    def listener_callback(self, data):
        self.get_logger().info('Receiving video frame')                     # 输出日志信息,提示已进入回调函数
        image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8')                  # 将图像消息转化成OpenCV图像
        self.image = image                                                  # 保存图像以便后续使用
        self.object_detect(image)                                           # 调用对象检测方法

    def openVino(self):
        text = "a photography of"
        inputs = self.processor(images=self.image, text=text, return_tensors="pt")  # 处理图像和文本输入

        # 检查是否包含所需参数
        assert 'input_ids' in inputs, "'input_ids' is missing"
        assert 'attention_mask' in inputs, "'attention_mask' is missing"

        input_ids = inputs.input_ids
        pixel_values = inputs.pixel_values
        attention_mask = inputs.attention_mask

        # 生成答案
        out = self.ov_model.generate_answer(pixel_values=pixel_values, input_ids=input_ids, attention_mask=attention_mask, max_length=1024)
        answer = self.processor.decode(out[0], skip_special_tokens=True)  # 解码输出结果
        return answer
    
    def object_information_callback(self, request, response):
        if self.image is None:
            self.get_logger().warn('No image received yet.')               # 输出日志信息,提示尚未接收到图像
            return response

        if request.get:
            response.s = self.openVino()                                   # 调用openVino方法获取结果
            self.get_logger().info('Object position\ns:%s' % (response.s))  # 输出日志信息,提示已经反馈
        else:
            response.s = "null"                                            # 请求无效
            self.get_logger().info('Invalid command')                       # 输出日志信息,提示请求无效
        return response

def main(args=None):                                                       # ROS2节点主入口main函数
    rclpy.init(args=args)                                                  # ROS2 Python接口初始化
    node = ImageSubscriber("service_object_server")                         # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                                                       # 循环等待ROS2退出
    node.destroy_node()                                                    # 销毁节点对象
    rclpy.shutdown()                                                       # 关闭ROS2 Python接口

if __name__ == '__main__':
    main()                                                                 # 主程序入口

10. 客户端节点实现

主要功能:订阅图像采集节点数据,向服务端节点提交推理请求,并接收图像描述反馈。

# ~/ros2_nezha_ws/nezha_ros2_service/llm_client.py
import rclpy
from rclpy.node import Node
from inference_service.srv import ImageInference
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
import numpy as np

class InferenceClient(Node):
    def __init__(self):
        super().__init__('llm_client')
        self.sub =self.create_subscription(Image,'image_raw',self.listener_callback,10)
        self.client = self.create_client(ImageInference, 'image_inference')
        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('服务未准备好,正在等待...')
        
        # 创建cv_bridge对象,用于图像消息与OpenCV图像之间的转换
        self.bridge = CvBridge()

    def send_request(self, image_path):
        # 加载图像
        img = cv2.imread(image_path)
        # 将图像转换为ROS消息
        img_msg = self.bridge.cv2_to_imgmsg(img, encoding='rgb8')
        
        # 创建请求对象
        req = ImageInference.Request()
        req.data = img_msg
        
        # 异步发送请求
        self.future = self.client.call_async(req)

def main(args=None):
    rclpy.init(args=args)
    inference_client = InferenceClient()
    
    # 替换此行为获取图像消息的实际代码
    image_path = "/path/to/your/image.jpg"  # 图像路径
    inference_client.send_request(image_path)

    # 主循环
    while rclpy.ok():
        rclpy.spin_once(inference_client)
        # 检查是否完成
        if inference_client.future.done():
            try:
                # 获取结果
                response = inference_client.future.result()
            except Exception as e:
                # 处理异常
                inference_client.get_logger().info('服务调用失败:%r' % (e,))
            else:
                # 输出结果
                inference_client.get_logger().info('推理结果:%s' % (response.result,))
            break
    inference_client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

11. 编译和运行

运行摄像头图像采集节点、服务端和客户端节点。

#编译整个工作空间
colcon build
source install/local_setup.bash

# 运行服务端节点
ros2 run nezha_ros2_service llm_server

# 在另一个终端窗口运行客户端节点
ros2 run nezha_ros2_service llm_client

服务节点运行结果如图2所示,客户节点运行结果如图3所示。

 2 服务节点视频采集图像

 

3客户节点请求BLIP分析结果

 

四、总结

以上是实现基于ROS2和blip大模型的巡逻机器人,采用blip-vqa-base模型实现图像分析方面的一个方案,该模型只能简单识别物体,考虑到哪吒开发板作为巡逻机器人的控制板,主要负责巡逻机器人的运动控制以及各种传感器的数据传输。因此,大模型不宜安装在哪吒开发板上,可考虑升级带有GPU的开发板,如算力魔方。或者把大模型安装在带有ROS2系统的边缘aipc/服务器上,通过ROS节点进行DDS通信。同时需要更换其它模型来优化识别物体的能力。该系统适用于工业自动化中的质量控制、智能仓库中的物品识别以及公共区域的安全监控等场景,能够提供高效、可靠的解决方案。

Logo

为开发者提供丰富的英特尔开发套件资源、创新技术、解决方案与行业活动。欢迎关注!

更多推荐