【昇腾AI项目实战】使用AscendCL完成图片分类推理全流程
开发一个能够准确识别1000类ImageNet物体的图像分类程序,实现从输入图像到分类结果的完整推理流程。昇腾AI处理器的基本编程模型AscendCL接口的核心使用方法端到端AI应用的开发流程性能分析与优化基础推荐学习路径进阶模型:尝试SSD/YOLOv5等检测模型复杂应用:开发人脸识别系统或多模态应用性能调优:学习使用Ascend Profiler进行性能分析框架集成:尝试MindSpore/P
·
昇腾AI实战:基于AscendCL的ResNet-50图像分类应用开发
本文将详细介绍如何在昇腾芯片上使用AscendCL开发一个完整的图像分类应用,基于预训练的ResNet-50模型实现端到端的推理流程。
1. 项目概述与准备
项目目标
开发一个能够准确识别1000类ImageNet物体的图像分类程序,实现从输入图像到分类结果的完整推理流程。
完整工作流程
- 模型加载:将预编译的ResNet-50模型(.om文件)加载到昇腾设备
- 数据预处理:
- 图像读取与解码
- 尺寸调整至224×224像素
- 颜色通道归一化(减去均值并除以标准差)
- 数据格式转换(HWC→CHW)
- 内存管理:
- 设备端内存分配
- 主机到设备的数据传输
- 推理执行:调用昇腾计算单元执行模型推理
- 结果处理:
- 设备到主机的数据传输
- 分类结果解析(softmax概率排序)
- 标签映射与结果输出
详细准备工作
-
模型准备:
- 官方途径:从昇腾ModelZoo获取预编译的ResNet-50模型(resnet50.om)
- 自定义模型:使用ATC工具将TensorFlow/PyTorch模型转换为.om格式
atc --model=resnet50.onnx --framework=5 --output=resnet50 --soc_version=Ascend310 -
测试数据:
- 准备标准测试图像(如ILSVRC2012验证集图片)
- 支持格式:JPEG/PNG/BMP等常见图像格式
- 示例图片尺寸建议:不低于224×224像素
-
标签文件:
- 准备ImageNet类别映射文件(imagenet1000_clsidx_to_labels.txt)
- 格式示例:
0: 'tench, Tinca tinca' 1: 'goldfish, Carassius auratus' ... 999: 'toilet tissue, toilet paper, bathroom tissue'
-
开发环境:
- 已安装Ascend-CANN-Toolkit开发套件
- 配置好AscendCL开发环境
- 可选:安装OpenCV等图像处理库
2. 核心代码模块详解
模块一:模型加载与描述
// 1. 完整模型加载流程
size_t modelSize = 0;
void* modelPtr = nullptr;
aclError ret = aclmdlLoadFromFileWithMem("resnet50.om", &modelSize, &modelPtr);
if (ret != ACL_ERROR_NONE) {
printf("Failed to load model, error code: %d\n", ret);
return -1;
}
// 2. 创建模型描述信息
uint32_t modelId = 0;
aclmdlDesc* modelDesc = aclmdlCreateDesc();
ret = aclmdlGetDesc(modelDesc, modelId);
if (ret != ACL_ERROR_NONE) {
printf("Failed to get model description, error code: %d\n", ret);
return -1;
}
// 3. 详细获取输入输出信息
size_t inputNum = aclmdlGetNumInputs(modelDesc);
size_t outputNum = aclmdlGetNumOutputs(modelDesc);
printf("Model has %zu inputs and %zu outputs\n", inputNum, outputNum);
// 获取输入维度(NCHW格式)
aclmdlIODims inputDims;
ret = aclmdlGetInputDims(modelDesc, 0, &inputDims);
if (ret != ACL_ERROR_NONE) {
printf("Failed to get input dimensions, error code: %d\n", ret);
return -1;
}
printf("Input dimensions: [%d, %d, %d, %d]\n",
inputDims.dims[0], inputDims.dims[1],
inputDims.dims[2], inputDims.dims[3]);
// 获取输出维度
aclmdlIODims outputDims;
ret = aclmdlGetOutputDims(modelDesc, 0, &outputDims);
if (ret != ACL_ERROR_NONE) {
printf("Failed to get output dimensions, error code: %d\n", ret);
return -1;
}
printf("Output dimensions: [%d, %d]\n",
outputDims.dims[0], outputDims.dims[1]);
模块二:输入数据准备
// 1. 设备内存分配
size_t inputSize = aclmdlGetInputSizeByIndex(modelDesc, 0);
void* devInput = nullptr;
ret = aclrtMalloc(&devInput, inputSize, ACL_MEM_MALLOC_NORMAL_ONLY);
if (ret != ACL_ERROR_NONE) {
printf("Failed to allocate device memory for input, size: %zu\n", inputSize);
return -1;
}
// 2. 使用OpenCV进行图像预处理
cv::Mat image = cv::imread("test.jpg", cv::IMREAD_COLOR);
if (image.empty()) {
printf("Failed to load image\n");
return -1;
}
// 调整尺寸
cv::Mat resizedImage;
cv::resize(image, resizedImage, cv::Size(224, 224));
// 转换颜色空间BGR→RGB
cv::cvtColor(resizedImage, resizedImage, cv::COLOR_BGR2RGB);
// 转换为浮点型并归一化(ImageNet标准)
cv::Mat floatImage;
resizedImage.convertTo(floatImage, CV_32FC3, 1.0/255.0);
floatImage = (floatImage - cv::Scalar(0.485, 0.456, 0.406)) /
cv::Scalar(0.229, 0.224, 0.225);
// 3. 转换为CHW格式
std::vector<cv::Mat> channels(3);
cv::split(floatImage, channels);
std::vector<float> inputData;
for (int c = 0; c < 3; ++c) {
inputData.insert(inputData.end(),
(float*)channels[c].data,
(float*)channels[c].data + 224*224);
}
// 4. 数据传输到设备
ret = aclrtMemcpy(devInput, inputSize,
inputData.data(), inputSize,
ACL_MEMCPY_HOST_TO_DEVICE);
if (ret != ACL_ERROR_NONE) {
printf("Failed to copy input data to device, error code: %d\n", ret);
return -1;
}
模块三:推理执行
// 1. 创建输入输出数据集
aclmdlDataset* inputDataset = aclmdlCreateDataset();
aclmdlDataset* outputDataset = aclmdlCreateDataset();
if (inputDataset == nullptr || outputDataset == nullptr) {
printf("Failed to create dataset\n");
return -1;
}
// 2. 封装输入数据
aclDataBuffer* inputBuffer = aclCreateDataBuffer(devInput, inputSize);
if (inputBuffer == nullptr) {
printf("Failed to create input data buffer\n");
return -1;
}
ret = aclmdlAddDatasetBuffer(inputDataset, inputBuffer);
if (ret != ACL_ERROR_NONE) {
printf("Failed to add input buffer to dataset, error code: %d\n", ret);
return -1;
}
// 3. 准备输出缓冲区
size_t outputSize = aclmdlGetOutputSizeByIndex(modelDesc, 0);
void* devOutput = nullptr;
ret = aclrtMalloc(&devOutput, outputSize, ACL_MEM_MALLOC_NORMAL_ONLY);
if (ret != ACL_ERROR_NONE) {
printf("Failed to allocate device memory for output, size: %zu\n", outputSize);
return -1;
}
aclDataBuffer* outputBuffer = aclCreateDataBuffer(devOutput, outputSize);
if (outputBuffer == nullptr) {
printf("Failed to create output data buffer\n");
return -1;
}
ret = aclmdlAddDatasetBuffer(outputDataset, outputBuffer);
if (ret != ACL_ERROR_NONE) {
printf("Failed to add output buffer to dataset, error code: %d\n", ret);
return -1;
}
// 4. 执行推理
printf("Start model inference...\n");
auto start = std::chrono::high_resolution_clock::now();
ret = aclmdlExecute(modelDesc, inputDataset, outputDataset);
auto end = std::chrono::high_resolution_clock::now();
if (ret != ACL_ERROR_NONE) {
printf("Failed to execute model, error code: %d\n", ret);
return -1;
}
std::chrono::duration<double> elapsed = end - start;
printf("Inference completed in %.3f ms\n", elapsed.count() * 1000);
模块四:输出解析
// 1. 获取输出数据
aclDataBuffer* outputDataBuffer = aclmdlGetDatasetBuffer(outputDataset, 0);
void* devOutputData = aclGetDataBufferAddr(outputDataBuffer);
size_t outputDataSize = aclGetDataBufferSize(outputDataBuffer);
// 2. 传输回主机
std::vector<float> hostOutput(outputDataSize / sizeof(float));
ret = aclrtMemcpy(hostOutput.data(), outputDataSize,
devOutputData, outputDataSize,
ACL_MEMCPY_DEVICE_TO_HOST);
if (ret != ACL_ERROR_NONE) {
printf("Failed to copy output data to host, error code: %d\n", ret);
return -1;
}
// 3. 解析分类结果
int topK = 5;
std::vector<int> indices(hostOutput.size());
std::iota(indices.begin(), indices.end(), 0);
std::partial_sort(indices.begin(), indices.begin() + topK, indices.end(),
[&hostOutput](int a, int b) {
return hostOutput[a] > hostOutput[b];
});
// 4. 读取标签文件并输出结果
std::ifstream labelFile("imagenet1000_clsidx_to_labels.txt");
std::vector<std::string> labels(1000);
for (int i = 0; i < 1000; ++i) {
std::string line;
std::getline(labelFile, line);
size_t colonPos = line.find(':');
labels[i] = line.substr(colonPos + 3, line.length() - colonPos - 4);
}
printf("\nTop-%d predictions:\n", topK);
for (int i = 0; i < topK; ++i) {
int idx = indices[i];
printf("%3d: %-30s (probability: %.4f)\n",
idx, labels[idx].c_str(), hostOutput[idx]);
}
3. 完整编译与运行指南
编译环境配置
-
确保已安装:
- Ascend-CANN-Toolkit (版本≥5.0.2)
- OpenCV (用于图像处理,版本≥3.4.0)
- CMake (版本≥3.12)
-
创建CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
project(ResNet_Inference)
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
# 查找OpenCV
find_package(OpenCV REQUIRED)
# 设置昇腾库路径
set(ASCEND_DIR /usr/local/Ascend/ascend-toolkit/latest)
include_directories(${ASCEND_DIR}/include)
link_directories(${ASCEND_DIR}/lib64)
# 添加可执行文件
add_executable(resnet_inference
src/main.cpp
src/preprocess.cpp
src/inference.cpp)
# 链接库
target_link_libraries(resnet_inference
${OpenCV_LIBS}
ascendcl
pthread)
编译命令
mkdir build && cd build
cmake ..
make -j$(nproc)
运行程序
# 基本运行
./resnet_inference test.jpg
# 带性能统计的运行
ASCEND_PROFILING_ENABLE=1 ./resnet_inference test.jpg
# 批量处理
for img in images/*.jpg; do
./resnet_inference "$img"
done
预期输出示例:
Model loaded successfully
Input dimensions: [1, 3, 224, 224]
Image preprocessed in 2.45 ms
Inference completed in 3.21 ms
Top-5 predictions:
285: Egyptian cat (probability: 0.8765)
281: tabby, tabby cat (probability: 0.1023)
282: tiger cat (probability: 0.0087)
292: lynx, catamount (probability: 0.0021)
287: leopard, Panthera pardus (probability: 0.0012)
4. 项目扩展与优化方向
功能扩展
-
多模型组合:
- 实现检测+分类流水线(YOLO检测后裁剪区域送ResNet分类)
- 多模型并行执行优化
-
视频处理:
- 实时视频流分析(25FPS+)
- 基于FFmpeg的视频解码集成
-
服务化部署:
- 封装为gRPC/RESTful服务
- 实现批处理队列管理
性能优化
-
内存管理:
- 实现内存池减少分配开销
- 使用异步内存传输重叠计算与数据传输
-
流水线优化:
- 多Stream并行处理
- 实现双缓冲机制
-
计算优化:
- 启用AI Core/Vector Core混合计算
- 使用DVPP加速图像预处理
示例优化代码片段
// 异步处理示例
aclrtStream stream;
aclrtCreateStream(&stream);
// 异步内存拷贝
aclrtMemcpyAsync(devInput, inputSize,
hostInput, inputSize,
ACL_MEMCPY_HOST_TO_DEVICE, stream);
// 异步推理
aclmdlExecuteAsync(modelDesc, inputDataset, outputDataset, stream);
// 等待异步操作完成
aclrtSynchronizeStream(stream);
5. 总结与学习路径
通过本项目的完整实现,你已经掌握了:
- 昇腾AI处理器的基本编程模型
- AscendCL接口的核心使用方法
- 端到端AI应用的开发流程
- 性能分析与优化基础
推荐学习路径:
- 进阶模型:尝试SSD/YOLOv5等检测模型
- 复杂应用:开发人脸识别系统或多模态应用
- 性能调优:学习使用Ascend Profiler进行性能分析
- 框架集成:尝试MindSpore/PyTorch等框架的昇腾后端
昇腾AI处理器强大的算力将为你的AI应用提供强劲动力,期待你在AI计算领域的进一步探索与创新!
2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接:https://www.hiascend.com/developer/activities/cann20252
更多推荐




所有评论(0)