轻量级高性能推理引擎MNN 学习笔记 03.在iOS运行MNN的示例
在iOS运行MNN的示例
详细请参考iOS示例 ,完整代码请在github下载
模型下载与转换:
首先编译(如果已编译可以跳过)MNNConvert,操作如下:
cd MNN mkdir build && cd build cmake -DMNN_BUILD_CONVERTER=ON .. make -j8
然后下载并转换模型: 切到编译了 MNNConvert 的目录,如上为 build 目录,执行
sh ../tools/script/get_model.sh
注意:此处需要开启vpn 进行下载模型。
编译运行:
代码位置:project/ios
使用xcode打开project/ios/MNN.xcodeproj, target选择demo,既可编译运行。
效果示例:
代码说明
iOS demo 工程用Objective-C编写,集成了 MNN 的 C++ 接口
主要思路
- 确定选择后端类型(CPU、Metal,默认为CPU)及线程数量(默认值为4)
- 确定算法模型
- 创建Interpreter & Session
- 对图像进行预处理
- 执行推理
- 将推理结果进行显示(只能在主线程中执行)
setType 设置MNN推理引擎的运行类型和线程数
@implementation Model /** * 设置MNN推理引擎的运行类型和线程数 * @param type 运行类型(CPU或Metal GPU) * @param threads 线程数量,用于CPU模式下的并行计算 */ - (void)setType:(MNNForwardType)type threads:(NSUInteger)threads { // 加锁保护,防止多线程并发访问导致的问题 std::unique_lock _l(_mutex); NSLog(@"setType: %d, threads: %lu", type, (unsigned long)threads); // 如果已存在会话,先释放旧会话 if (_session) { _net->releaseSession(_session); } // 如果GPU缓存未初始化,创建新的GPU资源缓存 if (nullptr == _cache) { _cache.reset(new GpuCache); } // 配置MNN推理引擎的运行参数 MNN::ScheduleConfig config; config.type = type; // 设置运行类型(CPU/GPU) config.numThread = (int)threads; // 设置CPU线程数 // 根据运行类型进行不同的会话创建 if (type == MNN_FORWARD_METAL) { // Metal GPU模式下的特殊配置 MNN::BackendConfig bnConfig; MNNMetalSharedContext context; // 设置Metal上下文,复用已创建的设备和命令队列 context.device = _cache->_device; context.queue = _cache->_queue; bnConfig.sharedContext = &context; config.backendConfig = &bnConfig; _session = _net->createSession(config); } else { // CPU模式下的标准配置 _session = _net->createSession(config); } // 获取会话的输入输出张量 _input = _net->getSessionInput(_session, nullptr); // 获取输入张量 _output = _net->getSessionOutput(_session, nullptr); // 获取输出张量 _type = type; // 保存当前运行类型 }
benchmark 执行模型推理的基准测试
/** * 执行模型推理的基准测试 * @param cycles 测试循环次数,用于计算平均推理时间 * @return 返回包含平均推理时间的字符串,如果初始化失败返回nil */ - (NSString *)benchmark:(NSInteger)cycles { // 加锁保护,确保多线程安全 std::unique_lock _l(_mutex); // 检查网络和会话是否正确初始化 if (!_net || !_session) { return nil; } // 获取模型的输出张量并创建副本 MNN::Tensor *output = _net->getSessionOutput(_session, nullptr); MNN::Tensor copy(output); // 获取模型的输入张量并创建缓存 auto input = _net->getSessionInput(_session, nullptr); MNN::Tensor tensorCache(input); // 将输入数据复制到主机内存 input->copyToHostTensor(&tensorCache); // 记录开始时间 NSTimeInterval begin = NSDate.timeIntervalSinceReferenceDate; // 执行多次推理,用于计算平均时间 for (int i = 0; i copyFromHostTensor(&tensorCache); // 运行一次推理会话 _net->runSession(_session); // 将输出结果复制到输出张量副本 output->copyToHostTensor(©); } // 计算总耗时 NSTimeInterval cost = NSDate.timeIntervalSinceReferenceDate - begin; // 格式化输出结果,计算平均每次推理的耗时(毫秒) NSString *string = @""; return [string stringByAppendingFormat:@"time elapse: %.3f ms", cost * 1000.f / cycles]; }
inferImage 对输入图像UIImage进行推理
/** * 对输入图像进行推理 * @param image 输入的UIImage图像 * @param cycles 推理循环次数(本实现中未使用) * @return 返回推理结果字符串 */ - (NSString *)inferImage:(UIImage *)image cycles:(NSInteger)cycles { // 加锁保护,确保线程安全 std::unique_lock _l(_mutex); // 获取输入图像的宽高 int w = image.size.width; int h = image.size.height; // 分配RGBA图像缓冲区,每个像素4个通道 unsigned char *rgba = (unsigned char *)calloc(w * h * 4, sizeof(unsigned char)); // 将UIImage转换为RGBA格式 { CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage); CGContextRef contextRef = CGBitmapContextCreate(rgba, w, h, 8, w * 4, colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault); CGContextDrawImage(contextRef, CGRectMake(0, 0, w, h), image.CGImage); CGContextRelease(contextRef); } // 设置图像预处理参数 const float means[3] = {103.94f, 116.78f, 123.68f}; // 均值 const float normals[3] = {0.017f, 0.017f, 0.017f}; // 归一化参数 // 创建图像预处理器,设置输入格式为RGBA,输出格式为BGR auto pretreat = std::shared_ptr( MNN::CV::ImageProcess::create(MNN::CV::RGBA, MNN::CV::BGR, means, 3, normals, 3)); // 设置图像缩放矩阵,将图像缩放到模型需要的大小(224x224) MNN::CV::Matrix matrix; matrix.postScale((w - 1) / 223.0, (h - 1) / 223.0); pretreat->setMatrix(matrix); // 获取模型的输入张量 auto input = _net->getSessionInput(_session, nullptr); // 执行图像预处理并将结果写入输入张量 pretreat->convert(rgba, w, h, 0, input); // 释放临时分配的图像缓冲区 free(rgba); // 调用父类的推理方法执行实际的模型推理 return [super inferNoLock:0]; }
iOS OC 语法相关
更新UI 界面只能在主线程中执行
以下代码中重点关注以下函数:
- dispatch_async :在指定的队列中异步执行任务
- dispatch_get_global_queue :获取全局队列
- dispatch_get_main_queue :获取主线程队列
- (IBAction)benchmark { // 检查相机是否在运行,只有在非相机模式下才能执行基准测试 if (!_session.running) { // 禁用所有UI控件,防止测试过程中的用户交互 self.cameraItem.enabled = NO; // 禁用相机切换按钮 self.runItem.enabled = NO; // 禁用运行按钮 self.benchmarkItem.enabled = NO; // 禁用基准测试按钮 self.modelItem.enabled = NO; // 禁用模型选择按钮 self.forwardItem.enabled = NO; // 禁用推理后端选择按钮 self.threadItem.enabled = NO; // 禁用线程数选择按钮 // 在全局后台队列中异步执行基准测试 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行100次推理并获取性能统计结果 NSString *str = [self->_currentModel benchmark:100]; // 在主线程更新UI dispatch_async(dispatch_get_main_queue(), ^{ // 显示测试结果 self.resultLabel.text = str; // 重新启用所有UI控件 self.cameraItem.enabled = YES; self.runItem.enabled = YES; self.benchmarkItem.enabled = YES; self.modelItem.enabled = YES; self.forwardItem.enabled = YES; self.threadItem.enabled = YES; }); }); } }
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。