
简介基于GoogLeNet的危险物品检测资源面向计算机视觉学习者、安全监控与安防项目开发者用于快速搭建危险品识别实验环境。该网络通过Inception模块并行处理不同尺度的卷积与池化在控制计算开销的同时增强特征提取能力适合对实时性有一定要求的检测场景。压缩包共12个文件其中两个Python脚本负责训练与推理pb与bin为模型权重xml描述网络结构mov为检测演示视频mapping提供类别映射整体大小约127.02MB。资源围绕危险物品数据集预处理、多标签分类建模、迁移学习微调以及结合SSD/YOLO实现目标定位等关键环节展开代码可直接参考预训练权重能有效降低重复训练成本实验者既可用自有数据重新训练也可加载现成模型进行推理测试。已有187人学习适合希望掌握深度学习目标识别方法并在安防领域动手实践的读者也可用于课堂实验、毕业设计或项目原型复用。1. 用 GoogLeNet 做危险物品检测为什么说这是安检场景最稳的起点在安检机屏幕前盯一天图人眼会疲劳但卷积神经网络不会。危险物品检测要解决的核心问题是在 X 光透视图像里把刀具、枪支、打火机、易燃易爆液体这类目标从背景里框出来。而 GoogLeNet 作为 2014 年 ImageNet 冠军它的 Inception 结构天生适合这种多尺度目标混杂的场景——安检图像里匕首和手枪可能只占几十个像素而压力罐却铺满半个视野普通网络需要很深才能覆盖这种尺度差异GoogLeNet 则通过不同尺寸卷积核的并行拼接在同一个 block 里就把多尺度特征提取了。我最初接触这个方向是因为手头有一个 X 光安检图像识别的项目数据量不大、标注质量参差用当时流行的 ResNet 和 VGG 试过VGG 实在太吃显存ResNet 在少数类别上反复过拟合。后来换到 GoogLeNet 做特征提取主干配合简单的检测头效果反而稳住了。如果你正打算入坑目标检测、手里又只有普通消费级显卡GoogLeNet 是个被低估的好起点——它的模型参数只有 VGG16 的六分之一左右推理速度在 CPU 上都能跑到每秒十几帧做原型验证非常合适。这篇文章不会去讲复杂的数学推导我会从数据集准备、模型结构选型、PyTorch 实现、训练调参到坑位排查给你一条能完整复现的路径。所有代码都是可以直接跑的你只需要一台有 CUDA 的机器显存 4GB 以上就够。2. 危险物品检测的数据集从公开数据到自建数据的四个关键处理2.1 数据从哪来先用公开数据集验证流程再考虑自建做危险物品检测第一个拦路虎往往不是模型而是数据。安检 X 光图像属于敏感数据公开的完整数据集并不多常见的选择有 SIXray 数据集和 OPIXray 数据集前者包含 100 万张以上的安检图像但类别不平衡严重后者是刀片类物品的专门数据集标注质量相对干净。这些数据集可以从学术网站直接下载你需要确认一下下载的文件是图像加 XML 标注Pascal VOC 格式还是 JSON 标注COCO 格式这决定了后面数据加载代码的写法。我的建议是先用 SIXray 的一个子集比如只取刀具、枪支、打火机三类每类几千张把整个训练流程跑通再决定要不要投入人力去标注自己的业务数据。因为自建数据集最耗时的不是拍照或者采集而是标注——一张 X 光图像里可能有多个目标、目标互相遮挡、不同角度的物品看起来差异巨大这些都会把你的标注周期拉到两倍以上。2.2 把 VOC 格式转成 YOLO 格式转换脚本与四个边界坑假设你下载的数据集是 VOC 格式XML 标注而你用的检测框架比如 YOLOv5 或者自定义 PyTorch 代码需要 YOLO 的 txt 格式每行一个类别加归一化的中心点坐标和宽高那就需要一个转换脚本。看起来简单实则四个边界坑我都在实际项目中踩过。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_file, class_list, output_dir): tree ET.parse(xml_file) root tree.getroot() img_name root.find(filename).text img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) # 坑1某些数据集的size可能为0需要从图像文件读取实际尺寸 if img_w 0 or img_h 0: from PIL import Image img_path Path(xml_file).parent / img_name img Image.open(img_path) img_w, img_h img.size yolo_lines [] for obj in root.findall(object): class_name obj.find(name).text if class_name not in class_list: continue class_id class_list.index(class_name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坑2边界框坐标可能超出图像边界需要裁剪到有效范围内 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) # 坑3有些标注是退化框xmin xmax这类样本直接跳过 if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 坑4归一化后的数值可能略超[0,1]因为浮点截断误差不需要管 yolo_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if yolo_lines: out_path Path(output_dir) / (Path(xml_file).stem .txt) out_path.write_text(\n.join(yolo_lines)) # 注意没有有效目标的图像对应的txt文件不应该生成训练时跳过即可这段脚本的逻辑很简单解析 XML把目标的边界框坐标从像素值转换为相对于图像尺寸的归一化值。四个坑里最隐蔽的是第一个——有些数据集的 XML 里写的尺寸和实际图片不一致如果直接用 XML 里的尺寸做归一化会导致训练时标签和图像对不上Loss 永远降不下来。我在一个公开数据集上就遇到过300 多张图全部标签偏移跑了一晚上才发现问题。2.3 类别失衡的处理危险品检测的特殊性危险物品检测的类别失衡比一般目标检测更严重你的样本里可能 95% 都是正常物品刀具只有几万张枪支只有几千张打火机可能就几百张。直接训练模型会为了降低整体损失而把所有目标都预测成背景这就是为什么很多新手跑出来的 mAP 虚高但实际检测率极低。我一般会做三件事第一对少样本类别做在线随机增强包括旋转、缩放、色彩抖动让模型见过更多变体第二在损失函数里为每个类别设置权重类别样本越少权重越高PyTorch 里直接给BCEWithLogitsLoss传pos_weight参数就行第三最有效的还是硬采样——训练时每个 batch 强制包含至少两张含稀有类别的图像这个可以通过自定义 Sampler 实现。这个思路和行业里做安检识别的主流做法是一致的光是这个操作就能把稀有类别的召回率提升 15 个百分点以上。3. GoogLeNet 检测架构Inception 结构为什么适合安检图像3.1 多尺度感知安检图像最需要的特性GoogLeNet 的核心是 Inception 模块它把 1x1、3x3、5x5 三种卷积和一个 3x3 的最大池化并行拼接在一起然后通过 1x1 卷积降维控制计算量。这意味着在同一层网络里它能同时关注到一个很小的刀刃细节1x1 卷积和一片大范围的物体轮廓5x5 卷积。安检 X 光图像有个特点那就是目标尺度方差极大。同一张图里一把折叠刀可能只占图像面积的 1%而一个装满了液体的保温杯可能占了 20%。如果用 VGG 那种串行堆叠的卷积浅层特征图分辨率高但语义信息弱深层特征图语义强但分辨率低如果想要覆盖两个极端尺度网络就会显着加深。而 Inception 结构天然就在每个 block 里做了多尺度融合这让 GoogLeNet 在安检数据上只需要相对浅的深度就能达到不错的效果。3.2 辅助分类器训练过程中的正则化利器GoogLeNet 原版网络在中间层挂了两个辅助分类器这在训练时相当于给网络中间层也加了梯度信号防止梯度消失。对于危险物品检测这种类别不平衡比较严重的任务辅助分类器特别有用——因为它让网络在前半部分就开始学会区分前景和背景而不是等到最后的分类层才被大比例的负样本淹没。在迁移学习场景下我一般会把辅助分类器保留下来但把它们的权重初始化给到较小的值weight_init * 0.1因为预训练模型的中间特征已经很好了辅助分类器喘得太猛反而会破坏底层特征。下面这段代码展示了加载预训练 GoogLeNet 并替换分类头的过程。import torch import torch.nn as nn from torchvision.models import googlenet def build_googlenet_detector(num_classes6, pretrainedTrue): # 加载在ImageNet上预训练的GoogLeNet model googlenet(weightsIMAGENET1K_V1 if pretrained else None) # 替换最后的全连接分类层 # 原版输出1000类我们只需要识别危险物品的类别数 in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) # 辅助分类器同样需要替换保持和主分类器输出维度一致 if hasattr(model, aux1) and model.aux1 is not None: model.aux1.fc2 nn.Linear(768, num_classes) # 辅助分类器权重初始化小一点防止训练初期干扰主路径 nn.init.normal_(model.aux1.fc2.weight, std0.01) nn.init.zeros_(model.aux1.fc2.bias) if hasattr(model, aux2) and model.aux2 is not None: model.aux2.fc2 nn.Linear(768, num_classes) nn.init.normal_(model.aux2.fc2.weight, std0.01) nn.init.zeros_(model.aux2.fc2.bias) # 如果是检测任务需要在分类头之上加一层简单的回归头用于输出边界框 # 这里以SSD-like的方式加两个卷积层作为检测头 model.detection_head nn.Sequential( nn.Conv2d(1024, 256, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(256, num_classes * 4, kernel_size3, padding1) # 每个类4个坐标 ) return model这段代码的核心在于两点第一全连接层和辅助分类器的输出维度必须改成你的类别数否则加载权重时维度对不上第二我在这里额外加了一个detection_head因为 GoogLeNet 本身是分类网络要做检测就需要在某个特征图上挂回归头。如果你用的是 Faster R-CNN 或者 YOLO 这类框架GoogLeNet 一般作为 backbone 替换进去不需要手动加这一层但如果你是想快速验证模型在危险品数据上的特征提取能力用这种直接加回归头的方式最省事。3.3 预训练权重的迁移策略冻结多少层是个问题用 ImageNet 预训练的 GoogLeNet 做危险品检测本质上是做迁移学习。但安检 X 光图像和自然图像有本质区别——X 光图是灰度透视的物体边缘清晰但没有颜色和纹理信息。所以我会建议冻结前两层的权重它们主要提取边缘、颜色等底层特征硬件相关性还能用从第三层开始微调这样可以防止预训练的特征被强干扰。实际操作上我在训练前会先跑一次推理把检测头的 Loss 打印出来看看初始水平。如果初始 Loss 是正常量级比如在 1~3 之间说明网络前向传播没有问题如果 Loss 直接是几十上百那多半是归一化出了问题——X 光图像往往被直接以 0~255 喂进去没有做 ImageNet 的标准化。注意GoogLeNet 预训练权重对应的是特定均值和标准差你不做标准化等于让网络看到了一种它从没见过的新分布收敛慢不说还可能完全训不动。4. 训练流程实战用 PyTorch 从零跑通一个可用的危险品检测器4.1 数据加载针对安检图像的预处理策略咱们把 GoogLeNet 作为检测器主干、在这个基础上做训练落地。数据加载这部分我用 PyTorch 的 Dataset 和 DataLoader 来实现。安检图像的预处理有几个要点灰度图要转成三通道把单通道复制三遍做随机翻转和随机裁剪增强但裁剪的时候要小心——目标可能正好在裁剪区域外面这就相当于把标注也裁掉了。import torch from torch.utils.data import Dataset, DataLoader, Sampler from PIL import Image import numpy as np import random class DangerDataset(Dataset): def __init__(self, img_dir, label_dir, class_list, is_trainTrue): self.img_paths sorted(list(Path(img_dir).glob(*.png))) self.label_dir label_dir self.class_list class_list self.is_train is_train # ImageNet预训练权重对应的标准化参数 self.mean torch.tensor([0.485, 0.456, 0.406]) self.std torch.tensor([0.229, 0.224, 0.225]) def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img_path self.img_paths[idx] img Image.open(img_path).convert(RGB) # 灰度转RGB三通道 label_path Path(self.label_dir) / (img_path.stem .txt) # 读取YOLO格式的标签每行 [class_id, x_center, y_center, w, h] boxes [] if label_path.exists(): for line in label_path.read_text().strip().splitlines(): parts line.split() class_id int(parts[0]) xc, yc, w, h map(float, parts[1:5]) boxes.append([class_id, xc, yc, w, h]) boxes np.array(boxes, dtypenp.float32) if boxes else np.zeros((0, 5)) # 训练时做增强推理时保持原图尺寸不变 if self.is_train and len(boxes) 0: # 随机水平翻转X光图像翻转后语义也成立 if random.random() 0.5: img img.transpose(Image.FLIP_LEFT_RIGHT) boxes[:, 1] 1.0 - boxes[:, 1] # x_center翻转 img img.resize((224, 224)) # GoogLeNet的标准输入尺寸 boxes[:, 1] * 224 boxes[:, 2] * 224 boxes[:, 3] * 224 boxes[:, 4] * 224 img torch.tensor(np.array(img), dtypetorch.float32) / 255.0 img img.permute(2, 0, 1) # HWC转CHW img (img - self.mean[:, None, None]) / self.std[:, None, None] return img, torch.tensor(boxes)这里有个重要参数输入尺寸统一缩放到 224 而不是更常见的 416 或 640因为 GoogLeNet 的设计输入就是 224x224。为检测任务用 ssd 风格头在低分辨率上也能出效果但如果你想检测体积特别小的物品比如 10 像素的刀尖224 分辨率可能不够建议把输入尺寸提到 320 或者 448代价是训练显存会翻倍。我一般先 224 跑通流程再用 320 微调。4.2 损失函数与优化器分类和回归一起优化的坑危险物品检测一般用多任务损失分类损失判断每个候选区域有没有物体、是什么类别 回归损失把边界框坐标回归准。如果直接用 SSD 的思路分类用交叉熵回归用 Smooth L1 Loss两个 Loss 直接相加。这里有个细节两个 Loss 的量级可能差 10 倍以上如果回归 Loss 太大模型会只顾着框住目标而不管框得是什么——导致检测率还行但误报率暴涨。我比较常用的做法是给回归 Loss 加一个 0.5 的权重系数然后把分类 Loss 按类别频率加权。还有一点要留意在如何设置pos_weight的问题上机场安检常见的 6 类危险品比少样本类别占比差异很大直接给稀有类别加权重可能导致 Loss 剧烈震荡。import torch.nn.functional as F def danger_loss(cls_pred, cls_target, box_pred, box_target, pos_weight, reg_weight0.5): cls_pred: [N, num_classes] 分类预测 cls_target: [N] 分类标签-1表示背景区域不需要参与损失 box_pred: [N, 4] 边界框回归预测 box_target: [N, 4] 边界框回归目标 pos_weight: [num_classes] 每个类别的正样本权重 # 背景样本参与分类损失但不参与回归损失 valid_mask cls_target 0 positive_mask cls_target 0 cls_loss F.cross_entropy( cls_pred[valid_mask], cls_target[valid_mask], weightpos_weight ) # 只有真正有目标的样本才计算回归损失 reg_loss F.smooth_l1_loss( box_pred[positive_mask], box_target[positive_mask], reductionsum ) / max(1, positive_mask.sum()) return cls_loss reg_weight * reg_loss参数说明pos_weight需要根据你的训练集统计每个类别的样本占比我一般按max(count) / count来计算。reg_weight是回归损失的权重系数调大这个值会让模型更侧重于框得准但分类准确性会下降。初始化时用 0.5 基本不会错等跑完一个 epoch 之后再根据验证集的 mAP 决定往哪个方向微调。4.3 训练循环学习率调度和早停的落地写法训练循环本身不复杂但有两个容易被忽略的点。第一GoogLeNet 的辅助分类器也会产生 Loss训练时要把辅助 Loss 加进总 Loss一般权重是 0.3在推理时辅助分类器可以直接去掉不影响预测结果。第二学习率调度采用余弦退火而不是固定步长衰减对危险品这种不平衡数据更友好。def train_one_epoch(model, dataloader, optimizer, scheduler, device): model.train() total_loss 0.0 for batch_idx, (images, targets) in enumerate(dataloader): images images.to(device) targets targets.to(device) # GoogLeNet前向传播返回主输出和两个辅助输出 if model.training: logits, aux1_logits, aux2_logits model(images) else: logits model(images) # 主分类loss 回归loss这里简化处理实际需要把每个anchor对应的预测取出来 main_loss compute_detection_loss(logits, targets) # 辅助loss作为正则项权重0.3 aux_loss 0.3 * (F.cross_entropy(aux1_logits, target_classes, reductionmean) F.cross_entropy(aux2_logits, target_classes, reductionmean)) loss main_loss aux_loss optimizer.zero_grad() loss.backward() # 梯度裁剪防止某些极端样本把权重拉飞 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm10.0) optimizer.step() scheduler.step() # 余弦退火每个step更新一次 total_loss loss.item() if batch_idx % 50 0: print(fBatch {batch_idx}, Loss: {loss.item():.4f}) return total_loss / len(dataloader)这段代码里compute_detection_loss是上一节定义的损失函数实际使用时你需要根据你的检测头结构把匹配好的正负样本索引传进去。scheduler.step()放在了每个 batch 后面这是余弦退火的标准用法——如果想用ReduceLROnPlateau则要等每个 epoch 结束拿验证集 Loss 来更新。梯度裁剪很重要因为 X 光图像偶尔会出现极亮或极暗的坏像素这些像素产生的梯度可能让权重瞬间飞掉裁剪到 10 基本能挡住这种意外。4.4 验证指标为什么 mAP 高不等于能落地模型训练完你最容易犯的错误是只看验证集 mAP 就宣布成功。危险物品检测的业务核心是召回率——你漏掉一个刀具后果比误报十次严重得多。所以我建议关注三个指标AP0.5IoU 阈值为 0.5 时的平均精度、召回率尤其是稀有类别、以及误报率放在机场场景里就是开包复核率。合理的目标是 AP0.5 达到 0.85 以上稀有类别召回率不低于 0.7误报控制在每百张图不超过一次。推理的时候还有一个小技巧把图像的亮度归一化到 ImageNet 预训练模型见过的分布。X 光图像本质是灰度图像素值集中在暗部如果直接缩放到 [0,1] 会导致 GoogLeNet 的 BatchNorm 统计量失配。用normalize操作把均值拉到 0.5 附近、标准差拉到 0.3 附近检测效果立竿见影。很多调参调不动的情况其实都是数据预处理的问题。5. 训练结果不好先别改模型常见问题与排查清单5.1 目标在图像里太小根本检测不到现象验证集上 AP 看起来还行但实际跑业务图时小目标比如小于 5% 图像面积的打火机几乎全部漏检。原因GoogLeNet 输入分辨率是 224对于中小目标来说下采样四次之后特征图只有 7x7小目标对应的特征几乎被池化吃掉了。解决两条路。第一条是提高输入分辨率到 320 或 448特征图上小目标占的像素就多了第二条是使用 FPN特征金字塔结构把高分辨率的浅层特征和低分辨率的深层特征融合后再做检测。我在实际项目里一般两者都做先用 320 分辨率微调一轮然后加一层 FPN 融合 P3、P4 两层特征。注意直接加 FPN 会让训练时间增加约三分之一但小目标召回率能提升 10 个点以上。5.2 训练 Loss 降不下去数据标注有错位现象训练 10 个 epoch 后 Loss 仍然在 2~4 之间震荡下降曲线几乎是平的偶尔还会突然跳高。原因十有八九是标注和图像对不上。我遇到过三次这种问题原因分别是XML 里图片尺寸和实际尺寸不一致导致归一化坐标错位、文件名排序不一致导致训练图和标签配对错误、有人在标注时把类别 ID 搞反。解决写一段可视化脚本把标注框画在图像上保存成图片随机抽 50 张图人工过一遍。我当时发现问题的方式是发现「刀具的标签框总是在刀尖偏移 1/4 个身位」——全是系统性的偏移修正转换脚本后 Loss 一个 epoch 就降到了 1.5 以下。注意这种排查要在训练之前做等训练完再发现就是在浪费电费。5.3 稀有类别全部被识别成背景正样本权重没生效现象训练结束后常规类别刀具、手机的识别效果还行但打火机、手枪的召回率接近于 0输出类别里几乎看不到它们。原因类别权重设置不正确。PyTorch 的CrossEntropyLoss的weight参数接受的是一个一维张量维度和类别数一致但很多人把权重按「样本总数的反比」设置导致稀有类别权重过大模型开始把所有目标都误判成稀有类别然后又因为稀有类别本身样本太少梯度不稳定最后模型干脆学成一个「啥也检测不到但 Loss 不高」的保守状态。解决把权重上限限制在 10 倍以内即任意两个类别的权重比不超过 10。另一个更稳妥的方案是采样法——构造 DataLoader 时用WeightedRandomSampler让每个 batch 里稀有类别的样本数量不低于 batch size 的 30%。这个方法不需要改 Loss实现的改动也小效果比单纯加权要稳定得多。5.4 训练时显存溢出OOM现象batch size 设为 32一进训练循环就报 CUDA out of memory。原因GoogLeNet 整体不算大但如果你输入分辨率提到了 448且 batch size 没降显存确实会爆。另外很多人不知道.train()模式会保留所有中间激活值用于反向传播这比推理模式占显存多得多。解决先把 batch size 减半如果还爆就再减半同时把输入分辨率暂时降到 224 跑通流程。还有一个常用技巧是开启torch.cuda.amp混合精度训练显存占用能减少一半。我自己的训练配置是输入 320p、batch size 16、混合精度在 8GB 显存的 RTX 2070 上刚好跑得动。如果你是 11GB 显存的卡完全不用为显存焦虑。5.5 CPU 推理慢得离谱没法上线现象训练完导出模型在 CPU 上做推理一张 224x224 的图要跑 220 毫秒远远达不到安检机实时检测的需求。原因直接在 PyTorch 的 eval 模式下做推理太慢了因为每个卷积都被框架动态调度没有针对 CPU 做算子融合。很多人在这一步就放弃了实际上这纯粹是优化策略的问题。解决按顺序做三步第一步自动用model.eval()torch.no_grad()去掉 Dropout 和 BatchNorm 的训练路径第二步用torch.jit.trace把模型序列化让算子可以融合第三步如果还慢转成 ONNX 再用 ONNX Runtime 加载一般能获得两到三倍的加速。如果最终目标是边缘端Jetson 或 RK3588直接导出 ONNX 是更省事的路——它和 TensorRT、RKNN 这些部署工具链的兼容性比 PyTorch 原生模型要好得多。6. 从上线的角度重新审视先固定验证集再折腾训练参数训练到这你会发现一个事真正能让你少加班的做法是在动手训练之前就固定一个你有信心的验证集。我会在数据准备阶段就从全量数据里挖出 15% 的样本单独放在一个文件夹里永远不参与训练。这个验证集要覆盖所有类别、所有物品摆放角度、所有常见遮挡情况并且包含一部分「人眼都要仔细看才能发现」的困难样本。原因很简单训练过程中你一定会频繁调整学习率、Loss 权重、增强策略如果没有一个稳定的验证集你根本判断不了改动到底是变好了还是变坏了。更进一步的建议是建立「难例回灌」机制。每次训练完把验证集上预测置信度介于 0.3~0.7 之间这就是“判断不动”的样本的图全部收集起来和训练集混合然后开始下一轮微调。跑上三轮回灌你会发现模型对高难度样本的判别能力有明显提升。这个说白了就是 Hard Example Mining 的工程化版本不需要额外写复杂的采样逻辑只需要把筛选出来的图复制到训练目录再重启训练就行。在模型选型和部署层面GoogLeNet 作为检测主干确实带不来当前最顶尖的精度——如果你是工业级项目、有几十万张高质量标注数据Cascade R-CNN 配上 ResNet101 会更强。但如果你只有几千张、几万张图没有专门的算法团队在有限的算力和标注资源下做危险物品检测GoogLeNet 这条路能让你用最少的成本拿到一个可以拿给客户演示、可以部署到边缘盒子的结果。最后分享一个我的习惯每次跑完一轮训练第一件事不是看 mAP而是打开三张代表图——一张在正常场景下、一张在极端曝光下、一张目标严重重叠用肉眼看看模型的输出框稳不稳定。这项工作是我从一个老同事那里学来的血泪经验当时我们推上线一个检测模型mAP 比旧版高了 8 个百分点结果现场一堆误报被迫回滚。定量指标很重要但视觉直觉才是决定模型能不能真上线的最后一道关口。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取