基于YOLOv3-Tiny 的智能门铃的人体检测模型详细部署指导(终结)


到这里已经有了一个 PyTorch YOLOv3-Tiny 模型,并且通过 QAT 进行了训练和优化,并考虑了锚框聚类。现在,是时候将其部署到目标嵌入式设备上,通常这将涉及将其转换为 TensorFlow Lite (TFLite) 格式。

下面是详细的 TFLite 部署步骤,涵盖了从 PyTorch 到 TFLite 的完整流程:


TFLite 部署

  1. 准备 PyTorch 模型进行导出: 确保模型已完成 QAT 并转换为量化模式。
  2. PyTorch 模型导出为 ONNX: 将 PyTorch 模型转换为 ONNX 格式。
  3. ONNX 模型转换为 TensorFlow SavedModel: 使用 ONNX-TensorFlow 工具将 ONNX 模型转换为 TensorFlow 的 SavedModel 格式。
  4. TensorFlow SavedModel 转换为 TFLite: 使用 TensorFlow Lite Converter 将 SavedModel 转换为最终的 .tflite 模型。
  5. 在嵌入式设备上运行 TFLite 模型: 使用 TFLite 运行时库进行推理。

详细步骤

步骤 1: 准备 PyTorch 模型进行导出

在你的 train.py 脚本中,当 Config.QUANT_MODETrue 且训练结束后,你会得到一个量化后的 PyTorch 模型。

# train.py (摘录,训练循环结束之后)

    # --- 训练完成后,如果进行了 QAT,需要转换为量化模型 ---
    if Config.QUANT_MODE:
        print("--- Converting QAT model to quantized model ---")
        model.eval()
        # 将 QAT 模型转换为实际的量化模型 (INT8)
        # 注意:这里的转换是针对 CPU 后端的
        quantized_model = torch.quantization.convert(model, inplace=False) 
        
        # 保存量化模型状态字典,方便后续加载和导出
        torch.save(quantized_model.state_dict(), "yolov3_tiny_person_quantized_state_dict.pth")
        print("Quantized model state dict saved to yolov3_tiny_person_quantized_state_dict.pth")
        
        # 这里需要创建一个新的 PyTorch 模型实例,并加载量化后的状态字典
        # 这是为了确保模型结构是量化兼容的,并且权重是量化后的
        
        # 重新初始化模型,并加载量化后的 state_dict
        # 你的 YOLOv3Tiny 模型在定义时已经考虑了量化兼容性(例如 ConvBlock 中的 bias=not use_bn)
        # 但是,torch.quantization.convert 会将量化操作插入到模型中,
        # 所以最直接的方式是保存 convert 之后的整个模型 (如果可以序列化) 或其 state_dict
        
        # 更好的是直接使用转换后的 quantized_model 变量进行下一步的 ONNX 导出
        # 确保这个 `quantized_model` 是 `torch.quantization.convert` 的输出
        print("Proceeding to ONNX export with the converted quantized model...")
        # 后续的 ONNX 导出将使用这个 `quantized_model` 对象
    else:
        print("Quantization mode is OFF. Exporting unquantized model.")
        quantized_model = model # 如果没有QAT,则使用原始模型

关键点: 确保你导出的 quantized_model 确实是经过 torch.quantization.convert() 处理后的模型实例,因为这个实例包含了量化操作和校准后的统计信息。


步骤 2: PyTorch 模型导出为 ONNX

这是一个通用的模型交换格式,TFLite Converter 可以通过 ONNX 间接处理 PyTorch 模型。

安装 ONNX 相关的库:

pip install onnx onnxruntime

Python 代码 (例如,可以单独写一个 export_to_onnx.py 脚本):

# export_to_onnx.py
import torch
from config import Config
from model import YOLOv3Tiny # 导入你的模型定义

def export_model_to_onnx(model_path, onnx_output_path, img_size, quant_mode=False):
    """
    加载 PyTorch 模型并导出为 ONNX 格式。
    model_path: 保存的 PyTorch 模型 state_dict 路径 (.pth)
    onnx_output_path: 导出的 ONNX 文件路径 (.onnx)
    img_size: 模型输入图像尺寸 (例如 416)
    quant_mode: 是否是量化模型
    """
    # 1. 实例化模型
    model = YOLOv3Tiny(num_classes=Config.NUM_CLASSES, config_anchors=Config.ANCHORS)
    model.to(Config.DEVICE) # 确保模型在正确的设备上
    
    # 2. 加载模型权重
    if quant_mode:
        # 如果是量化模型,需要先准备QAT,然后加载 state_dict
        model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm') # 或 'qnnpack'
        # 融合模块 (必须与训练时一致)
        model_fused = torch.quantization.fuse_modules(model, [['features.0.conv', 'features.0.bn', 'features.0.activation'],
                                                               ['features.2.conv', 'features.2.bn', 'features.2.activation'],
                                                               ['features.4.conv', 'features.4.bn', 'features.4.activation'],
                                                               ['features.6.conv', 'features.6.bn', 'features.6.activation'],
                                                               ['features.8.conv', 'features.8.bn', 'features.8.activation'],
                                                               ['features.10.conv', 'features.10.bn', 'features.10.activation'],
                                                               ['features.12.conv', 'features.12.bn', 'features.12.activation'],
                                                               ['features.13.conv', 'features.13.bn', 'features.13.activation'],
                                                               ['features.14.conv', 'features.14.bn', 'features.14.activation'],
                                                               ['head2_conv1.conv', 'head2_conv1.bn', 'head2_conv1.activation'],
                                                               ['head2_conv2.conv', 'head2_conv2.bn', 'head2_conv2.activation'],
                                                               ])
        # 准备QAT
        model_prepared = torch.quantization.prepare_qat(model_fused, inplace=True)
        # 加载量化训练后的 state_dict
        model_prepared.load_state_dict(torch.load(model_path, map_location='cpu')) # QAT后的state_dict可能需要CPU加载
        # 转换为量化模式
        model = torch.quantization.convert(model_prepared, inplace=True)
        print("Loaded and converted quantized PyTorch model.")
    else:
        # 非量化模型直接加载 state_dict
        model.load_state_dict(torch.load(model_path, map_location='cpu'))
        print("Loaded unquantized PyTorch model.")
    
    model.eval() # 切换到评估模式

    # 3. 创建一个虚拟输入张量
    # 批次大小通常为 1 进行部署
    dummy_input = torch.randn(1, 3, img_size, img_size).to(Config.DEVICE) 

    # 4. 导出模型到 ONNX
    print(f"Exporting model to ONNX: {onnx_output_path}")
    torch.onnx.export(model,
                      dummy_input,
                      onnx_output_path,
                      verbose=False,
                      opset_version=13, # 推荐使用 11 或更高版本,确保兼容性
                      do_constant_folding=True,
                      input_names=['input'],
                      output_names=['output'],
                      dynamic_axes={'input': {0: 'batch_size'}, # 如果希望支持动态批次大小
                                    'output': {0: 'batch_size'}})
    print("ONNX model export complete!")

if __name__ == "__main__":
    # 使用你训练好的模型路径,如果进行了QAT,就是QAT保存的 state_dict
    pytorch_model_path = "yolov3_tiny_person_quantized_state_dict.pth" 
    onnx_output_file = "yolov3_tiny_person_quantized.onnx"
    
    export_model_to_onnx(pytorch_model_path, onnx_output_file, Config.IMAGE_SIZE, quant_mode=Config.QUANT_MODE)

运行这个脚本: python export_to_onnx.py

步骤 3: ONNX 模型转换为 TensorFlow SavedModel

TensorFlow Lite Converter 可以直接从 ONNX 导入模型(需要 onnx-tf 库)。这会将其转换为 TensorFlow SavedModel 格式,这是 TFLite Converter 的首选输入格式。

安装 ONNX-TensorFlow:

pip install onnx-tf tensorflow==2.x # 确保安装兼容的TensorFlow版本

Python 代码 (例如,可以单独写一个 convert_onnx_to_tf.py 脚本):

# convert_onnx_to_tf.py
import onnx
from onnx_tf.backend import prepare
import tensorflow as tf
import os

def convert_onnx_to_tf_savedmodel(onnx_path, saved_model_path):
    """
    将 ONNX 模型转换为 TensorFlow SavedModel 格式。
    onnx_path: 输入 ONNX 文件路径 (.onnx)
    saved_model_path: 输出 SavedModel 目录路径
    """
    print(f"Loading ONNX model from: {onnx_path}")
    onnx_model = onnx.load(onnx_path)
    
    print("Converting ONNX model to TensorFlow SavedModel...")
    # prepare 函数会创建一个 TensorFlow Backend 对象
    tf_rep = prepare(onnx_model)
    
    # 导出为 SavedModel
    tf_rep.export_graph(saved_model_path)
    print(f"TensorFlow SavedModel exported to: {saved_model_path}")

if __name__ == "__main__":
    onnx_input_file = "yolov3_tiny_person_quantized.onnx"
    tf_saved_model_dir = "yolov3_tiny_person_tf_savedmodel"

    # 创建输出目录
    os.makedirs(tf_saved_model_dir, exist_ok=True)
    
    convert_onnx_to_tf_savedmodel(onnx_input_file, tf_saved_model_dir)

运行这个脚本: python convert_onnx_to_tf.py

步骤 4: TensorFlow SavedModel 转换为 TFLite

现在,我们有了 TensorFlow SavedModel,可以将其转换为最终的 .tflite 格式。

Python 代码 (例如,可以单独写一个 convert_tf_to_tflite.py 脚本):

# convert_tf_to_tflite.py
import tensorflow as tf
from config import Config
import numpy as np # 用于代表性数据集

def convert_tf_to_tflite(saved_model_path, tflite_output_path, quant_mode=False):
    """
    将 TensorFlow SavedModel 转换为 TFLite 格式。
    saved_model_path: 输入 SavedModel 目录路径
    tflite_output_path: 输出 TFLite 文件路径 (.tflite)
    quant_mode: 是否进行整数后量化 (如果模型已经是 QAT,则为 True)
    """
    print(f"Loading TensorFlow SavedModel from: {saved_model_path}")
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_path)

    if quant_mode:
        print("Enabling default optimizations for quantization (INT8)...")
        converter.optimizations = [tf.lite.Optimize.DEFAULT]
        
        # 确保 TFLite Runtime 支持 INT8 操作
        converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
        # 如果有自定义操作或 ONNX 转换引入了非标准 TF op,可能需要添加 SELECT_TF_OPS
        # converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8, tf.lite.OpsSet.SELECT_TF_OPS]
        
        # 如果你在 ONNX -> TF 转换过程中失去了 QAT 的量化信息
        # 并且模型最终没有被正确识别为量化模型,你可能需要进行后训练整数量化 (Post-Training Integer Quantization)
        # 这需要提供一个代表性数据集来校准量化范围
        
        # def representative_data_gen():
        #     # 从你的训练数据集中加载少量样本(例如 100-500 张)
        #     # 确保数据预处理与训练时一致
        #     # 假设你的数据集在 Config.TRAIN_IMG_DIR
        #     from dataset import YOLODataset # 导入数据集
        #     import os
        #     
        #     # 创建一个临时的、只用于校准的数据加载器
        #     calibration_dataset = YOLODataset(
        #         Config.TRAIN_IMG_DIR,
        #         Config.TRAIN_LABEL_DIR,
        #         Config.ANCHORS,
        #         img_size=Config.IMAGE_SIZE,
        #         num_classes=Config.NUM_CLASSES,
        #         is_train=False, # 不进行数据增强
        #     )
        #     calibration_loader = torch.utils.data.DataLoader(
        #         dataset=calibration_dataset,
        #         batch_size=1, # 必须是1
        #         shuffle=False,
        #         num_workers=0,
        #         collate_fn=custom_collate_fn,
        #     )
        #     
        #     num_calibration_steps = Config.NUM_CALIBRATION_BATCHES # 和 QAT 校准批次数量保持一致
        #     print(f"Generating representative dataset for TFLite quantization ({num_calibration_steps} batches)...")
        #     for i, (image, _) in enumerate(calibration_loader):
        #         if i >= num_calibration_steps:
        #             break
        #         # 将 PyTorch tensor 转换为 NumPy 数组,并确保数据类型和范围正确
        #         # TFLite 通常期望 float32 输入,尽管它是量化模型
        #         yield [image.cpu().numpy()] 
        # converter.representative_dataset = representative_data_gen
        
        # 如果是 QAT 模型,通常不需要上述的 `representative_dataset` 再次校准
        # 因为 QAT 已经在训练时完成了这个过程。
        # ONNX -> TF SavedModel 转换后,如果量化信息能保留下来,则 TFLite Converter 会识别。
        # 如果没有保留,它会尝试进行后训练量化,这时 `representative_dataset` 就很重要了。
        # 最佳实践是:QAT -> ONNX -> TF SavedModel -> TFLite,并验证 TFLite 模型的量化状态。
        
    else:
        print("Converting to TFLite without explicit quantization (float32).")
        # 如果没有进行 QAT,这里会生成一个 float32 的 TFLite 模型。
        # 可以在此进行 Post-Training Dynamic Range Quantization 或 Full Integer Quantization (PTIQ)
        # converter.optimizations = [tf.lite.Optimize.DEFAULT] # 默认包含动态范围量化
        # converter.target_spec.supported_types = [tf.float16] # 也可以转为FP16

    print("Converting to TFLite...")
    tflite_model = converter.convert()

    with open(tflite_output_path, "wb") as f:
        f.write(tflite_model)
    print(f"TFLite model saved to: {tflite_output_path}")

if __name__ == "__main__":
    tf_saved_model_input_dir = "yolov3_tiny_person_tf_savedmodel"
    tflite_output_file = "yolov3_tiny_person_detector_quantized.tflite"

    convert_tf_to_tflite(tf_saved_model_input_dir, tflite_output_file, quant_mode=Config.QUANT_MODE)

    # 验证 TFLite 模型大小
    tflite_model_size_mb = os.path.getsize(tflite_output_file) / (1024 * 1024)
    print(f"TFLite model size: {tflite_model_size_mb:.2f} MB")

运行这个脚本: python convert_tf_to_tflite.py

步骤 5: 在嵌入式设备上运行 TFLite 模型

一旦你有了 .tflite 文件,就可以将其部署到你的 ARM Cortex-A 处理器上,并使用 TFLite 运行时库进行推理。

C++ 部署示例 (概念性代码):

// inference_on_device.cpp (伪代码)
#include <iostream>
#include <vector>
#include <string>
#include <memory> // For std::unique_ptr

// 假设已经包含了 TensorFlow Lite C++ API 的头文件
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model.h"
#include "tensorflow/lite/tools/command_line_helpers.h" // 可能有用

// 图像处理库 (例如 OpenCV)
// #include <opencv2/opencv.hpp>

// 你的模型输入尺寸
const int INPUT_IMG_SIZE = 416;
const int INPUT_CHANNELS = 3;

// 加载 TFLite 模型
std::unique_ptr<tflite::FlatBufferModel> model =
    tflite::FlatBufferModel::BuildFromFile("yolov3_tiny_person_detector_quantized.tflite");
if (!model) {
    std::cerr << "Failed to load model" << std::endl;
    return 1;
}

// 构建解释器 (Interpreter)
tflite::ops::builtin::BuiltinOpResolver resolver;
std::unique_ptr<tflite::Interpreter> interpreter;
tflite::InterpreterBuilder(*model, resolver)(&interpreter);

if (!interpreter) {
    std::cerr << "Failed to construct interpreter" << std::endl;
    return 1;
}

// 分配张量 (Allocate Tensors)
// 这会为模型中的所有张量分配内存
if (interpreter->AllocateTensors() != kTfLiteOk) {
    std::cerr << "Failed to allocate tensors" << std::endl;
    return 1;
}

// 确保输入张量和模型期望的类型和形状匹配
// 对于量化模型,输入张量可能期望 int8 或 uint8 类型
// 在推理前需要对输入图像进行预处理,使其符合模型输入要求
TfLiteTensor* input_tensor = interpreter->input_tensor(0);

// 获取输入张量的类型和维度
// 例如,如果 input_tensor->type == kTfLiteUInt8, 则输入图像需要转换为 uint8 格式
// input_tensor->dims[0] 是 batch_size, dims[1] 是 height, dims[2] 是 width, dims[3] 是 channels

// ---------- 推理循环 (智能门铃的核心逻辑) ----------
while (true) {
    // 1. 捕获图像 (例如从摄像头)
    // cv::Mat frame = camera.read(); // 假设有摄像头接口
    // if (frame.empty()) continue;

    // 2. 图像预处理
    // 调整大小: cv::resize(frame, resized_frame, cv::Size(INPUT_IMG_SIZE, INPUT_IMG_SIZE));
    // 颜色空间转换: cv::cvtColor(resized_frame, resized_frame, cv::COLOR_BGR2RGB);
    
    // 对于量化模型:
    // 如果模型输入是 uint8,你需要将像素值缩放到 [0, 255] 范围
    // 如果模型输入是 float32,则需要进行归一化 (除以 255.0) 和 ImageNet 标准化
    // (例如:(pixel_value / 255.0 - mean) / std)
    
    // 这里以 uint8 输入为例 (常见于边缘量化模型)
    // 伪代码: 将图像数据复制到 input_tensor
    // for (int i = 0; i < INPUT_IMG_SIZE * INPUT_IMG_SIZE * INPUT_CHANNELS; ++i) {
    //     input_tensor->data.uint8[i] = preprocessed_image_data[i];
    // }
    
    // 模拟输入数据 (实际应用中替换为摄像头数据)
    std::vector<uint8_t> dummy_input_data(INPUT_IMG_SIZE * INPUT_IMG_SIZE * INPUT_CHANNELS);
    // 填充一些模拟数据
    // ...
    memcpy(input_tensor->data.uint8, dummy_input_data.data(), dummy_input_data.size());


    // 3. 运行推理
    // 可以测量推理时间
    // auto start_time = std::chrono::high_resolution_clock::now();
    if (interpreter->Invoke() != kTfLiteOk) {
        std::cerr << "Failed to invoke interpreter" << std::endl;
        break;
    }
    // auto end_time = std::chrono::high_resolution_clock::now();
    // auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
    // std::cout << "Inference latency: " << duration << " ms" << std::endl;


    // 4. 获取输出
    TfLiteTensor* output_tensor = interpreter->output_tensor(0);
    // 输出张量的形状和类型取决于你的模型 (YOLOv3-Tiny 的输出通常是 (1, total_boxes, 5+num_classes))
    // 对于量化模型,输出张量可能是 int8 或 float32。
    // 如果是 int8,你需要进行反量化 (dequantization) 才能得到实际的浮点结果:
    // float real_value = (output_tensor->data.int8[i] - output_tensor->params.zero_point) * output_tensor->params.scale;
    
    // 5. 后处理 (NMS, 阈值过滤)
    // 遍历 output_tensor 中的预测框
    // 根据置信度阈值和 NMS 过滤结果
    // 识别出“人”的边界框
    
    // 伪代码: 解析输出并执行NMS
    // auto detections = parse_tflite_output(output_tensor, original_w, original_h);
    // auto nms_results = apply_nms(detections, Config.NMS_IOU_THRESH, Config.CONF_THRESHOLD);

    // 6. 决策和触发警报
    // if (person_detected_in_nms_results) {
    //     std::cout << "Person detected!" << std::endl;
    //     // 触发门铃警报,发送通知等
    // }

    // 7. 功耗优化 (跳帧检测等)
    // std::this_thread::sleep_for(std::chrono::milliseconds(FRAME_INTERVAL_MS)); // 控制处理帧率
    // 或者根据运动检测器触发
}

在嵌入式设备上编译和运行

  1. 获取 TensorFlow Lite C++ 库: 你需要在你的嵌入式 Linux 系统上交叉编译 TensorFlow Lite 运行时库。通常,你可以从 TensorFlow 的 GitHub 仓库下载源码,然后按照其 tensorflow/lite/tools/build_lib.sh 脚本进行交叉编译。
    • 交叉编译: 这意味着在你的开发主机(例如 x86 Ubuntu)上为目标设备(例如 ARM 处理器)编译库。
    • 取决于你的设备: 不同的 ARM 处理器可能有不同的编译标志(例如针对 NEON 指令集)。
  2. 集成: 将编译好的 TFLite 库以及你的推理代码集成到你的嵌入式项目(例如使用 CMake 或 Makefile)中。
  3. 图像输入/输出: 确保你的摄像头驱动和图像处理管道能够高效地将图像数据送入 TFLite 模型,并处理模型输出。

注意事项和优化

  • 测试与验证: 在真实设备上对转换后的 TFLite 模型进行全面的测试。验证其精度是否符合预期(与 PyTorch 模型的精度损失是否在可接受范围内),推理延迟和功耗是否满足要求。
  • 输入预处理: TFLite 模型的输入预处理非常关键。确保图像缩放、归一化(如果是非量化模型)或量化(如果是 INT8 模型)的方式与训练时完全一致。
  • 输出后处理: 同样,模型的输出需要进行适当的后处理,包括将边界框坐标从模型输出的相对值转换为图像的绝对像素值,以及执行 NMS。
  • 硬件加速 (NPU/DSP): 如果你的嵌入式芯片有 NPU (Neural Processing Unit) 或 DSP (Digital Signal Processor),请确保你的 TFLite 运行时库已经编译并配置为利用这些硬件加速器。这通常需要在 TFLite InterpreterBuilder 中添加相应的委托 (Delegate),例如 TfLiteGpuDelegateV2 (用于 GPU) 或 TfLiteXNNPackDelegate (用于 XNNPACK,一个高性能 CPU 运行时)。
    • 对于 ARM 处理器,通常默认的 TFLite CPU 运行时就包含针对 ARM NEON 指令集的优化。
    • 对于特定的 NPU,你可能需要使用芯片供应商提供的 TFLite Delegate。
  • 内存优化: TFLite 提供了 SetNumThreads() 来控制 CPU 推理线程数,以及 SetCancellable 等功能。合理配置这些参数以平衡性能和内存使用。
  • 功耗管理:
    • 跳帧检测: 在推理循环中实现跳帧检测或运动触发机制,是降低整体功耗的最有效方法。
    • 低功耗模式: 在没有检测到人时,让门铃进入低功耗模式(例如关闭摄像头或降低帧率)。
  • 模型更新: 考虑远程 OTA (Over-The-Air) 更新机制,以便未来能够更新模型或修复 bug。

你对这些部署步骤有什么疑问,或者想深入了解其中某个特定部分吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术与健康

你的鼓励将是我最大的创作动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值