简介:一套专门用于打电话行为检测的Pascal VOC格式数据集,面向需要使用YOLO等目标检测模型进行训练与评估的研究者、算法工程师。数据集共包含5364张jpg图片、5364份xml标注和1份txt使用说明,合计10729个文件,压缩包约414MB,图片与xml同名一一对应,能直接接入常规VOC格式训练管线,省去格式转换和筛选时间;标签文件采用标准xml结构,便于二次处理与统计。标注由labelImg完成,类别为phone与head,分别有5045个和415个矩形框。标注规则经过严格设计:手机贴近耳朵通话时,将手机、头部(或耳朵)及手部合并成一个框,以捕捉完整打电话动作,同时借助head类别辅助抑制误检。相比网上很多把玩手机也算作打电话的粗放标注,这套数据明确区分“打电话”与“玩手机”,更贴近监控、驾驶等真实落地场景。目前已有1600人浏览学习,适合作为行为识别、目标检测等项目的训练集或评测集。
1. 5364张VOC格式打电话检测数据集:先确认它适合谁、能干什么
监控安防和车载安全场景里,打电话检测是识别模型的刚性需求。这套5364张的VOC格式打电话检测数据集,涵盖手持接听、贴近耳旁、车内驾驶员、办公区域等多种姿态,对YOLO系检测器来说正好卡在“够用又不冗余”的区间。VOC格式保留了object级别的bbox框、类别名、截断与难例标记,既能当迁移学习的底座,也能转成YOLO训练要的txt标注再继续补数据。
用途建议先框成三个:手头没有干净标注集、想练熟YOLO训练流程的初学者,拿来即用,省下最枯燥的标注时间;做边缘盒子和摄像头端算法验证的工程师,先确认模型在这个数据分布上能收敛,再收集自己现场的图片做增量训练;需要快速出演示效果的售前或方案岗位,用这套数据跑一个可展示的检测demo,比从零标注快太多。
有一点要提前说清楚:这套数据不是VOC 20类,而是“电话使用”单类目标,转换标签时不要套用PASCAL VOC的20类映射表。目录结构倒是规范的VOC布局——JPEGImages存图片、Annotations存XML标注、ImageSets/Main放划分文件,但图片尺寸不统一,有1920×1080的监控大图,也有640×480的中等画面,这个变量在训练时要专门处理。下面直接从格式转换开始,按我实际跑通这条链路的方式来写。
2. VOC转成YOLO能直接用的txt标注:解析XML到归一化坐标的完整脚本
VOC格式和YOLO训练格式之间最直接的差别就在标注文件上。VOC的标注是XML,每个目标对应一个<object>节点,里面写类别名、截断标志、难例标志和一个<bndbox>四角坐标;YOLO训练的标注则是和图片同名的txt文件,每行五个数字:class_id x_center y_center width height,坐标全部归一化到0到1之间。
直接把XML文件夹路径塞给YOLO训练是不行的,第一步必须是写转换脚本。但我不建议照抄网上的通用脚本跑完就进训练,先把标注内容打开看一遍,确认干净再转换。这样坐标越界、标签名拼写不一致、空标注这类问题能提前被拦下来,而不是等到loss曲线变成一条直线时才回头查数据。
2.1 VOC标注里最容易忽略的字段:size、difficult与name拼写
先打开一个典型XML看看结构。下面这段是数据集中某张驾驶场景图片的标注节选,文件字段和这套数据完全一致:
<annotation> <folder>JPEGImages</folder> <filename>driver_phone_00235.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>phone_call</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>732</xmin> <ymin>215</ymin> <xmax>826</xmax> <ymax>466</ymax> </bndbox> </object> </annotation>三个字段必须单独提醒。
<size>块里的width和height,是整张图像的像素尺寸,不是标注框的尺寸。所有归一化计算都要拿它当分母。一旦有人误用了标注框自身的宽高做分母,生成的txt坐标会整体错位,训练出来的模型预测框全部漂在目标旁边。转换脚本里应当从XML的<size>块读取尺寸,不要依赖外部传入的宽高参数。
<truncated>和<difficult>表示目标在画面边缘被截断,或者小到难以辨认。打电话检测里很常见的情况是手机被方向盘挡了一半,或驾驶员侧过身只露出半个屏幕,这类样本如果difficult=1,我默认先过滤掉,单独留一个文件夹做困难样本挖掘。不过滤的话,模型会把“半截手机”学成强特征,误检率会明显上升。
<name>的拼写一致性也要查。真实标注过程里,同一个类别在不同XML里可能被写成phone_call和phonecall,甚至中英文混用。转换前对所有XML的name字段做一次去重统计,能自动暴露这类问题。
2.2 用Python脚本把VOC的XML转成YOLO的txt
转换逻辑的核心是四个换算:把xmin和xmax换算成中心点x_center,ymin和ymax换算成中心点y_center,再整体除以图像宽高完成归一化。下面是完整可跑的脚本。
import os import glob import xml.etree.ElementTree as ET # 类别映射表:VOC标注里的类名 -> YOLO类别ID # 这个数据集只有 phone_call 一个正类,ID从0开始 CLASS_MAP = {"phone_call": 0} def voc_to_yolo(xml_path, output_dir): tree = ET.parse(xml_path) root = tree.getroot() # 图像尺寸从XML的<size>块读取,不要靠外部传入 size = root.find("size") width = int(size.find("width").text) height = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in CLASS_MAP: # 遇到映射表里没有的类就跳过,不报错,方便后续扩展多类 continue difficult = 0 if obj.find("difficult") is not None: difficult = int(obj.find("difficult").text) if difficult == 1: continue # 难例单独留作困难样本挖掘,不进入常规训练 bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) # 归一化中心点与宽高,分母是整图尺寸 x_center = ((xmin + xmax) / 2) / width y_center = ((ymin + ymax) / 2) / height box_w = (xmax - xmin) / width box_h = (ymax - ymin) / height lines.append( f"{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}" ) txt_name = os.path.basename(xml_path).replace(".xml", ".txt") with open(os.path.join(output_dir, txt_name), "w", encoding="utf-8") as f: f.write("\n".join(lines)) if __name__ == "__main__": xml_dir = "Annotations" txt_dir = "labels_for_yolo" os.makedirs(txt_dir, exist_ok=True) for xml_file in glob.glob(os.path.join(xml_dir, "*.xml")): voc_to_yolo(xml_file, txt_dir)脚本运行结果是把每张图片的标注写进同名txt,没有有效标注的图片会生成空文件。CLASS_MAP目前只把phone_call映射到0;如果这套数据以后要合并进头盔佩戴、分心驾驶等多类检测,往字典里继续加键值就行。difficult=1在这里直接跳过,后面做困难样本挖掘时可以把它们单独导出、单独训练。
写完后务必抽查:随机取十张图,用OpenCV读出实际宽高,再对照txt里的归一化坐标乘以宽高,还原出的整数坐标和XML里bndbox是否能对上。多数坐标漂移问题在这一步就能抓出来。
2.3 数据划分:按采集片段切train/val/test,而不是随机切
txt转换完成后的下一步是划分三个集合。很多教程只教随机打乱,但在打电话检测这种场景下,随机切分等于给验证集喂答案。
这套数据里有相当一部分图片是从监控视频里逐帧抽取的。同一段十几秒的视频,相邻帧之间可能只有手臂抬高几个像素的差异。如果随机切分,同一视频的帧会同时出现在train和val里,模型等于提前见过验证画面的构图,mAP虚高。等模型上了真实摄像头画面,漏检和误检立刻打回原形。我习惯按采集片段分组,整段划进同一个集合。
import random from collections import defaultdict from pathlib import Path image_dir = Path("images") train_ratio, val_ratio = 0.8, 0.15 # 按文件名前缀聚类,同一采集片段不跨集合 groups = defaultdict(list) for img in sorted(image_dir.glob("*.jpg")): stem = img.name.split("_")[0] # driver_phone_00235.jpg -> driver groups[stem].append(img.stem) all_stems = [] for imgs in groups.values(): all_stems.extend(imgs) random.seed(42) random.shuffle(all_stems) total = len(all_stems) train_set = set(all_stems[:int(total * train_ratio)]) val_set = set(all_stems[int(total * train_ratio):int(total * (train_ratio + val_ratio))]) test_set = set(all_stems[int(total * (train_ratio + val_ratio)):]) lists = { "train": train_set, "val": val_set, "test": test_set, } for name, stems in lists.items(): with open(f"{name}.txt", "w") as f: for stem in sorted(stems): f.write(f"{image_dir / (stem + '.jpg')}\n")脚本按文件名首个下划线前的字段聚类,比如driver_、office_、street_,把同一段采集来源的视频帧捆成一组再切分。random.seed(42)保证不同机器上划分结果一致。test_set先留在那里不用,等第一版模型训完后做最终验收,不要过早消费它。
这样分组的前提是文件名前缀能代表采集片段。如果某个前缀混入了不同批次的图片,分组就不准。折中方案是按“前缀 + 图片拍摄时间戳”双重聚类,脚本会多几十行,但更稳。
2.4 转换与划分后的文件清单核对
进训练之前花两分钟核对一遍,省得训练一个小时后才发现数据路径是错的。核对项整理成下表:
| 核对项 | 命令 | 预期结果 |
|---|---|---|
| 标签与图片数量一致 | ls labels_for_yolo | wc -l和ls images/*.jpg | wc -l | 两边数量相等 |
| 空标签文件数量 | find labels_for_yolo -name "*.txt" -empty | wc -l | 允许少量,但要知道是哪些 |
| 坐标范围是否越界 | awk '{if ($3<0 || $3>1) print}' labels_for_yolo/*.txt | 无输出 |
| train与val是否有交集 | comm -12 train.txt val.txt | 无输出 |
空txt文件通常就是difficult过滤太狠导致的,这类图片训练时会被YOLO跳过,样本会少几个,不影响流程但要知道原因。坐标越界的检查建议直接在转换脚本里做,发现越界就打印对应的XML文件名,回头去查标注源。
到这里,VOC标注已经变成YOLO能直接吃的txt,train/val/test按采集片段隔离开,路径清单也核对过了。下一步是真正把训练跑起来。
3. 用5364张图跑通YOLOv8训练:data.yaml、训练命令与收敛判断
3.1 先把环境和data.yaml配好:相对路径、类别数、图片目录
环境这边,Ultralytics YOLOv8依赖Python 3.8以上和PyTorch 1.8以上,有NVIDIA显卡就装CUDA版PyTorch。我一般在conda环境里一次性装好:
conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics torch torchvision如果只用CPU训练,这套5364张图也不是不能跑,但一个epoch要好几分钟,一百个epoch下来时间成本太高。我建议至少有一张8GB显存的卡,哪怕是老款的GTX 1080Ti都行,训练速度能快出一个数量级,而且后面调小目标分辨率时显存大是硬道理。
数据配置文件是训练的第一步。在数据集根目录放一个phone_call.yaml:
# 数据集根路径,建议写绝对路径,避免yolo进程的工作目录不同导致图片找不到 path: /home/user/phone_call_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: phone_calltrain和val字段写的是图片目录,Ultralytics会到对应目录旁找同名的txt标签。如果你用的是上一节生成的txt列表文件,也可以直接写train: train.txt,效果一样。nc: 1和names里的ID必须和转换脚本里的CLASS_MAP对齐,一旦这里写成两个类,模型输出维度就多一维,训练会变慢且类别编号对不上。写错了会在第一个epoch报维度不匹配,即使没报错,后面mAP也是乱的。
3.2 训练命令逐条拆解:从模型选型到超参设置
我常用的一条训练命令长这样:
yolo detect train \ data=phone_call.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=1280 \ batch=8 \ lr0=0.01 \ cos_lr=True \ patience=30 \ cache=True \ project=runs/detect \ name=phone_call_v1逐条说明参数含义。
model=yolov8n.pt会加载YOLOv8n在COCO上的预训练权重做迁移学习起点。显存紧就保持nano不变,12GB以上显存可以换yolov8s.pt或yolov8m.pt,精度会涨但训练时间翻倍。打电话检测属于目标小、数量少的任务,nano在1280分辨率下也能跑出不错的mAP,没有必要一上来就上最大模型。
epochs=100配patience=30,意思是连续30个epoch验证指标没刷新就早停,不会傻跑满100轮。单类数据集的mAP50-95波动比多类大,patience设太小容易误杀训练,所以我不建议用默认的15。
imgsz这里直接设了1280而不是640,原因在下一章专门讲,核心是手机目标太小。8GB显存下1280配batch=8是极限,再往上就会OOM。
lr0=0.01是初始学习率,对加载预训练权重的迁移学习来说常规;如果是从零训练,我一般降到0.001。cos_lr=True开启余弦退火,让学习率在训练后期平滑降低,小数据集上收敛更稳。
cache=True把图片提前缓存进内存。5364张图大概占几百MB,内存够就开着,能省掉大量磁盘IO等待时间。
另外提一个容易被忽略的参数:Mosaic数据增强。YOLOv8默认开启,把四张图拼成一张训练。如果训练时发现目标被缩得太小、模型老漏检,不要把Mosaic整个关掉,而是调成mosaic=0.5,让拼接图和原图各占一半,降低尺度畸变。
3.3 损失函数曲线怎么看:DFL、CIoU与分类损失,别只看一个数
YOLOv8的损失由三部分组成:分类的BCE损失、回归的CIoU损失,以及DFL分布聚焦损失。Ultralytics训练时会打印box_loss、cls_loss、dfl_loss三列,很多人只盯着总loss看,这是不对的。
我的观察方法是拆开看。第一,box_loss和dfl_loss如果平稳下降,说明框的位置在学对。第二,cls_loss在单类数据集上通常降得最快,因为只有“电话”和“背景”两个选择,分类本身简单。第三,如果三个loss都在降但val的mAP不涨,问题基本不在损失函数,而是验证集和训练集的图片分布差异太大,回头检查第2章的采集片段分组切分。
训练结束后重点看mAP50和mAP50-95两个指标。mAP50是IoU阈值取0.5时的平均精度,只关心“大致框中了没有”;mAP50-95是从0.5到0.95每隔0.05取一次IoU再平均,对框的精确度要求高得多。打电话检测这类告警场景,我以mAP50为主,因为系统更关心目标是否出现,框精不精确可以靠后处理修正。mAP50在0.85以上、mAP50-95在0.5以上,模型就可以进入验证阶段。
补充一个早停细节。patience对应的验证指标默认是mAP50-95,这个指标在单类小目标上的波动比mAP50大,很容易触发“看似不涨、其实在涨”的误杀。除了把patience调大,训练命令里加save_period=10定期存checkpoint,防止中间断了没存下模型。Ultralytics每个epoch结束都会保存last.pt,best.pt会自动跟踪最优权重,中断续训用resume=True加载last.pt就行。
训练结束后,Ultralytics会在runs/detect/phone_call_v1/下自动生成混淆矩阵和PR曲线。第一次训完如果发现PR曲线在低召回率时就掉头,先别急着调参,回去看训练集里有没有大量模糊标注——大部分模型不收敛的情况,根因在数据质量而不是网络结构。这个在避坑章节会继续说。
4. 打电话场景的三个调参点:小目标、单目标与迁移学习
第3章把流程跑通了,但基线mAP50可能只有0.6到0.7。这套数据想在指标上拉到0.9以上,有三个点必须处理:小目标怎么应对、单类任务怎么压制背景误检、迁移学习怎么冻结层才不浪费预训练权重。
4.1 手机在画面里只有几十像素:先统计bbox尺寸再决定分辨率
训练分辨率怎么定,不是拍脑袋,先把标注框尺寸分布统计出来。用转换好的txt做一个快速统计:
import glob import numpy as np widths, heights = [], [] for txt in glob.glob("labels_for_yolo/*.txt"): with open(txt) as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue # 第4、5列是归一化宽高,这里按常见监控分辨率1920x1080还原成像素 widths.append(float(parts[3]) * 1920) heights.append(float(parts[4]) * 1080) widths = np.array(widths) heights = np.array(heights) print("中位宽:", np.median(widths), "90分位宽:", np.percentile(widths, 90)) print("中位高:", np.median(heights), "90分位高:", np.percentile(heights, 90))打电话检测场景里,驾驶监控1920×1080画幅中,手部区域往往只有100×100像素;公交车前门摄像头拍到的乘客,手机可能只有40×60像素。40×60的目标在640×640输入下被压缩成13×20,经过YOLOv8的五次下采样,特征图上只剩一两个像素点,检测基本靠蒙。这也是很多人在这个数据集上把imgsz调到1280后mAP50突然涨五六个点的原因。
不过imgsz=1280的显存开销不能忽略。我的经验:8GB显存用imgsz=960、batch=8;12GB以上用1280、batch=8。训练时间大约翻倍,但对小目标的收益非常直接。
还有一个辅助手段是调整Mosaic比例。Mosaic把四张图拼接后再缩放,小目标被缩得更小,容易让模型学到错误的尺度感。把mosaic=0.5试一下,保留一半原图不做拼接,模型对小目标的尺度感知会稳很多。
4.2 单类别训练时的背景误检压制
单类任务看起来简单,实际翻车点集中在背景误检。负样本没有显式标注,模型只能靠“图里没有标注框”来隐式学习背景。当背景里出现和手机颜色、纹理接近的物品时,比如深色对讲机、方向盘上的按键、白色茶杯,模型会持续输出高置信度框。
我一般从两个方向压制。第一是往训练集里补负样本:单独建一个background/目录,放两百张不含打电话行为的监控截图,对应txt留空。空标签文件不会产生反向传播信号,但图片本身参与了背景分布的学习,误检能压下来不少。第二是推理阶段的阈值策略:训练时无所谓,部署时把conf=0.3提到conf=0.5,宁可漏几个小目标,也不要一个画面里冒三四个假框。
压完背景误检后,一定要回头看一眼PR曲线尾部。如果召回率到0.9附近精度掉得很猛,说明大量低置信度预测是误检。这时候加负样本比调阈值更根本。
4.3 迁移学习:冻结层与学习率策略
这套数据质量整体在线,但只有5364张,不算大。迁移学习的策略直接决定训练效率。我习惯第一版加载yolov8n.pt后,先冻结backbone跑20个epoch,让neck和head先适应单类任务,再解冻所有层,把学习率降到之前的十分之一,继续跑完剩余epoch。
冻结backbone在Ultralytics里很轻量,训练命令加一个freeze=10就行:
yolo detect train \ data=phone_call.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=1280 \ batch=8 \ freeze=10 \ lr0=0.01freeze=10代表冻结前10层,也就是backbone的绝大部分,训练时这部分参数不动。冻结层不参与反向传播,单卡显存能再省一点。学习率方面,冻结阶段用lr0=0.01没问题;解冻之后如果发现val_loss震荡明显,重新加载best.pt,把lr0=0.001再续训一轮,通常能稳下来。
迁移学习还要注意一点:预训练权重和模型结构必须匹配。yolov8n.pt对应YOLOv8n结构,如果换成了yolov8s.pt但配置里还是n的yaml,加载时要么报shape不匹配,要么模型前向输出错乱却不报错。这是特别常见的新手翻车点。我在团队里要求所有训练脚本的model参数只写权重文件名,不额外改yaml,就是这个原因。
5. 训练翻车避坑:这些坑我替你踩过,按现象对号入座
5.1 坐标越界:xmax比图像宽还大,loss曲线直接飞掉
现象:训练第一个epoch还没跑完,loss出现NaN,终端里刷满坐标相关的warning。
原因:原始XML里部分xmax、ymax超出了图像尺寸,转换脚本没做裁剪,归一化坐标出现了大于1的值。YOLO在计算IoU时遇到非法框,梯度直接爆炸。另一个常见诱因是标注员把框拉到了画布外面,保存的坐标带负值。
解决:转换脚本里加一个clip操作,把所有坐标限制在图像范围内,同时把越界情况打印出来人工确认。补丁式写法如下:
xmin = max(0, float(bndbox.find("xmin").text)) ymin = max(0, float(bndbox.find("ymin").text)) xmax = min(width, float(bndbox.find("xmax").text)) ymax = min(height, float(bndbox.find("ymax").text))clip之后再判断一次,如果框宽或框高小于5像素,说明这个框已经没有训练价值,直接跳过。clip对边缘露出的半截手机会稍微压小框的大小,但比让训练彻底崩溃好得多。
5.2 batch size太小导致BN层不稳,验证集指标像锯齿
现象:loss正常下降,但每个epoch的验证mAP波动巨大,val_loss曲线像锯齿。更诡异的是同一个模型在同一张图上重复推理,两次结果不完全一致。
原因:batch size设成2甚至1时,BatchNorm层的统计量在每步之间剧烈变化。训练阶段BN的滑动均值不收敛,验证时就出现这种“玄学”抖动。8GB显存上调大imgsz后很容易被迫用小batch。
解决:优先保证batch不小于8。显存不够就先降imgsz,再不行就换nano模型,不要牺牲batch。如果4是极限,可以考虑用累积梯度模拟更大batch,等效于加大batch size但显存不变。机器显存拿不准时,Ultralytics里可以用batch=-1自动探测最大安全batch,我一般先用这个值跑一轮,再手动调小一档留余量。
5.3 验证集和训练集没隔离,mAP虚高得离谱
现象:训练集mAP50到0.92,验证集mAP50也0.88,看着非常完美。但把模型拿到现场相机帧上测试,漏检率和误检率都高得吓人。
原因:用了随机切分,同一段视频的相邻帧同时进了train和val,模型相当于背了答案。监控视频抽帧的数据尤其致命,相邻帧的构图差异太小,验证指标完全失真。
解决:重新按采集片段分组切分,严格保证同一前缀的图片全部落到同一个集合。切完以后用文件名stem做一次交集检查,确认train和val没有任何重复样本。这个检查脚本只有三行,我每次准备新数据都会跑一遍,已经成为固定动作。
5.4 预训练权重结构错位:loss不降,先别急着调学习率
现象:加载权重后前10个epoch的box_loss纹丝不动,mAP一直是0,换学习率也没用。
原因:权重文件与模型结构不匹配。比如用yolov8s.pt的权重配了nano的模型配置,某些层的shape对不上。PyTorch加载时有时不报错,但模型前向传播的输出已经错乱,训练自然不收敛。
解决:模型结构和权重来源必须统一,训练命令里model=yolov8n.pt时不要再改模型yaml。从其他框架转换过来的权重,先打印state_dict的key前缀和shape,逐层核对。另外,loss完全不动也有可能是学习率设太低,比如lr0=0.0001。区分这两类问题有个简单办法:打印第一个epoch的梯度范数,梯度范数接近0说明是学习率问题;梯度正常但loss不降,才是结构问题。
6. 验证模型不是在测试集上跑一遍:混淆矩阵、PR曲线与TensorRT导出
6.1 单类别模型的正确验证姿势
单类数据集上,mAP数字不是唯一标准。我验证模型时会单独跑一次test集,生成混淆矩阵和PR曲线,然后看三个位置:background列的误检率、召回率0.8附近精度是否掉头、1080p视频里小目标区域的目检结果。
yolo detect val \ data=phone_call.yaml \ model=runs/detect/phone_call_v1/weights/best.pt \ imgsz=1280 \ conf=0.3 \ plots=Trueplots=True会生成混淆矩阵、F1曲线和PR曲线。如果conf=0.3下背景误检偏高,部署时把阈值提到0.5。这不是调模型,是调运营策略,阈值和模型是两件事。
6.2 导出ONNX与TensorRT:量化注意与NMS处理
确认模型可用后,导出TensorRT我习惯走两条腿:先导出ONNX,再用trtexec转engine。这样中途能检查出量化问题。
yolo export model=best.pt format=onnx imgsz=1280 opset=17 trtexec --onnx=best.onnx --saveEngine=best.engine --fp16这个流程里最要注意的就是导出尺寸必须与训练一致。1280训练的模型导出时不要用640,否则特征图尺度不同,输出会整体错乱。用INT8量化时,TRT需要校准集,直接从训练集抽500张做校准表即可,但校准集的图片分布要贴近现场光照。校准集和现场环境差太远,INT8精度下降可能超过两个百分点,这在打电话检测场景里会直接造成边缘设备漏报,属于生产事故级别的问题。
我自己每次部署前都有一个固定动作:把现场的1080p视频切出十段不重复的片段,用导出的engine跑一遍,统计误检和漏检的帧编号。然后对比FP16和INT8在同一段视频上的结果,FP16掉点不明显就量产,明显就继续用FP16。这个习惯替我挡掉了好几个项目的返工,也希望帮到你。
本文还有配套的精品资源,点击获取