首页
/ OpenCV 中转换 TensorFlow 目标检测模型并使用 Python API 推理:SSD MobileNetV1 完整实战

OpenCV 中转换 TensorFlow 目标检测模型并使用 Python API 推理:SSD MobileNetV1 完整实战

2026-09-06 18:06:06作者:秋阔奎Evelyn

TensorFlow Object Detection API 产出的检测模型不能直接被 OpenCV 的 cv.dnn 使用,需要先把训练好的模型"冻结"成 .pb 图,再转换为 OpenCV 可读的 .pbtxt 文本图描述。本教程以 COCO 数据集上预训练的 SSD MobileNetV1 为例,完整演示如何从零获取 frozen graph、生成配套文本图,并用 OpenCV Python API(object_detection.py 示例)完成真实图片的目标检测推理与结果后处理。读完本文,你将能独立把任意一个 TensorFlow 检测模型迁移到 OpenCV DNN 模块并调优推理参数。

本文依据 tf_det_model_conversion_tutorial.md 展开,并结合仓库内对应源码逐行印证,帮助读者既会用、又知其所以然。

学习目标

完成本教程后,你将掌握:

  • 获取 TensorFlow(TF)检测模型的 frozen graph(冻结图);
  • 用 OpenCV Python API 运行转换后的 TensorFlow 检测模型并解析输出。

两条核心能力将围绕 SSD MobileNetV1 这一经典检测模型逐一展开。本教程对应的运行环境要求为 OpenCV >= 4.5(原作者:Anastasia Murzova)。

关键概念:Frozen Graph 与 .pb 文本图

把 TensorFlow 模型迁移到 cv.dnn.Net 的第一步,是得到模型的 frozen graph(冻结图)。冻结图把"模型图结构"与"所需变量(例如权重)的取值"合并为一个整体,去掉了训练所需的 checkpoint、优化器等冗余信息,因此可直接用于部署与推理。冻结图以 protobuf 序列化格式保存在 .pb 文件中。

OpenCV 提供了两个专用于读取 .pb 图的函数:

  • cv.dnn.readNetFromTensorflow —— 直接读取 TensorFlow 冻结图;
  • cv.dnn.readNet —— 通用读取函数,可通过参数同时指定模型与配置文件(object_detection.py 即使用此函数)。

注意:.pb 二进制图描述了网络算子结构,但像 SSD 这类检测模型通常还带有训练配置(anchor、NMS、类别数等),这些信息编码在训练时使用的 .config 管道文件中。要让 OpenCV 完整重建检测网络,还需要一张"文本图" .pbtxt,其生成方法见后文。

环境准备:虚拟环境与依赖安装

为便于隔离依赖,官方推荐使用 Python 3.7+ 的虚拟环境:

virtualenv -p /usr/bin/python3.7 <env_dir_path>
source <env_dir_path>/bin/activate

随后需要在本机源码编译安装 OpenCV-Python(因为要用到较新的 DNN 模块能力),构建步骤见官方 Python 教程的安装章节。

仓库为转换流程预置了依赖清单 requirements.txt,可先按需增删(例如补入 opencv-python),再一次性安装:

pip install -r requirements.txt

模型准备:拉取 SSD MobileNetV1 并抽取冻结图

本阶段的完整代码位于 py_to_py_ssd_mobilenet.py,它属于 samples/dnn/dnn_model_runner 模块。以模块方式执行即可:

python -m dnn_model_runner.dnn_conversion.tf.detection.py_to_py_ssd_mobilenet

主流程只有寥寥几行:指定模型名、下载并抽取冻结图、打印其路径。

    tf_model_name = 'ssd_mobilenet_v1_coco_2017_11_17'
    graph_extraction_dir = "./"
    frozen_graph_path = extract_tf_frozen_graph(tf_model_name, graph_extraction_dir)
    print("Frozen graph path for {}: {}".format(tf_model_name, frozen_graph_path))

extract_tf_frozen_graph 的实现逻辑(见 py_to_py_ssd_mobilenet.py)如下:

# define model archive name
tf_model_tar = model_name + '.tar.gz'
# define link to retrieve model archive
model_link = DETECTION_MODELS_URL + tf_model_tar

tf_frozen_graph_name = 'frozen_inference_graph'

try:
    urllib.request.urlretrieve(model_link, tf_model_tar)
except Exception:
    print("TF {} was not retrieved: {}".format(model_name, model_link))
    return

print("TF {} was retrieved.".format(model_name))

tf_model_tar = tarfile.open(tf_model_tar)
frozen_graph_path = ""

for model_tar_elem in tf_model_tar.getmembers():
    if tf_frozen_graph_name in os.path.basename(model_tar_elem.name):
        tf_model_tar.extract(model_tar_elem, extracted_model_path)
        frozen_graph_path = os.path.join(extracted_model_path, model_tar_elem.name)
        break
tf_model_tar.close()

代码细节背后有几个值得留意的点:

  • 下载地址拼接自模块常量 DETECTION_MODELS_URL = 'http://download.tensorflow.org/models/object_detection/'(源码 py_to_py_ssd_mobilenet.py),与 samples/dnn/models.ymlssd_tf 配置块声明的下载源一致;
  • 官方模型压缩包内部结构为 ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb,脚本按文件名关键字 frozen_inference_graph 在 tar 成员中定位并只解压该文件,避免解出无用的完整训练目录。

成功执行后终端输出应为:

TF ssd_mobilenet_v1_coco_2017_11_17 was retrieved.
Frozen graph path for ssd_mobilenet_v1_coco_2017_11_17: ./ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb

准备测试素材

推理使用下面这张 Pexels 协议授权的双层巴士街景照片作为输入(原图位于 images 目录):

双层巴士检测输入图片

从训练配置生成文本图 .pbtxt

仅有 .pb 冻结图还不够。SSD 检测网络带有大量由训练配置决定的超参数(anchor 尺度/宽高比、类别数、NMS 阈值、输入尺寸等),OpenCV 需要一张 .pbtxt 文本图来描述这些结构。这份 .config 来自 TensorFlow Object Detection API 的官方示例 ssd_mobilenet_v1_coco.config,该框架自带的检测模型工具机制可帮助完成这类转换。

仓库提供了 tf_text_graph_ssd.py 脚本来执行这一"二进制图 → 文本图"的转换。运行命令如下:

python tf_text_graph_ssd.py --input ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb --config ssd_mobilenet_v1_coco_2017_11_17/ssd_mobilenet_v1_coco.config --output ssd_mobilenet_v1_coco_2017_11_17.pbtxt

三个参数含义(对应脚本 argparse 定义):

参数 含义
--input TensorFlow 冻结图路径(.pb
--config 训练时使用的 .config 管道文件路径
--output 输出文本图路径(.pbtxt

成功执行后,工作目录下会生成 ssd_mobilenet_v1_coco_2017_11_17.pbtxt

如果希望了解这一步背后究竟做了什么,可以阅读 createSSDGraph 函数,它的关键动作包括:

  • 算子白名单裁剪:只保留 keepOps 中列出的算子(Conv2DBiasAddRelu/Relu6DepthwiseConv2dNativeFusedBatchNorm(V3) 等),并移除 MultipleGridAnchorGenerator/Postprocessor/Preprocessor/ 等前缀节点,大幅精简网络图;
  • BatchNorm 融合fuse_nodes 会把反卷积后的 Add/Rsqrt/Mul/Sub 子图重写为单个 FusedBatchNorm 算子并带入 epsilon=0.001
  • 配置驱动重建检测头:从 .config 中读取 num_classesimage_resizer 的宽高、anchor generator(ssd_anchor_generatormultiscale_anchor_generator)与 box_coderx/y/width/height_scale,据此为每个特征层生成 PriorBox,再拼接出 ClassPredictorBoxEncodingPredictor 与最终的 DetectionOutput 层;
  • 保留标准输出名:将 num_detectionsdetection_scoresdetection_boxesdetection_classes 作为网络输出张量名(源码 输出名定义)。

正是这套"解析 config → 重建 PriorBox 与 NMS 头 → 精简算子"的机制,保证了 OpenCV 能拿到与 TensorFlow 原生推理等价的检测语义。

查看 SSD MobileNetV1 的测试默认配置

在运行推理脚本之前,先了解仓库为 SSD MobileNetV1 预设的预处理参数。它们统一维护在 models.ymlssd_tf 键下(见 models.yml 第 158-177 行):

ssd_tf:
  load_info:
    url: "http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_2017_11_17.tar.gz"
    sha1: "9e4bcdd98f4c6572747679e4ce570de4f03a70e2"
    download_name: "ssd_mobilenet_v1_coco_2017_11_17.tar.gz"
    member: "ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb"
  model: "ssd_mobilenet_v1_coco_2017_11_17.pb"
  config: "ssd_mobilenet_v1_coco_2017_11_17.pbtxt"
  mean: [0, 0, 0]
  scale: 1.0
  width: 300
  height: 300
  rgb: true
  labels: "object_detection_classes_coco.txt"
  postprocessing: "ssd"
  sample: "object_detection"

这些参数中,meanscalewidthheightrgb 会在运行时通过 common.add_preproc_args(见 samples/dnn/common.py)注入命令行解析器,最终进入 cv.dnn.blobFromImage 完成输入预处理(object_detection.py 中 blob 构造代码)。关键含义:

  • width/height: 300:SSD MobileNetV1 的固定输入尺寸为 300×300,与 .configfixed_shape_resizer 一致;
  • mean: [0, 0, 0]scale: 1.0:MobileNet 系列训练时使用 [-1, 1] 归一化,OpenCV 侧通过 rgb: true(即 swapRB)配合 scale=1/127.5 之外的组合在此处正好抵消,因此保持均值为 0、缩放为 1.0;
  • labels:COCO 80 类名称清单,文件为 object_detection_classes_coco.txt
  • postprocessing: "ssd":告知示例脚本按 SSD 的 1x1xNx7 输出格式解码。

运行检测推理

模型的默认配置已就绪,现在用双层巴士图片启动推理。以别名 ssd_tf 引用 models.yml 配置的方式最简洁:

python object_detection.py ssd_tf --input ../data/pexels_double_decker_bus.jpg

这条命令与下面显式指定各参数的完整写法完全等价ssd_tf 别名会展开成 model/config/width/height/classes 等默认项):

python object_detection.py --model ssd_mobilenet_v1_coco_2017_11_17.pb --config  ssd_mobilenet_v1_coco_2017_11_17.pbtxt  --input ../data/pexels_double_decker_bus.jpg --width 300 --height 300 --classes ../data/dnn/object_detection_classes_coco.txt

其中 ../data 指向 OpenCV samples 数据目录(对应仓库中的 samples/data)。推理结果如下图所示:

OpenCV SSD 双层巴士检测结果

从源码看,示例的运行主链路位于 object_detection.py

  1. 加载网络net = cv.dnn.readNet(args.model, args.config, "", engine),同时读入 .pb.pbtxt
  2. 设置后端net.setPreferableBackend / net.setPreferableTarget 支持切换 CPU、OpenVINO、CUDA、Vulkan 等计算后端;
  3. 前向推理net.setInput(blob)outs = net.forward(outNames)
  4. 解码后处理postprocess() 解析 SSD 输出并在 drawPred() 中绘制检测框与类别标签,窗口上还提供 "Confidence threshold" 滑条可实时调节置信度阈值。

SSD 输出解码与 NMS 后处理

SSD 网络的原始输出是一个形状为 1x1xNx7 的 blob,其中 N 是候选检测数量,每个检测是一组 7 元向量:

[batchId, classId, confidence, left, top, right, bottom]

示例脚本在 postprocess 的 ssd 分支中按行解码:保留 confidence > confThreshold 的检测,取 detection[1] - 1 作为类别编号(classId 含背景类 0,因此减 1 跳过背景标签),坐标默认按输入 300×300 归一化,必要时再乘回原图宽高。

由于 ssd_tf 生成文本图时已把 NMS 逻辑内嵌进 DetectionOutput 层(配置 post_processing.batch_non_max_suppressioniou_thresholdscore_thresholdmax_total_detections 等会写入该层属性,见 tf_text_graph_ssd.py),单输出网络在 OpenCV 后端下无需额外 NMS。当后端不提供内置 NMS 或网络存在多输出时,脚本会回退调用 cv.dnn.NMSBoxes 做非极大值抑制(object_detection.py NMS 调用)。

可调参数:threshold 与 NMS 阈值

针对不同场景的检测结果,OpenCV 还提供了两个最常用的"结果修正"参数:

  • --thr:置信度阈值,默认 0.5(见 object_detection.py 参数定义)。调高可过滤更多低置信度误检,调低可召回更弱的目标;
  • --nms:非极大值抑制阈值(IoU),默认 0.4object_detection.py 参数定义)。值越小,重叠框被抑制得越彻底,同一目标越不容易输出多个框。

ssd_tf 之外,models.yml 中还维护了 faster_rcnn_tf(Faster-RCNN Inception v2)、YOLO 系列等检测模型条目,它们的文本图可由 tf_text_graph_faster_rcnn.py 等脚本生成——这也说明"TensorFlow 检测模型 → OpenCV DNN"是一套覆盖 SSD、Faster-RCNN 等多个系列的通用方法论,掌握本文的 SSD MobileNetV1 流程即可举一反三。

登录后查看全文
热门项目推荐
相关项目推荐