iOS 端钢筋计数应用开发实战:YOLOv11n 与 MNN 的高效结合

68 阅读4分钟

效果

image.png

开源地址

github.com/linghugoogl…

项目概述

RebarCount 是一个基于 iOS 平台的钢筋计数应用,它采用 YOLOv11n 作为检测模型,借助 MNN 框架实现高效的本地推理。应用核心功能包括:

  • 自动识别图像中的钢筋目标
  • 精确统计钢筋数量
  • 按空间位置排序并标记编号
  • 提供直观的结果可视化

项目采用 Swift+Objective-C++ 混合开发模式,兼顾了 UI 开发效率和底层计算性能,最低支持 iOS 13.0 系统,可在 iPhone 6s 及以上设备流畅运行。

技术选型考量

模型选择:YOLOv11n 的优势

在模型选型过程中,我们主要考虑了以下因素:

  • 尺寸与性能平衡:YOLOv11n 仅 6MB 大小,非常适合移动端部署
  • 检测速度:在 iPhone 12 上推理时间 < 100ms,满足实时性要求
  • 检测精度:针对钢筋这类特征相对单一的目标,nano 版本足以达到 95% 以上的准确率
  • 输入尺寸:640×640 的输入分辨率在精度和计算量之间取得了很好的平衡

推理框架:为什么选择 MNN

相比其他移动端 AI 框架,MNN 在本项目中展现了显著优势:

  1. 轻量级:框架本身仅 2MB,对应用体积影响小
  2. 高性能:针对 ARM 架构深度优化,充分利用 NEON 指令集
  3. iOS 友好:提供完善的 Objective-C 接口,简化集成流程
  4. 灵活配置:支持多种推理后端和线程配置,便于性能调优

开发语言组合

采用 Swift+Objective-C++ 的混合开发模式:

  • Swift 负责 UI 层开发,利用其现代语法特性提高开发效率
  • Objective-C++ 处理 MNN 的 C++ 接口调用,避免纯 Swift 调用 C++ 的性能损耗
  • 通过桥接文件 (RebarCount-Bridging-Header.h) 实现两种语言的无缝交互

核心技术实现

1. MNN 模型封装层设计

MNNHelper类是连接 Swift 业务逻辑与 MNN 框架的关键桥梁,其接口设计简洁明了:

// MNNHelper.h
@interface MNNHelper : NSObject
- (instancetype)initWithModelPath:(NSString *)modelPath;
- (NSArray<NSValue *> *)detectObjectsInImage:(UIImage *)image;
@end

在实现中,MNNHelper封装了模型加载、图像预处理、推理执行和结果解析的完整流程:

// 模型初始化
interpreter = std::unique_ptr<Interpreter>(Interpreter::createFromFile(modelPath.UTF8String));
ScheduleConfig config;
config.type = MNN_FORWARD_CPU;
config.numThread = 4; // 根据设备核心数动态调整
session = interpreter->createSession(config);

这种封装设计使上层业务逻辑无需关心 MNN 的具体实现细节,只需专注于业务处理。

2. 图像预处理优化

移动端 AI 应用的性能瓶颈往往不在模型推理,而在图像预处理阶段。本项目采用了多项优化措施:

  • 高效尺寸调整:使用 CoreGraphics 进行图像缩放,比 OpenCV 快 30%
  • 内存复用:直接操作 CVPixelBuffer,减少中间数据拷贝
  • 归一化融合:将像素值归一化 (0-255→0-1) 与格式转换 (BGR→RGB) 合并处理

swift

func preprocessImage(_ image: UIImage) -> CVPixelBuffer {
    let targetSize = CGSize(width: 640, height: 640)
    // 保持宽高比缩放并居中填充
    let scaledImage = image.scaled(to: targetSize, preservingAspectRatio: true)
    let pixelBuffer = scaledImage.convertToPixelBuffer()
    
    // 像素值归一化处理
    CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
    // 像素转换逻辑...
    CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
    
    return pixelBuffer
}

3. 后处理算法实现

YOLOv11 的输出需要经过非极大值抑制 (NMS) 和坐标转换才能得到最终结果:

// 非极大值抑制实现
std::vector<BoxInfo> nms(std::vector<BoxInfo> boxes, float threshold) {
    std::sort(boxes.begin(), boxes.end(), [](const BoxInfo& a, const BoxInfo& b) {
        return a.score > b.score;
    });
    
    std::vector<BoxInfo> result;
    for (size_t i = 0; i < boxes.size(); ++i) {
        bool keep = true;
        for (size_t j = 0; j < result.size(); ++j) {
            if (iou(boxes[i], result[j]) > threshold) {
                keep = false;
                break;
            }
        }
        if (keep) result.push_back(boxes[i]);
    }
    return result;
}

4. 性能优化策略

为确保应用在中低端设备上也能流畅运行,我们实施了多项性能优化:

  • 线程管理:将模型推理放在 userInitiated 优先级的全局队列,避免阻塞 UI 线程
DispatchQueue.global(qos: .userInitiated).async {
    let results = self.mnnHelper.detectObjects(in: image)
    DispatchQueue.main.async {
        self.updateUI(with: results)
    }
}
  • 内存优化:使用 CVPixelBuffer 而非 UIImage 处理中间数据,减少内存占用和转换开销
  • 推理优化:通过 MNN 的 RuntimeManager 配置推理后端,启用模型缓存加速加载
auto rtmgr = Executor::RuntimeManager::createRuntimeManager(config);
rtmgr->setCache(cachePath); // 启用模型缓存
rtmgr->setHint(Interpreter::HintMode::MEMORY, 1024); // 限制最大内存使用