如何创建Xamarin.TensorFlow.Lite目标检测RunForMultipleInputsOutputs输出对象
问题现象
使用Xamarin.TensorFlow.Lite的Interpreter.RunForMultipleInputsOutputs()方法执行目标检测推理时,运行到该方法抛出未将对象引用设置到对象的实例空引用异常,无法确定正确的输出对象构造逻辑。
本次使用的目标检测模型从Azure Custom Vision导出,共包含3个输出张量,符合TensorFlow Lite官方目标检测输出规范(不含检测计数字段):
- detected_boxes:检测到的边界框,每个边界框表示为
[x1, y1, x2, y2],(x1, y1)、(x2, y2)分别为边界框两个对角点的坐标 - detected_scores:每个检测框对应的置信度概率
- detected_classes:每个检测框对应的类别索引
目前公开可查的相关示例大多适配仅单输出的图像分类场景,参考TensorFlow官方Java API文档、GitHub同名issue中的示例代码编写的输出构造逻辑无法正常运行,报错代码如下:
int detectedBoxesOutputIndex = Interpreter.GetOutputIndex("detected_boxes"); // 0 int detectedClassesOutputIndex = Interpreter.GetOutputIndex("detected_classes"); // 1 int detectedScoresOutputIndex = Interpreter.GetOutputIndex("detected_scores"); // 2 int numDetections = Interpreter .GetOutputTensor(detectedClassesOutputIndex) .NumElements(); var outputDict = new Dictionary<Java.Lang.Integer, Java.Lang.Object>(); int batchSize = 1; // new float [][][] _OutputBoxes = CreateJaggedArray(batchSize, numDetections, 4); _OutputClasses = CreateJaggedArray(batchSize, numDetections); _OutputScores = CreateJaggedArray(batchSize, numDetections); var mOutputBoxes = OutputBoxes; var mOutputClasses = OutputClasses; var mOutputScores = OutputScores; Java.Lang.Object[] inputArray = { imageByteBuffer }; var outputMap = new Dictionary<Java.Lang.Integer, Java.Lang.Object>(); outputMap.Add(new Java.Lang.Integer(detectedBoxesOutputIndex), mOutputBoxes); outputMap.Add(new Java.Lang.Integer(detectedClassesOutputIndex), mOutputClasses); outputMap.Add(new Java.Lang.Integer(detectedScoresOutputIndex), mOutputScores); // 运行到此处抛出空引用异常 Interpreter.RunForMultipleInputsOutputs(inputArray, outputMap);
已完成模型有效性、图像ByteBuffer预处理流程验证:调用单输出方法Interpreter.Run(img, output)时,可正常获取float[64][4]格式的detected_boxes张量输出,仅多输出调用失败。
故障原因
空引用异常来自两个Xamarin绑定Java库的典型适配问题:
RunForMultipleInputsOutputs方法的第二个参数要求传入实现Java侧java.util.Map接口的对象,C#原生Dictionary<,>没有实现该Java接口,传入后Java层无法识别容器结构,直接读取到空值- 代码中用
CreateJaggedArray生成的C#交错数组是.NET特有类型,无法被Xamarin绑定层自动映射为Java原生的多维浮点数组,Java侧读取输出缓冲区时拿到无效引用
修复方案
- 替换输出容器:使用
Java.Util.HashMap代替C#原生Dictionary作为输出存储结构 - 替换数组创建逻辑:通过Java反射API创建Java原生类型的多维数组,不要使用C#的交错数组或.NET原生多维数组
修正后的可运行代码如下:
int detectedBoxesOutputIndex = Interpreter.GetOutputIndex("detected_boxes"); int detectedClassesOutputIndex = Interpreter.GetOutputIndex("detected_classes"); int detectedScoresOutputIndex = Interpreter.GetOutputIndex("detected_scores"); int numDetections = Interpreter .GetOutputTensor(detectedClassesOutputIndex) .NumElements(); int batchSize = 1; // 创建Java原生多维数组,替代C#交错数组 var boxesArray = Java.Lang.Reflect.Array.NewInstance( Java.Lang.Class.FromType(typeof(float)), batchSize, numDetections, 4 ); var classesArray = Java.Lang.Reflect.Array.NewInstance( Java.Lang.Class.FromType(typeof(float)), batchSize, numDetections ); var scoresArray = Java.Lang.Reflect.Array.NewInstance( Java.Lang.Class.FromType(typeof(float)), batchSize, numDetections ); Java.Lang.Object[] inputArray = { imageByteBuffer }; // 使用Java HashMap替代C# Dictionary var outputMap = new Java.Util.HashMap(); outputMap.Put(new Java.Lang.Integer(detectedBoxesOutputIndex), boxesArray); outputMap.Put(new Java.Lang.Integer(detectedClassesOutputIndex), classesArray); outputMap.Put(new Java.Lang.Integer(detectedScoresOutputIndex), scoresArray); // 正常执行推理 Interpreter.RunForMultipleInputsOutputs(inputArray, outputMap); // 推理完成后读取结果即可 _OutputBoxes = (float[][][]) boxesArray; _OutputClasses = (float[][]) classesArray; _OutputScores = (float[][]) scoresArray;
注意:如果模型的detected_classes输出是整数类型,需要将对应数组创建时的类型替换为
typeof(int),读取时强转为int[][]即可。
内容的提问来源于stack exchange,提问作者TwoFingerRightClick
相关产品推荐
相关产品推荐

