TensorFlow LSTM Object Detection 模型导出 TFLite 完整指南:从 checkpoint 到 FlatBuffer 的两步转换
2026/9/7 19:05:27 网站建设 项目流程

TensorFlow LSTM Object Detection 模型导出 TFLite 完整指南:从 checkpoint 到 FlatBuffer 的两步转换

【免费下载链接】modelsModels and examples built with TensorFlow项目地址: https://gitcode.com/GitHub_Trending/mode/models

导读

LSTD(LSTM SSD)视频目标检测模型在训练完成后,若要部署到移动端 / 嵌入式 TFLite 运行时,需要把 TensorFlow 训练出的 checkpoint 依次转换成「TFLite 兼容的冻结图」与「TFLite FlatBuffer 模型」两个中间产物。本文基于 exporting_models.md 的官方流程,结合 models 仓库 中export_tflite_lstd_graph.pyexport_tflite_lstd_model.py及其底层库实现,完整讲解每一步的命令、参数含义、图结构与节点规格,并给出使用test_tflite_model.py校验导出的模型是否可正常推理的收尾方法。读完本文,你将能够独立把一个训练好的 LSTD checkpoint 导出为可在应用侧直接加载的*.tflite文件,并理解冻结图输入 / 输出张量为何被设计成视频序列形式。

为什么 LSTD 的 TFLite 导出需要两步

LSTD 模型包含 LSTM 时序单元,其输入不是单张静态图片,而是一段长度为unroll_length的视频帧序列(维度为[unroll_length, height, width, 3])。这就决定了它与普通 SSD 模型的导出路径不同:不能直接走标准的 saved_model / frozen graph 单步转换。

从 exporting_models.md 可以提炼出官方推荐的固定两步流水线:

  1. 从 checkpoint 导出 TFLite 兼容的冻结图(frozen graph)——把训练权重固化成一张输入 / 输出已按 TFLite 约定定义的tflite_graph.pb
  2. 从冻结图导出 TFLite FlatBuffer 模型——把上一步的tflite_graph.pb交给 TFLite 转换器,产出最终供应用加载的*.tflite文件。

在 research/lstm_object_detection 目录下,这两个步骤分别对应可执行脚本 export_tflite_lstd_graph.py 与 export_tflite_lstd_model.py。下面逐一展开。

导出前的工程与配置前提

运行目录与依赖

  • 导出命令设计为从tensorflow/models/research目录(即本仓库的 research 目录)发起执行,命令行中以lstm_object_detection/xxx.py的相对模块路径调用脚本;
  • 底层实现 export_tflite_lstd_graph_lib.py 除导入lstm_object_detection自身外,还依赖同仓库 research/object_detection 下的exporterbuilders.graph_rewriter_builderbuilders.post_processing_buildercore.box_list等模块,因此需要保证该目录位于PYTHONPATH中;
  • 脚本基于tensorflow.compat.v1编写(见两处脚本的 import 与tf.app.run),请使用兼容 TensorFlow 1.x API 的 TensorFlow 版本环境运行。

pipeline config 中的硬性约束

从源码实现(export_tflite_lstd_graph_lib.py)可以确认,参与导出的 pipeline config 必须满足以下条件,否则会直接抛错:

约束说明源码行为
检测模型类型只能是 SSDmodel { ssd { ... } }支持 TFLite 导出非 SSD 时抛出ValueError('Only ssd models are supported in tflite. ...')
图像缩放器必须是fixed_shape_resizerTFLite 侧需要完全确定的高度 / 宽度其它 resizer 类型抛出ValueError
lstm_model配置块必须存在需要其中的eval_unroll_length决定输入序列长度导出时读取lstm_config.eval_unroll_length
输入必须是固定尺寸的 RGB(或灰度)视频序列输入占位符为[eval_unroll_length, height, width, 3](若开启convert_to_grayscale则通道数为 1)依 config 动态构造 placeholder

仓库给出的两份参考配置可以印证这些字段的实际写法:

  • lstm_ssd_mobilenet_v1_imagenet.config:train_unroll_length: 4eval_unroll_length: 4image_resizer { fixed_shape_resizer { height: 256 width: 256 } },检测类别数num_classes: 30,使用 Faster R-CNN box coder(y_scale/x_scale: 10.0height_scale/width_scale: 5.0),后处理采用batch_non_max_suppressionscore_converter: SIGMOID
  • lstm_ssd_interleaved_mobilenet_v2_imagenet.config:基于 Interleaved MobileNet V2 的 LSTD 变体,image_resizer为 320×320,同样配置了eval_unroll_length: 4

注意 TFLite 导出读取的是eval 路径下的固定分辨率与eval_unroll_length,这些字段最终决定了冻结图的输入张量形状,训练与导出配置需保持一致。

第一步:从 checkpoint 导出 TFLite 冻结图

官方命令

在 exporting_models.md 中,官方给出从tensorflow/models/research目录执行如下命令:

# 位于 research 目录下执行 PIPELINE_CONFIG_PATH={path to pipeline config} TRAINED_CKPT_PREFIX=/{path to model.ckpt} EXPORT_DIR={path to folder that will be used for export} python lstm_object_detection/export_tflite_lstd_graph.py \ --pipeline_config_path ${PIPELINE_CONFIG_PATH} \ --trained_checkpoint_prefix ${TRAINED_CKPT_PREFIX} \ --output_directory ${EXPORT_DIR} \ --add_preprocessing_op

执行成功后,${EXPORT_DIR}目录下将出现两个文件:

  • tflite_graph.pb:二进制格式的 TFLite 兼容冻结图;
  • tflite_graph.pbtxt:同一张图的文本格式版本,便于人工检查节点结构。

脚本完整参数表(以源码为准)

对照 export_tflite_lstd_graph.py 中的 flag 定义,可以拿到比文档更完整的参数语义与默认值。注意文档命令行中写作--add_preprocessing_op,而当前仓库源码中对应的 flag 实际名为add_postprocessing_op,且默认值为True(历史版本命名可能有差异,请以你所 checkout 的源码为准)。

Flag类型默认值必填含义
--output_directorystringNone导出产物输出目录
--pipeline_config_pathstringNonepipeline_pb2.TrainEvalPipelineConfig格式的配置文件路径
--trained_checkpoint_prefixstringNone训练 checkpoint 前缀(如.../model.ckpt
--max_detectionsinteger10最多输出的检测框数量
--max_classes_per_detectioninteger1每个检测框最多输出的类别数
--detections_per_classinteger100Regular NMS 中每个类别使用的 anchor 数量
--add_postprocessing_opboolTrue是否在图中追加 TFLite 自定义后处理算子
--use_regular_nmsboolFalse后处理算子使用 Regular NMS 还是 Fast NMS
--config_overridestring''覆盖 pipeline config 指定字段的文本 proto

导出图的输入与输出规格

根据脚本与库实现,导出的tflite_graph.pb中节点约定如下(也是后续第二步转换时引用张量名的依据):

输入节点

  • input_video_tensorfloat32张量,形状[unroll_length, height, width, 3],内容是归一化后的输入视频帧序列;
  • 其中height/width必须与 pipeline config 的fixed_shape_resizer中配置一致,unroll_length取自lstm_model.eval_unroll_length

输出节点(未附加后处理时,即add_postprocessing_op=False

  • raw_outputs/box_encodingsfloat32,形状[1, num_anchors, 4],编码后的框回归预测;
  • raw_outputs/class_predictionsfloat32,形状[1, num_anchors, num_classes],经过 score conversion(SIGMOID 等)后的每 anchor 类别得分;
  • anchorsfloat32常量张量,形状[num_anchors, 4],中心点 / 尺寸编码后的 anchor 集合。

输出节点(附加后处理时,即add_postprocessing_op=True

图中会追加名为TFLite_Detection_PostProcess的自定义算子节点,其四个输出为:

  • detection_boxesfloat32,形状[1, num_boxes, 4],框坐标;
  • detection_classesfloat32,形状[1, num_boxes],类别索引;
  • detection_scoresfloat32,形状[1, num_boxes],类别得分;
  • num_boxes:大小为 1 的float32张量,表示检测到的框数量。

底层做了什么:冻结 + 追加 TFLite 自定义算子

深入 export_tflite_lstd_graph_lib.py 可以看到导出函数的完整调用链:

  1. 读取并校验配置config_util.get_configs_from_pipeline_file解析 pipeline config,取出modellstm_modeleval_config,并校验 SSD 与fixed_shape_resizer
  2. 收集解码与 NMS 所需参数:从batch_non_max_suppression读取score_threshold/iou_threshold,从faster_rcnn_box_coder读取y_scale/x_scale/h_scale/w_scale,从model.ssd.num_classes读取类别数(export_tflite_lstd_graph_lib.py);
  3. 构造视频输入与推理图:创建形状为[eval_unroll_length, height, width, 3]input_video_tensor占位符,调用model_builder.build(...)构建检测模型并执行 preprocess / predict(export_tflite_lstd_graph_lib.py),NMS 被有意推迟到 TFLite 自定义算子中完成;
  4. 固化原始输出:在raw_outputs命名空间下输出box_encodingsclass_predictions与常量anchors(anchor 通过get_const_center_size_encoded_anchors转成常量节点);若配置中存在graph_rewriter还会在此处执行量化图改写;若eval_config.use_moving_averages为真,则先将滑动平均变量替换进图再冻结(export_tflite_lstd_graph_lib.py);
  5. 冻结图:使用exporter.freeze_graph_with_def_protos将 checkpoint 权重固化,输出节点为raw_outputs/box_encodingsraw_outputs/class_predictionsanchors
  6. 按需追加后处理算子:若add_postprocessing_op=True,调用append_postprocessing_opTFLite_Detection_PostProcess节点挂到raw_outputs/*anchors之上,并通过TransformGraphstrip_unused_nodes变换剪掉无用节点(export_tflite_lstd_graph_lib.py)。该节点的属性(max_detectionsnms_score_thresholdnms_iou_thresholdy_scalex_scale等)全部来自第 2 步收集的配置;
  7. 落盘:分别写出tflite_graph.pbtflite_graph.pbtxt

其中第 6 步正是 TFLite 目标检测模型常见的做法:把 NMS 等后处理做成TFLite custom op在端上执行,从而避免 TFLite 转换器不支持这些算子的兼容性问题。

用 config_override 微调推理行为而不改训练配置

脚本还提供了--config_override参数,允许在不改动训练 / 评估配置的前提下,对导出的推理图做小规模参数覆盖。它同样接受pipeline_pb2.TrainEvalPipelineConfig文本 proto,源码 docstring 中给出的例子是把 NMS 的iou_threshold改为 0.5、score_threshold改为 0.0:

python lstm_object_detection/export_tflite_lstd_graph.py \ --pipeline_config_path path/to/lstm_pipeline.config \ --trained_checkpoint_prefix path/to/model.ckpt \ --output_directory path/to/exported_model_directory \ --config_override " \ model{ \ ssd{ \ post_processing { \ batch_non_max_suppression { \ score_threshold: 0.0 \ iou_threshold: 0.5 \ } \ } \ } \ } \ "

可以看到该机制在导出「低阈值召回更多框」或「收紧重叠抑制」等推理变体时非常实用。

第二步:从冻结图导出 TFLite FlatBuffer 模型

官方命令

拿到第一步的tflite_graph.pb后,在tensorflow/models/research目录执行:

# 位于 research 目录下执行 FROZEN_GRAPH_PATH={path to exported tflite_graph.pb} EXPORT_PATH={path to filename that will be used for export} PIPELINE_CONFIG_PATH={path to pipeline config} python lstm_object_detection/export_tflite_lstd_model.py \ --export_path ${EXPORT_PATH} \ --frozen_graph_path ${FROZEN_GRAPH_PATH} \ --pipeline_config_path ${PIPELINE_CONFIG_PATH}

三个参数均为必填(见脚本中的mark_flag_as_required)。执行成功后,${EXPORT_PATH}即为可直接交付给移动应用加载的 FlatBuffer 模型文件。

脚本内部如何调用转换器

对照 export_tflite_lstd_model.py 的main实现,转换逻辑如下:

configs = config_util.get_configs_from_pipeline_file(FLAGS.pipeline_config_path) lstm_config = configs['lstm_model'] input_arrays = ['input_video_tensor'] output_arrays = [ 'TFLite_Detection_PostProcess', 'TFLite_Detection_PostProcess:1', 'TFLite_Detection_PostProcess:2', 'TFLite_Detection_PostProcess:3', ] input_shapes = { 'input_video_tensor': [lstm_config.eval_unroll_length, 320, 320, 3], } converter = tf.lite.TFLiteConverter.from_frozen_graph( FLAGS.frozen_graph_path, input_arrays, output_arrays, input_shapes=input_shapes) converter.allow_custom_ops = True tflite_model = converter.convert()

几个值得注意的细节:

  • 输入张量为第一步冻结图唯一的输入input_video_tensor
  • 输出张量为后处理自定义算子的 4 个输出端口TFLite_Detection_PostProcess及其:1:2:3,分别对应detection_boxes / detection_classes / detection_scores / num_boxes——这要求第一步导出时必须开启后处理算子,否则冻结图不存在该节点;
  • 输入形状中的序列长度取自lstm_model.eval_unroll_length,而当前仓库源码中高 / 宽硬编码为320, 320,因此该流程面向 320×320 固定输入分辨率设计(若训练配置使用其它分辨率,需要相应调整此处源码,这也侧面印证了导出的图对固定形状的强约束);
  • converter.allow_custom_ops = True是关键开关:由于图中包含TFLite_Detection_PostProcess这一自定义算子,必须允许 custom ops,否则转换器会因遇到未知算子而报错;
  • 转换完成后直接以二进制方式写入${EXPORT_PATH}

收尾验证:用随机输入跑通导出的 tflite

仓库在 test_tflite_model.py 中提供了面向 TFLite 模型的冒烟测试工具,可用于快速确认 FlatBuffer 可被解释器加载并完成一次前向推理:

python lstm_object_detection/test_tflite_model.py \ --model_path ${EXPORT_PATH}

该脚本会依次执行:

  1. 通过tf.lite.Interpreter(model_path=...)加载模型并allocate_tensors()
  2. 打印输入 / 输出张量的input_detailsoutput_details,其中会显示输入形状,可据此核对是否为[unroll_length, 320, 320, 3]
  3. np.random.random_sample生成随机输入喂入网络并invoke()
  4. 打印第一个输出张量的内容。

脚本 docstring 也提示:随机输入仅用于验证,实际使用时应把input_data替换为按同形状组织好的真实归一化视频帧。若该步能正常打印输出,说明两步导出产出的 tflite 模型在运行时层面是完整的。

小结与常见问题排查

回顾整条链路,从 checkpoint 到可交付的 TFLite FlatBuffer 共两步、对应两个脚本:

阶段脚本输入输出
导出冻结图export_tflite_lstd_graph.pypipeline config + checkpointtflite_graph.pb/tflite_graph.pbtxt
导出 TFLiteexport_tflite_lstd_model.pytflite_graph.pb+ pipeline config${EXPORT_PATH}(FlatBuffer)
冒烟验证test_tflite_model.pytflite 模型路径前向推理输出

排障时可以优先对照以下几点(均可从前述源码与配置文件中找到依据):

  • pipeline config 必须是 SSD 模型且使用fixed_shape_resizer,同时存在[lstm_object_detection.protos.lstm_model]配置块(参考两个示例 config);
  • 输入分辨率与eval_unroll_length在训练、pipeline config、导出脚本三处必须自洽;
  • 若应用侧只消费检测框结果(detection_boxes等 4 项输出),第一步必须保留add_postprocessing_op=True(默认即开启),第二步才能找到TFLite_Detection_PostProcess输出端口;
  • 第二步转换时allow_custom_ops必须开启,否则自定义后处理算子会导致转换失败;
  • 若希望推理图与训练 / 评估配置在 NMS 阈值等参数上有所差异,无需改动原配置,直接通过--config_override覆盖即可。

【免费下载链接】modelsModels and examples built with TensorFlow项目地址: https://gitcode.com/GitHub_Trending/mode/models

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询