You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter通过tflite_flutter集成MoveNet Multipose遇precondition报错

Flutter tflite_flutter 接入MoveNet Multipose报错修复

问题现象

在Flutter 3.0项目中使用tflite_flutter包实现MoveNet Multipose多人姿态检测功能,已成功运行官方长期未更新的目标检测示例,接入姿态模型时抛出如下运行错误:

Bad state: failed precondition
E/flutter (27000): #0      checkState (package:quiver/check.dart:74:5)
E/flutter (27000): #1      Tensor.setTo (package:tflite_flutter/src/tensor.dart:146:5)
E/flutter (27000): #2      Interpreter.runForMultipleInputs (package:tflite_flutter/src/interpreter.dart:186:33)
E/flutter (27000): #3      Interpreter.run (package:tflite_flutter/src/interpreter.dart:157:5)

当前已编写实现代码如下:

import 'dart:math';
import 'dart:ui';

import 'package:image/image.dart';
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:tflite_flutter_helper/tflite_flutter_helper.dart';
import 'package:untitled/features/pose_detection/utils/recognition.dart';

class Classifier {
  static const String modelFileName = 'pose.tflite';
  static const String labelFileName = 'labelmap.txt';
  Interpreter? interpreter;
  List<String> labels = [];
  List<List<int>> outputShapes = [];
  ImageProcessor? imageProcessor;
  TensorBuffer? outputBuffer;
  TfLiteType? inputType;

  /// Input size of image (height = width = 300)
  static const int inputSize = 300;

  /// Number of results to show
  static const int numResults = 2;

  /// Result score threshold
  static const double threshold = 0.3;

  /// Types of output tensors
  List<TfLiteType> outputTypes = [];

  Classifier({List<String>? labels, Interpreter? interpreter}) {
    loadModel(interpreter: interpreter);
  }

  void loadModel({Interpreter? interpreter}) async {
    try {
      final localInterpreter = interpreter ??
          await Interpreter.fromAsset(
            modelFileName,
            options: InterpreterOptions()..threads = 4,
          );
      final outputTensors = localInterpreter.getOutputTensors();
      this.interpreter = localInterpreter;
      outputBuffer = TensorBuffer.createFixedSize(
        outputTensors[0].shape,
        outputTensors[0].type,
      );
      inputType = localInterpreter.getInputTensor(0).type;
    } catch (e) {
      print('Error creating interpreter: $e');
    }
  }

  List<Recognition> predict(Image image) {
    if (interpreter == null || outputBuffer == null || inputType == null) {
      throw Exception('Interpreter is not loaded');
    }
    var inputImage = TensorImage(inputType!);
    inputImage.loadImage(image);
    inputImage = getProcessedImage(inputImage);
    // MoveNet Multipose model spec
    interpreter!.run(inputImage.buffer, outputBuffer!.getBuffer());

    return [];
  }

  TensorImage getProcessedImage(TensorImage inputImage) {
    const multiplier = 32;
    const defaultSize = 256;
    final isWidthGreater = inputImage.width > inputImage.height;

    final ratio = isWidthGreater
        ? inputImage.height / inputImage.width
        : inputImage.width / inputImage.height;

    final height = isWidthGreater ? (ratio * defaultSize) : defaultSize;
    final width = isWidthGreater ? defaultSize : (ratio * defaultSize);

    final widthMultiplier = (height / multiplier).ceil();
    final heightMultiplier = (width / multiplier).ceil();

    final finalHeight = (heightMultiplier * multiplier);
    final finalWidth = (widthMultiplier * multiplier);
    return ImageProcessorBuilder()
        .add(ResizeOp(finalHeight, finalWidth, ResizeMethod.BILINEAR))
        .add(ResizeWithCropOrPadOp(finalHeight, finalWidth))
        .build()
        .process(inputImage);
  }
}

对应pubspec.yaml依赖配置如下:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.17.0


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  get: ^4.6.5
  rxdart: ^0.27.4
  #  provider: ^6.0.3
  flutter_riverpod: ^1.0.4
  camera: ^0.8.1+3
  image: ^3.2.0
  tflite_flutter: ^0.9.0
  tflite_flutter_helper:
    git:
      url: https://github.com/filofan1/tflite_flutter_helper.git
      ref: 783f15e5a87126159147d8ea30b98eea9207ac70

错误根因

报错failed precondition是tflite运行时前置校验失败导致,核心原因有3个:

  • 输入预处理逻辑不符合模型要求:MoveNet Multipose Lightning固定输入尺寸为256*256,现有代码动态计算的输入尺寸不固定,且预处理流程缺少像素归一化步骤,输入张量shape、数值范围和模型预期不匹配。
  • 输出缓冲区初始化逻辑有缺陷:现有代码仅动态读取输出张量shape创建缓冲区,没有做类型和shape校验,容易出现缓冲区大小不匹配的问题。
  • 异步初始化时序问题:loadModel是异步方法,直接在构造函数中调用无法保证模型、缓冲区初始化完成后再调用推理逻辑,即使有判空逻辑也可能出现初始化不完整的问题。

修复方案

按以下步骤修改代码即可正常运行:

  1. 替换原有的异步构造逻辑,使用工厂构造方法确保模型初始化完成后再返回实例,修改后的类初始化部分代码如下:
class Classifier {
  static const String modelFileName = 'pose.tflite';
  Interpreter? interpreter;
  TensorBuffer? outputBuffer;
  TfLiteType? inputType;
  static const int inputSize = 256;
  static const double threshold = 0.3;

  // 私有构造
  Classifier._();
  // 异步工厂构造,确保初始化完成
  static Future<Classifier> create({Interpreter? interpreter}) async {
    final classifier = Classifier._();
    await classifier.loadModel(interpreter: interpreter);
    return classifier;
  }

  Future<void> loadModel({Interpreter? interpreter}) async {
    try {
      final localInterpreter = interpreter ??
          await Interpreter.fromAsset(
            modelFileName,
            options: InterpreterOptions()..threads = 4,
          );
      this.interpreter = localInterpreter;
      // MoveNet Multipose输出固定shape为[1, 6, 56],对应最多6个人,每人56个关键点+框+置信度数据
      outputBuffer = TensorBuffer.createFixedSize(
        [1, 6, 56],
        TfLiteType.float32,
      );
      inputType = localInterpreter.getInputTensor(0).type;
    } catch (e) {
      print('Error creating interpreter: $e');
      rethrow;
    }
  }
}
  1. 修正图像预处理逻辑,固定resize到256*256尺寸,增加归一化操作将像素值从0-255映射到0-1区间,替换原有getProcessedImage方法:
TensorImage getProcessedImage(TensorImage inputImage) {
  return ImageProcessorBuilder()
      // 固定resize到模型要求的256*256
      .add(ResizeOp(inputSize, inputSize, ResizeMethod.BILINEAR))
      // 像素值归一化到0-1
      .add(NormalizeOp(0, 255))
      .build()
      .process(inputImage);
}
  1. 推理完成后增加输出解析逻辑,从输出缓冲区中提取每个人的关键点、检测框、置信度,过滤低置信度结果后返回。

注意:推理得到的关键点坐标是归一化到0-1区间的相对值,需要乘以原图对应的宽高才能得到实际绘制坐标。

内容的提问来源于stack exchange,提问作者Hariki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:15:30