RDK X5/S100上使用多模态大模型实现视觉定位

引言

继论坛中帖子《S100上运行VLM模型》以及《RDK X5 运行VLM模型》我们成功地在S100和X5开发板上运行了多模态大模型之后,这次我们将更进一步,探讨如何利用这个已部署的模型来完成一项具体的应用——“视觉定位”。本帖将详细介绍准备工作、关键的Prompt设计以及具体的实战步骤,并提供一个Python脚本来帮助大家可视化定位结果。希望这篇教程能为大家在机器人视觉应用开发上提供一些有益的参考。

一、概要

在帖子 S100上运行VLM模型RDK X5 运行VLM模型 中,我们详细介绍了如何在S100以及X5平台上编译llama.cpp,部署多模态大模型,最后利用BPU进行硬件加速的流程。-
本帖将以此为基础,进一步演示如何使用已部署的多模态大模型来完成“视觉定位”任务。我们将通过具体的指令和代码示例,向您展示如何让模型识别图像中的特定物体并给出其边界框Bbox坐标。

二、准备工作

在开始之前,请确保您已完成以下准备:

  1. Llama.cpp 仓库:

    • 准备好一个适配RDK系列开发板(如S100)并支持BPU加速的 llama.cpp 仓库。
    • 重要: 在X5和S100上完成llama.cpp编译的过程并不完全相同,请您先阅读上面对应开发平台的帖子,确保该仓库已经成功编译。
  2. 模型权重:

    • 获取 InternVL2.5-1B 的视觉(vision)和文本(text)权重。
    • 注意: 视觉权重需要区分RDK X5版本(.bin结尾的权重)和S100版本(.hbm结尾的权重),请选择与您硬件匹配的权重。
  3. 关键Prompt:-
    视觉定位任务的核心在于使用精确的Prompt。以下是推荐的Prompt格式:

    Please provide the bounding box coordinate of the region this sentence describes: <ref>The object wants to detect</ref>
    
    • 使用方法: 将上述Prompt中的 <ref>The object wants to detect</ref> 部分替换为您希望模型检测的具体物体名称。例如,如果您想检测“杯子”,则Prompt应修改为 Please provide the bounding box coordinate of the region this sentence describes: <ref>cup</ref>

温馨提示: 为了使vLLM可以完全发挥Grounding能力,只可以修改这句prompt中<ref></ref>中的词语,请不要修改prompt的其它部分!-
InternVL2.5-1B在视觉定位方面的能力尚有提升空间。我们计划在后期提供可在S100上部署的InternVL3-2B权重,其视觉定位能力将有显著增强,敬请期待。

三、实战演练

本节将基于 S100上运行VLM模型 中部署好的项目,演示如何执行视觉定位任务。

1. 执行命令

使用以下命令格式运行视觉定位:

./build/bin/llama-intern2vl-bpu-cli -m <vLLM语言部分的gguf模型路径> \
--mmproj <vLLM视觉部分的hbm模型路径> \
--image <待检测的图片路径> \
-p 'Please provide the bounding box coordinate of the region this sentence describes: <ref>物体名称</ref>' \
--temp <温度参数,例如0.3> \
--threads <线程数,例如6>

注意-
temp参数推荐使用0.3或者0.4,如果将温度值调高,模型很容易乱输出;-
<vLLM语言部分的gguf模型> 应该使用量化为Q8_0及以上的权重,如InternVL2.5-1B,推荐使用Qwen2.5-0.5B-Instruct-F16.gguf这个权重-
输入的图片 分辨率应为448x448

2. 具体实践示例

以下是一个检测图片中“bottle”(瓶子)的实际命令:

./build/bin/llama-intern2vl-bpu-cli -m ./Qwen2.5-0.5B-Instruct-F16.gguf \
--mmproj ./vit_model_int16.hbm \
--image /root/my_codes/InternVL-test/test_imgs/output_cropped3.jpg \
-p 'Please provide the bounding box coordinate of the region this sentence describes: <ref>bottle</ref>' \
--temp 0.3 --threads 6

3. 模型输出与可视化

模型将会返回检测到的物体边界框Bbox坐标。以下是一些可视化结果示例:

检测 “bottle”:-

bottle的检测结果1-
bottle的检测结果2

检测 “keyboard”:-

keyboard的检测结果

检测 “mouse”:-

mouse的检测结果

4. 坐标转换说明

InternVL系列vLLM给出的Bbox坐标并非直接对应图片像素的绝对坐标,而是基于一个1000x1000的归一化网格。您需要将这些归一化坐标按比例转换回原始图片的实际坐标。

转换公式示例:-
假设模型返回的Bbox左上角归一化坐标为 (nx1, ny1),例如 (100, 500),而原始图片尺寸为 (W, H),例如 (448, 448)。-
那么,在图片上的实际坐标 (px1, py1) 计算如下:

  • px1 = (nx1 / 1000.0) * W
  • py1 = (ny1 / 1000.0) * H

代入示例数据:

  • px1 = (100 / 1000.0) * 448 = 44.8 (取整后为 44)
  • py1 = (500 / 1000.0) * 448 = 224

因此,归一化坐标 (100, 500) 对应于 448x448 图像上的实际坐标 (44, 224)

5. 可视化脚本 (Python)

为了方便您验证和可视化模型输出的Bbox坐标,我们提供以下Python脚本。请根据您的实际情况修改脚本中的TODO标记行。

import cv2
import numpy as np
import os

# TODO: <<< --- 请修改以下变量 --- >>>
image_path = '/root/my_codes/InternVL-test/test_imgs/output_cropped31.jpg' # 图像文件路径
output_image_path = "/root/my_codes/InternVL-test/test_imgs/out0.jpg"      # 处理后图像的保存路径

# 模型输出的归一化Bbox坐标 (左上角x, 左上角y, 右下角x, 右下角y)
# 示例: nx1, ny1 = 613, 612 和 nx2, ny2 = 896, 925 代表一个Bbox
nx1, ny1 = 30, 704   # TODO: 归一化左上角坐标 (范围 0-1000)
nx2, ny2 = 313, 998  # TODO: 归一化右下角坐标 (范围 0-1000)

# TODO: <<< --- 可选修改 --- >>>
normalization_range = 1000.0  # 坐标归一化的范围 (通常是1000)
box_color_bgr = (36, 255, 12) # BGR格式的边界框颜色 (石灰绿) OpenCV使用BGR
box_thickness = 2             # 边界框线条粗细 (像素)

def draw_bounding_box_on_image():
    """
    在图像上绘制边界框并保存.
    """
    # 检查图像文件是否存在
    if not os.path.exists(image_path):
        print(f"错误: 图像文件未找到 '{image_path}'")
        return

    # 读取图像
    image = cv2.imread(image_path)
    if image is None:
        print(f"错误: 无法从 '{image_path}' 加载图像. 文件可能已损坏或格式不受支持.")
        return

    # 获取图像尺寸 (高, 宽, 通道数)
    H, W = image.shape[:2]
    print(f"成功加载图像: '{os.path.basename(image_path)}' (宽: {W}px, 高: {H}px)")

    # 将归一化坐标转换为像素坐标
    final_x1 = int((nx1 / normalization_range) * W)
    final_y1 = int((ny1 / normalization_range) * H)
    final_x2 = int((nx2 / normalization_range) * W)
    final_y2 = int((ny2 / normalization_range) * H)

    print(f"使用归一化坐标 [{nx1},{ny1},{nx2},{ny2}] 转换为:")
    print(f"  像素坐标: 左上角=({final_x1},{final_y1}), 右下角=({final_x2},{final_y2})")

    # 准备OpenCV rectangle函数的点 (需要整数元组)
    point1 = (final_x1, final_y1)
    point2 = (final_x2, final_y2)

    # 在图像上绘制矩形框
    try:
        cv2.rectangle(image, point1, point2, box_color_bgr, box_thickness)
        print("已在图像上绘制边界框。")
    except Exception as e:
        print(f"绘制矩形时出错: {e}")
        print("请检查计算出的坐标是否有效。")
        return

    # 保存带边界框的图像
    try:
        cv2.imwrite(output_image_path, image)
        print(f"已将带边界框的图像保存至: '{output_image_path}'")
        # 如果需要在窗口中显示 (可选)
        # window_title = '带边界框的图像 (按任意键关闭)'
        # cv2.imshow(window_title, image)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()
    except Exception as e:
        print(f"保存图像时出错: {e}")

if __name__ == '__main__':
    draw_bounding_box_on_image()

希望这篇教程能够帮助您实现使用vLLM进行视觉定位功能!如果您在实践过程中遇到任何问题,欢迎在评论区留言讨论。

1 个赞

实时性如何?如果是摄像头视频流的话

是的,最好有性能说明,更实用些

目前实时性较低,低于 1 fps,更多强调通用检测能力。