news 2026/10/1 17:21:40

YOLO钓鱼行为检测数据集实战:标签处理与训练避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLO钓鱼行为检测数据集实战:标签处理与训练避坑指南

简介:面向yolo系列算法研发的钓鱼与垂钓行为数据集,共含九百零二张带标签图像,适合研究人员、算法工程师以及计算机视觉学习者用于训练、验证与评估钓鱼检测模型。压缩包内共两千个文件,主要包括一百九十五张jpg原始图像、九百零二份yolo格式的txt标签、九百零二份voc格式的xml标签以及一份yaml配置文件;其中txt标签采用类别、归一化中心坐标与宽高的标准写法,xml标签与yolo标签分开保存于不同目录,使用者可根据不同训练框架灵活选用。数据集已经完成训练集与验证集的科学划分,可直接供yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流模型开展训练、验证与测试,省去自行标注与整理数据的麻烦。资源包整体大小约为三十七兆,已有三百零三人学习下载,特别适用于垂钓行为识别、水域安全监控、渔业管理等场景的快速算法验证与项目部署落地。

1. 为什么说“钓鱼行为检测”卡你的不是网络结构

拿到这个 yolo算法-钓鱼-垂钓行为数据集-902张图像带标签.zip,先别急着解压。这个标题看起来像个标准的“YOLO目标检测数据集”:902张图、带标签、zip打包,解压后理论上就能丢进yolov8训练自己的数据集。但“钓鱼/垂钓行为检测”和行人、车辆检测有一个本质差别:你要检的不是一类形状稳定的物体,而是人、鱼竿、鱼线、水面共同构成的行为场景。网上讲yolo算法讲解ppt能给你网络结构图和指标曲线,真落地时卡住你的反而是鱼竿太细、钓鱼人太远、902张图到底扛不扛得住训练。下面这些内容的目的是陪你把这902张图的每一张都榨干,从解压一路跑到部署前。

2. zip解压后先别急:902张图的目录结构与标签体检

压缩包拿到手,第一件事不是写训练脚本,而是搞清楚两件事:标签是什么格式,标注质量能不能用。这两个问题决定你后面是“直接开跑”还是“先写两小时转换脚本”。很多下载过数据集的人都踩过同一个坑:解压后以为images和labels一一对应,结果跑训练时一半图片找不到标签,程序也不报错,只是默默跳过。902张图如果少了100个标签,训练出来的模型对那类场景就是瞎的。

2.1 先看清labels是txt还是xml,这决定你要不要写转换脚本

解压这个zip包,我一般会在Linux或macOS上执行:

unzip -O gbk yolo算法-钓鱼-垂钓行为数据集-902张图像带标签.zip -d fishing_dataset

-O gbk是给Windows压缩的中文文件名解乱码用的,如果你当前系统是Windows,直接右键解压即可。解压完先不要急着打开图片,先看目录结构:

find fishing_dataset -maxdepth 2 -type d

常见的数据集布局有两种:一种是images/配labels/,另一种是JPEGImages/配Annotations/。前者大概率是YOLO格式的txt标签,后者有极大概率是Pascal VOC的xml格式。用一条命令确认标签扩展名:

from pathlib import Path root = Path("fishing_dataset") for sub in ["labels", "Annotations", "annotations"]: p = root / sub if p.exists(): exts = {f.suffix for f in p.iterdir() if f.is_file()} print(sub, exts)

这段脚本会打印每个标签目录下出现的扩展名集合。看到{'.txt'}就是YOLO格式,看到{'.xml'}就是VOC格式,看到{'.json'}则可能是COCO格式。这三种格式的后续处理差别很大:YOLO txt里面存的是归一化的class_id x_center y_center width height,VOC xml存的是像素坐标xmin ymin xmax ymax,COCO json则是一个大文件加id字段关联。网上很多现成脚本能互相转,但转换前必须知道源头格式。

提示:如果labels目录里既有txt又有xml,说明这个数据集的标签是半手工整理出来的,别直接用,先跑一遍2.2的统计脚本。

2.2 用一个小脚本统计目标数、类别与长宽比

确定标签格式后,先跑一次目标统计。902张图的数据集,标注框数量、空标签数量、小目标占比,这些数字直接决定你的训练策略。写一个简单的统计脚本:

from pathlib import Path labels_dir = Path("fishing_dataset/labels") imgs_dir = Path("fishing_dataset/images") label_files = list(labels_dir.glob("*.txt")) img_files = list(imgs_dir.glob("*")) empty_frames = 0 total_boxes = 0 tiny_boxes = 0 ratios = [] for lb in label_files: lines = lb.read_text(encoding="utf-8", errors="ignore").strip().splitlines() if not lines: empty_frames += 1 for line in lines: parts = line.split() if len(parts) != 5: continue _, _, _, w, h = float(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) total_boxes += 1 ratios.append(w / h) if w * h < 0.005: # 框面积小于图面积的0.5% tiny_boxes += 1 print(f"图片文件数: {len(img_files)}") print(f"标签文件数: {len(label_files)}") print(f"空标签数: {empty_frames}") print(f"目标总数: {total_boxes}") print(f"小目标占比: {tiny_boxes / max(1, total_boxes) * 100:.1f}%")

这段脚本会告诉你三个关键风险。第一,图片和标签文件数如果对不上,说明有漏标或空标签的图,训练时要决定是丢弃还是保留空图当负样本。第二,小目标占比如果超过30%,你后面必须考虑提升imgsz或者做滑窗切片,否则学习到的特征对远处的垂钓者毫无反应。第三,ratios里如果出现大量大于5:1或小于0.2的极端值,说明标注框里有大量细长目标——典型的鱼竿框,这类框在YOLO的anchor匹配阶段容易被当成背景。

2.3 把标注画回原图,900张图足够肉眼抽检

统计脚本只能发现数值问题,标注框到底包得准不准,必须肉眼抽检。写一个画框脚本,把YOLO格式的坐标还原成像素框:

import cv2 from pathlib import Path def draw_yolo_boxes(img_path, txt_path, class_color=(0, 0, 255)): img = cv2.imread(str(img_path)) if img is None: return None h, w = img.shape[:2] if not txt_path.exists(): return img for line in txt_path.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: continue _, xc, yc, bw, bh = map(float, parts) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), class_color, 2) return img img_path = Path("fishing_dataset/images/0001.jpg") # 换成实际文件名 txt_path = Path("fishing_dataset/labels/0001.txt") result = draw_yolo_boxes(img_path, txt_path) cv2.imwrite("check_0001.jpg", result)

画出来的图建议重点看三类样本:目标在画面边缘的、目标尺寸小于32×32像素的、有水草或栏杆遮挡的。钓鱼行为检测里最常见的问题不是框错,而是只框了鱼竿没框人,或者把整个场景连水面一起框进去。后一种框虽然面积大,但学习到的特征是“有水的地方”,部署到无水场景立刻失效。画框这一步,我建议你每类场景至少抽30张图看一眼,不要只看前10张——数据集经常按场景排序,前10张往往是同一个监控角度的连续帧,不具有代表性。

3. 处理数据集用于yolov8训练:格式统一与细长框修正

数据集如果是干净的YOLO txt,这章你只需要看3.3的数据划分;如果带的是VOC xml,恭喜你,手动活开始了。处理数据集用于yolov8训练,核心原则只有一条:标签文件必须和图像文件同名同目录映射,且坐标必须严格落在图像边界内。违反其中任意一条,训练过程都可能“翻车”——表现为loss正常下降但验证集mAP奇低,你查不到原因,只能一张张翻标签。

3.1 XML转YOLO txt:别把class_id算错

VOC xml转YOLO txt的脚本网上到处都有,但大多数人照抄时都会漏掉两步:类别去重和size校验。先写一个脚本打印xml里所有的name去重结果:

import xml.etree.ElementTree as ET from pathlib import Path xml_dir = Path("fishing_dataset/Annotations") names = set() for xml_file in xml_dir.glob("*.xml"): root = ET.parse(xml_file).getroot() for obj in root.findall("object"): names.add(obj.find("name").text) print(sorted(names))

打印出来的类别列表决定了你的cls_map怎么写。常见情况是只有fishing一个类,有时会出现person和rod分开标。这两种做法的训练目标完全不同:单类学的是“这个画面里有人钓鱼”,双类学的是“哪里有人、哪里是鱼竿”。如果你只关心垂钓行为,我建议把person和rod合并成同一个fishing类,但前提是它们的边界框都完整包住了对应目标。接着执行转换:

import xml.etree.ElementTree as ET from pathlib import Path xml_dir = Path("fishing_dataset/Annotations") txt_dir = Path("fishing_dataset/labels") txt_dir.mkdir(exist_ok=True) cls_map = {"fishing": 0, "person": 0, "rod": 0} # 合并成单类 for xml_file in xml_dir.glob("*.xml"): root = ET.parse(xml_file).getroot() size = root.find("size") im_w, im_h = int(size.find("width").text), int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in cls_map: continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = (xmin + xmax) / 2 / im_w y_center = (ymin + ymax) / 2 / im_h w = (xmax - xmin) / im_w h = (ymax - ymin) / im_h lines.append(f"{cls_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") txt_path = txt_dir / (xml_file.stem + ".txt") txt_path.write_text("\n".join(lines))

这里最关键的参数是cls_map。如果你把rod标成1、person标成0,训练时模型会拼命区分“人”和“鱼竿”,但由于鱼竿太细,这个区分任务几乎无解。合并成单类后,模型学的是“这些像素组合起来像钓鱼场景”,反而稳定。另外注意im_w和im_h取自xml里的size,如果这个值和图片真实尺寸不一致,转换出的坐标全会偏。所以转换完要抽一张图,拿2.3的画框脚本再验证一次。

3.2 修正越界框:clip前先算中心点

VOC转YOLO时最常见的坐标问题是越界。图片是600×800,标注的xmax却到了850,这类错误通常来自标注工具的缩放bug,或者标注时图片被拉伸过。YOLO格式虽然允许归一化坐标后框的中心点超出边界,但训练时目标会被剪裁,导致学习到的特征残缺。修正逻辑不是简单地把w裁到图像宽度,而是先夹住边角再重新计算中心点:

def clip_yolo_box(xc, yc, bw, bh): x1 = max(0.0, xc - bw / 2) y1 = max(0.0, yc - bh / 2) x2 = min(1.0, xc + bw / 2) y2 = min(1.0, yc + bh / 2) if x2 <= x1 or y2 <= y1: return None new_xc = (x1 + x2) / 2 new_yc = (y1 + y2) / 2 new_w = x2 - x1 new_h = y2 - y1 return new_xc, new_yc, new_w, new_h

这段代码的核心逻辑是:先把框的四个边角限制在[0,1]区间内,再反推中心点、宽和高。中心点必须从夹住后的边角计算,而不是直接夹住原始中心点,否则框会整体漂移。我见过有人写xc = min(1.0, max(0.0, xc)),这样修完后框虽然不越界了,但位置偏了半个身子,训练出来检测框总是偏向目标一侧。

另外提醒一句:钓鱼场景的鱼竿框特别容易出现一种“假越界”——鱼竿斜伸出画面,框的一个角在画面外。这种框修正后面积会缩小,但如果你用的是2.3的脚本抽查,会发现修正后的框仍然包住鱼竿的可见部分,这样就够了。不要试图让框完整包住画面外的鱼竿,那是不可能的。

3.3 按场景划分train/val,而不是纯随机

902张图的数据集,train/val划分看起来是个不起眼的步骤,但划分策略直接决定你验证集数字可信不可信。纯随机划分在大多数数据集上没问题,但垂钓监控数据有个特点:连续帧高度相似。同一监控点位、同一时段采集的图片,背景几乎一样。如果这些相似帧被同时分到train和val,模型相当于提前看到了答案,val mAP会虚高至少10个百分点。

我一般按“场景文件夹”划分,而不是按文件随机打散:

import random from pathlib import Path img_root = Path("fishing_dataset/images") scenes = {} for img in img_root.glob("*"): scene_key = img.name.split("_")[0] # 假设文件名前缀是场景编号,实际按你的命名规则改 scenes.setdefault(scene_key, []).append(img) val_ratio = 0.15 rng = random.Random(42) val_imgs = [] train_imgs = [] for scene_imgs in scenes.values(): rng.shuffle(scene_imgs) n_val = int(len(scene_imgs) * val_ratio) val_imgs.extend(scene_imgs[:n_val]) train_imgs.extend(scene_imgs[n_val:]) print(f"train: {len(train_imgs)}, val: {len(val_imgs)}")

这个脚本按文件名前缀把同一场景的图聚在一起,再从每个场景里抽15%进验证集。如果你的数据集目录本身就是按场景分文件夹的,直接把scenes换成子文件夹名。这样划分的train和val在背景分布上更接近真实部署时遇到的新场景。902张图至少要保证val里有120张以上,否则mAP的置信区间太宽,一个epoch的随机波动都能让它跳3-5个点。

划分完成后,别忘把train和val的图片分别拷到images/train和images/val,标签也同步拷贝。很多人在这一步只拷图片忘了拷标签,训练时yolov8的data.yaml里配好了路径,结果图像全找到了,标签全是空的,训练出来的模型对任何目标都输出空检测框。

4. 用yolov8训练钓鱼检测模型:最小命令与4个必调参数

图片和标签都准备好了,接下来就是正式用yolov8训练自己的数据集。这一章不讨论网络结构内部怎么算的,只看落地时要做什么。yolov8训练自己的数据集这件事,本质上是三个文件的事:一个数据配置yaml、一份命令、一套参数。把这套流程固化下来,以后换任何数据集都是同一套操作。

4.1 目录结构与一份minimal的fishing.yaml

训练前把目录整理成yolov8最容易识别的方式。我常用的结构是:

fishing_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── fishing.yaml

注意labels目录必须和images目录平级,yolov8会自动把images替换成labels去找标签。如果你把标签放在其他名字的目录里,必须在yaml里显式指定,否则它只会傻傻地去images的兄弟目录找。

fishing.yaml内容如下:

path: ./fishing_dataset train: images/train val: images/val nc: 1 names: ['fishing']

path是相对路径,建议不要写绝对路径,尤其是这个zip包名字里带中文和特殊符号。有人把这个yaml放在项目根目录,path直接写./fishing_dataset,跑起来没问题;有人图省事把图片放在桌面中文路径下,结果训练时cuda报错路径找不到——不是yolov8不支持中文路径,而是底层依赖库对编码的处理不一致,后面避坑章会细说。nc是类别数,names是类别名列表,顺序必须和标签txt里的class_id严格对应。如果你用的是单类,names里只有fishing,所有txt里默认都是0开头,这两者一旦错位,模型训练时会把标签当成错误样本丢掉。

4.2 最小训练命令与4个必调参数

训练命令可以很简短,先跑通流程再调优:

yolo detect train data=fishing.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 device=0

这一行命令会先下载预训练权重(如果本机没有缓存),然后在runs/detect/下生成一个实验目录。第一次训练建议用yolov8n.pt起步,这个模型只有约300万参数量,902张图半小时内能跑完一个完整的epoch周期,适合快速验证数据管线和标签质量。等确认一切正常,再换yolov8s.pt或yolov8m.pt找精度上限。

训练前必须先确认四个参数,这几个参数在钓鱼检测场景里尤其关键:

参数建议值说明
imgsz640起步显存够就上1280;鱼竿细长目标在小分辨率下只有1-2像素宽
batch16显存不够时优先降imgsz而不是降batch,否则BN统计量不稳定
mosaic0.5默认1.0会把细长目标拼碎并缩小,钓鱼场景背景相似反而帮倒忙
patience30902张图很容易过拟合,早停30个epoch能防止后段震荡浪费时间

mosaic在yolov8里默认是1.0,也就是每张训练图都由4张图拼接。这个增强对通用目标检测很有效,但对鱼竿这种细长目标是个灾难:拼接时每张子图被缩小到一半甚至四分之一,鱼竿变成一条1像素宽的斜线,模型学到的是“斜线”而不是“鱼竿”。我建议显式设置mosaic=0.5,保留一部分干净样本。完整的调参命令长这样:

yolo detect train data=fishing.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 mosaic=0.5 patience=30 project=runs/fishing name=exp1 device=0

4.3 中断恢复与训练过程的三个风险点

训练中途断开是常态,尤其是用远程服务器跑数据集的场景。yolov8支持恢复训练,只要训练过程正常退出时last.pt保存过:

yolo detect train resume=True

这个命令会自动找最近一次训练的实验目录。如果实验目录被手动移动过,可以指定model=runs/fishing/exp1/weights/last.pt。恢复训练时要特别注意:data.yaml的参数必须和中断时一致,否则会自动重新划分数据集,之前的epoch学习率状态全乱。

训练过程中有三个风险点,我建议每20个epoch看一次runs/fishing/exp1/下的曲线图。第一,train loss下降但val mAP长期不动,先考虑是不是mosaic太大或lr0不合适,而不是盲目加epoch。第二,val mAP在前30个epoch冲到高点然后开始锯齿轮动,这是典型的过拟合信号,902张图训练集太小,epoch超过80后模型开始记背景纹理。第三,loss曲线出现断崖式跳水,排查是不是有标签文件损坏导致某张图被跳过。这几个风险点的共同特征是:损失指标看起来正常,但可视化输出完全不能看。所以不要只盯loss,定期跑一次验证集推理看真实效果。

5. 避坑指南:钓鱼数据集训练最常见的5个坑

把训练流程跑通之后,真正的麻烦才开始显形。下面这五个坑在垂钓行为这种小目标、细长目标、小数据集组合场景里几乎必现,按出现频次从高到低排列。

5.1 类别ID错位:names顺序和txt里的0/1对不上

现象:训练loss正常下降,验证时mAP@0.5也能到0.7以上,但打开验证集的PR曲线,发现精确率在置信度0.2附近有一个断崖式下跌,大量检测框被标记为错误的类别。

原因:data.yaml里的names列表顺序和txt标签里的class_id对不上。比如标签txt里0代表fishing,但yaml里写的是names: ['rod', 'fishing'],0对应rod。yolov8的损失函数不会报错,它只会把“渔竿”的标签按“钓鱼”去学,两个类特征混淆在一起。

解决:训练前写一个五行脚本,把标签里出现的所有class_id打印出来,和names列表做映射核对。902张图的数据集,class_id只应该出现你定义的几个整数,一旦出现越界值,说明xml转txt时映射表写错或者某个标签文件损坏。我的习惯是在训练脚本里加一段断言,凡是出现未知class_id直接抛异常,而不是让训练带病跑完100个epoch。

5.2 细长鱼竿漏检:置信度阈值和imgsz怎么配合

现象:模型在验证集上mAP不错,但用摄像头实拍时,画面里的鱼竿完全没有框;把同一帧截图调低置信度阈值后,又能看到微弱的检测框。

原因:鱼竿在640分辨率下通常只有1-2像素宽,yolov8n的特征图最深层下采样到20×20左右,这个级别的感受野对细长目标不敏感,模型输出的置信度普遍偏低。默认推断阈值conf=0.25会把这些低置信度框全部过滤掉。

解决:推理时把置信度阈值降到0.05,并配合NMS参数调整:

yolo predict model=runs/fishing/exp1/weights/best.pt source=test_video.mp4 conf=0.05 iou=0.5 imgsz=1280

这行命令会把输出置信度阈值降到0.05,同时把输入分辨率提到1280。如果换成1280后漏检明显减少,说明模型本身学到了鱼竿特征,只是分辨率不够。如果1280下依然漏检,就要考虑把训练时的imgsz也提到1280重新训练一轮。注意推理分辨率可以和训练分辨率不一致,一般推理设为训练的1.5倍以内效果最好,过大会引入大量插值噪声。

5.3 902张图过拟合:增强策略与backbone冻结

现象:训练集loss降到0.03,验证集mAP却在50个epoch后开始下滑,F1曲线峰值越来越低。可视化验证集预测结果,模型把地面纹理、水面波纹都检测成目标。

原因:902张图对“人+鱼竿+水面”这个组合来说太小。模型在训练后期开始记忆背景纹理,而不是学习目标结构。尤其鱼竿框内有大片水面背景,模型会误认为“蓝色区域”是正样本特征。

解决:先冻结backbone训练10个epoch,再解冻全部微调。yolov8的freeze参数可以指定冻结前几层:

yolo detect train data=fishing.yaml model=yolov8n.pt epochs=100 freeze=10 mosaic=0.5

freeze=10表示冻结backbone的前10层,让模型先训练检测头,把已经预训练过的特征提取器保留下来。同时开启适当的颜色增强:hsv_h=0.015 hsv_s=0.5,让模型对水面颜色变化更鲁棒。不要开mixup,这个增强方式对细长目标的边缘特征干扰极大。

5.4 zip伪加密与损坏包:不是每层压缩包都能直接解

现象:解压时提示End-of-central-directory signature not found,或者解压到一半报CRC校验错误。文件名带中文的zip包还可能解出来全是乱码目录。

原因:数据集zip包的压缩层数通常不止一层,有时外层的zip是伪加密——它只是设置了加密标志但没有实际加密内容,普通解压工具会要求输入密码。另外下载中断会导致zip的中央目录损坏,标准的unzip会直接罢工。

解决:先判断是不是伪加密,用7-Zip打开zip包,如果能直接看到文件列表且能拖出来,说明只是zip伪加密,直接用7-Zip解压到目标目录。中央目录损坏的zip包别急着扔,用zip -F尝试修复:

zip -F damaged.zip --out repaired.zip

修复后解压出来优先校验图片文件数是不是902张。我遇到过修复后图片文件都在但标签目录少了一个子文件夹的情况,所以解压后务必跑一遍2.2的统计脚本,确认标签文件和图片文件数量一致再进入训练流程。

5.5 中文文件与路径:Windows上好好的,Linux上找不到图

现象:在Windows本地解压后训练正常,把整个fishing_dataset目录传到Linux服务器后再跑训练,报Image not found,但图片明明就在目录里。

原因:zip包里的中文文件名在Windows解压时用的是GBK编码,Linux服务器默认UTF-8读取,文件名里的中文变成了一串乱码。yolov8的图片加载器找不到路径,就会静默跳过,最终导致训练时有效图片数远小于902。

解决:在任何平台解压后,立即把文件名统一改成纯英文和数字:

import re from pathlib import Path for sub_dir in ["images", "labels"]: for p in Path(f"fishing_dataset/{sub_dir}").glob("*"): if p.is_dir(): continue new_name = re.sub(r"[^\w.]", "_", p.name) if new_name != p.name: p.rename(p.with_name(new_name))

这段脚本把所有非字母数字下划线的字符替换成下划线,把中文、空格、括号全部清理干净。我当时在这个问题上浪费了一个晚上,最后发现罪魁祸首是数据集里有一张图片名称里带着全角括号。训练脚本查不到文件时会跳过而不是报错,所以检查日志里image count是否等于902,是最快的定位方式。

6. 模型出来先别信mAP:漏检回放与两阶段验证

训练完拿到best.pt,先不要看mAP数字,直接跑一次验证集推理,把漏检图回放出来看。

yolo val model=runs/fishing/exp1/weights/best.pt data=fishing.yaml

运行结束后打开runs/detect/val/下的PR_curve.png和F1_curve.png。重点看F1曲线的峰值位置:如果峰值在conf=0.3附近且F1值高于0.75,说明模型学到的是有效特征,阈值选在峰值即可;如果F1峰值低于0.6,提高置信度阈值只会让漏检更严重,这时候要回数据而不是调阈值。PR曲线如果在小recall区间精确率就开始下跌,说明模型把大量水面背景误判为目标,需要补充负样本或重新检查标签框是否包进了太多背景。

6.1 用PR曲线和F1曲线判断该调数据还是调阈值

曲线图呈现在你面前的时候,判断逻辑要清晰:调阈值只能解决“检测到了但置信度低”的问题,解决不了“压根没检测到”的问题。如果F1曲线的峰值出现在低置信度区域,比如conf=0.1,说明所有框的置信度都被压低,这是特征学习问题,要回去调训练参数;如果峰值出现在0.5以上,说明模型区分得很自信,实际部署时可以直接把阈值设到0.5,减少误报。我一般会在验证集上多测几个阈值点:0.1、0.25、0.5,把每个阈值下的精确率和召回率记下来,选精确率大于0.8且召回率损失最小的那个点。

6.2 单帧漏检不是死局:用5帧状态窗口做行为判定

垂钓是一个持续时间较长的行为,单帧偶尔漏检并不致命,致命的是漏掉整个行为过程。在监控场景里,我习惯把YOLO的单帧检测结果喂给一个极简的状态窗口:

from collections import deque class FishingTemporalJudge: def __init__(self, window=5, threshold=3): self.window = window self.threshold = threshold self.state = deque(maxlen=window) def update(self, person_box, rod_box, img_shape): if person_box is None or rod_box is None: self.state.append(0) return False ih, iw = img_shape rx = (rod_box[0] + rod_box[2]) / 2 / iw ry = (rod_box[1] + rod_box[3]) / 2 / ih hit = int(person_box[0] / iw < rx < person_box[2] / iw and person_box[1] / ih < ry < person_box[3] / ih) self.state.append(hit) return sum(self.state) >= self.threshold

这个类维护一个长度为5的滑动窗口,每次推理传入人和鱼竿的边界框,只有鱼竿中心落在人框内才算一次有效观测。连续5帧里如果有3帧满足条件,就判定为“垂钓行为进行中”。这个策略能容忍短暂遮挡和单帧漏检,同时过滤掉路人手持长棍经过造成的瞬时误报。我现在的习惯是:任何新数据集都不直接上线单帧模型,至少套一层这样的时序逻辑再交给业务方。这套方法不复杂,但能让误报率降一半以上。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 17:17:25

网站开发公司怎么选?解析模板、定制、SaaS与AI建站

我不是卖网站的&#xff0c;也不靠给建站公司带流量吃饭。但这十一年下来&#xff0c;前前后后接触过几百个做网站的人——有找外包的创业者&#xff0c;有内部想搭一套官网的中型企业&#xff0c;也有想从模板站升级成定制站的电商老板。他们问我的第一句话几乎都是同一个&…

作者头像 李华
网站建设 2026/10/1 17:16:49

BPG图片格式完全指南:Windows下查看、转换与缩略图预览实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 17:14:47

Python实战第6期:列表与元组

文章目录 引言:为什么需要列表和元组? 一、列表的创建和操作 1. 创建列表 2. 访问列表元素 3. 修改列表元素 4. 列表切片 5. 列表运算 6. 列表长度 二、列表常用方法 1. 添加元素 2. 删除元素 3. 查找元素 4. 排序 5. 复制列表 6. 列表推导式 三、元组的特点 1. 什么是元组 2…

作者头像 李华