视觉 · 图像分类
1. 模块概述
- 主要功能:基于 ResNet50 的图像分类,输入一张图片,输出 Top-K 类别预测与置信度。支持 ImageNet 1000 类。
- 规格或特性:
- 支持模型:ResNet50
- 输入尺寸:
[1, 3, 224, 224] - 量化类型:int8
- 输出:Top-K 类别 ID + 置信度
- 推理后端:ONNX Runtime + SpaceMITExecutionProvider
- 接口形态:C++(
vision_service.h)、Python(spacemit_visionwheel:VisionServiceNative)
- 相关目录结构:
examples/resnet/
├── config/resnet50.yaml # 配置文件
├── cpp/resnet.cpp # C++ 示例
├── python/resnet.py # Python 示例
└── scripts/ # 模型下载脚本
src/deploy/resnet/ # 部署实现(C++ / Python)
assets/labels/imagenet.txt # ImageNet 1000 类标签
2. 环境准备
前置条件
SDK 源码获取和基础编译环境配置统一参考 2.3-构建编译。完成 SDK 初始化后,回到本文继续执行"构建编译"。
后续命令默认在 spacemit_robot SDK 根目录执行。
构建编译
系统缺少依赖时先安装:
sudo apt install python3-spacemit-ort opencv-spacemit libeigen3-dev spacemit-onnxruntime libyaml-cpp-dev
在 SDK 根目录加载构建环境后编译视觉组件:
source build/envsetup.sh
cd components/model_zoo/vision
mm
SDK 集成构建会把 resnet 等示例程序安装到 output/staging/bin,加载 build/envsetup.sh 后可直接运行。
运行 Python 示例前,需先安装 spacemit_vision wheel:
cd components/model_zoo/vision
python3 -m pip install -U pybind11 build setuptools wheel
cmake -S . -B build && cmake --build build -j
python3 -m pip install --force-reinstall src/python/dist/*.whl
python3 -c "from spacemit_vision import VisionServiceNative; print('ok')"
模型权重默认存放路径为 ~/.cache/models/vision/resnet/。须先手动执行下载脚本;缺失时程序会直接报错(Model file not found)。
3. 示例使用(从 0 跑通)
3.1 ResNet50 图像分类(Python)
前置:见 §2。
步骤 1:下载模型
cd components/model_zoo/vision
bash examples/resnet/scripts/download_models.sh
预期现象:模型文件下载至 ~/.cache/models/vision/resnet/resnet50.q.onnx。
步骤 2:下载测试素材
bash scripts/download_assets.sh
步骤 3:运行推理
python3 examples/resnet/python/resnet.py --config examples/resnet/config/resnet50.yaml
3.2 ResNet50 图像分类(C++)
前置:见 §2,C++ 编译完成。
步骤 1:下载模型(同 §3.1 步骤 1)
步骤 2:运行推理
resnet examples/resnet/config/resnet50.yaml
3.3 运行结果示例
终端输出示例:
Classification results:
1. tabby, tabby cat (confidence: 0.5056)
4. 应用开发
本章面向应用开发者,说明如何在自己的 C++ 或 Python 应用中集成图像分类组件。完整接口定义以 include/vision_service.h 和 src/python/spacemit_vision/vision_service_native.py 为准;本节介绍常用公开接口和典型调用方式。
4.1 接口说明
图像分类组件的核心入口是 VisionService(C++)和 VisionServiceNative(Python,spacemit_vision wheel)。应用侧通过这些接口加载 ResNet 模型,并发起图像分类请求。
4.1.1 常用数据结构
| 类型 | 说明 |
|---|---|
| VisionServiceResponse | 推理响应。results 为 vision::ResultList(std::vector<vision::Result>,variant),分类任务每个元素为 vision::Classification;ok 表示是否成功,error_message 为错误信息。 |
| vision::Classification | 分类结果,包含类别 ID(label)、置信度(score)、全部类别概率(class_scores)。 |
4.1.2 服务初始化
C++ 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| VisionService::Create | 从 YAML 配置文件创建分类服务实例 | config_path:YAML 配置文件路径 | VisionService 智能指针 |
| VisionService::LastCreateError | 获取最近一次创建失败的错误信息 | 无 | 错误描述字符串 |
Python 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| VisionServiceNative.create | 从 YAML 配置文件创建服务实例 | config_path:YAML 路径;model_path_override:可选覆盖模型路径 | VisionServiceNative 实例 |
| VisionServiceNative.last_create_error | 获取最近一次创建失败的错误信息 | 无 | 错误描述字符串 |
4.1.3 图像分类
C++ 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| Infer | 对图像文件进行分类 | image_path:图像文件路径;response:输出响应;params:可选推理参数 | VisionServiceStatus(VISION_SERVICE_OK 表示成功) |
| Infer | 对 cv::Mat 图像进行分类 | image:OpenCV Mat 对象;response:输出响应;params:可选推理参数 | VisionServiceStatus(VISION_SERVICE_OK 表示成功) |
| GetClassNames | 获取配置中加载的类别名表,用 label 索引可得类别名 | 无 | 类别名字符串向量 |
| LastError | 获取最近一次推理的错误信息 | 无 | 错误描述字符串 |
读取分类结果使用访问器:vision::get_label(result) 取类别 ID、vision::get_score(result) 取置信度;如需全部类别概率,可用 const vision::Classification* cls = std::get_if<vision::Classification>(&result); 再读取 cls->class_scores。
Python 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| infer_image | 对图像进行分类 | image_or_path:BGR numpy 数组或图像路径 | (VisionServiceStatus, results 列表;读取 results[0].class_scores 得各类概率) |
| get_class_names | 获取配置中的类别名称列表 | 无 | 字符串列表 |
| last_error | 获取最近一次推理错误 | 无 | 错误描述字符串 |
4.1.4 性能监控
C++ 接口
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| SetTimingOptions | 启用/禁用性能计时 | options:VisionServiceTimingOptions(enabled、print_to_stdout) | void |
| GetLastTiming | 获取最近一次推理的各阶段耗时 | 无 | VisionServiceTiming 结构体(preprocess_ms、model_infer_ms、postprocess_ms、infer_ms) |
4.2 典型调用流程
4.2.1 C++ 单图分类
#include "vision_service.h"
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 1. 创建服务
auto service = VisionService::Create("examples/resnet/config/resnet50.yaml");
if (!service) {
std::cerr << "Failed to create service: "
<< VisionService::LastCreateError() << std::endl;
return -1;
}
// 2. 启用性能计时(可选)
VisionServiceTimingOptions timing_options;
timing_options.enabled = true;
service->SetTimingOptions(timing_options);
// 3. 执行推理
VisionServiceResponse response;
VisionServiceStatus ret = service->Infer("test.jpg", &response);
if (ret != VISION_SERVICE_OK) {
std::cerr << "Inference failed: " << service->LastError() << std::endl;
return -1;
}
// 4. 处理结果(Top-5)
std::cout << "Classification results (Top-5):" << std::endl;
const size_t max_results = std::min<size_t>(response.results.size(), 5);
for (size_t i = 0; i < max_results; ++i) {
std::cout << " " << (i + 1) << ". Class "
<< vision::get_label(response.results[i])
<< ", Confidence: " << vision::get_score(response.results[i])
<< std::endl;
}
// 5. 查看性能指标(可选)
VisionServiceTiming timing = service->GetLastTiming();
std::cout << "Preprocess: " << timing.preprocess_ms << " ms" << std::endl;
std::cout << "Inference: " << timing.model_infer_ms << " ms" << std::endl;
std::cout << "Postprocess: " << timing.postprocess_ms << " ms" << std::endl;
return 0;
}
4.2.2 C++ 批量图像分类
#include "vision_service.h"
#include <opencv2/opencv.hpp>
#include <algorithm>
#include <iostream>
#include <filesystem>
int main() {
auto service = VisionService::Create("examples/resnet/config/resnet50.yaml");
// 遍历目录中的所有图像
for (const auto& entry : std::filesystem::directory_iterator("images/")) {
if (entry.path().extension() == ".jpg") {
VisionServiceResponse response;
service->Infer(entry.path().string(), &response);
// 输出 Top-1 结果
if (!response.results.empty()) {
const auto& top1 = *std::max_element(
response.results.begin(), response.results.end(),
[](const auto& a, const auto& b) {
return vision::get_score(a) < vision::get_score(b);
});
std::cout << entry.path().filename() << ": Class "
<< vision::get_label(top1) << " ("
<< vision::get_score(top1) << ")" << std::endl;
}
}
}
return 0;
}
4.2.3 Python 单图分类
import cv2
from spacemit_vision import VisionServiceNative, VisionServiceStatus
svc = VisionServiceNative.create("examples/resnet/config/resnet50.yaml")
image = cv2.imread("test.jpg")
status, results = svc.infer_image(image)
if status != VisionServiceStatus.OK:
raise RuntimeError(svc.last_error())
scores = list(results[0].class_scores)
order = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:5]
print("Top-5:")
for rank, i in enumerate(order, 1):
print(f" {rank}. Class {i} ({scores[i]:.4f})")
4.2.4 Python 批量图像分类
import glob
import cv2
from spacemit_vision import VisionServiceNative, VisionServiceStatus
svc = VisionServiceNative.create("examples/resnet/config/resnet50.yaml")
for path in glob.glob("images/*.jpg"):
image = cv2.imread(path)
status, results = svc.infer_image(image)
if status != VisionServiceStatus.OK:
continue
scores = list(results[0].class_scores)
top = max(range(len(scores)), key=lambda i: scores[i])
print(f"{path}: Class {top} ({scores[top]:.4f})")
4.3 配置说明
YAML 配置文件是模型加载和推理参数的核心,以下是完整配置项说明:
# 模型文件路径(ResNet50 ImageNet 分类模型)
model_path: ~/.cache/models/vision/resnet/resnet50.q.onnx
# 测试图像路径(用于示例程序)
test_image: ~/.cache/assets/image/005_kitten.jpg
# 类别标签文件路径(ImageNet 1000 类)
label_file_path: assets/labels/imagenet.txt
# 模型输入尺寸 [height, width](ResNet 标准输入为 224×224)
image_size: [224, 224]
# 部署类名(C++ 模型工厂注册名,Python 通过 yaml 路径间接使用)
class: deploy.resnet.ResNetClassifier
# 推理参数
default_params:
# 推理线程数(示例默认 8;K3 平台 intra-op 最大建议 8,可按场景调低)
num_threads: 8
# 推理后端(优先使用 SpaceMITExecutionProvider)
providers:
- SpaceMITExecutionProvider
- CPUExecutionProvider # 备用后端
参数调优建议:
- image_size:ResNet 标准输入为 [224, 224],不建议修改。
- num_threads:示例 yaml 默认为 8,K3 平台 intra-op 线程数最大建议 8。若 CPU 竞争或延迟不稳定,可尝试降至 4 观察效果,不建议超过 8。
- providers:优先使用 SpaceMITExecutionProvider 以获得最佳性能。
- label_file_path:确保指向正确的 ImageNet 标签文件,否则只能输出类别 ID。
4.4 性能监控
通过启用性能计时,可以分析推理各阶段的耗时,用于性能优化和瓶颈定位。
C++ 示例:
VisionServiceTimingOptions timing_options;
timing_options.enabled = true;
service->SetTimingOptions(timing_options);
VisionServiceResponse response;
service->Infer("test.jpg", &response);
VisionServiceTiming timing = service->GetLastTiming();
std::cout << "Preprocess: " << timing.preprocess_ms << " ms" << std::endl;
std::cout << "Inference: " << timing.model_infer_ms << " ms" << std::endl;
std::cout << "Postprocess: " << timing.postprocess_ms << " ms" << std::endl;
std::cout << "Total: " << timing.infer_ms << " ms" << std::endl;
性能优化建议:
- 预处理耗时高:图像分类的预处理相对简单(缩放 + 归一化),耗时应较低。
- 推理耗时高:确认使用 SpaceMITExecutionProvider,检查线程数设置。
- 后处理耗时高:分类任务的后处理仅为 softmax + 排序,耗时应极低。
参考 demo 路径:examples/resnet/
5. 调试指南
- 启用计时:通过
SetTimingOptions查看各阶段耗时 - 分类结果不准确:确认输入图片已正确预处理(缩放至 224×224)
- 标签不匹配:确认
label_file_path指向正确的imagenet.txt
6. 常见问题
| 现象 | 可能原因 | 处理 |
|---|---|---|
Model file not found | 模型未下载 | 执行 bash examples/resnet/scripts/download_models.sh |
No module named 'spacemit_vision' | wheel 未安装 | 按 §2 编译并 pip install src/python/dist/*.whl |
| 分类结果全为低置信度 | 输入图片不在 ImageNet 类别范围内 | 换用 ImageNet 类别内的测试图片 |
| 输出类别 ID 而非名称 | 标签文件缺失 | 确认 assets/labels/imagenet.txt 存在 |
附录:性能与测试数据
以下数据摘自 Vision 组件 README.md 附录「不包含前后处理」(纯 ONNX 模型推理,不含图像预处理与后处理)。为 K3 平台阶段性实测结果,完整表见 README。
K3 平台
| 具体模型 | 输入大小 | 数据类型 | 帧率 (4核) | 帧率 (8核) |
|---|---|---|---|---|
| resnet50 | [1,3,224,224] | int8 | 139.5 | 197.0 |
复现方法:使用 onnxruntime_perf_test 工具(4 线程):
onnxruntime_perf_test ~/.cache/models/vision/resnet/resnet50.q.onnx -e spacemit -r 20 -x 1 -S 1 -s -I -c 1 -i "SPACEMIT_EP_INTRA_THREAD_NUM|4"