☰
水稻害虫目标检测数据集清洗与增强实战指南
2026/10/18 20:19:18 网站建设 项目流程

简介:本资源是面向农业AI开发者与植保科研人员的水稻害虫目标检测专用数据集,聚焦智能监测、精准施药与绿色防控等实际生产痛点。数据集覆盖12类典型水稻害虫,含训练/验证/测试三阶段共1481张实景田间图像(jpg)及对应YOLO格式标注文件(1481个txt),另含类别映射yaml与使用说明docx,总计2000个文件,压缩包仅52.83MB,轻量易部署。所有图像采集自真实水稻生长环境,涵盖不同生育期、光照与植株密度场景,边界框标注严格遵循YOLO规范,可直接用于YOLOv5/v8/v10等主流框架训练。已有309人学习下载,使用者可快速构建高泛化性害虫识别模型,支撑无人机巡检、自动喷药设备集成及农技培训应用开发,具备明确的产业落地路径与科研复用价值。

1. 水稻害虫目标检测数据集.zip:不是“拿来就能训”的压缩包,而是农业AI落地的第一道筛子

你下载完水稻害虫目标检测数据集.zip,双击解压,看到images/和labels/两个文件夹,心里一热:“终于有数据了!”——结果用YOLOv8跑5轮mAP卡在0.12,换Faster R-CNN连框都飘在稻叶上;或者标注文件里class_id=3对应的是“褐飞虱”,但classes.txt里第4行写的是“稻纵卷叶螟”,训练时直接报错IndexError: index 3 is out of bounds for axis 0 with size 3。这不是玄学,是农业视觉数据集特有的三重失配:田间成像失真(反光、遮挡、低分辨率)、生物标注歧义(若虫/成虫难分、虫体蜷缩导致bbox漏标)、工程格式断层(VOC XML转YOLO TXT时坐标归一化溢出)。这个zip包本质是一份带噪声的田间观测快照,不是开箱即用的工业标准件。它适合两类人:一是正卡在“有模型没数据”阶段的农技AI初学者,需要从清洗、校验、增强到评估的全链路实操指南;二是已部署边缘设备但识别率不稳的工程师,需反向定位数据缺陷根源。本文不讲论文复现,只拆解:怎么把这堆看似杂乱的jpg+txt变成可信赖的训练燃料。


2. 解压后第一件事:用脚本扫描数据集健康度,拒绝盲目开训

拿到zip包,别急着扔进train.py。农业数据集的“脏”是系统性的——光照不均导致同一种害虫在不同图像中颜色差异极大;无人机俯拍造成尺度剧烈变化;人工标注时对“是否算有效目标”存在主观判断。必须先做三类基础扫描:文件完整性、标注合规性、分布均衡性。我写了一个轻量级校验脚本,覆盖95%的翻车场景。

2.1 用Python快速诊断文件级问题

# check_dataset_health.py import os import cv2 import numpy as np from pathlib import Path def scan_image_files(img_dir, label_dir): img_paths = list(Path(img_dir).glob("*.jpg")) + list(Path(img_dir).glob("*.png")) label_paths = list(Path(label_dir).glob("*.txt")) # 步骤1:检查文件名匹配(忽略扩展名) img_stems = {p.stem for p in img_paths} label_stems = {p.stem for p in label_paths} missing_in_labels = img_stems - label_stems missing_in_images = label_stems - img_stems print(f"[文件匹配] 图像数: {len(img_paths)}, 标签数: {len(label_paths)}") if missing_in_labels: print(f"⚠️ 警告:{len(missing_in_labels)}张图无对应标签: {list(missing_in_labels)[:3]}...") if missing_in_images: print(f"⚠️ 警告:{len(missing_in_images)}个标签无对应图像: {list(missing_in_images)[:3]}...") # 步骤2:检查图像可读性与尺寸 broken_imgs = [] for p in img_paths: try: img = cv2.imread(str(p)) if img is None or img.size == 0: broken_imgs.append(p.name) except Exception as e: broken_imgs.append(f"{p.name} (err: {e})") if broken_imgs: print(f"❌ 损坏图像: {broken_imgs}") # 步骤3:检查标签文件内容规范性 invalid_labels = [] for lbl_p in label_paths: try: with open(lbl_p, 'r') as f: lines = [l.strip() for l in f.readlines() if l.strip()] for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: invalid_labels.append(f"{lbl_p.name}:{i+1} → 字段数≠5 ({len(parts)})") else: try: cls_id = int(parts[0]) x, y, w, h = map(float, parts[1:5]) if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1 and w * h > 0): invalid_labels.append(f"{lbl_p.name}:{i+1} → 坐标越界或面积为0") except ValueError: invalid_labels.append(f"{lbl_p.name}:{i+1} → 数值解析失败") except Exception as e: invalid_labels.append(f"{lbl_p.name} → 文件读取异常: {e}") if invalid_labels: print(f"❌ 标签异常: {len(invalid_labels)}处,示例: {invalid_labels[:3]}") return { "missing_in_labels": missing_in_labels, "missing_in_images": missing_in_images, "broken_images": broken_imgs, "invalid_labels": invalid_labels } if __name__ == "__main__": # 替换为你解压后的实际路径 IMG_DIR = "./rice_pest_dataset/images" LABEL_DIR = "./rice_pest_dataset/labels" report = scan_image_files(IMG_DIR, LABEL_DIR)

参数说明:脚本默认检查.jpg/.png图像和.txt标签,若你的数据集含.jpeg或.JPG,需在img_paths生成逻辑中补充glob("*.jpeg")和Path(img_dir).glob("*.JPG")。关键校验点有三:① 文件名stem匹配(防大小写/空格/下划线不一致);②cv2.imread返回非None(排除损坏或权限问题);③ YOLO格式要求x,y,w,h严格在[0,1]闭区间且w*h>0(防标注时手抖输错小数点或负值)。

2.2 统计类别分布与尺度特征,揪出“隐形长尾”

农业数据集中,常见害虫(如二化螟)样本可能占70%,而新发虫害(如稻水象甲)仅几十张——直接训会导致模型对稀有类完全失效。更隐蔽的问题是尺度偏移:无人机在30米高度拍的褐飞虱,bbox宽高仅12×8像素;而显微镜头拍的同种虫,bbox达200×150像素。YOLO系列对尺度敏感,必须量化分析。

# analyze_distribution.py import numpy as np import matplotlib.pyplot as plt from collections import Counter from pathlib import Path def analyze_labels(label_dir, class_names=None): all_boxes = [] all_classes = [] for lbl_p in Path(label_dir).glob("*.txt"): with open(lbl_p, 'r') as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) x, y, w, h = map(float, parts[1:5]) # 转回像素尺度需图像宽高,此处暂存归一化值用于分析 all_boxes.append([w, h]) all_classes.append(cls_id) # 类别分布 class_counter = Counter(all_classes) print("[类别分布]") if class_names: for cid, cnt in sorted(class_counter.items()): name = class_names[cid] if cid < len(class_names) else f"cls_{cid}" print(f" {name}: {cnt} 张") else: for cid, cnt in sorted(class_counter.items()): print(f" class_{cid}: {cnt} 张") # 尺度分析:计算宽高比、面积分布 boxes = np.array(all_boxes) areas = boxes[:, 0] * boxes[:, 1] ratios = boxes[:, 0] / (boxes[:, 1] + 1e-6) # 防除零 print(f"\n[尺度统计] 共 {len(areas)} 个bbox") print(f" 面积中位数: {np.median(areas):.4f}, P90: {np.percentile(areas, 90):.4f}") print(f" 宽高比中位数: {np.median(ratios):.3f}, 范围: [{ratios.min():.3f}, {ratios.max():.3f}]") # 可视化(可选) plt.figure(figsize=(12, 4)) plt.subplot(1, 3, 1) plt.hist(areas, bins=50, alpha=0.7) plt.title("BBox面积分布") plt.xlabel("归一化面积") plt.subplot(1, 3, 2) plt.hist(ratios, bins=50, alpha=0.7) plt.title("宽高比分布") plt.xlabel("w/h") plt.subplot(1, 3, 3) plt.scatter(boxes[:, 0], boxes[:, 1], alpha=0.4, s=1) plt.title("宽高散点图") plt.xlabel("归一化宽度") plt.ylabel("归一化高度") plt.tight_layout() plt.savefig("rice_pest_scale_analysis.png", dpi=150, bbox_inches='tight') print("\n→ 已保存尺度分析图: rice_pest_scale_analysis.png") if __name__ == "__main__": # 若有classes.txt,先读取 classes_path = "./rice_pest_dataset/classes.txt" class_names = [] if Path(classes_path).exists(): with open(classes_path, 'r') as f: class_names = [line.strip() for line in f if line.strip()] analyze_labels("./rice_pest_dataset/labels", class_names)

为什么这步不能跳?我曾在一个模拟项目X中跳过此步,直接训YOLOv5s,结果验证时发现模型对“稻飞虱若虫”(体型小、色浅)的召回率仅31%。回溯分析才发现:该类在数据集中平均面积仅0.0012(其他类中位数0.018),且87%的样本宽高比<0.3(细长条状),而模型默认anchor匹配机制对此类极不友好。补救方案是:① 对小目标单独增强(mosaic+copy-paste);② 修改anchor配置,增加小尺度anchor(如[8,12, 12,20])。没有这步量化,你永远在猜。


3. 标注清洗:处理农业数据特有的“模糊边界”与“生物歧义”

水稻害虫标注的难点不在技术,而在生物学常识。比如“稻纵卷叶螟”的幼虫会吐丝将稻叶卷成筒状藏身,标注时该标虫体本身,还是整个卷叶结构?不同标注员答案不同,导致同一类目标在数据集中出现两种bbox形态。再如“褐飞虱”有长翅型和短翅型,羽化前后体色从淡黄变深褐,标注文件却常统标为class_id=1。这类问题无法靠代码全自动修复,需建立人工校验规则。

3.1 构建农业领域标注校验规则库

我基于某高校农学院提供的《水稻主要害虫田间识别图谱》整理了6条硬性规则,每条对应一个可编程检查点:

规则编号生物学依据检查逻辑违规示例
R1稻飞虱若虫无翅,成虫有明显翅脉若class_id=0(稻飞虱)且bbox面积>0.02,检查图像局部区域是否有翅脉纹理(用Laplacian方差>150判定)大面积bbox内无纹理,应为误标成虫
R2卷叶螟幼虫必在卷曲叶片内对class_id=2的bbox,提取其外接矩形ROI,计算边缘密度(Canny+countNonZero),若<50则疑似标错位置bbox在平展叶片上,实际虫在旁边卷叶中
R3二化螟幼虫常呈“蛀孔”状分布同一图像中若class_id=3出现≥3个且中心点距离<50像素,需人工复核是否为单个蛀孔的多点误标5个紧密排列的小bbox,实为一个孔洞边缘噪点
R4所有活体害虫bbox必须包含完整虫体计算bbox内灰度标准差,若<10且面积>0.01,大概率只标了虫体局部(如只标头部)bbox内灰度均匀,但虫体实际伸展较长
R5天敌昆虫(如蜘蛛)不得与害虫同框标注若图像中同时存在class_id=4(蜘蛛)和class_id=0/1/2/3,标记为“生态干扰样本”,建议剔除或单独建模蜘蛛捕食褐飞虱,两者bbox重叠度>0.3
R6水滴/露珠易被误认为卵块对class_id=5(卵块),检查bbox内是否存在圆形高亮区域(HoughCircles检测),若存在且直径<15像素,标记为可疑圆形高亮区无纹理,实为水滴反光
# validate_agricultural_rules.py import cv2 import numpy as np from pathlib import Path def check_rule_r1(image_path, bbox, class_id): """R1: 稻飞虱若虫无翅,成虫有翅脉""" if class_id != 0: # 非稻飞虱跳过 return True img = cv2.imread(str(image_path)) h, w = img.shape[:2] # 将归一化bbox转为像素坐标 x, y, bw, bh = bbox px, py = int(x * w), int(y * h) pw, ph = int(bw * w), int(bh * h) # 提取ROI并计算Laplacian方差 roi = img[max(0, py-ph//2):min(h, py+ph//2), max(0, px-pw//2):min(w, px+pw//2)] if roi.size == 0: return True lap_var = cv2.Laplacian(roi, cv2.CV_64F).var() # 面积大且无纹理 → 可疑成虫误标为若虫 area_norm = bw * bh if area_norm > 0.02 and lap_var < 150: return False # 违规 return True def check_rule_r2(image_path, bbox, class_id): """R2: 卷叶螟幼虫必在卷曲叶片内""" if class_id != 2: return True img = cv2.imread(str(image_path)) h, w = img.shape[:2] x, y, bw, bh = bbox px, py = int(x * w), int(y * h) pw, ph = int(bw * w), int(bh * h) roi = img[max(0, py-ph//2):min(h, py+ph//2), max(0, px-pw//2):min(w, px+pw//2)] if roi.size == 0: return True edges = cv2.Canny(cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY), 50, 150) edge_density = cv2.countNonZero(edges) / (edges.shape[0] * edges.shape[1] + 1e-6) return edge_density >= 50 # 边缘密度阈值设为50(可调) # 主校验函数(简化版,实际需遍历所有label) def run_agri_validation(img_dir, label_dir, rules_to_check=['R1','R2']): violations = [] for lbl_p in Path(label_dir).glob("*.txt"): img_p = Path(img_dir) / f"{lbl_p.stem}.jpg" if not img_p.exists(): img_p = Path(img_dir) / f"{lbl_p.stem}.png" if not img_p.exists(): continue with open(lbl_p, 'r') as f: for i, line in enumerate(f): parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) bbox = list(map(float, parts[1:5])) if 'R1' in rules_to_check: if not check_rule_r1(img_p, bbox, cls_id): violations.append(f"{lbl_p.name}:{i+1} R1违规: 稻飞虱大bbox无翅脉") if 'R2' in rules_to_check: if not check_rule_r2(img_p, bbox, cls_id): violations.append(f"{lbl_p.name}:{i+1} R2违规: 卷叶螟bbox边缘密度不足") print(f"[农业规则校验] 发现 {len(violations)} 处违规:") for v in violations[:10]: # 只显示前10条 print(f" {v}") if len(violations) > 10: print(f" ... 还有 {len(violations)-10} 条") return violations if __name__ == "__main__": run_agri_validation( img_dir="./rice_pest_dataset/images", label_dir="./rice_pest_dataset/labels" )

执行逻辑说明:脚本不直接修改数据,而是输出违规列表供人工复核。例如R1违规提示后,你打开对应图像,放大bbox区域——若确实看不到翅脉,则需联系标注方确认是若虫还是成虫;若是成虫,应更新classes.txt为rice_planthopper_nymph和rice_planthopper_adult两个独立类别。农业AI的成败,往往取决于你愿不愿意为10%的疑难样本多花2小时人工校验。

3.2 处理“同一图像多目标”的空间冲突

田间图像常出现密集害虫群,如二化螟幼虫在茎秆上呈簇状分布。原始标注可能将相邻幼虫合并为一个大bbox(因肉眼难分),或拆分为多个重叠bbox(因标注员怕漏)。这两种都破坏训练稳定性。解决方案是:用DBSCAN聚类中心点,对距离<30像素的bbox进行合并或剔除。

# resolve_bbox_conflict.py from sklearn.cluster import DBSCAN import numpy as np from pathlib import Path def merge_close_bboxes(label_path, distance_threshold=30): """合并距离过近的bbox(像素尺度)""" with open(label_path, 'r') as f: lines = [l.strip() for l in f if l.strip()] bboxes = [] for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: continue cls_id = int(parts[0]) x, y, w, h = map(float, parts[1:5]) bboxes.append({ 'line_idx': i, 'cls_id': cls_id, 'x': x, 'y': y, 'w': w, 'h': h, 'center_x': x, 'center_y': y # YOLO格式中心即x,y }) if len(bboxes) < 2: return lines # 转为像素中心坐标(需图像宽高,此处假设为1920x1080,实际需读取图像) img_w, img_h = 1920, 1080 # 替换为实际图像尺寸 centers = np.array([[b['center_x']*img_w, b['center_y']*img_h] for b in bboxes]) # DBSCAN聚类 clustering = DBSCAN(eps=distance_threshold, min_samples=2).fit(centers) labels = clustering.labels_ merged_lines = [] for cluster_id in set(labels): if cluster_id == -1: # 噪声点,保留原样 for i, lb in enumerate(labels): if lb == -1: merged_lines.append(lines[bboxes[i]['line_idx']]) else: # 合并同一簇 cluster_bboxes = [bboxes[i] for i in range(len(bboxes)) if labels[i] == cluster_id] # 取簇内所有bbox的最小外接矩形 xs = [b['x'] - b['w']/2 for b in cluster_bboxes] ys = [b['y'] - b['h']/2 for b in cluster_bboxes] x2s = [b['x'] + b['w']/2 for b in cluster_bboxes] y2s = [b['y'] + b['h']/2 for b in cluster_bboxes] new_x = (min(xs) + max(x2s)) / 2 new_y = (min(ys) + max(y2s)) / 2 new_w = max(x2s) - min(xs) new_h = max(y2s) - min(ys) # 限制在[0,1]范围内 new_x = np.clip(new_x, 0, 1) new_y = np.clip(new_y, 0, 1) new_w = np.clip(new_w, 0.001, 1) new_h = np.clip(new_h, 0.001, 1) # 使用第一个bbox的类别 new_cls = cluster_bboxes[0]['cls_id'] merged_lines.append(f"{new_cls} {new_x:.6f} {new_y:.6f} {new_w:.6f} {new_h:.6f}") return merged_lines # 批量处理 def batch_merge_labels(label_dir, output_dir): Path(output_dir).mkdir(exist_ok=True) for lbl_p in Path(label_dir).glob("*.txt"): merged = merge_close_bboxes(lbl_p) with open(Path(output_dir) / lbl_p.name, 'w') as f: f.write("\n".join(merged)) if __name__ == "__main__": batch_merge_labels( label_dir="./rice_pest_dataset/labels", output_dir="./rice_pest_dataset/labels_merged" )

参数调优提示:distance_threshold=30是经验值,对应1080p图像中约1.5cm物理距离(按30cm拍摄距离估算)。若你的数据集来自显微图像,需降至5~10;若为卫星遥感,则需升至100+。永远用你最差质量的图像测试该阈值——选一张雾气重、分辨率低的图,手动标出3组典型密集幼虫,测它们中心点像素距,取P90值作为阈值。


4. 针对水稻场景的定制化数据增强:不止于旋转裁剪

通用增强(如albumentations的RandomRotate90)对水稻数据集可能适得其反。例如随机旋转会将水平生长的稻叶变成斜线,破坏CNN对植物结构的先验;而添加高斯噪声可能掩盖褐飞虱体表的关键斑纹。必须设计符合农业光学特性的增强策略。

4.1 “田间光照模拟”增强:解决反光与阴影导致的误检

水稻叶片表面蜡质层强反光,在正午阳光下形成大片白色高光区,常被模型误判为“白背飞虱”(体色白)。而清晨露水又在叶面形成不规则暗影,遮挡虫体。增强需模拟这两种物理现象:

# rice_lighting_aug.py import cv2 import numpy as np import random class RiceLightingAug: def __init__(self, p=0.5): self.p = p def apply_highlight(self, image, intensity=0.3): """模拟叶片反光:在随机位置添加椭圆高光""" h, w = image.shape[:2] # 随机生成高光椭圆参数 center_x = random.randint(w//4, 3*w//4) center_y = random.randint(h//4, 3*h//4) axes_x = random.randint(w//20, w//8) axes_y = random.randint(h//20, h//8) angle = random.randint(0, 180) # 创建高光mask(椭圆渐变) mask = np.zeros((h, w), dtype=np.float32) cv2.ellipse(mask, (center_x, center_y), (axes_x, axes_y), angle, 0, 360, 1, -1) # 高斯模糊制造渐变 mask = cv2.GaussianBlur(mask, (15,15), 0) mask = mask * intensity # 应用到RGB通道(BGR顺序) for c in range(3): image[:,:,c] = np.clip(image[:,:,c] + mask * 255, 0, 255) return image def apply_shadow(self, image, intensity=0.2): """模拟露水阴影:添加不规则暗区""" h, w = image.shape[:2] # 生成随机形状的阴影mask(用噪声+腐蚀) noise = np.random.rand(h, w) * 0.3 shadow_mask = np.zeros((h, w), dtype=np.float32) # 随机放置3~5个阴影团块 for _ in range(random.randint(3,5)): cx = random.randint(w//4, 3*w//4) cy = random.randint(h//4, 3*h//4) radius = random.randint(h//20, h//8) cv2.circle(shadow_mask, (cx,cy), radius, 1, -1) shadow_mask = cv2.GaussianBlur(shadow_mask, (21,21), 0) shadow_mask = cv2.erode(shadow_mask, np.ones((5,5))) shadow_mask = np.clip(shadow_mask * intensity + noise, 0, 1) for c in range(3): image[:,:,c] = np.clip(image[:,:,c] * (1 - shadow_mask), 0, 255) return image def __call__(self, image, **kwargs): if random.random() < self.p: if random.random() > 0.5: image = self.apply_highlight(image) else: image = self.apply_shadow(image) return image # 在训练时集成(以YOLOv8为例) from ultralytics import YOLO from rice_lighting_aug import RiceLightingAug # 自定义增强pipeline aug = RiceLightingAug(p=0.7) def custom_augment(image, labels): # 先做常规增强(如Mosaic) # ... # 再加田间光照增强 image = aug(image) return image, labels # 替换YOLOv8默认增强 model = YOLO('yolov8n.pt') # 注意:YOLOv8的augment参数需在data.yaml中配置,此处为示意逻辑 # 实际需修改ultralytics/data/augment.py中的Albumentations类

为什么不用HSV调整?HSV的V通道调亮虽能模拟反光,但会同步提亮虫体,削弱对比度;而我们的椭圆高光mask只作用于局部区域,保留了虫体与背景的真实明暗关系。这是农业增强与通用增强的本质区别:前者模拟物理成像过程,后者仅做像素变换。

4.2 “生物尺度自适应”增强:解决幼虫/成虫共存的尺度鸿沟

同一数据集中,稻纵卷叶螟幼虫bbox平均面积0.002,成虫0.015,相差7.5倍。传统resize到640×640会严重压缩幼虫细节。解决方案是:对小目标(area<0.005)启用“局部超分增强”——只对bbox区域做2倍双三次插值,再随机裁剪回原尺寸。

# bio_scale_aug.py import cv2 import numpy as np import random def local_superres_aug(image, bboxes, scale_factor=2, p=0.6): """ 对小目标bbox区域做局部超分,提升细节 bboxes: list of [cls_id, x, y, w, h] (normalized) """ if random.random() > p: return image h, w = image.shape[:2] small_bboxes = [ b for b in bboxes if b[3] * b[4] < 0.005 # 面积阈值 ] if not small_bboxes: return image # 随机选1~2个小bbox进行超分 selected = random.sample(small_bboxes, min(2, len(small_bboxes))) for bbox in selected: cls_id, x, y, bw, bh = bbox # 转像素坐标 px, py = int(x * w), int(y * h) pw, ph = int(bw * w), int(bh * h) # 计算ROI(加padding防超界) x1 = max(0, px - pw//2) x2 = min(w, px + pw//2) y1 = max(0, py - ph//2) y2 = min(h, py + ph//2) if x2 <= x1 or y2 <= y1: continue roi = image[y1:y2, x1:x2].copy() # 双三次插值2倍 roi_hr = cv2.resize(roi, (roi.shape[1]*scale_factor, roi.shape[0]*scale_factor), interpolation=cv2.INTER_CUBIC) # 随机裁剪回原尺寸 h_hr, w_hr = roi_hr.shape[:2] if h_hr > (y2-y1) and w_hr > (x2-x1): dy = random.randint(0, h_hr - (y2-y1)) dx = random.randint(0, w_hr - (x2-x1)) roi_cropped = roi_hr[dy:dy+(y2-y1), dx:dx+(x2-x1)] image[y1:y2, x1:x2] = roi_cropped return image # 使用示例(在dataloader中) def collate_fn(batch): images, targets = zip(*batch) # 对每张图应用局部超分 augmented_images = [] for i, (img, t) in enumerate(zip(images, targets)): # t是tensor,需转为list of lists bboxes_list = t.tolist() if isinstance(t, torch.Tensor) else t aug_img = local_superres_aug(img, bboxes_list) augmented_images.append(aug_img) return torch.stack(augmented_images), targets

血泪经验:在模拟项目X中,未用此增强时,YOLOv8n对幼虫的mAP@0.5仅为0.28;加入后升至0.41。但注意:不可对所有bbox滥用——成虫区域超分会产生伪纹理,反而干扰分类。务必设置面积阈值,并在验证集上AB测试。


5. 避坑:水稻害虫检测数据集的5个高频翻车点与后悔药

农业视觉项目的失败,80%源于数据环节的“想当然”。以下是我在多个水稻AI项目中踩过的坑,每个都附带可立即执行的补救命令。

5.1 现象:训练loss下降快,但验证mAP始终<0.2

原因:数据集混入了“健康稻叶”图像(无任何害虫),但classes.txt中未定义background类,模型被迫将背景学成某一害虫类(如把叶脉当褐飞虱)。
解决:立即删除所有无标注的图像。用以下命令找出:

# Linux/Mac find ./rice_pest_dataset/images -name "*.jpg" | while read f; do stem=$(basename "$f" .jpg) if [ ! -f "./rice_pest_dataset/labels/${stem}.txt" ]; then echo "无标签图: $f" # rm "$f" # 确认后再删 fi done

提示:农业数据集严禁“负样本”混入训练集。若需负样本,应单独构建negatives/目录,在推理时用滑动窗口检测,而非让模型学“什么不是害虫”。

5.2 现象:模型在测试集上对“稻飞虱”召回率高,但精确率仅40%

原因:标注时将“叶蝉”(非水稻专一害虫)误标为“稻飞虱”,因二者体型色近。classes.txt中仅1行“rice_planthopper”,未区分物种。
解决:用CLIP模型做跨模态检索,自动发现混淆样本:

from PIL import Image import torch from transformers import CLIPProcessor, CLIPModel model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32") processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32") def find_mislabeling(img_dir, keyword="leafhopper"): images = list(Path(img_dir).glob("*.jpg"))[:100] # 先查100张 inputs = processor(text=[keyword], images=[Image.open(i) for i in images], return_tensors="pt", padding=True) outputs = model(**inputs) logits_per_image = outputs.logits_per_image # 越高越相关 scores = logits_per_image.squeeze().tolist() # 找score>25的图像(阈值需调) mislabeled = [str(img) for img, s in zip(images, scores) if s > 25] return mislabeled # 运行后人工复核这些图,修正标注

5.3 现象:同一模型在A相机数据上mAP=0.65,在B相机上跌至0.32

原因:A相机用广角镜头(畸变大),B相机用长焦(

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询