rknn_model_zoo 中 CLIP 多模态模型的 RKNN 部署指南:图像-文本匹配全流程实战
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++ 结果一致的关键所在。