效果
开源地址
项目概述
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 在本项目中展现了显著优势:
- 轻量级:框架本身仅 2MB,对应用体积影响小
- 高性能:针对 ARM 架构深度优化,充分利用 NEON 指令集
- iOS 友好:提供完善的 Objective-C 接口,简化集成流程
- 灵活配置:支持多种推理后端和线程配置,便于性能调优
开发语言组合
采用 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); // 限制最大内存使用