# CV Sample示例 ### 分类模型 分类模型的示例代码含完整注释如下: ```c++ #include "common/args.hpp" // 包含分割令行参数的头文件 #include "common/types.hpp" // 包含常用类型定义的头文件 #include "common/sampler.hpp" // 包含图像预处理的头文件 #include "common/topk.hpp" // 包含获取 Top K 类别的函数 #include "common/imagenet.hpp" // 包含 ImageNet 数据集的类别定义 #include "utilities/timer.hpp" // 包含计时器类 #include "utilities/file.hpp" // 包含文件操作的工具函数 #include "utilities/scalar_guard.hpp" // 包含资源管理类,确保资源的释放 #include "utilities/vector_guard.hpp" // 包含动态数组的资源管理类 #include // AXCL API 头文件 #include // 命令行解析库 #include // OpenCV 图像处理库 // 设置默认配置文件路径 constexpr char DEFAULT_CONFIG[] = "/usr/local/axcl/axcl.json"; // 设置默认输入图像的高度和宽度 constexpr int DEFAULT_IMG_H = 224; constexpr int DEFAULT_IMG_W = 224; // 设置默认循环次数 constexpr int DEFAULT_LOOP_COUNT = 1; // 设置默认 Top K 值 constexpr int DEFAULT_TOP_K = 5; int main(int argc, char* argv[]) { cmdline::parser cmd; // 创建命令行解析器 // 添加命令行参数 cmd.add("config", 'c', "config file: axcl.json", false, DEFAULT_CONFIG); // 配置文件路径 cmd.add("device", 'd', "device index (if multi-card available)", false, 0, cmdline::range(0, 3)); // 设备索引 #if defined(ENV_CHIP_SERIES_MC50) cmd.add("kind", 'k', "visual npu kind", false, 0, cmdline::range(0, 3)); // NPU 类型 #endif #if defined(ENV_CHIP_SERIES_MC20E) cmd.add("kind", 'k', "visual npu enable flag", false, 0, cmdline::range(0, 1)); // NPU 启用标志 #endif cmd.add("model", 'm', "model file(a.k.a. .axmodel)", true, ""); // 必填参数,模型文件 cmd.add("image", 'i', "image file", true, ""); // 必填参数,图像文件 cmd.add("size", 'g', "input_h,input_w", false, std::to_string(DEFAULT_IMG_H) + "," + std::to_string(DEFAULT_IMG_W)); // 输入图像大小 cmd.add("swap-rb", 's', "swap rgb(bgr) to bgr(rgb)"); // 可选参数,是否交换颜色通道 cmd.add("topk", 't', "top k values", false, DEFAULT_TOP_K, cmdline::range(1, static_cast(std::size(IMAGENET_CLASSES)))); // Top K 值 cmd.add("repeat", 'r', "repeat count", false, DEFAULT_LOOP_COUNT, cmdline::range(1, std::numeric_limits::max())); // 重复次数 cmd.parse_check(argc, argv); // 解析命令行参数并进行检查 // 0-0. 检查配置文件 if (const auto config = cmd.get("config"); cmd.exist("config") && (!utilities::exists(config) || !utilities::is_regular_file(config))) { fprintf(stderr, "Config file %s is not exist, please check it.\n", config.c_str()); // 输出错误信息 return -1; // 退出程序 } // 0-1. 检查模型和图像文件是否存在 const auto model_file = cmd.get("model"); const auto image_file = cmd.get("image"); if (const auto model_file_flag = utilities::exists(model_file), image_file_flag = utilities::exists(image_file); !model_file_flag | !image_file_flag) { auto show_error = [](const std::string& kind, const std::string& value) { fprintf(stderr, "Input file %s(%s) is not exist, please check it.\n", kind.c_str(), value.c_str()); }; if (!model_file_flag) { show_error("model", model_file); } // 输出模型文件不存在的错误 if (!image_file_flag) { show_error("image", image_file); } // 输出图像文件不存在的错误 return -1; // 退出程序 } // 0-2. 获取输入图像大小 std::array input_size = {DEFAULT_IMG_H, DEFAULT_IMG_W}; { auto input_size_string = cmd.get("size"); if (auto input_size_flag = common::parse_args(input_size_string, input_size); !input_size_flag) { auto show_error = [](const std::string& kind, const std::string& value) { fprintf(stderr, "Input %s(%s) is not allowed, please check it.\n", kind.c_str(), value.c_str()); }; show_error("size", input_size_string); // 输出输入大小错误 return -1; // 退出程序 } } // 0-3. 获取重复次数 auto repeat = cmd.get("repeat"); // 1-0. 获取设备索引 const auto device_index = static_cast(cmd.get("device")); // 1-1 初始化 AXCL,使用 scalar_guard 确保资源最终释放 fprintf(stdout, "axcl initializing...\n"); auto env_guard = utilities::scalar_guard( axclInit(cmd.exist("config") ? cmd.get("config").c_str() : nullptr), // 初始化 AXCL [](const int32_t& code) { if (0 == code) { std::ignore = axclFinalize(); // 确保在初始化失败时调用清理 } } ); // 1-2. 检查初始化结果 if (const int ret = env_guard.get(); 0 != ret) { fprintf(stderr, "Init axcl failed{0x%08X}.\n", ret); // 输出初始化失败的信息 return false; // 退出程序 } fprintf(stdout, "axcl inited.\n"); // 1-3. 获取设备列表 axclrtDeviceList lst; if (const auto ret = axclrtGetDeviceList(&lst); 0 != ret || 0 == lst.num) { fprintf(stderr, "Get axcl device failed{0x%08X}, find total %d device.\n", ret, lst.num); // 输出获取设备失败的信息 return false; // 退出程序 } // 1-4. 检查设备索引 if (device_index >= lst.num) { fprintf(stderr, "Specified device index{%u} is out of range{total %d}.\n", device_index, lst.num); // 输出设备索引超出范围的信息 return false; // 退出程序 } // 1-5. 设置设备 if (const auto ret = axclrtSetDevice(lst.devices[device_index]); 0 != ret) { fprintf(stderr, "Set axcl device as index{%u} failed{0x%08X}.\n", device_index, ret); // 输出设置设备失败的信息 return false; // 退出程序 } fprintf(stdout,"Select axcl device{index: %u} as {%d}.\n", device_index, lst.devices[device_index]); // 1-6. 初始化 NPU const int kind = cmd.get("kind"); // 获取 NPU 类型 if (const int ret = axclrtEngineInit(static_cast(kind)); 0 != ret) { fprintf(stderr, "Init axclrt Engine as kind{%s} failed{0x%08X}.\n", common::get_visual_mode_string(kind).c_str(), ret); // 输出初始化 NPU 失败的信息 return false; // 退出程序 } fprintf(stdout, "axclrt Engine inited.\n"); // 1-7. 打印参数信息 fprintf(stdout, "--------------------------------------\n"); fprintf(stdout, "model file : %s\n", model_file.c_str()); fprintf(stdout, "image file : %s\n", image_file.c_str()); fprintf(stdout, "img height : %d\n", input_size[0]); fprintf(stdout, "img width : %d\n", input_size[1]); fprintf(stdout, "--------------------------------------\n"); // 2-1. 加载模型 auto m = utilities::scalar_guard( [&model_file]() { if (uint64_t id; 0 == axclrtEngineLoadFromFile(model_file.c_str(), &id)) { // 从文件加载模型 return id; // 返回模型 ID } fprintf(stderr, "Create model{%s} handle failed.\n", model_file.c_str()); return uint64_t{0}; // 返回 0 表示失败 }, [](const uint64_t& id) { if (uint64_t{0} != id) { std::ignore = axclrtEngineUnload(id); // 卸载模型 } } ); // 2-2. 创建上下文 uint64_t ctx = 0; if (const auto ret = axclrtEngineCreateContext(m.get(), &ctx); 0 != ret) { fprintf(stderr, "Create model{%s} context failed.\n", model_file.c_str()); // 输出创建上下文失败的信息 return false; // 退出程序 } // 2-3. 获取 IO 信息 auto info = utilities::scalar_guard( [&m]() { axclrtEngineIOInfo i; if (0 == axclrtEngineGetIOInfo(m.get(), &i)) { return i; // 返回 IO 信息 } fprintf(stderr, "Get model io info failed.\n"); return axclrtEngineIOInfo{}; // 返回空结构体表示失败 }, [](const axclrtEngineIOInfo& i) { std::ignore = axclrtEngineDestroyIOInfo(i); // 释放 IO 信息 } ); // 2-4. 创建 IO auto io = utilities::scalar_guard( [&info]() { axclrtEngineIO i; if (0 == axclrtEngineCreateIO(info.get(), &i)) { return i; // 返回创建的 IO } fprintf(stderr, "Create model io failed.\n"); return axclrtEngineIO{}; // 返回空结构体表示失败 }, [](const axclrtEngineIO& i) { std::ignore = axclrtEngineDestroyIO(i); // 释放 IO } ); // 2-5. 获取输入数量 uint32_t input_count = 0; if (input_count = axclrtEngineGetNumInputs(info.get()); 0 == input_count) { fprintf(stderr, "Get model input count failed.\n"); // 输出获取输入数量失败的信息 return false; // 退出程序 } // 2-6. 获取输入大小 std::vector inputs_size(input_count, 0); for (uint32_t i = 0; i < input_count; i++) { inputs_size[i] = axclrtEngineGetInputSizeByIndex(info.get(), 0, i); // 获取每个输入的大小 } // 2-7. 分配输入内存 auto inputs = utilities::vector_guard( [&input_count, &inputs_size]() { std::vector ptrs(input_count, nullptr); for (uint32_t i = 0; i < input_count; i++) { if (const auto ret = axclrtMalloc(&ptrs[i], inputs_size[i], axclrtMemMallocPolicy{}); 0 != ret) { fprintf(stderr, "Memory allocation for input tensor{index: %d} failed{0x%08X}.\n", i, ret); // 输出内存分配失败的信息 } } return ptrs; // 返回分配的内存指针数组 }, [](void* ptr) { if (nullptr != ptr) { std::ignore = axclrtFree(ptr); // 释放内存 ptr = nullptr; } } ); // 2-8. 检查输入内存是否分配成功 for (uint32_t i = 0; i < input_count; i++) { if (nullptr == inputs.get()[i]) { return false; // 退出程序 } } // 2-9. 设置输入缓冲区 for (uint32_t i = 0; i < input_count; i++) { if (const auto ret = axclrtEngineSetInputBufferByIndex(io.get(), i, inputs.get()[i], inputs_size[i]); 0 != ret) { fprintf(stderr, "Set input buffer{index: %d} failed{0x%08X}.\n", i, ret); // 输出设置输入缓冲区失败的信息 return false; // 退出程序 } } // 2-10. 获取输出数量 uint32_t output_count = 0; if (output_count = axclrtEngineGetNumOutputs(info.get()); 0 == output_count) { fprintf(stderr, "Get model output count failed.\n"); // 输出获取输出数量失败的信息 return false; // 退出程序 } // 2-11. 获取输出大小 std::vector outputs_size(output_count, 0); for (uint32_t i = 0; i < output_count; i++) { outputs_size[i] = axclrtEngineGetOutputSizeByIndex(info.get(), 0, i); // 获取每个输出的大小 } // 2-12. 分配输出内存 auto outputs = utilities::vector_guard( [&output_count, &outputs_size]() { std::vector ptrs(output_count, nullptr); for (uint32_t i = 0; i < output_count; i++) { if (const auto ret = axclrtMalloc(&ptrs[i], outputs_size[i], axclrtMemMallocPolicy{}); 0 != ret) { fprintf(stderr, "Memory allocation for output tensor{index: %d} failed{0x%08X}.\n", i, ret); // 输出内存分配失败的信息 } } return ptrs; // 返回分配的内存指针数组 }, [](void* ptr) { if (nullptr != ptr) { std::ignore = axclrtFree(ptr); // 释放内存 ptr = nullptr; } } ); // 2-13. 检查输出内存是否分配成功 for (uint32_t i = 0; i < output_count; i++) { if (nullptr == outputs.get()[i]) { return false; // 退出程序 } } // 2-14. 设置输出缓冲区 for (uint32_t i = 0; i < output_count; i++) { if (const auto ret = axclrtEngineSetOutputBufferByIndex(io.get(), i, outputs.get()[i], outputs_size[i]); 0 != ret) { fprintf(stderr, "Set output buffer{index: %d} failed{0x%08X}.\n", i, ret); // 输出设置输出缓冲区失败的信息 return false; // 退出程序 } } // 3-0. 读取输入图像 cv::Mat src = cv::imread(image_file); if (src.empty()) { fprintf(stderr, "Read image failed.\n"); // 输出读取图像失败的信息 return -1; // 退出程序 } // 3-1. 分配输入主机缓冲区 std::vector input_buffer(input_size[1] * input_size[0] * 3, 0); // 分配输入缓冲区(RGB) auto dst = cv::Mat(cv::Size(input_size[1], input_size[0]), CV_8UC3, input_buffer.data()); // 创建 OpenCV Mat 结构 // 3-2. 预处理图像(调整大小和交换 RGB 通道) preprocess::sampler sampler(preprocess::crop_type::imagenet, preprocess::resize_type::linear, cmd.exist("swap-rb")); sampler(src, dst); // 对输入图像进行处理 // 3-3. 将输入数据发送到设备 if (const auto ret = axclrtMemcpy(inputs.get()[0], input_buffer.data(), input_buffer.size(), AXCL_MEMCPY_HOST_TO_DEVICE); 0 != ret) { fprintf(stderr, "Copy input data to device failed{0x%08X}.\n", ret); // 输出数据复制失败的信息 return false; // 退出程序 } // 3-4. 执行模型推理 std::vector time_costs(repeat, 0); // 记录每次推理的时间 for (int i = 0; i < repeat; ++i) { utilities::timer tick; // 创建计时器 const auto ret = axclrtEngineExecute(m.get(), ctx, 0, io.get()); // 执行推理 time_costs[i] = tick.elapsed(); // 记录推理时间 if ( 0 != ret) { fprintf(stderr, "Run model failed{0x%08X}.\n", ret); // 输出推理失败的信息 return false; // 退出程序 } } // 3-5. 获取输出结果 std::vector output_buffer(outputs_size[0], 0.f); // 初始化输出缓冲区 if (const auto ret = axclrtMemcpy(output_buffer.data(), outputs.get()[0], outputs_size[0], AXCL_MEMCPY_DEVICE_TO_HOST); 0 != ret) { fprintf(stderr, "Copy output data to host failed{0x%08X}.\n", ret); // 输出数据复制失败的信息 return false; // 退出程序 } // 3-6. 后处理,提取检测结果 const auto topK = classification::topK(output_buffer.data(), output_buffer.size(), cmd.get("topk")); // 获取 Top K 结果 for (const auto& [prob, index] : topK) { fprintf(stdout, "%3d: %4.1f%%, %s\n", index, prob, IMAGENET_CLASSES[index]); // 输出每个类别的概率和名称 } fprintf(stdout, "--------------------------------------\n"); return 0; // 正常结束程序 } ``` 以 [`imagenet`](https://image-net.org/) 数据集的 `imagenet_cat.jpg` 作为分类对象,`sample` 执行完了会有如下输出(注意,模型和输入图像要根据实际情况微调): ![](../res/imagenet_cat.jpg) ```bash /root # /opt/bin/axcl/axcl_sample_classification -m /opt/data/npu/models/mobilenetv2.axmodel -i /opt/data/npu/images/cat.jpg axcl initializing... axcl inited. Select axcl device{index: 0} as {129}. axclrt Engine inited. -------------------------------------- model file : /opt/data/npu/models/mobilenetv2.axmodel image file : /opt/data/npu/images/cat.jpg img height : 224 img width : 224 -------------------------------------- 282: 9.8%, tiger cat 285: 9.8%, Egyptian cat 283: 9.5%, Persian cat 281: 9.4%, tabby, tabby cat 463: 7.5%, bucket, pail -------------------------------------- ``` 可见菊猫 Top1 被分类为老虎猫;Top5 分类基本正确。 ### 检测模型 检测模型的示例代码含完整注释如下: ```c++ #include "common/args.hpp" // 包含分割令行参数的头文件 #include "common/types.hpp" // 包含常用类型定义的头文件 #include "common/sampler.hpp" // 包含图像预处理的头文件 #include "common/detection.hpp" // 包含目标检测相关的数据结构和算法 #include "common/mscoco.hpp" // 包含 MS COCO 数据集的类别定义 #include "common/palette.hpp" // 包含调色板生成和颜色转换的函数 #include "utilities/timer.hpp" // 包含计时器类,用于测量时间 #include "utilities/file.hpp" // 包含文件操作的工具函数 #include "utilities/scalar_guard.hpp" // 包含资源管理类,确保资源的释放 #include "utilities/vector_guard.hpp" // 包含动态数组的资源管理类 #include // AXCL API 头文件 #include // 命令行解析库 #include // OpenCV 图像处理库 #include // 包含算法函数 #include // 包含数值计算函数 // 设置默认配置文件路径 constexpr char DEFAULT_CONFIG[] = "/usr/local/axcl/axcl.json"; // 设置默认输入图像的高度和宽度 constexpr int DEFAULT_IMG_H = 640; constexpr int DEFAULT_IMG_W = 640; // 设置默认的概率阈值和 IOU 阈值 constexpr float DEFAULT_PROB_THRESHOLD = 0.45f; constexpr float DEFAULT_IOU_THRESHOLD = 0.45f; // 设置默认循环次数 constexpr int DEFAULT_LOOP_COUNT = 1; // 定义锚框 const std::vector> anchors{ {{10, 13}, {16, 30}, {33, 23}}, {{30, 61}, {62, 45}, {59, 119}}, {{116, 90}, {156, 198}, {373, 326}} }; // 定义模型输出头下采样的步幅 const std::vector strides{8, 16, 32}; // 生成颜色调色板 std::vector palette = common::palette(std::size(MSCOCO_CLASSES)); // 绘制检测到的对象 static void draw_objects(const cv::Mat& bgr, const std::string& name, const std::vector& objects) { // 克隆输入图像以便绘制 cv::Mat image = bgr.clone(); // 遍历检测到的每个对象 for (const auto& [rect, prob, label] : objects) { const auto& [x, y, w, h] = rect; // 提取矩形的坐标和大小 fprintf(stdout, "%2d: %3.0f%%, [%4.0f, %4.0f, %4.0f, %4.0f], %s\n", label, prob * 100, x, y, x + w, y + h, MSCOCO_CLASSES[label]); // 输出对象信息 const cv::Rect obj_rect{static_cast(x), static_cast(y), static_cast(w), static_cast(h)}; // 创建矩形对象 const auto& color = palette[label]; // 获取该对象的颜色 cv::rectangle(image, obj_rect, color, 2); // 在图像上绘制矩形框 char text[256]; sprintf(text, "%s %.1f%%", MSCOCO_CLASSES[label], prob * 100); // 准备显示的文本 // 计算文本位置 int baseLine = 0; const cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_TRIPLEX, 0.5, 1, &baseLine); int cv_x = std::max(static_cast(x - 1), 0); // 确保文本不会超出图像边界 int cv_y = std::max(static_cast(y + 1) - label_size.height - baseLine, 0); if (cv_x + label_size.width > image.cols) { cv_x = image.cols - label_size.width; // 调整文本 x 坐标 } if (cv_y + label_size.height > image.rows) { cv_y = image.rows - label_size.height; // 调整文本 y 坐标 } // 绘制文本背景 const cv::Size text_background_size(label_size.width, label_size.height + baseLine); const cv::Rect text_background(cv::Point(cv_x, cv_y), text_background_size); cv::rectangle(image, text_background, color, -1); // 绘制填充矩形 const cv::Point text_origin(cv_x, cv_y + label_size.height); // 文本的起始位置 const auto text_color = common::get_complementary_color(color); // 获取对比色 cv::putText(image, text, text_origin, cv::FONT_HERSHEY_TRIPLEX, 0.5, text_color); // 在图像上绘制文本 } // 将结果图像保存为文件 cv::imwrite(std::string(name) + ".jpg", image); } int main(int argc, char* argv[]) { cmdline::parser cmd; // 创建命令行解析器 // 添加命令行参数 cmd.add("config", 'c', "config file: axcl.json", false, DEFAULT_CONFIG); cmd.add("device", 'd', "device index (if multi-card available)", false, 0, cmdline::range(0, 3)); #if defined(ENV_CHIP_SERIES_MC50) cmd.add("kind", 'k', "visual npu kind", false, 0, cmdline::range(0, 3)); #endif #if defined(ENV_CHIP_SERIES_MC20E) cmd.add("kind", 'k', "visual npu enable flag", false, 0, cmdline::range(0, 1)); #endif cmd.add("model", 'm', "model file(a.k.a. .axmodel)", true, ""); // 必填参数,模型文件 cmd.add("image", 'i', "image file", true, ""); // 必填参数,图像文件 cmd.add("size", 'g', "input_h,input_w", false, std::to_string(DEFAULT_IMG_H) + "," + std::to_string(DEFAULT_IMG_W)); // 输入图像大小 cmd.add("swap-rb", 's', "swap rgb(bgr) to bgr(rgb)"); // 可选参数,是否交换颜色通道 cmd.add("repeat", 'r', "repeat count", false, DEFAULT_LOOP_COUNT, cmdline::range(1, std::numeric_limits::max())); // 重复次数 cmd.parse_check(argc, argv); // 解析命令行参数并进行检查 // 0-0. 检查配置文件 if (const auto config = cmd.get("config"); cmd.exist("config") && (!utilities::exists(config) || !utilities::is_regular_file(config))) { fprintf(stderr, "Config file %s is not exist, please check it.\n", config.c_str()); // 输出错误信息 return -1; // 退出程序 } // 0-1. 检查模型和图像文件是否存在 const auto model_file = cmd.get("model"); const auto image_file = cmd.get("image"); if (const auto model_file_flag = utilities::exists(model_file), image_file_flag = utilities::exists(image_file); !model_file_flag | !image_file_flag) { auto show_error = [](const std::string& kind, const std::string& value) { fprintf(stderr, "Input file %s(%s) is not exist, please check it.\n", kind.c_str(), value.c_str()); }; if (!model_file_flag) { show_error("model", model_file); } // 输出模型文件不存在的错误 if (!image_file_flag) { show_error("image", image_file); } // 输出图像文件不存在的错误 return -1; // 退出程序 } // 0-2. 获取输入图像大小 std::array input_size = {DEFAULT_IMG_H, DEFAULT_IMG_W}; { auto input_size_string = cmd.get("size"); if (auto input_size_flag = common::parse_args(input_size_string, input_size); !input_size_flag) { auto show_error = [](const std::string& kind, const std::string& value) { fprintf(stderr, "Input %s(%s) is not allowed, please check it.\n", kind.c_str(), value.c_str()); }; show_error("size", input_size_string); // 输出输入大小错误 return -1; // 退出程序 } } // 0-3. 获取重复次数 auto repeat = cmd.get("repeat"); // 1-0. 获取设备索引 const auto device_index = static_cast(cmd.get("device")); // 1-1 初始化 AXCL,使用 scalar_guard 确保资源最终释放 fprintf(stdout, "axcl initializing...\n"); auto env_guard = utilities::scalar_guard( axclInit(cmd.exist("config") ? cmd.get("config").c_str() : nullptr), // 初始化 AXCL [](const int32_t& code) { if (0 == code) { std::ignore = axclFinalize(); // 确保在初始化失败时调用清理 } } ); // 1-2. 检查初始化结果 if (const int ret = env_guard.get(); 0 != ret) { fprintf(stderr, "Init axcl failed{0x%08X}.\n", ret); // 输出初始化失败的信息 return false; // 退出程序 } fprintf(stdout, "axcl inited.\n"); // 1-3. 获取设备列表 axclrtDeviceList lst; if (const auto ret = axclrtGetDeviceList(&lst); 0 != ret || 0 == lst.num) { fprintf(stderr, "Get axcl device failed{0x%08X}, find total %d device.\n", ret, lst.num); // 输出获取设备失败的信息 return false; // 退出程序 } // 1-4. 检查设备索引 if (device_index >= lst.num) { fprintf(stderr, "Specified device index{%u} is out of range{total %d}.\n", device_index, lst.num); // 输出设备索引超出范围的信息 return false; // 退出程序 } // 1-5. 设置设备 if (const auto ret = axclrtSetDevice(lst.devices[device_index]); 0 != ret) { fprintf(stderr, "Set axcl device as index{%u} failed{0x%08X}.\n", device_index, ret); // 输出设置设备失败的信息 return false; // 退出程序 } fprintf(stdout,"Select axcl device{index: %u} as {%d}.\n", device_index, lst.devices[device_index]); // 1-6. 初始化 NPU const int kind = cmd.get("kind"); // 获取 NPU 类型 if (const int ret = axclrtEngineInit(static_cast(kind)); 0 != ret) { fprintf(stderr, "Init axclrt Engine as kind{%s} failed{0x%08X}.\n", common::get_visual_mode_string(kind).c_str(), ret); // 输出初始化 NPU 失败的信息 return false; // 退出程序 } fprintf(stdout, "axclrt Engine inited.\n"); // 1-7. 打印参数信息 fprintf(stdout, "--------------------------------------\n"); fprintf(stdout, "model file : %s\n", model_file.c_str()); fprintf(stdout, "image file : %s\n", image_file.c_str()); fprintf(stdout, "img height : %d\n", input_size[0]); fprintf(stdout, "img width : %d\n", input_size[1]); fprintf(stdout, "--------------------------------------\n"); // 2-1. 加载模型 auto m = utilities::scalar_guard( [&model_file]() { if (uint64_t id; 0 == axclrtEngineLoadFromFile(model_file.c_str(), &id)) { // 从文件加载模型 return id; // 返回模型 ID } fprintf(stderr, "Create model{%s} handle failed.\n", model_file.c_str()); return uint64_t{0}; // 返回 0 表示失败 }, [](const uint64_t& id) { if (uint64_t{0} != id) { std::ignore = axclrtEngineUnload(id); // 卸载模型 } } ); // 2-2. 创建上下文 uint64_t ctx = 0; if (const auto ret = axclrtEngineCreateContext(m.get(), &ctx); 0 != ret) { fprintf(stderr, "Create model{%s} context failed.\n", model_file.c_str()); // 输出创建上下文失败的信息 return false; // 退出程序 } // 2-3. 获取 IO 信息 auto info = utilities::scalar_guard( [&m]() { axclrtEngineIOInfo i; if (0 == axclrtEngineGetIOInfo(m.get(), &i)) { return i; // 返回 IO 信息 } fprintf(stderr, "Get model io info failed.\n"); return axclrtEngineIOInfo{}; // 返回空结构体表示失败 }, [](const axclrtEngineIOInfo& i) { std::ignore = axclrtEngineDestroyIOInfo(i); // 释放 IO 信息 } ); // 2-4. 创建 IO auto io = utilities::scalar_guard( [&info]() { axclrtEngineIO i; if (0 == axclrtEngineCreateIO(info.get(), &i)) { return i; // 返回创建的 IO } fprintf(stderr, "Create model io failed.\n"); return axclrtEngineIO{}; // 返回空结构体表示失败 }, [](const axclrtEngineIO& i) { std::ignore = axclrtEngineDestroyIO(i); // 释放 IO } ); // 2-5. 获取输入数量 uint32_t input_count = 0; if (input_count = axclrtEngineGetNumInputs(info.get()); 0 == input_count) { fprintf(stderr, "Get model input count failed.\n"); // 输出获取输入数量失败的信息 return false; // 退出程序 } // 2-6. 获取输入大小 std::vector inputs_size(input_count, 0); for (uint32_t i = 0; i < input_count; i++) { inputs_size[i] = axclrtEngineGetInputSizeByIndex(info.get(), 0, i); // 获取每个输入的大小 } // 2-7. 分配输入内存 auto inputs = utilities::vector_guard( [&input_count, &inputs_size]() { std::vector ptrs(input_count, nullptr); for (uint32_t i = 0; i < input_count; i++) { if (const auto ret = axclrtMalloc(&ptrs[i], inputs_size[i], axclrtMemMallocPolicy{}); 0 != ret) { fprintf(stderr, "Memory allocation for input tensor{index: %d} failed{0x%08X}.\n", i, ret); // 输出内存分配失败的信息 } } return ptrs; // 返回分配的内存指针数组 }, [](void* ptr) { if (nullptr != ptr) { std::ignore = axclrtFree(ptr); // 释放内存 ptr = nullptr; } } ); // 2-8. 检查输入内存是否分配成功 for (uint32_t i = 0; i < input_count; i++) { if (nullptr == inputs.get()[i]) { return false; // 退出程序 } } // 2-9. 设置输入缓冲区 for (uint32_t i = 0; i < input_count; i++) { if (const auto ret = axclrtEngineSetInputBufferByIndex(io.get(), i, inputs.get()[i], inputs_size[i]); 0 != ret) { fprintf(stderr, "Set input buffer{index: %d} failed{0x%08X}.\n", i, ret); // 输出设置输入缓冲区失败的信息 return false; // 退出程序 } } // 2-10. 获取输出数量 uint32_t output_count = 0; if (output_count = axclrtEngineGetNumOutputs(info.get()); 0 == output_count) { fprintf(stderr, "Get model output count failed.\n"); // 输出获取输出数量失败的信息 return false; // 退出程序 } // 2-11. 获取输出大小 std::vector outputs_size(output_count, 0); for (uint32_t i = 0; i < output_count; i++) { outputs_size[i] = axclrtEngineGetOutputSizeByIndex(info.get(), 0, i); // 获取每个输出的大小 } // 2-12. 分配输出内存 auto outputs = utilities::vector_guard( [&output_count, &outputs_size]() { std::vector ptrs(output_count, nullptr); for (uint32_t i = 0; i < output_count; i++) { if (const auto ret = axclrtMalloc(&ptrs[i], outputs_size[i], axclrtMemMallocPolicy{}); 0 != ret) { fprintf(stderr, "Memory allocation for output tensor{index: %d} failed{0x%08X}.\n", i, ret); // 输出内存分配失败的信息 } } return ptrs; // 返回分配的内存指针数组 }, [](void* ptr) { if (nullptr != ptr) { std::ignore = axclrtFree(ptr); // 释放内存 ptr = nullptr; } } ); // 2-13. 检查输出内存是否分配成功 for (uint32_t i = 0; i < output_count; i++) { if (nullptr == outputs.get()[i]) { return false; // 退出程序 } } // 2-14. 设置输出缓冲区 for (uint32_t i = 0; i < output_count; i++) { if (const auto ret = axclrtEngineSetOutputBufferByIndex(io.get(), i, outputs.get()[i], outputs_size[i]); 0 != ret) { fprintf(stderr, "Set output buffer{index: %d} failed{0x%08X}.\n", i, ret); // 输出设置输出缓冲区失败的信息 return false; // 退出程序 } } // 3-0. 读取输入图像 cv::Mat src = cv::imread(image_file); if (src.empty()) { fprintf(stderr, "Read image failed.\n"); // 输出读取图像失败的信息 return -1; // 退出程序 } // 3-1. 分配输入主机缓冲区 std::vector input_buffer(input_size[1] * input_size[0] * 3, 0); // 分配输入缓冲区(RGB) auto dst = cv::Mat(cv::Size(input_size[1], input_size[0]), CV_8UC3, input_buffer.data()); // 创建 OpenCV Mat 结构 // 3-2. 预处理图像(调整大小和交换 RGB 通道) preprocess::sampler sampler(preprocess::crop_type::letterbox, preprocess::resize_type::linear, cmd.exist("swap-rb")); sampler(src, dst); // 对输入图像进行处理 // 3-3. 将输入数据发送到设备 if (const auto ret = axclrtMemcpy(inputs.get()[0], input_buffer.data(), input_buffer.size(), AXCL_MEMCPY_HOST_TO_DEVICE); 0 != ret) { fprintf(stderr, "Copy input data to device failed{0x%08X}.\n", ret); // 输出数据复制失败的信息 return false; // 退出程序 } // 3-4. 执行模型推理 std::vector run_costs(repeat, 0); // 记录每次推理的时间 for (int i = 0; i < repeat; ++i) { utilities::timer tick; // 创建计时器 const auto ret = axclrtEngineExecute(m.get(), ctx, 0, io.get()); // 执行推理 run_costs[i] = tick.elapsed(); // 记录推理时间 if ( 0 != ret) { fprintf(stderr, "Run model failed{0x%08X}.\n", ret); // 输出推理失败的信息 return false; // 退出程序 } } // 3-5. 获取输出结果 std::vector output_buffer(outputs_size[0], 0.f); // 初始化输出缓冲区 if (const auto ret = axclrtMemcpy(output_buffer.data(), outputs.get()[0], outputs_size[0], AXCL_MEMCPY_DEVICE_TO_HOST); 0 != ret) { fprintf(stderr, "Copy output data to host failed{0x%08X}.\n", ret); // 输出数据复制失败的信息 return false; // 退出程序 } // 3-6. 后处理,提取检测结果 const auto topK = classification::topK(output_buffer.data(), output_buffer.size(), cmd.get("topk")); // 获取 Top K 结果 for (const auto& [prob, index] : topK) { fprintf(stdout, "%3d: %4.1f%%, %s\n", index, prob, IMAGENET_CLASSES[index]); // 输出每个类别的概率和名称 } fprintf(stdout, "--------------------------------------\n"); return 0; // 正常结束程序 } ``` 以 [`PASCAL VOC`](http://host.robots.ox.ac.uk/pascal/VOC/) 数据集的 voc_dog.jpg 作为检测对象,sample 执行完了会有如下输出(注意,模型和输入图像要根据实际情况微调): ![](../res/voc_dog.jpg) ```bash /root # /opt/bin/axcl/axcl_sample_yolov5s -m yolov5s.axmodel -i voc_dog.jpg axcl initializing... axcl inited. Select axcl device{index: 0} as {129}. axclrt Engine inited. -------------------------------------- model file : /opt/data/npu/models/yolov5s.axmodel image file : /opt/data/npu/images/dog.jpg img height : 640 img width : 640 -------------------------------------- post process cost time:1.86 ms -------------------------------------- Repeat 1 times, avg time 8.02 ms, max_time 8.02 ms, min_time 8.02 ms -------------------------------------- 16: 91%, [ 138, 218, 310, 541], dog 2: 69%, [ 470, 76, 690, 173], car 1: 56%, [ 158, 120, 569, 420], bicycle ``` 可见检测到了 3 个目标,并且给出了类别 `ID`、置信度和坐标。在 `sample` 执行的目录下,会保存一个名为 `yolov5s_out.jpg` 的检测结果,可以图片浏览器打开预览一下输出结果。 ![](../res/voc_dog_yolov5s_out.jpg) 以 [`PASCAL VOC`](http://host.robots.ox.ac.uk/pascal/VOC/) 数据集的 voc_horse.jpg 作为检测对象,sample 执行完了会有如下输出(注意,模型和输入图像要根据实际情况微调): ![](../res/voc_horse.jpg) ```bash /root # /opt/bin/axcl/axcl_sample_yolov5s -m yolov5s.axmodel -i voc_horse.jpg axcl initializing... axcl inited. Select axcl device{index: 0} as {129}. axclrt Engine inited. -------------------------------------- model file : /opt/data/npu/models/yolov5s.axmodel image file : voc_horse.jpg img height : 640 img width : 640 -------------------------------------- post process cost time:1.80 ms -------------------------------------- Repeat 1 times, avg time 8.02 ms, max_time 8.02 ms, min_time 8.02 ms -------------------------------------- 17: 84%, [ 208, 52, 431, 374], horse 16: 84%, [ 142, 201, 197, 350], dog 0: 83%, [ 273, 16, 350, 226], person 7: 74%, [ 0, 107, 132, 195], truck 0: 73%, [ 430, 123, 449, 179], person 0: 47%, [ 402, 130, 412, 148], person ``` 可见检测到了 6 个目标,并且给出了类别 `ID`、置信度和坐标。在 `sample` 执行的目录下,会保存一个名为 `yolov5s_out.jpg` 的检测结果,可以图片浏览器打开预览一下输出结果。 ![](../res/voc_horse_yolov5s_out.jpg)