简介:这是一套面向YOLO系列目标检测模型(如YOLOv5)开发者的轻量级数据增强工具集,专为解决小样本场景下带标注图像扩增难题而设计,适用于算法工程师、计算机视觉初学者及课程实验实践者。资源共23个文件,包含16张示例JPG图像、4个核心Python脚本(rename_file.py用于批量重命名;DataAugmentforLabelImg.py和DataAugmentforLabelMe.py分别适配LabelImg与LabelMe两种主流标注格式,支持翻转、仿射变换、高斯模糊、亮度调节、平移、剪切及自适应高斯噪声等策略的随机组合增强)、1个XML标注样例、1个JSON标注样例及1份README说明文档,压缩包仅1.64MB,结构清晰、开箱即用。已有876人学习下载,提供完整可运行的增强流程封装,覆盖数据预处理全链路——从路径配置、标注同步校验到多策略混合增强,显著降低手动编写增强逻辑门槛,助力快速构建高质量训练集。
1. YOLO数据集增强不是“加图凑数”,而是让模型在真实场景里不认怂
你有没有遇到过:YOLOv5训练时mAP卡在72%不上不下,验证集上小目标漏检严重,但翻遍标注文件——框是准的、类别是对的、格式也符合YOLO规范;再查数据量,300张图+1200个标注框,不算少,可模型就是学不会“在雨雾里辨人”“在强光下识车牌”“在遮挡中判姿态”。问题不在标注质量,也不在模型结构,而在于原始数据太“干净”、太“理想”、太“不现实”。这套yolo数据集的数据增强方案,就是专治这种“训练很稳、部署就翻车”的玄学现场——它不靠堆GPU硬训,而是用带标签同步变换的确定性增强策略,把1张实拍图掰成8~12张“有物理意义”的新样本:镜像后bbox坐标反向重算、仿射旋转时mask顶点实时映射、高斯噪声叠加前先做亮度自适应归一化……所有增强操作都严格绑定label,确保图像像素变、标签数值也跟着动,零错位、零偏移、零手动校验。适合正在做YOLOv5/v7/v8目标检测毕设、课设的同学,也适合工业场景下需快速扩充小样本数据集的算法工程师——尤其当你手头只有几十张高质量实拍图,又没时间重标、不敢用GAN生成假数据时,这套方案就是你的后悔药。
2. 总体架构设计:三类标注格式适配 + 四层增强策略 + 双轨文件管理
这套工具不是写死一个pipeline的脚本集合,而是按“输入-处理-输出”分层解耦的轻量框架。核心逻辑藏在三个Python主文件里:rename_file.py负责前置治理,DataAugmentforLabelImg.py和DataAugmentforLabelMe.py分别对接两种主流标注生态。它们共享同一套增强引擎(封装在DataAugForObjectDetection/Segmentation包内),但解析label的逻辑完全隔离——这是避免LabelImg的YOLO txt格式和LabelMe的JSON格式互相污染的关键设计。整个流程不依赖任何GUI或配置文件,全靠路径参数和函数调用控制,方便嵌入到你的训练前处理流水线中。
2.1 文件组织与双轨管理机制:为什么必须区分“原始区”和“增强区”
项目目录结构看似简单,实则暗含工程经验:
Data-enhancement/ ├── rename_file.py # 独立预处理脚本(非增强,但必跑) ├── DataAugmentforLabelImg.py # 主增强入口(LabelImg用户用这个) ├── DataAugmentforLabelMe.py # 主增强入口(LabelMe用户用这个) ├── DataAugForObjectDetection/ # 检测专用增强模块(含bbox变换核) ├── DataAugForObjectSegmentation/ # 分割专用增强模块(含polygon/mask映射核) ├── data/ # 原始数据存放区(只读) │ └── Camera_1_*.jpg + *.txt # LabelImg格式:图片+同名txt(YOLO格式) ├── Imgs/ # 增强后输出区(可直接喂给YOLO训练) │ └── Camera_1_1_aug_001.jpg # 命名含源图ID+增强序号+类型标识 └── README.md提示:
data/是只读区,所有增强操作绝不修改原始文件;Imgs/是纯输出区,每次运行前建议清空。这种双轨制不是多此一举——某高校课程设计组曾因直接在原图目录下覆盖增强图,导致3天标注成果被os.remove()误删,最后靠Git历史才抢回。我一般会在rename_file.py开头强制加一句assert not os.path.exists('Imgs/') or len(os.listdir('Imgs/')) == 0,宁可报错中断,也不让脏数据混入。
2.2 三类标注格式的解析隔离设计:LabelImg vs LabelMe 的本质差异
LabelImg导出的是YOLO格式txt:每行class_id center_x center_y width height(归一化值),无图像尺寸信息;LabelMe导出的是JSON:含shapes[]数组(每个shape含points顶点列表)、imageHeight/imageWidth字段,且支持多边形、旋转矩形、线条等复杂标注。二者无法共用同一套坐标变换逻辑:
- 对LabelImg:增强时需先从txt读出bbox,转为绝对坐标(乘以原图宽高),再经几何变换,最后归一化回YOLO格式;
- 对LabelMe:直接读取JSON中的
imageHeight/imageWidth,对points数组逐点应用仿射矩阵,再写回JSON的shapes字段。
DataAugmentforLabelImg.py中关键坐标转换代码如下:
# python def bbox_normalize(bbox, img_w, img_h): """将绝对坐标bbox转为YOLO归一化格式""" x_center = (bbox[0] + bbox[2] / 2) / img_w y_center = (bbox[1] + bbox[3] / 2) / img_h w_norm = bbox[2] / img_w h_norm = bbox[3] / img_h return [x_center, y_center, w_norm, h_norm] def apply_affine_bbox(bbox_abs, M, img_w, img_h): """对绝对坐标bbox应用仿射变换M,返回变换后绝对坐标""" # 将bbox四角点转为齐次坐标 pts = np.array([ [bbox_abs[0], bbox_abs[1]], [bbox_abs[0] + bbox_abs[2], bbox_abs[1]], [bbox_abs[0] + bbox_abs[2], bbox_abs[1] + bbox_abs[3]], [bbox_abs[0], bbox_abs[1] + bbox_abs[3]] ]) pts_h = np.hstack([pts, np.ones((4, 1))]) pts_trans = (M @ pts_h.T).T[:, :2] # 计算变换后最小外接矩形 x_min, y_min = pts_trans.min(axis=0) x_max, y_max = pts_trans.max(axis=0) return [x_min, y_min, x_max - x_min, y_max - y_min]这段代码背后是血泪经验:早期版本直接对中心点+宽高做线性变换,结果旋转后bbox严重失真。后来改成四角点变换再包络,才真正解决“旋转90°后框变歪”的问题。注意apply_affine_bbox返回的是绝对坐标,后续必须再调用bbox_normalize才能写入YOLO txt——这个两步走流程不能省,否则增强后的txt文件会直接导致YOLO训练崩溃。
2.3 四层增强策略的组合逻辑:随机但可控,强但不崩
增强不是“越多越好”,而是按物理合理性分层:
| 层级 | 策略类型 | 典型操作 | 触发条件 | 设计意图 |
|---|---|---|---|---|
| L1基础层 | 几何不变性 | 水平/垂直翻转、随机裁剪(保留≥80%面积) | 每图必选1种 | 解决视角单一、尺度固定问题 |
| L2光照层 | 光度不变性 | 自适应高斯噪声(σ随亮度动态调整)、高斯模糊(kernel=3×3)、亮度/对比度±15% | 随机触发(p=0.7) | 模拟不同天气、光照、设备差异 |
| L3形变层 | 几何扰动 | 仿射旋转(±10°)、平移(±5%图像宽高)、轻微缩放(0.95~1.05) | 随机触发(p=0.5) | 应对目标姿态变化、距离波动 |
| L4合成层 | 复合扰动 | L1+L2+L3中任选2~3种组合(如:翻转+噪声+平移) | 仅对30%样本启用 | 制造极端但合理的边缘案例 |
DataAugForObjectDetection/augment_core.py中组合调度逻辑如下:
# python def get_augmentation_pipeline(): """返回一个可调用的增强函数链,按层级概率组合""" pipeline = [] # L1:必选一种 if random.random() < 0.5: pipeline.append(hflip_image) else: pipeline.append(vflip_image) # L2:70%概率启用 if random.random() < 0.7: if random.random() < 0.4: pipeline.append(add_gaussian_noise_adaptive) # 自适应噪声 elif random.random() < 0.5: pipeline.append(blur_image) else: pipeline.append(adjust_brightness_contrast) # L3:50%概率启用 if random.random() < 0.5: ops = [rotate_affine, translate_image, scale_image] pipeline.append(random.choice(ops)) # L4:仅30%样本走复合路径 if random.random() < 0.3 and len(pipeline) >= 2: # 随机插入第2个L2或L3操作 extra_op = random.choice([add_gaussian_noise_adaptive, blur_image, rotate_affine, translate_image]) pipeline.insert(random.randint(1, len(pipeline)-1), extra_op) return pipeline # 使用示例 aug_funcs = get_augmentation_pipeline() for func in aug_funcs: img, labels = func(img, labels) # labels可能是bbox列表或polygon列表这里的关键是add_gaussian_noise_adaptive——它不像普通cv2.randn()那样固定σ,而是先计算图像平均亮度mean_lum = np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)),再设sigma = max(5, 25 - mean_lum * 0.2)。这样白天强光图加低噪,黄昏暗图加高噪,更符合真实传感器特性。很多同学抄网上代码直接用固定σ=10,结果增强后白天图全是雪花、夜晚图却平滑如新,模型根本学不到光照鲁棒性。
3. 核心模块实现:LabelImg标注增强的完整落地链路
LabelImg是YOLO训练最常用的标注工具,其txt格式虽简单,但增强时极易因坐标未归一化/未还原导致bbox错位。本节以Camera_1_1.jpg为例,完整演示从原始图到增强图+txt的端到端流程,所有代码均可直接复用。
3.1rename_file.py:为什么重命名是增强前不可跳过的一步
很多同学跳过这步,直接把一堆IMG_001.jpg、IMG_002.jpg丢进data目录,结果增强后生成IMG_001_aug_001.jpg、IMG_001_aug_002.jpg……训练时YOLO读取train.txt发现路径存在,但实际Imgs/里文件名是IMG_001_aug_001.jpg,而train.txt里写的是IMG_001.jpg——直接报错FileNotFoundError。rename_file.py的核心作用是:统一前缀+数字序号+补零对齐,确保后续增强命名可预测、路径可追溯。
原始文件名混乱示例:
Camera_1.jpg Camera_1_1.jpg Camera_1_10.jpg Camera_1_2.jpg运行python rename_file.py --src_dir ./data --prefix "img" --start_idx 1后变为:
img_001.jpg img_002.jpg img_003.jpg img_004.jpg关键代码段:
# python def batch_rename(src_dir, prefix, start_idx=1, exts=('.jpg', '.jpeg', '.png')): files = [] for ext in exts: files.extend(glob(os.path.join(src_dir, f"*{ext}"))) files.sort(key=lambda x: int(re.search(r'(\d+)', os.path.basename(x)).group(1)) if re.search(r'(\d+)', os.path.basename(x)) else 0) for i, old_path in enumerate(files, start=start_idx): old_ext = os.path.splitext(old_path)[1].lower() new_name = f"{prefix}_{i:03d}{old_ext}" new_path = os.path.join(src_dir, new_name) os.rename(old_path, new_path) print(f"Renamed: {os.path.basename(old_path)} → {new_name}") # 同步重命名同名txt文件(LabelImg配套) for old_path in files: old_txt = os.path.splitext(old_path)[0] + ".txt" if os.path.exists(old_txt): new_txt = os.path.join(src_dir, f"{prefix}_{i:03d}.txt") os.rename(old_txt, new_txt) print(f"Renamed TXT: {os.path.basename(old_txt)} → {os.path.basename(new_txt)}")注意:该脚本默认按文件名中首个数字排序(
re.search(r'(\d+)', ...)),所以Camera_1_10.jpg会排在Camera_1_2.jpg后面。若你的原始文件名无规律,建议先人工整理或改用os.listdir()+sorted()按创建时间排序。我一般会在课程设计答辩前,强制要求学生先跑一遍rename,再截图展示data/目录前后对比——这是最直观的工程规范意识体现。
3.2DataAugmentforLabelImg.py:从读图、读txt到写图、写txt的闭环
假设data/下已有img_001.jpg和img_001.txt(YOLO格式),执行:
python DataAugmentforLabelImg.py --src_dir ./data --dst_dir ./Imgs --num_aug 5 --seed 42将生成5张增强图及对应txt。核心流程分四步:
Step 1:安全读取与校验
# python def load_labelimg_sample(img_path, txt_path): """安全加载LabelImg样本,含异常捕获""" try: img = cv2.imread(img_path) if img is None: raise ValueError(f"Failed to load image: {img_path}") h, w = img.shape[:2] # 读txt并校验格式 with open(txt_path, 'r') as f: lines = [l.strip() for l in f.readlines() if l.strip()] bboxes = [] for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: raise ValueError(f"Invalid YOLO format at line {i+1} in {txt_path}: {line}") try: cls_id = int(parts[0]) x_c, y_c, w, h = map(float, parts[1:5]) # 归一化值合法性检查 if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and 0 < w <= 1 and 0 < h <= 1): raise ValueError(f"Out-of-bound coordinate at line {i+1}") bboxes.append([cls_id, x_c, y_c, w, h]) except ValueError as e: raise ValueError(f"Parse error at line {i+1}: {e}") return img, bboxes, (w, h) except Exception as e: print(f"[ERROR] Failed to load {img_path}: {e}") return None, None, None这段校验代码救过不止一个毕设学生——有同学用LabelImg导出时勾选了“保存相对路径”,txt里写的是0 0.5 0.5 0.2 0.3但图片实际是1920×1080,结果增强后bbox全飞出画布。load_labelimg_sample强制检查归一化值范围,一发现x_c>1立刻报错,避免错误扩散。
Step 2:增强链执行(调用2.3节的pipeline)
# python def augment_single_sample(img, bboxes, img_size, aug_func_list): """对单样本执行增强链,同步更新图像和bbox""" w_orig, h_orig = img_size # 转换为绝对坐标便于几何变换 bboxes_abs = [] for cls_id, x_c, y_c, w_norm, h_norm in bboxes: x_abs = int(x_c * w_orig) y_abs = int(y_c * h_orig) w_abs = int(w_norm * w_orig) h_abs = int(h_norm * h_orig) bboxes_abs.append([cls_id, x_abs, y_abs, w_abs, h_abs]) # 执行增强链 for func in aug_func_list: img, bboxes_abs = func(img, bboxes_abs, (w_orig, h_orig)) # 更新当前图像尺寸(几何变换可能改变宽高) w_orig, h_orig = img.shape[1], img.shape[0] # 转回YOLO归一化格式 bboxes_norm = [] for cls_id, x_abs, y_abs, w_abs, h_abs in bboxes_abs: if w_abs <= 0 or h_abs <= 0: # 过滤被裁掉的bbox continue x_c = max(0.001, min(0.999, (x_abs + w_abs / 2) / w_orig)) y_c = max(0.001, min(0.999, (y_abs + h_abs / 2) / h_orig)) w_norm = max(0.001, min(0.999, w_abs / w_orig)) h_norm = max(0.001, min(0.999, h_abs / h_orig)) bboxes_norm.append([cls_id, x_c, y_c, w_norm, h_norm]) return img, bboxes_norm注意max(0.001, min(0.999, ...))——这是防止归一化后坐标为0或1导致YOLO解析失败的兜底。YOLOv5官方要求bbox坐标严格在(0,1)开区间内,边界值会引发ZeroDivisionError。
Step 3:写入增强结果
# python def save_augmented_sample(img, bboxes, dst_img_path, dst_txt_path): """安全写入增强结果""" # 写图 cv2.imwrite(dst_img_path, img) # 写txt(严格按YOLO格式,无空行,浮点数保留6位小数) with open(dst_txt_path, 'w') as f: for bbox in bboxes: line = f"{int(bbox[0])} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f} {bbox[4]:.6f}\n" f.write(line) print(f"Saved: {os.path.basename(dst_img_path)} + {os.path.basename(dst_txt_path)}")这里.6f精度是硬性要求。有学生用默认str(bbox[1])导致0.5000000000000001写入txt,YOLO训练时解析出错,调试3小时才发现是浮点打印精度问题。
3.3 增强效果可视化验证:别信日志,要亲眼看到bbox没飘
增强后不能只看Saved: xxx.jpg就完事。我要求所有课程设计必须提供aug_visualize.py——用OpenCV在增强图上画出bbox,生成对比图:
# python def visualize_augmentation(orig_img, orig_bboxes, aug_img, aug_bboxes, save_path): """在原图和增强图上绘制bbox,生成对比图""" def draw_bboxes(img, bboxes, color=(0, 255, 0), thickness=2): h, w = img.shape[:2] for cls_id, x_c, y_c, w_norm, h_norm in bboxes: x1 = int((x_c - w_norm / 2) * w) y1 = int((y_c - h_norm / 2) * h) x2 = int((x_c + w_norm / 2) * w) y2 = int((y_c + h_norm / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness) cv2.putText(img, str(cls_id), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) return img vis_orig = draw_bboxes(orig_img.copy(), orig_bboxes, color=(0, 255, 0)) vis_aug = draw_bboxes(aug_img.copy(), aug_bboxes, color=(0, 0, 255)) # 拼接对比图 h1, w1 = vis_orig.shape[:2] h2, w2 = vis_aug.shape[:2] max_h = max(h1, h2) vis_orig_pad = cv2.copyMakeBorder(vis_orig, 0, max_h-h1, 0, 0, cv2.BORDER_CONSTANT, value=(0,0,0)) vis_aug_pad = cv2.copyMakeBorder(vis_aug, 0, max_h-h2, 0, 0, cv2.BORDER_CONSTANT, value=(0,0,0)) combined = np.hstack([vis_orig_pad, vis_aug_pad]) cv2.imwrite(save_path, combined) print(f"Visualization saved to {save_path}") # 使用 orig_img, orig_bboxes, _ = load_labelimg_sample("./data/img_001.jpg", "./data/img_001.txt") aug_img, aug_bboxes = augment_single_sample(orig_img, orig_bboxes, (1920, 1080), get_augmentation_pipeline()) visualize_augmentation(orig_img, orig_bboxes, aug_img, aug_bboxes, "./vis_compare.jpg")生成的vis_compare.jpg左半是原图+绿框,右半是增强图+红框。答辩时这张图比100行日志都有说服力——老师一眼就能看出“旋转后框是否还包住目标”“裁剪后小目标是否被切掉”。
4. LabelMe标注增强的特殊处理:多边形分割的顶点映射与JSON保真
LabelMe常用于实例分割、细粒度检测(如电路板焊点、医疗细胞轮廓),其JSON格式含points顶点数组,增强时必须保证所有顶点经同一变换矩阵映射,且闭合性、顺序性不被破坏。DataAugmentforLabelMe.py不是LabelImg版的简单改写,而是针对JSON结构的深度适配。
4.1 JSON结构解析与顶点提取:为什么不能直接用cv2.polylines
LabelMe JSON关键字段示例:
{ "version": "5.3.0", "flags": {}, "shapes": [ { "label": "person", "points": [[100.0, 200.0], [150.0, 200.0], [150.0, 300.0], [100.0, 300.0]], "group_id": null, "shape_type": "rectangle", "flags": {} }, { "label": "car", "points": [[300.0, 150.0], [400.0, 150.0], [400.0, 250.0], [300.0, 250.0], [350.0, 220.0]], "shape_type": "polygon", "flags": {} } ], "imagePath": "img_001.jpg", "imageHeight": 1080, "imageWidth": 1920 }注意:shape_type可能是rectangle(两点定义)或polygon(多点定义),points中坐标是绝对值(非归一化)。DataAugmentforLabelMe.py首先要做类型识别:
# python def parse_labelme_shapes(json_data): """解析LabelMe JSON,统一转为顶点列表""" shapes = [] for shape in json_data["shapes"]: if shape["shape_type"] == "rectangle": # 矩形转为4点polygon(顺时针) x1, y1 = shape["points"][0] x2, y2 = shape["points"][1] points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] elif shape["shape_type"] == "polygon": points = shape["points"] else: continue # 跳过circle、line等不支持类型 # 保证闭合:首尾点相同(cv2.fillPoly需要) if points[0] != points[-1]: points.append(points[0]) shapes.append({ "label": shape["label"], "points": np.array(points, dtype=np.float32), "shape_type": shape["shape_type"] }) return shapes提示:
cv2.fillPoly要求多边形顶点首尾闭合,否则填充区域错乱。很多开源脚本忽略这点,导致增强后mask出现缺口。
4.2 仿射变换下的顶点映射:矩阵运算必须用np.float32
LabelMe增强的核心是cv2.warpAffine+cv2.transform配合:
# python def apply_affine_to_points(points, M): """对points数组应用仿射变换M""" # points: (N, 2) numpy array # M: 2x3 affine matrix pts_h = np.hstack([points, np.ones((len(points), 1))]) # (N, 3) pts_trans = (M @ pts_h.T).T # (N, 2) return pts_trans.astype(np.float32) def augment_labelme_sample(img, json_data, aug_func_list): """LabelMe专用增强:同步变换图像和所有shapes""" h_orig, w_orig = json_data["imageHeight"], json_data["imageWidth"] shapes = parse_labelme_shapes(json_data) # 对每个shape的points单独映射(因不同shape可能需不同处理) for shape in shapes: # 几何变换前,先记录原始points用于后续校验 shape["points_orig"] = shape["points"].copy() # 执行增强链(仅几何类操作需更新points) for func in aug_func_list: if func.__name__ in ["rotate_affine", "translate_image", "scale_image", "hflip_image", "vflip_image"]: # 获取当前变换矩阵 M = get_affine_matrix(func, img.shape[1], img.shape[0]) for shape in shapes: shape["points"] = apply_affine_to_points(shape["points"], M) # 光度类操作只改img img = func(img) if func.__name__ in ["add_gaussian_noise_adaptive", "blur_image", "adjust_brightness_contrast"] else img # 更新JSON for i, shape in enumerate(shapes): json_data["shapes"][i]["points"] = shape["points"].tolist() # 更新imageHeight/imageWidth(几何变换可能改变尺寸) json_data["imageHeight"], json_data["imageWidth"] = img.shape[0], img.shape[1] return img, json_data关键点:get_affine_matrix必须返回cv2.warpAffine兼容的2×3矩阵,且apply_affine_to_points中pts_h必须是np.float32——用float64会导致cv2.transform结果偏差0.5像素,累积后bbox飘移。这是踩过坑的结论:某次调试发现增强后mask偏右2像素,追查发现是np.array(points)默认float64,强制astype(np.float32)后问题消失。
4.3 JSON写入的保真原则:字段顺序、空格、编码一个都不能少
LabelMe官方读取JSON时对格式敏感。DataAugmentforLabelMe.py写入时严格遵循:
- 字段顺序与原始JSON一致(用
collections.OrderedDict) - 缩进用2个空格(非tab)
- 中文label不转义(
ensure_ascii=False) - 文件编码UTF-8 with BOM(兼容Windows记事本)
# python def save_labelme_json(json_data, json_path): """保真写入LabelMe JSON""" # 重建OrderedDict保持字段顺序 ordered_data = OrderedDict([ ("version", json_data.get("version", "5.3.0")), ("flags", json_data.get("flags", {})), ("shapes", json_data["shapes"]), ("imagePath", json_data.get("imagePath", "")), ("imageData", None), # LabelMe通常为空 ("imageHeight", json_data["imageHeight"]), ("imageWidth", json_data["imageWidth"]) ]) with open(json_path, 'w', encoding='utf-8-sig') as f: json.dump(ordered_data, f, indent=2, ensure_ascii=False) print(f"Saved LabelMe JSON: {json_path}")注意:
encoding='utf-8-sig'中的-sig是关键。没有它,Windows系统下用记事本打开JSON会显示乱码,学生常因此误以为JSON写坏,反复重跑脚本浪费时间。
5. 常见问题排查:这5个坑,90%的初学者至少踩中3个
增强工具看似简单,但实际运行时高频报错。以下是我在带3届毕设、指导12个课程设计中,统计出的最高发5类问题,按“现象→原因→解决”给出可立即执行的方案。
5.1 现象:增强后图片全黑/全白,或bbox全部消失
原因:adjust_brightness_contrast函数中对比度参数alpha过大(如alpha=3.0),导致像素值溢出;或cv2.normalize未指定dtype=cv2.CV_8U,输出为float64导致显示异常。
解决:
- 在
adjust_brightness_contrast中加入钳位:# python def adjust_brightness_contrast(img, alpha=1.2, beta=10): # alpha: 0.5~1.5, beta: -30~30 alpha = np.clip(alpha, 0.5, 1.5) beta = np.clip(beta, -30, 30) img = cv2.convertScaleAbs(img, alpha=alpha, beta=beta) return np.clip(img, 0, 255).astype(np.uint8) # 强制钳位并转uint8 - 所有增强函数末尾加
return img.astype(np.uint8),杜绝float类型残留。
5.2 现象:LabelImg增强后,train.txt里路径正确但训练报IndexError: list index out of range
原因:img_001.txt中某行只有4个字段(如漏写class_id),load_labelimg_sample未捕获,后续int(parts[0])报错;或增强后bbox面积≤0被过滤,导致txt行数<原图bbox数,YOLO解析时索引越界。
解决:
- 在
load_labelimg_sample中增加字段数校验(见3.2节代码); - 在
augment_single_sample的bbox过滤后,添加日志:
并跳过写入,避免生成空txt。# python if len(bboxes_norm) == 0: print(f"[WARN] All bboxes filtered out for {os.path.basename(img_path)}. Skipping.") return None, None
5.3 现象:LabelMe增强后,用LabelMe软件打开JSON报“Invalid JSON format”
原因:JSON写入时用了json.dumps(data, indent=2)但未设ensure_ascii=False,中文label被转为\u4eba\u7269,LabelMe解析失败;或shapes中points含nan值(仿射矩阵奇异导致)。
解决:
- 写入JSON必须用
ensure_ascii=False(见4.3节); - 在
apply_affine_to_points后加nan检查:# python if np.isnan(pts_trans).any(): print(f"[ERROR] NaN detected in transformed points. Skipping this augmentation.") return points # 返回原points
5.4 现象:rename_file.py运行后,部分txt文件没重命名,或重命名错乱
原因:脚本按文件名数字排序,但IMG_1.jpg和IMG_10.jpg中1和10被当字符串比较("10"<"2"),导致顺序错;或.txt文件名与.jpg不完全匹配(如IMG_001.jpeg配IMG_001.txt,但脚本只搜.jpg)。
解决:
- 排序改用自然排序(natural sort):
# python import re def natural_key(s): return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', s)] files.sort(key=lambda x: natural_key(os.path.basename(x))) - 搜索txt时遍历所有图片文件,用
os.path.splitext(img_path)[0] + ".txt"构造txt路径,而非glob搜索。
5.5 现象:增强速度极慢(单图>10秒),CPU占用率100%
原因
本文还有配套的精品资源,点击获取