C++集成ViT图像分类模型:高性能推理引擎开发指南
C集成ViT图像分类模型高性能推理引擎开发指南你是不是也遇到过这样的场景手头有一个训练好的ViT图像分类模型在Python里跑得挺好但一到实际产品里就发现Python那点性能根本不够用延迟高、内存占用大稍微并发多点就扛不住了。这时候用C来部署模型就成了刚需。但真动手的时候问题就来了模型怎么转接口怎么封内存怎么管多线程怎么搞一堆坑等着你。今天我就结合自己踩过的坑给你讲讲怎么在C项目里集成ViT模型打造一个真正高性能的推理引擎。咱们不搞虚的直接上干货从模型转换到优化技巧一步步带你走通。1. 环境准备与模型选择在开始之前得先把基础环境搭好。C做AI推理现在主流的选择就那么几个框架咱们得根据实际需求来选。1.1 推理框架选择目前比较成熟的C推理框架主要有这几个ONNX Runtime微软出品支持多种后端CPU、CUDA、TensorRT等社区活跃文档齐全TensorRTNVIDIA亲儿子在GPU上优化到极致但生态相对封闭OpenVINOIntel主打在Intel CPU上表现优秀对边缘设备友好LibTorchPyTorch的C版本如果你模型本来就是PyTorch的用这个最省事我个人的建议是优先考虑ONNX Runtime。原因很简单它跨平台、跨硬件从云端到边缘都能用而且维护得不错。咱们今天也主要用它来演示。1.2 模型获取与转换从搜索内容看ViT图像分类-中文-日常物品这个模型挺实用的覆盖1300类常见物体日常场景够用了。咱们就用它来举例。首先得把PyTorch模型转成ONNX格式。这是最关键的一步转不好后面全白搭。# convert_to_onnx.py import torch from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks # 加载模型 model_id damo/cv_nextvit-small_image-classification_Dailylife-labels image_classification pipeline(Tasks.image_classification, modelmodel_id) # 获取实际的模型 model image_classification.model # 设置为评估模式 model.eval() # 创建示例输入注意这个模型输入是224x224 dummy_input torch.randn(1, 3, 224, 224) # 导出为ONNX torch.onnx.export( model, dummy_input, vit_dailylife.onnx, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size}, output: {0: batch_size} }, opset_version13 ) print(模型转换完成)转换的时候有几个坑要注意输入尺寸这个模型固定输入224x224别搞错了动态轴加上dynamic_axes这样推理时才能支持不同的batch sizeopset版本用13或以上兼容性更好1.3 开发环境搭建C项目我习惯用CMake来管理这样跨平台方便。下面是个基础的CMakeLists.txtcmake_minimum_required(VERSION 3.14) project(ViTInference) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找ONNX Runtime find_package(onnxruntime REQUIRED) # OpenCV用于图像处理 find_package(OpenCV REQUIRED) # 添加可执行文件 add_executable(vit_inference src/main.cpp src/inference_engine.cpp) # 链接库 target_link_libraries(vit_inference PRIVATE onnxruntime::onnxruntime ${OpenCV_LIBS} ) # 包含目录 target_include_directories(vit_inference PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${OpenCV_INCLUDE_DIRS} )2. 核心推理引擎实现环境搭好了模型也转好了现在开始写核心的推理代码。这部分是性能的关键得仔细设计。2.1 基础推理类设计先设计一个基础的推理引擎类把ONNX Runtime的接口封装起来// inference_engine.h #pragma once #include onnxruntime_cxx_api.h #include opencv2/opencv.hpp #include vector #include string #include memory class ViTInferenceEngine { public: // 构造函数加载模型创建会话 ViTInferenceEngine(const std::string model_path, bool use_gpu false); // 析构函数 ~ViTInferenceEngine(); // 单张图片推理 std::vectorfloat infer(const cv::Mat image); // 批量推理 std::vectorstd::vectorfloat infer_batch( const std::vectorcv::Mat images); // 获取模型信息 int get_input_width() const { return input_width_; } int get_input_height() const { return input_height_; } int get_num_classes() const { return num_classes_; } private: // 预处理把OpenCV的Mat转成模型需要的Tensor std::vectorfloat preprocess(const cv::Mat image); // 后处理把模型输出转成可读的结果 std::vectorfloat postprocess(const std::vectorfloat raw_output); // ONNX Runtime相关 Ort::Env env_; Ort::SessionOptions session_options_; std::unique_ptrOrt::Session session_; // 模型信息 int input_width_ 224; int input_height_ 224; int num_classes_ 1300; // 根据实际模型调整 // 输入输出名称 std::vectorconst char* input_names_; std::vectorconst char* output_names_; // 内存分配器 Ort::AllocatorWithDefaultOptions allocator_; };2.2 预处理实现预处理是性能瓶颈之一得好好优化。ViT模型的预处理通常包括调整大小、归一化、通道转换。// inference_engine.cpp #include inference_engine.h #include chrono #include iostream ViTInferenceEngine::ViTInferenceEngine(const std::string model_path, bool use_gpu) { // 初始化ONNX Runtime环境 env_ Ort::Env(ORT_LOGGING_LEVEL_WARNING, ViTInference); // 设置会话选项 session_options_.SetIntraOpNumThreads(1); session_options_.SetGraphOptimizationLevel( GraphOptimizationLevel::ORT_ENABLE_ALL); // 选择执行提供者 if (use_gpu) { OrtCUDAProviderOptions cuda_options; cuda_options.device_id 0; session_options_.AppendExecutionProvider_CUDA(cuda_options); } // 创建会话 session_ std::make_uniqueOrt::Session( env_, model_path.c_str(), session_options_); // 获取输入输出信息 Ort::AllocatorWithDefaultOptions allocator; auto input_info session_-GetInputTypeInfo(0); auto input_tensor_info input_info.GetTensorTypeAndShapeInfo(); auto input_shape input_tensor_info.GetShape(); if (input_shape.size() 4) { // NCHW格式 input_height_ static_castint(input_shape[2]); input_width_ static_castint(input_shape[3]); } // 获取输出信息 auto output_info session_-GetOutputTypeInfo(0); auto output_tensor_info output_info.GetTensorTypeAndShapeInfo(); auto output_shape output_tensor_info.GetShape(); if (output_shape.size() 2) { num_classes_ static_castint(output_shape[1]); } // 设置输入输出名称 input_names_.push_back(input); output_names_.push_back(output); std::cout 模型加载成功 std::endl; std::cout 输入尺寸: input_width_ x input_height_ std::endl; std::cout 类别数: num_classes_ std::endl; } std::vectorfloat ViTInferenceEngine::preprocess(const cv::Mat image) { cv::Mat processed; // 1. 调整大小保持长宽比 int target_size std::min(input_width_, input_height_); cv::Mat resized; float scale static_castfloat(target_size) / std::min(image.cols, image.rows); cv::resize(image, resized, cv::Size(), scale, scale, cv::INTER_LINEAR); // 2. 中心裁剪 int start_x (resized.cols - input_width_) / 2; int start_y (resized.rows - input_height_) / 2; cv::Rect roi(start_x, start_y, input_width_, input_height_); cv::Mat cropped resized(roi); // 3. 转换为float并归一化 // ViT通常使用ImageNet的均值和标准差 cropped.convertTo(processed, CV_32FC3, 1.0 / 255.0); // 减去均值除以标准差 cv::Scalar mean(0.485, 0.456, 0.406); cv::Scalar std(0.229, 0.224, 0.225); cv::subtract(processed, mean, processed); cv::divide(processed, std, processed); // 4. 从HWC转为CHW std::vectorcv::Mat channels(3); cv::split(processed, channels); std::vectorfloat result; result.reserve(3 * input_width_ * input_height_); for (const auto channel : channels) { result.insert(result.end(), channel.ptrfloat(), channel.ptrfloat() channel.total()); } return result; }2.3 推理核心实现预处理做好了推理部分就相对简单了std::vectorfloat ViTInferenceEngine::infer(const cv::Mat image) { // 1. 预处理 auto start_preprocess std::chrono::high_resolution_clock::now(); std::vectorfloat input_tensor preprocess(image); auto end_preprocess std::chrono::high_resolution_clock::now(); // 2. 准备输入Tensor std::vectorint64_t input_shape {1, 3, input_height_, input_width_}; Ort::MemoryInfo memory_info Ort::MemoryInfo::CreateCpu( OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault); Ort::Value input_tensor_ort Ort::Value::CreateTensorfloat( memory_info, input_tensor.data(), input_tensor.size(), input_shape.data(), input_shape.size() ); // 3. 运行推理 auto start_infer std::chrono::high_resolution_clock::now(); auto output_tensors session_-Run( Ort::RunOptions{nullptr}, input_names_.data(), input_tensor_ort, 1, output_names_.data(), 1 ); auto end_infer std::chrono::high_resolution_clock::now(); // 4. 获取输出 float* output_data output_tensors[0].GetTensorMutableDatafloat(); std::vectorfloat output(output_data, output_data num_classes_); // 5. 后处理softmax std::vectorfloat probabilities postprocess(output); // 输出耗时信息 auto preprocess_time std::chrono::duration_caststd::chrono::milliseconds( end_preprocess - start_preprocess); auto infer_time std::chrono::duration_caststd::chrono::milliseconds( end_infer - start_infer); std::cout 预处理耗时: preprocess_time.count() ms std::endl; std::cout 推理耗时: infer_time.count() ms std::endl; return probabilities; } std::vectorfloat ViTInferenceEngine::postprocess( const std::vectorfloat raw_output) { // 简单的softmax实现 std::vectorfloat probabilities(num_classes_); // 防止数值溢出减去最大值 float max_val *std::max_element(raw_output.begin(), raw_output.end()); float sum 0.0f; for (int i 0; i num_classes_; i) { probabilities[i] std::exp(raw_output[i] - max_val); sum probabilities[i]; } // 归一化 for (int i 0; i num_classes_; i) { probabilities[i] / sum; } return probabilities; }3. 性能优化技巧基础功能实现了但离高性能还有距离。下面这些优化技巧能让你的推理引擎快上加快。3.1 内存池优化ONNX Runtime默认会为每次推理分配新内存频繁分配释放很耗时间。我们可以用内存池来优化class MemoryPool { public: MemoryPool(size_t max_batch_size, int input_size) { // 预分配输入输出内存 input_pool_.reserve(max_batch_size); output_pool_.reserve(max_batch_size); for (size_t i 0; i max_batch_size; i) { input_pool_.push_back(std::vectorfloat(input_size)); output_pool_.push_back(std::vectorfloat(num_classes_)); } } std::vectorfloat get_input_buffer() { std::lock_guardstd::mutex lock(mutex_); if (free_inputs_.empty()) { // 动态扩展尽量避免 input_pool_.emplace_back(input_size_); return input_pool_.back(); } auto buffer input_pool_[free_inputs_.back()]; free_inputs_.pop_back(); return buffer; } void return_input_buffer(size_t index) { std::lock_guardstd::mutex lock(mutex_); free_inputs_.push_back(index); } private: std::vectorstd::vectorfloat input_pool_; std::vectorstd::vectorfloat output_pool_; std::vectorsize_t free_inputs_; std::mutex mutex_; size_t input_size_; int num_classes_; };3.2 批量推理优化单张推理效率低批量推理能充分利用硬件并行能力std::vectorstd::vectorfloat ViTInferenceEngine::infer_batch( const std::vectorcv::Mat images) { size_t batch_size images.size(); std::vectorstd::vectorfloat batch_inputs(batch_size); // 并行预处理 #pragma omp parallel for for (size_t i 0; i batch_size; i) { batch_inputs[i] preprocess(images[i]); } // 合并batch std::vectorfloat merged_input; merged_input.reserve(batch_size * 3 * input_height_ * input_width_); for (const auto input : batch_inputs) { merged_input.insert(merged_input.end(), input.begin(), input.end()); } // 准备batch输入 std::vectorint64_t input_shape { static_castint64_t(batch_size), 3, input_height_, input_width_ }; Ort::MemoryInfo memory_info Ort::MemoryInfo::CreateCpu( OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault); Ort::Value input_tensor_ort Ort::Value::CreateTensorfloat( memory_info, merged_input.data(), merged_input.size(), input_shape.data(), input_shape.size() ); // 批量推理 auto output_tensors session_-Run( Ort::RunOptions{nullptr}, input_names_.data(), input_tensor_ort, 1, output_names_.data(), 1 ); // 处理批量输出 float* batch_output output_tensors[0].GetTensorMutableDatafloat(); std::vectorstd::vectorfloat results(batch_size); for (size_t i 0; i batch_size; i) { results[i].resize(num_classes_); std::copy(batch_output i * num_classes_, batch_output (i 1) * num_classes_, results[i].begin()); results[i] postprocess(results[i]); } return results; }3.3 异步推理实现对于实时应用异步推理能大幅提升吞吐量class AsyncInferenceEngine { public: AsyncInferenceEngine(const std::string model_path, size_t worker_count 2) : engine_(model_path), stop_(false) { // 创建工作线程 for (size_t i 0; i worker_count; i) { workers_.emplace_back([this]() { worker_loop(); }); } } ~AsyncInferenceEngine() { stop_ true; cv_.notify_all(); for (auto worker : workers_) { if (worker.joinable()) { worker.join(); } } } // 提交推理任务 std::futurestd::vectorfloat submit(const cv::Mat image) { auto promise std::make_sharedstd::promisestd::vectorfloat(); auto future promise-get_future(); { std::lock_guardstd::mutex lock(queue_mutex_); task_queue_.push({image, promise}); } cv_.notify_one(); return future; } private: struct InferenceTask { cv::Mat image; std::shared_ptrstd::promisestd::vectorfloat promise; }; void worker_loop() { while (!stop_) { InferenceTask task; { std::unique_lockstd::mutex lock(queue_mutex_); cv_.wait(lock, [this]() { return !task_queue_.empty() || stop_; }); if (stop_) break; task std::move(task_queue_.front()); task_queue_.pop(); } try { auto result engine_.infer(task.image); task.promise-set_value(result); } catch (...) { task.promise-set_exception(std::current_exception()); } } } ViTInferenceEngine engine_; std::queueInferenceTask task_queue_; std::mutex queue_mutex_; std::condition_variable cv_; std::vectorstd::thread workers_; std::atomicbool stop_; };4. 实际应用示例理论讲完了来看看怎么在实际项目里用。咱们写个完整的示例程序// main.cpp #include inference_engine.h #include iostream #include fstream #include algorithm // 加载类别标签根据实际模型调整 std::vectorstd::string load_labels(const std::string label_path) { std::vectorstd::string labels; std::ifstream file(label_path); if (!file.is_open()) { std::cerr 无法打开标签文件: label_path std::endl; return labels; } std::string line; while (std::getline(file, line)) { labels.push_back(line); } return labels; } int main(int argc, char* argv[]) { if (argc 3) { std::cout 用法: argv[0] 模型路径 图片路径 std::endl; return 1; } std::string model_path argv[1]; std::string image_path argv[2]; try { // 1. 创建推理引擎 std::cout 正在加载模型... std::endl; ViTInferenceEngine engine(model_path, false); // 使用CPU // 2. 加载图片 cv::Mat image cv::imread(image_path); if (image.empty()) { std::cerr 无法加载图片: image_path std::endl; return 1; } std::cout 图片尺寸: image.cols x image.rows std::endl; // 3. 执行推理 auto start std::chrono::high_resolution_clock::now(); auto probabilities engine.infer(image); auto end std::chrono::high_resolution_clock::now(); auto total_time std::chrono::duration_caststd::chrono::milliseconds( end - start); std::cout 总耗时: total_time.count() ms std::endl; // 4. 解析结果 // 找到top-5预测结果 std::vectorint indices(probabilities.size()); std::iota(indices.begin(), indices.end(), 0); std::partial_sort(indices.begin(), indices.begin() 5, indices.end(), [probabilities](int a, int b) { return probabilities[a] probabilities[b]; }); std::cout \nTop-5预测结果: std::endl; for (int i 0; i 5; i) { int idx indices[i]; std::cout i 1 . 类别 idx : probabilities[idx] * 100 % std::endl; } // 5. 批量推理示例 std::cout \n 批量推理示例 std::endl; std::vectorcv::Mat batch_images {image, image, image}; // 重复3次 auto batch_results engine.infer_batch(batch_images); std::cout 批量推理完成处理了 batch_results.size() 张图片 std::endl; } catch (const std::exception e) { std::cerr 错误: e.what() std::endl; return 1; } return 0; }编译运行mkdir build cd build cmake .. make ./vit_inference vit_dailylife.onnx test_image.jpg5. 总结走完这一整套流程你应该对C集成ViT模型有了比较清晰的认识。从模型转换到接口封装从基础推理到性能优化每个环节都有需要注意的地方。实际用下来ONNX Runtime的表现确实不错跨平台部署很方便。预处理部分是最容易出性能瓶颈的地方一定要做好优化。批量推理和异步处理对于高并发场景是必须的能大幅提升吞吐量。内存管理是C项目的永恒话题预分配内存池、合理使用智能指针能避免很多内存泄漏的问题。多线程环境下更要注意线程安全该加锁的地方不能省。如果你刚开始接触这块建议先从单张图片推理做起把流程跑通然后再逐步加入批量处理、异步推理等高级功能。遇到问题多查文档ONNX Runtime的社区挺活跃的大部分问题都能找到解决方案。最后提醒一点不同的应用场景对性能的要求不同。如果是实时视频分析延迟是关键如果是后台批量处理吞吐量更重要。根据实际需求来调整优化策略别过度优化。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

相关新闻

技术突破:如何利用RDP Wrapper实现Windows多用户远程访问效率提升

技术突破:如何利用RDP Wrapper实现Windows多用户远程访问效率提升

技术突破:如何利用RDP Wrapper实现Windows多用户远程访问效率提升 【免费下载链接】rdpwrap RDP Wrapper Library 项目地址: https://gitcode.com/gh_mirrors/rd/rdpwrap 在现代办公与家庭场景中,多用户并发访问同一台Windows设备的需求日益凸显。…

2026/7/3 3:22:46 阅读更多 →
VSCode插件开发:Qwen3-ASR-1.7B语音编程助手实战

VSCode插件开发:Qwen3-ASR-1.7B语音编程助手实战

VSCode插件开发:Qwen3-ASR-1.7B语音编程助手实战 想象一下,你正在专注地构思一段复杂的算法逻辑,双手在键盘上飞舞,思路却突然被一个拼写错误打断。或者,你是一位行动不便的开发者,传统的键盘输入方式让你…

2026/5/17 3:46:04 阅读更多 →
Coze-Loop与人工智能模型训练优化

Coze-Loop与人工智能模型训练优化

Coze-Loop:让AI模型训练优化不再“碰运气” 如果你做过AI模型训练,肯定经历过这样的时刻:盯着训练曲线看了半天,不知道是该继续等还是该调整参数;试了各种优化方法,效果时好时坏,像在“碰运气”…

2026/5/17 3:46:01 阅读更多 →

最新新闻

Go语言JWT认证实战:从原理到生产级安全实现

Go语言JWT认证实战:从原理到生产级安全实现

1. 项目概述:为什么Go和JWT是API安全的黄金搭档最近在重构一个微服务项目,认证模块的选型又让我重新审视了一遍JWT。说实话,在Go语言生态里做API认证,JWT几乎成了默认选项,但真正能把它用“安全”的团队并不多。大部分…

2026/7/4 17:10:57 阅读更多 →
嵌入式系统三重降压转换方案设计与优化

嵌入式系统三重降压转换方案设计与优化

1. 为什么需要三重降压转换方案在嵌入式系统和低功耗设备开发中,多电压域供电一直是个棘手问题。我最近接手的一个工业控制器项目就遇到了典型场景:主控MCU需要3.3V核心电压,传感器模块要求1.8V工作电压,而外围接口又得维持5V电平…

2026/7/4 17:10:57 阅读更多 →
基于YOLOv8的番茄叶片病变识别系统设计与实现

基于YOLOv8的番茄叶片病变识别系统设计与实现

1. 项目概述这个基于YOLOv8的番茄叶片病变识别系统是我在毕业设计期间完成的一个实用项目。作为一名计算机视觉方向的毕业生,我选择将深度学习技术应用于农业领域,解决传统病害检测方法效率低下的问题。系统能够自动识别番茄叶片上的多种常见病害&#x…

2026/7/4 17:08:57 阅读更多 →
Transformers.js终极指南:如何在浏览器中运行AI模型而无需服务器支持

Transformers.js终极指南:如何在浏览器中运行AI模型而无需服务器支持

Transformers.js终极指南:如何在浏览器中运行AI模型而无需服务器支持 【免费下载链接】transformers.js State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server! 项目地址: https…

2026/7/4 17:08:57 阅读更多 →
QRazyBox终极指南:5分钟学会修复损坏二维码的完整教程

QRazyBox终极指南:5分钟学会修复损坏二维码的完整教程

QRazyBox终极指南:5分钟学会修复损坏二维码的完整教程 【免费下载链接】qrazybox QR Code Analysis and Recovery Toolkit 项目地址: https://gitcode.com/gh_mirrors/qr/qrazybox 你是否遇到过这样的烦恼?重要的二维码因为打印模糊、表面划痕或图…

2026/7/4 17:06:57 阅读更多 →
如何在Windows和Linux上获得完整的AirPods体验:免费开源工具终极指南

如何在Windows和Linux上获得完整的AirPods体验:免费开源工具终极指南

如何在Windows和Linux上获得完整的AirPods体验:免费开源工具终极指南 【免费下载链接】AirPodsDesktop ☄️ AirPods desktop user experience enhancement program, for Windows and Linux (WIP) 项目地址: https://gitcode.com/gh_mirrors/ai/AirPodsDesktop …

2026/7/4 17:04:56 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻