如何解读Hololens2 Unity项目中ONNX目标检测模型的输出
处理Azure Custom Vision导出的ONNX目标检测模型输出(Hololens 2 + Unity)
一、明确输出张量结构
你的模型输出维度(n:1, h:13, w:13, c:30)对应:
n=1:批量大小,单张输入图像可忽略该维度h=13/w=13:输入图像被划分为13×13的网格,每个网格负责检测对应区域的目标c=30:每个网格的输出通道,对应3个预设锚框,每个锚框包含5个边界框参数(x,y,w,h,置信度) + 5个类别概率(3*(5+5)=30),类别数量和你在Azure Custom Vision中训练的类别数一致
二、解析输出数据的步骤
1. 准备基础参数
提前确认以下信息:
- Hololens相机捕获的纹理原始宽高(如1280×720)
- 模型训练时的预设锚框尺寸(可在导出模型的zip包中
anchors.txt文件查看) - 置信度阈值(如0.5,过滤低置信度结果)
- 类别置信度阈值(如0.3,过滤类别概率低的结果)
- NMS(非极大值抑制)阈值(如0.5,去除重复检测框)
2. 遍历网格与锚框,解析检测结果
将Barracuda输出的数组转换为可识别的检测结果:
// 输入图像尺寸,替换为你的实际纹理尺寸 int inputWidth = 1280; int inputHeight = 720; // 替换为模型对应的锚框尺寸 float[][] anchors = new float[][] { new float[] {10f, 13f}, new float[] {16f, 30f}, new float[] {33f, 23f} }; float confThreshold = 0.5f; float classThreshold = 0.3f; List<DetectionResult> rawResults = new List<DetectionResult>(); // 遍历13×13网格 for (int gridY = 0; gridY < 13; gridY++) { for (int gridX = 0; gridX < 13; gridX++) { // 遍历3个锚框 for (int anchorIdx = 0; anchorIdx < 3; anchorIdx++) { // 计算当前锚框在输出数组中的起始索引 int channelStart = anchorIdx * 10; int dataIndex = gridY * 13 * 30 + gridX * 30 + channelStart; // 过滤低置信度锚框 float confidence = Sigmoid(outputData[dataIndex + 4]); if (confidence < confThreshold) continue; // 解析边界框坐标,转换为图像绝对坐标 float xOffset = Sigmoid(outputData[dataIndex]); float yOffset = Sigmoid(outputData[dataIndex + 1]); float boxWidth = Mathf.Exp(outputData[dataIndex + 2]) * anchors[anchorIdx][0]; float boxHeight = Mathf.Exp(outputData[dataIndex + 3]) * anchors[anchorIdx][1]; float gridSizeX = inputWidth / 13f; float gridSizeY = inputHeight / 13f; float centerX = (gridX + xOffset) * gridSizeX; float centerY = (gridY + yOffset) * gridSizeY; float left = centerX - boxWidth / 2; float top = centerY - boxHeight / 2; float right = centerX + boxWidth / 2; float bottom = centerY + boxHeight / 2; // 解析最高概率的类别 float maxClassProb = 0; int targetClassIdx = -1; for (int c = 0; c < 5; c++) { float prob = Sigmoid(outputData[dataIndex + 5 + c]); if (prob > maxClassProb) { maxClassProb = prob; targetClassIdx = c; } } // 过滤低类别置信度结果 float finalConf = confidence * maxClassProb; if (finalConf < classThreshold) continue; rawResults.Add(new DetectionResult() { Left = left, Top = top, Right = right, Bottom = bottom, ClassIndex = targetClassIdx, Confidence = finalConf }); } } } // Sigmoid激活函数,模型输出的原始值需转换为0-1区间的概率 float Sigmoid(float x) { return 1f / (1f + Mathf.Exp(-x)); } // 检测结果数据类 public class DetectionResult { public float Left; public float Top; public float Right; public float Bottom; public int ClassIndex; public float Confidence; }
3. 非极大值抑制(NMS)去除重复框
多个锚框可能检测同一个目标,用NMS保留最准确的结果:
// 按置信度降序排序 rawResults.Sort((a, b) => b.Confidence.CompareTo(a.Confidence)); List<DetectionResult> finalResults = new List<DetectionResult>(); bool[] isSuppressed = new bool[rawResults.Count]; for (int i = 0; i < rawResults.Count; i++) { if (isSuppressed[i]) continue; finalResults.Add(rawResults[i]); for (int j = i + 1; j < rawResults.Count; j++) { if (isSuppressed[j]) continue; float iou = CalculateIOU(rawResults[i], rawResults[j]); if (iou > 0.5f) { isSuppressed[j] = true; } } } // 计算交并比(IOU) float CalculateIOU(DetectionResult a, DetectionResult b) { float intersectLeft = Mathf.Max(a.Left, b.Left); float intersectTop = Mathf.Max(a.Top, b.Top); float intersectRight = Mathf.Min(a.Right, b.Right); float intersectBottom = Mathf.Min(a.Bottom, b.Bottom); if (intersectRight < intersectLeft || intersectBottom < intersectTop) return 0; float intersectArea = (intersectRight - intersectLeft) * (intersectBottom - intersectTop); float areaA = (a.Right - a.Left) * (a.Bottom - a.Top); float areaB = (b.Right - b.Left) * (b.Bottom - b.Top); return intersectArea / (areaA + areaB - intersectArea); }
三、判断是否存在目标
处理完成后,若finalResults列表不为空,则说明输入纹理中包含自定义标记对象。你可以遍历该列表,获取目标的位置、类别和置信度,进而在Hololens中实现3D边界框可视化等操作。
注意事项
- 锚框尺寸必须与Azure Custom Vision训练时的配置一致,否则边界框计算会出错
- 输入图像尺寸需与模型训练时的输入尺寸匹配,若不一致需先缩放纹理
- 若模型输出的置信度、类别概率已经是0-1区间的值,可跳过Sigmoid转换
内容的提问来源于stack exchange,提问作者Brian Park
相关产品推荐
相关产品推荐

