rknn_model_zoo 中 CLIP 多模态模型的 RKNN 部署指南:图像-文本匹配全流程实战

原创2026-10-03 16:34:36517 阅读
文章标签:示例工程人工智能嵌入式边缘计算计算机视觉模型优化

rknn_model_zoo 中 CLIP 多模态模型的 RKNN 部署指南:图像-文本匹配全流程实战

CLIP(Contrastive Language-Image Pre-training)通过双塔编码器将图像与文本映射到同一语义空间,实现"图像-文本"跨模态相似度匹配。本文以 rknn_model_zoo 仓库的 examples/clip 示例为骨架,完整讲解如何将 OpenAI CLIP ViT-Base/Patch32 模型转换为 RKNN 格式,并分别在 Python、Android 与 Linux(RKNN NPU)环境中运行图像与文本匹配推理。读完本文,你将掌握 CLIP 双模型(图像编码器 + 文本编码器)的 ONNX 导出、RKNN 转换参数配置、预处理归一化细节以及端侧部署与结果解读的全套方法。

1. 示例简介与工作原理

本示例使用的模型源自开源项目 openai/clip-vit-base-patch32(Hugging Face 官方仓库,详见 export_onnx.md 的导出流程)。部署时模型被拆分为两个独立的 RKNN 子模型:

  • clip_images 模型(图像编码器):输入 pixel_values(1×3×224×224 的 RGB 图像张量),输出 512 维 image_embeds 图像特征向量;
  • clip_text 模型(文本编码器):输入 input_ids(1×20 的 token ID 序列),输出 512 维 text_embeds 文本特征向量。

推理时,将图像特征与多条文本特征做矩阵乘法(余弦相似度),再经 logit scale 缩放与 softmax 归一化,即可得到该图像与各条文本的匹配分数,分数最高者即为匹配结果。整个过程可从 Python 侧 clip.py 与 C++ 侧 postprocess.cc 中相互印证。

2. 当前支持的平台

示例的 RKNN 模型转换与运行适配以下 NPU 平台:

RK3562、RK3566、RK3568、RK3576、RK3588、RV1126B

注意:RV1126B 在转换脚本中写为 rv1126b(小写),实际使用时应与所选开发板的 NPU 型号严格对应。

3. 预训练模型下载

示例提供了两个已导出的 ONNX 模型下载链接(图像编码器与文本编码器),在 model 目录下执行仓库自带的 download_model.sh 即可一键获取:

cd model
./download_model.sh

脚本内部通过 wget 分别下载 clip_images.onnx 与 clip_text.onnx 到当前 model 目录。下载完成后,目录内同时包含测试图片 dog_224x224.jpg 与测试文本 text.txt,用于后续验证推理流程。

4. 导出 CLIP ONNX 模型

如果希望从原始权重自行导出 ONNX(而非直接下载现成模型),请参考仓库内的 export_onnx.md。整体分两步:

第一步:导出完整模型

在满足 huggingface/openai/clip-vit-base-patch32 原始仓库的安装环境要求后,使用 optimum-cli 导出:

optimum-cli export onnx --model weights_path/ --task image-to-text --opset 18 path_to_output/

# weights_path   指原始模型文件路径
# image-to-text  指模型任务类型
# path_to_output 指导出模型保存目录(执行后在该目录生成 model.onnx)

第二步:拆分为图像与文本两个子模型

进入 python 目录执行仓库提供的 truncated_onnx.py:

cd python
python truncated_onnx.py --model path_to_output/

该脚本通过 onnx.utils.extract_model 完成子图提取:

  • 以 ['input_ids', 'attention_mask'] 为输入、['text_embeds'] 为输出,导出 ../model/clip_text.onnx;
  • 以 ['pixel_values'] 为输入、['image_embeds'] 为输出,导出 ../model/clip_images.onnx。

附加说明(来自原文档):如需移除 clip_text 模型中的 attention_mask 输入,可使用 onnx-modifier 类工具完成修改。此外,导出后的模型输入输出命名需与转换脚本中的 inputs/输出假定保持一致,见下一节。

5. 转换为 RKNN 模型

转换入口为 python 目录下的转换脚本。原 README 给出的通用用法如下:

cd python
python convert.py <onnx_model> <TARGET_PLATFORM> <output_rknn_path(optional)>

# 示例:
python convert.py ../model/clip_images.onnx rk3588
# 输出模型默认保存为 ../model/clip_images.rknn

参数说明:

  • <onnx_model>:指定 ONNX 模型路径;
  • <TARGET_PLATFORM>:指定 NPU 平台名,支持平台见第 2 节;
  • <output_rknn_path>(可选):指定 RKNN 模型保存路径,默认与 ONNX 同目录,命名为 clip_images.rknn。

需要说明的是,仓库目录中并没有名为 convert.py 的顶层脚本,实际转换逻辑分布在 python/images/convert.py(图像模型)与 python/text/convert.py(文本模型)两个脚本中,上层 convert.sh 会依据传入的 ONNX 文件名自动分发到对应脚本执行(内部测试用脚本)。两个脚本均支持以下调用形式:

python3 <脚本> onnx_model_path [platform] [dtype(optional)] [output_rknn_path(optional)]
# platform 可选 rk3562, rk3566, rk3568, rk3576, rk3588, rv1126b
# dtype 目前仅支持 fp(浮点,不做量化)

5.1 图像模型转换的关键配置

images/convert.py 中与 CLIP 预处理严格对齐的归一化参数是转换的核心:

rknn.config(target_platform=platform,
            mean_values=[[0.48145466*255, 0.4578275*255, 0.40821073*255]],
            std_values=[[0.26862954*255, 0.26130258*255, 0.27577711*255]])
rknn.load_onnx(model=model_path,
               inputs=['pixel_values'],
               input_size_list=[[1, 3, 224, 224]])
  • 均值/标准差取自 CLIP 官方图像预处理统计量(mean=[0.48145466, 0.4578275, 0.40821073]、std=[0.26862954, 0.26130258, 0.27577711]),并乘以 255 换算到 0-255 像素空间,保证 NPU 侧归一化与原始模型训练一致;
  • 输入名固定为 pixel_values,输入尺寸为 1×3×224×224(NCHW)。

5.2 文本模型转换的关键配置

text/convert.py 不设置归一化参数(token 输入本身为整数 ID),关键约束如下:

rknn.config(target_platform=platform)
rknn.load_onnx(model=model_path,
               inputs=['input_ids'],
               input_size_list=[[TEXT_BATCH_SIZE, SEQUENCE_LEN]])  # [1, 20]
  • 输入名固定为 input_ids;
  • 批量固定为 1,序列长度固定为 20(与推理端 padding 逻辑一致,见下节)。

两个脚本默认均不启用量化(do_quant=False,即 fp 模式),以保证跨模态匹配精度;构建完成后调用 rknn.export_rknn(output_path) 导出 RKNN 模型。

6. Python Demo

Python 推理入口为 python/clip.py,用法如下:

cd python

# 使用 RKNN 模型推理
python clip.py --text_model <rknn_model> --img_model <rknn_model> --target <TARGET_PLATFORM>

参数说明:

  • --target(必填):指定 NPU 平台名,例如 rk3576;
  • --text_model / --img_model:分别指定文本与图像 RKNN 模型路径(默认指向 ../model/clip_text.rknn 与 ../model/clip_images.rknn);
  • --img:测试图片路径,默认 ../model/dog_224x224.jpg;
  • --text:测试文本列表,默认 [['a photo of dog', 'a photo of cat']]。

6.1 推理前处理细节(源码级)

  • 文本 tokenizer:使用 Hugging Face AutoTokenizer.from_pretrained("openai/clip-vit-base-patch32") 对文本分词,得到 input_ids;随后统一 padding 到固定长度 SEQUENCE_LEN = 20,超出部分截断,不足部分以 PAD_VALUE = 49407(CLIP 词表的 eot/pad token)填充,最终逐条文本送入文本模型推理;
  • 图像预处理:读取 BGR 图并转为 RGB;若宽或高小于 224 则先做居中零填充,若两边均大于 224 则居中裁剪到 224×224,最后 resize 到 224×224 并扩展 batch 维度,与转换脚本中的 mean/std 归一化衔接(<a href="https://link.gitcode.com/i/b19c6689be2c4a1a0296849284c5ffb8" target="_blank">clip.py</a>)。

6.2 相似度计算(源码级)

两个模型的输出在 clip.py 的 run() 中完成匹配打分:

res = np.matmul(text_outp, img_outp.reshape(512, 1))
res = np.multiply(res, np.exp(4.605170249938965))
res = np.exp(res) / np.sum(np.exp(res))  # softmax

其中 4.605170249938965 正是 CLIP 官方 logit_scale(约 100)的自然对数,与 C++ 端 postprocess.cc 中 logit_scale = 4.605170249938965 完全一致。最终取分数最大值对应的 (图片索引, 文本索引) 作为匹配结果。

7. Android Demo(C++ 端)

7.1 编译构建

回到 rknn_model_zoo 根目录,通过统一构建脚本编译:

cd ../../
export ANDROID_NDK_PATH=<android_ndk_path>

./build-android.sh -t <TARGET_PLATFORM> -a <ARCH> -d clip

# 例如
./build-android.sh -t rk3588 -a arm64-v8a -d clip

参数说明:

  • <android_ndk_path>:指定 Android NDK 路径;
  • <TARGET_PLATFORM>:NPU 平台名,支持平台见第 2 节;
  • <ARCH>:设备系统架构,查询方式:
adb shell cat /proc/version
# Android 设备日志中应显示 'arm64-v8a' 或 'armeabi-v7a'

7.2 推送 demo 文件到设备

设备通过 USB 连接后:

adb root
adb remount
adb push install/<TARGET_PLATFORM>_android_<ARCH>/rknn_clip_demo/ /data/

7.3 运行 demo

adb shell
cd /data/rknn_clip_demo

export LD_LIBRARY_PATH=./lib
./rknn_clip_demo clip_images_fp16.rknn model/dog_224x224.jpg clip_text_fp16.rknn model/text.txt

程序入口为 cpp/main.cc,其命令行参数固定为 5 个:<image_model_path> <image_path> <text_model_path> <text_path>。运行流程为:init_clip_model 初始化图像/文本两个 RKNN 模型上下文 → read_image 读取图片 → read_lines_from_file 按行读取候选文本 → inference_clip_model 完成双塔推理与相似度计算 → 打印最高分匹配结果。C++ 侧结构体 rknn_app_context_t 中定义了 MAX_TEXT_NUM 16,即单次最多支持 16 条候选文本(clip.h)。

8. Linux Demo(C++ 端)

8.1 编译构建

cd ../../

# 若编译时找不到编译器,请先设置 GCC_COMPILER 路径(可选)
export GCC_COMPILER=<GCC_COMPILER_PATH>

./build-linux.sh -t <TARGET_PLATFORM> -a <ARCH> -d clip

# 例如
./build-linux.sh -t rk3588 -a aarch64 -d clip

参数说明:

  • <GCC_COMPILER_PATH>:指定交叉编译 GCC 编译器路径;
  • <TARGET_PLATFORM>:NPU 平台名;
  • <ARCH>:设备系统架构,查询方式:
adb shell cat /proc/version
# Linux 设备日志中应显示 'aarch64' 或 'armhf'

8.2 推送 demo 文件到设备

  • USB 连接设备时:
adb push install/<TARGET_PLATFORM>_linux_<ARCH>/rknn_clip_demo/ /userdata/
  • 其他开发板可使用 scp 等方式,将 install/<TARGET_PLATFORM>_linux_<ARCH>/rknn_clip_demo/ 下所有文件推送到 /userdata。

8.3 运行 demo

adb shell
cd /userdata/rknn_clip_demo

export LD_LIBRARY_PATH=./lib
./rknn_clip_demo clip_images_fp16.rknn model/dog_224x224.jpg clip_text_fp16.rknn model/text.txt

9. 预期结果

运行后程序会打印测试图像与候选文本的匹配分数,示例如下:

      images                 text         score
-------------------------------------------------
model/dog_224x224.jpg @ a photo of a dog: 0.989

即测试图 dog_224x224.jpg(仓库内置的狗狗图片)与文本 a photo of a dog 的匹配分数约为 0.989,直观验证了 CLIP 跨模态检索能力在 RKNN NPU 上的正确运行。

说明:不同平台、不同版本的工具链与驱动,输出结果可能存在轻微差异,属正常现象;若希望获得确定性更强的匹配,可配合 postprocess.cc 中快速排序取 Top-1 的逻辑观察多文本分数分布。

10. 小结

通过本示例,可以完整掌握 CLIP 双塔模型在 RKNN 平台上的部署链路:从 ONNX 导出与子图拆分(export_onnx.md、truncated_onnx.py),到图像/文本模型的 RKNN 转换参数配置(images/convert.py、text/convert.py),再到 Python 快速验证(clip.py)与 Android/Linux 的 C++ 端部署运行(main.cc)。其中图像归一化均值/标准差必须与 CLIP 官方预处理严格一致,文本序列需 padding 到固定长度 20,相似度计算需套用 logit scale 与 softmax,这三处是保证跨模态匹配精度与 Python/C++ 结果一致的关键所在。

登录后查看全文
rknn_model_zoo