这个项目最早是去年夏天启动的。当时实验室要评估一款新型驱避剂对埃及伊蚊的现场效果,靠人眼从视频里逐帧数“哪只蚊子开始吸血了”,一天下来眼睛都快瞎了。后来我索性做了一个专门用于埃及伊蚊吸血行为检测的数据集:一共7800张标注图,全部按YOLO格式整理,训练了YOLO系列模型做行为状态识别。整套做完之后,我发现这个项目的价值不止在一个“能用”的模型上,从采集方案、行为类别设计到标注规范、小目标调优再到边缘设备部署,每个环节都有不少坑值得展开说。
如果你正在做目标检测练手、准备自己标注一份特定场景数据集,或者在做昆虫行为识别、病媒生物监测相关的活儿,这篇文章可以作为一套完整参考。本文不灌水,只讲实际操作和踩过的坑。
1. 项目背景与实际价值
1.1 为什么偏偏是埃及伊蚊的“吸血行为”
埃及伊蚊是登革热、寨卡、黄热病等虫媒传染病的主要传播媒介,和按蚊、库蚊相比,它的特点是喜欢在城市室内活动,白天也能咬人,防控难度更大。传统蚊虫监测主要靠诱蚊灯捕蚊和人工分类计数,能知道“有多少蚊子”,但拿不到“蚊子正在干什么”的信息。
在驱避剂效果评估、杀虫剂药效评价、甚至登革热传播风险预警这些场景里,“吸血行为”本身才是关键指标。一瓶驱避剂是否有效,不看笼子里有多少只蚊子,而是看有多少只蚊子敢在含有药剂的手臂或模拟装置上落下来并尝试吸血。人工观察视频里的吸血状态,费时费力,还带主观偏差。用目标检测自动识别蚊子的行为状态,本质上就是把“人眼数状态”变成“模型定状态”。
单纯检测蚊子和检测吸血行为是两码事。蚊子本身是个小目标,吸血行为更是只有特定姿态、特定腹部膨大程度才能判定,这要求数据集里的标注必须是“带行为语义”的,而不仅仅是“这里有只虫”。
1.2 为什么这个数据集值得认真做一遍
公开数据集里,人、车、猫狗、常见物品的检测数据集一抓一大把,但“昆虫个体行为状态”这种垂直场景的数据集非常稀缺。即便有,也多是用在实验室已有设备、固定背景下采集的,很难直接迁移到别的课题组里复用。
自己做一份数据集,收益不止是“训练出一个模型”这么简单。第一,你会把数据采集、标注规范、质量校验、数据划分、模型训练、部署推理这一整条链路跑通,这是公开数据集给不了的经验。第二,你可以按自己的需求设计类别体系,比如我这次把吸血过程拆成了几个状态,这是通用数据集做不到的。第三,对于做病媒生物监测的团队来说,一份整理好的、带标注规范说明的数据集,本身就是可以复用的资源。
1.3 哪些人适合参考这篇内容
- 正在学YOLO、想找个正经场景练手的新手。这套数据集的整理方式、YAML配置、训练命令都是可以直接照抄的。
- 做目标检测但主要接触常规大目标的人。埃及伊蚊在画面里经常只有几十个像素,是练小目标检测的好材料。
- 从事病媒生物防控、昆虫行为生态研究的人员。想知道怎么用视觉方法替代一部分人工观察。
- 要在Jetson、树莓派这类边缘设备上跑检测的开发者。最后部署部分有实际配置记录。
2. 整体设计思路与方案选型
2.1 为什么不用Faster R-CNN,非选YOLO不可
建模之前我其实先试过两阶段的Faster R-CNN,识别精度在小目标上确实不算差,但两个问题劝退了我。第一是推理速度:实验室后续要在边缘设备上做实时监测,单帧推理速度在CPU上完全跑不动,在GPU上也只能勉强到十几毫秒级别,再做视频流分析时帧率直接不够用。第二是训练和部署链路相对繁琐,从数据格式转换到模型导出,每一步的坑都比YOLO多。
YOLO系列是单阶段检测器,把检测当成回归问题直接输出边界框和类别概率,在速度和精度上取得了一个实际工程里最“省心”的平衡点。现在Ultralytics的YOLOv8、YOLOv10生态做得非常成熟,一个Python包就覆盖了训练、验证、导出、部署全流程。对于这种特定行为识别的任务,YOLO的小模型版本比如n/s级别,在目标很小的场景下搭配合适的分辨率,效果完全够用,同时推理速度能跑到实时。
2.2 行为状态类别体系的设计逻辑
这是整个数据集最核心的设计决策。我一开始想得比较简单,只标“蚊子静止”和“蚊子在吸血”两类,标注了一会儿就发现不对劲。蚊子的吸血行为不是一个瞬间动作,而是一个持续过程:先是爬行或飞行接近宿主,然后在皮肤表面试探着找下口的位置,刺入口器之后开始吸食血液,随着血液摄入腹部逐渐膨胀,最后吃饱了腹部鼓胀、动作迟缓。如果强行只分两类,标注员会遇到大量“它算是在吸血还是没在吸血?”的纠结,模型学到的特征也会很不稳定。
最终我采用了四类行为状态:
| 类别 | 状态定义 | 标注时的判断依据 |
|---|---|---|
| resting | 静止或爬行,未与宿主表面接触吸血 | 口器未接触皮肤/膜面,翅位正常 |
| probing | 试探性刺探,寻找穿刺点 | 口器反复接触表面,身体有明显的刺探动作,腹部未膨大 |
| feeding | 刺入吸血中,腹部可见膨胀 | 口器稳定刺入,腹部逐渐变红变大 |
| engorged | 饱血状态,即将离开 | 腹部明显膨大、呈半透明红褐色,动作迟缓 |
这里有个经验之谈:probing 和 feeding 的边界是最细的。我的处理办法是,只要口器已经稳定刺入并且腹部开始出现膨大迹象超过两三帧,就标成feeding;如果口器还在反复试探或刚接触就收回,标probing。标注规范必须写得够具体,否则几个人标同一段视频,标签一致性会很差。
2.3 7800张数据的构成与分布
整个数据集由50段室内采集视频抽帧而来,最终保留7800张有效标注图像。按照约7:2:1的比例划分训练集、验证集、测试集,划分时我将同一段视频的所有帧归入同一集合,避免“视频泄露”——这个问题后面在常见问题里会细说。
每张图里的目标数量不太均匀。静止状态的蚊子往往成群出现,画面的角落里可能同时待着十几只;吸血状态的蚊子则是分散的,通常一个画面里只有三五只。为了防止模型被静止状态的样本带偏,我在抽帧时对吸血状态的视频片段做了过采样,提高这两个中后期行为在数据中的占比。
从目标尺寸的角度说,埃及伊蚊体长只有4到7毫米,在1280×720分辨率的画面里,即使近距离拍摄,单只蚊子的标注框宽度也常在30到80像素之间,属于典型的小目标。所以我在设计采集方案时就要求镜头必须贴近吸血装置,宁可画面中蚊子的数量少一点,也要让单只蚊子的像素尽量大一些。这批数据里,约占总数四成的标注框长边小于32像素,这也是后面训练时把输入分辨率拉到960的重要原因。
3. 数据采集、标注与预处理实操
3.1 一套省钱的室内采集装置可以这样搭
关于实验室里怎么搭采集环境,我直接说一套低成本可复制的方案。核心部件是一个40厘米见方的亚克力蚊笼,笼内放入实验饲养的埃及伊蚊雌蚊(雄蚊不吸血,别放进去),笼顶开一个直径约15厘米的操作口。吸血装置用一个小型喂血器,里面灌入水浴加热到37℃左右的新鲜血液,喂血器底面覆盖一层石蜡膜(parafilm),模拟皮肤表面。把喂血器从操作口下沉到笼内,让蚊子接触膜面吸血。
摄像头选普通的1080P USB工业相机,加一个手动调焦的微距镜头,固定在离吸血膜面大概10到15厘米的正上方。镜头和膜面呈约60度俯视角,这个角度最方便观察腹部膨胀和口器刺入动作,比完全正视或完全顶视都更容易标注。拍摄时补一盏亮度均匀的LED灯,避免蚊子在膜面上产生过强阴影,否则后续标注会看不清口器位置。
每段视频录制大约30到45分钟,覆盖从蚊子开始靠近膜面、试探刺探、吸血到饱血飞走的完整过程。的采集批次我做了三种背景色(浅色、深色、原木色),目的就是让模型不要依赖背景颜色来下结论。
3.2 标注工具、标注规则与质量复核
标注工具上用X-AnyLabeling,它比老牌的LabelImg多了很多辅助功能,比如自动保存、标签筛选、直接输出YOLO格式,尤其在连续帧上标注效率高很多。同样配置下,也能用CVAT走多人协作,但单机标注的话X-AnyLabeling就够了。
标注规则里有几条是血泪教训:
- 用矩形框紧紧框住蚊子的身体主体,不要把翅膀边缘和细长腿部扩进去。埃及伊蚊休止时翅膀平叠、腿部伸展,腿特别容易让标注框变得巨大,混入大量背景,模型后期很容易把小片背景当成目标的一部分。
- 如果一只蚊子被另一只蚊子遮住了60%以上,不标;遮住少于60%但是能明确判断行为状态的,正常标。
- 吸血状态的判定必须看“腹部是否逐渐膨大”,而不是只看“蚊子是否停在一个位置”。要结合前后几帧的画面来定,不能只盯着当前这一帧。
- 一张画面中出现同一只蚊子的“残影”(运动模糊)时,不标。
标注完成后必须做质量复核。我的做法是,一个人先标完,另一个人跑一遍标签统计脚本,检查每张图上各类别的数量比例是否异常,然后随机抽5%的图像人工复核边界框与类别。明显不合理的框直接删除重新标。这个环节能省下后面训练时排查错误的不少时间。
3.3 抽帧策略与数据增强
视频素材不能一帧不落地全进数据集,相邻帧高度相似,会让训练集的有效信息量远低于看起来的规模。我的抽帧策略是:吸血状态相对持续且静态,正常抽取时每隔5到10帧取一帧;从静止状态切换到试探状态的瞬时动作,为了不让模型漏掉行为转折点,做2到3帧的密集抽取。
抽完帧先做两轮清洗。第一轮清洗运动模糊严重的图像,这类图像中蚊子轮廓已经虚化,标注框位置很难定准。第二轮清洗长时间无目标变化的冗余帧,尤其是在笼子角落里静止休息的蚊子,连续几十帧一模一样的情况会让训练集“虚胖”。
增强策略需要结合场景特点来定。实验室背景相对干净,模型很容易把“室内暖色背景”当成隐含特征,部署到其他环境时泛化变差。所以我开了较重的HSV扰动和旋转平移增强,并在YOLO的mosaic增强上做了一点调整:把mosaic概率设置为0.8,但把mosaic的缩放范围控制在0.5到1.5之间,避免把本来就小的蚊子目标缩到看不见。
3.4 数据集文件组织与标签校验
训练数据全部整理成标准YOLO格式,目录结构如下:
aedes_behavior/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── aedes_behavior.yaml每张图对应一个同名txt标签文件,每行内容为“类别序号 x_center y_center width height”,坐标按图像宽高归一化。类别顺序和YAML文件里必须一致。我在项目里固定了这样的类别映射:0代表resting,1代表probing,2代表feeding,3代表engorged。
这里特别提醒,标签文件与图片文件必须严格同名,否则直接报错。我第一次批量转换标注时,有几个文件名因为包含空格和中文,导致标签对不上图,训练时莫名其妙少了大量样本,我建议导入训练前写一个脚本统一检查:比对每个标注框坐标是否在0到1之间、文件名是否一一对应、是否有空标签文件。这类脚本十几行就能写完,能避免后续几个小时的无头绪排错。
4. 模型训练、调参与部署
4.1 基线模型与超参数设定
我直接用了Ultralytics的YOLOv8框架做训练,对比了v8n和v8s两个尺寸。小模型v8n训练快、部署省资源,但在这种小目标场景下,它的特征表达容易不够。我的经验是,优先试v8s,如果后续要大面积部署、且边缘设备性能一般,再换成v8n或量化版。
数据集配置文件aedes_behavior.yaml内容如下:
path: ./datasets/aedes_behavior train: images/train val: images/val test: images/test nc: 4 names: 0: resting 1: probing 2: feeding 3: engorged训练命令我写在下面,参数是按这批数据量调整过的:
yolo detect train \ data=aedes_behavior.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=960 \ batch=8 \ optimizer=AdamW \ lr0=0.0008 \ mosaic=0.8 \ patience=30 \ device=0有几个参数要额外解释。输入分辨率我用了960而不是默认的640,这是基于目标尺寸分布决定的。前文提到四成框的长边小于32像素,640分辨率下这些框会被压缩到十几像素,模型特征图上的信息几乎消失;抬到960后,小目标的检测效果肉眼可见地变好。代价是训练显存占用变大,我在单张12GB显存的卡上batch只能开到8左右,再高就会OOM。
训练轮数设到200,配合patience=30早停。实际跑到150轮左右就稳定收敛了,后面几十轮主要在震荡。这组参数下最终验证集mAP50在0.87左右,mAP50-95在0.61左右。
4.2 针对小目标与类别不平衡的调优
第一轮训练结束,我看了混淆矩阵,发现两个问题:resting和probing容易混淆,feeding和engorged容易混淆。这说明模型把行为状态当成静态形态学特征来学,但蚊子从静止到试探的动作变化非常细微,单帧信息有限。
做了三件事后有明显改善。一是给损失函数里的类别分支加了权重,把probing和feeding这两类稀有类别的权重提高,resting作为占比最大的类别适当降低权重。二是把测试和训练中误检最多的图像挑出来重新审查标注,发现有一小部分probing帧的标签当时标错了,修正之后类别边界的精度直接涨了。三是在验证时把相邻两帧的预测结果做简单投票,用时间一致性拉高单个状态的置信度。
如果显存允许,更激进的做法是直接用像YOLOv8 1280分辨率训练。实测n/s级别模型在1280下对小目标更友好,但训练时间差不多翻倍,推理端的耗时也会增加不少。对在线视频分析场景来说,960是一个比较平衡的选择。
4.3 导出与边缘部署实测
训练完成后,我做过一轮部署验证。实验室的后续设备打算用Jetson系列的边缘板卡做推理,模型导出走了TensorRT路线。先用框架自带功能导出ONNX,再转engine:
yolo export model=best.pt format=onnx imgsz=960 half=True yolo export model=best.pt format=engine device=0 half=True imgsz=960导出之后在Jetson设备上推理,实测单帧960分辨率的推理耗时大约在25到35毫秒之间,也就是每秒能处理30帧左右,满足实时性要求。如果再极限一点,可以把推理分辨率降到640同时打开NMS后处理优化,帧率能再往上提,但小目标召回率会掉三到五个点。
部署时另一个容易忽视的问题是行为状态的时序抖动。即使单帧模型精度足够,连续视频里很容易出现“前一帧feeding、后一帧resting、再后一帧又feeding”的闪烁预测。我加了一个轻量的状态机,限定状态只能按resting → probing → feeding → engorged的顺序转移,不允许跳变。这个后处理彻底消除了输出状态的乱跳,代价只有几行代码。
5. 常见问题与避坑记录
5.1 漏检、误检集中爆发时,先查这三件事
- 确认标签文件没有错位。我一开始把训练和验证集里的部分标签文件张冠李戴,结果验证指标飘忽不定,模型看起来“训练得很好一验就掉线”,排查了一天最后是文件名匹配问题。
- 检查输入分辨率和目标尺寸的匹配。如果目标框普遍小于16像素,640分辨率下几乎没有学习信号,先放大输入,而不是急着换更重的模型。
- 看训练集里负样本占比。如果画面中绝大多数帧只有零星的蚊子,模型会学出“默认画面里没有目标”的倾向。可以专门抽一些有密集目标的帧,或者调高每张图的检测上限。
5.2 行为状态类别混淆和咬文嚼字式的标注
probing和feeding的边界问题,通过静态图像本身很难彻底解决。除了多帧投票和后处理状态机,我建议在标注阶段就把这两个类别从“形态学定义”上升到“过程学定义”。给标注员的规则里写清楚:口器离开膜面再接触的行为都算probing,只有当口器保持接触且视频中该蚊子的落点稳定超过3帧,才允许标feeding。规则越机械,标注一致性越高。
如果做完这些还是混淆,就考虑把问题简化:实际业务里不区分probing和feeding,全部合并成“吸血相关行为”。牺牲部分行为细节,换来模型稳定性和标注难度的降低,很多时候是划算的。
5.3 按视频划分数据,而不是随机划分图像
随机划分数据会让来自同一视频的相似帧同时出现在训练集和验证集,模型看着“验证集mAP挺高”,但换一段新视频立刻原形毕露。我前期的A/B实验里,随机划分时验证集mAP甚至比按视频划分高出0.15,这个水分没有参考价值。正确做法是按视频分组,在视频级别上做随机分配,保证验证集的每一帧都来自模型没见过的时间段。
5.4 训练不收敛或过拟合的快速排查
如果损失一直不降,先检查数据配置文件和标签格式,标签错位是最常见的原因。如果训练集损失很低、验证集损失还在高位,多半是数据集规模撑不住模型复杂度,这时候优先砍模型尺寸,比如从v8s降到v8n,或者把输入分辨率降回640,而不是盲目加数据增强。增强过猛在小目标数据集上会让目标变得更小,反而恶化学习效果。
5.5 部署时的显存占用和批次冲突
TensorRT导出过程中,imgsz参数必须与训练时一致。如果训练用960、导出用640,模型不会报错,但精度会无形中下降,这类问题最隐蔽。另外,边缘设备通常显存远小于训练机,部署时建议配合profile逐个算子看显存占用。我遇到过一次Jetson设备的显存只有4GB,导出时如果不加half=True直接报显存不足,加上之后顺利通过,速度还提升了一截。
6. 写在最后的一点真实感受
整套项目做完,我最深的体会是:做一份垂直领域的行为检测数据集,最难的不是跑通YOLO训练,而是在采集素材之前把“行为定义”彻底想清楚。标注框画偏了可以回头改,类别定义含糊不清则会让整份数据的价值大打折扣。如果现在有人要复现这套流程,我会建议先别急着开摄像头,花一天时间把行为状态的定义、判定规则、模糊情况都写成文档,找另一个人一起演一遍,再开始正式采集和标注。
另外,这套数据集和模型还有不少可以继续扩展的方向。比如结合蚊虫飞行时的翅膀振动信号做多模态识别,或者在检测基础上叠加多目标追踪,统计单只蚊子的吸血持续时长和个体活动轨迹。这些后续工作都需要以稳定可靠的行为检测作为底座,希望这篇记录能帮你少走几步弯路。