手把手教你用Python脚本搞定NightOwls和WiderPerson数据集转YOLO格式(附完整代码)

张开发
2026/5/6 21:55:45 15 分钟阅读

分享文章

手把手教你用Python脚本搞定NightOwls和WiderPerson数据集转YOLO格式(附完整代码)
Python实战NightOwls与WiderPerson数据集高效转YOLO格式全攻略当我们需要训练一个行人检测模型时NightOwls和WiderPerson是两个极具价值的数据集。NightOwls专注于夜间场景下的行人检测而WiderPerson则提供了更广泛的日常场景。本文将带你从零开始通过Python脚本将这两个数据集转换为YOLO格式为后续的模型训练做好准备。1. 环境准备与数据获取1.1 安装必要依赖在开始之前确保你的Python环境已经安装了以下必要的库pip install opencv-python numpy tqdm pillow这些库将帮助我们处理图像、进行坐标转换以及显示进度条。1.2 数据集下载与结构NightOwls数据集可以从官方网站下载建议先下载验证集进行测试。WiderPerson数据集可以从官方页面获取。下载完成后建议按照以下目录结构组织文件datasets/ ├── NightOwls/ │ ├── Validation/ │ │ ├── nightowls_validation.json │ │ └── nightowls_validation/ │ │ └── *.png └── WiderPerson/ ├── Images/ │ └── *.jpg └── Annotations/ └── *.txt2. NightOwls数据集转换实战2.1 JSON到YOLO格式的核心转换NightOwls的标注信息存储在JSON文件中我们需要将其转换为YOLO格式的TXT文件。以下是核心转换代码import json import os from tqdm import tqdm def convert_nightowls_to_yolo(json_path, output_dir, class_mappingNone): 将NightOwls JSON标注转换为YOLO格式 :param json_path: JSON标注文件路径 :param output_dir: 输出目录 :param class_mapping: 类别映射字典 os.makedirs(output_dir, exist_okTrue) with open(json_path) as f: data json.load(f) # 创建图像ID到图像信息的映射 image_info {img[id]: img for img in data[images]} # 按图像ID分组标注 annotations_dict {} for ann in data[annotations]: if ann[ignore] 0: # 忽略标记为ignore的标注 img_id ann[image_id] if img_id not in annotations_dict: annotations_dict[img_id] [] annotations_dict[img_id].append(ann) # 处理每个图像的标注 for img_id, anns in tqdm(annotations_dict.items()): img image_info[img_id] txt_path os.path.join(output_dir, f{os.path.splitext(img[file_name])[0]}.txt) with open(txt_path, w) as f: for ann in anns: # 获取类别ID应用映射如果存在 class_id class_mapping[ann[category_id]] if class_mapping else ann[category_id] # 转换边界框坐标 bbox ann[bbox] x_center (bbox[0] bbox[2]/2) / img[width] y_center (bbox[1] bbox[3]/2) / img[height] width bbox[2] / img[width] height bbox[3] / img[height] f.write(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n)提示NightOwls原始类别包括行人、自行车驾驶员、摩托车驾驶员和忽略区域。在实际应用中我们通常将前三者合并为person类。2.2 处理数据不平衡问题NightOwls数据集存在严重的正负样本不平衡问题——标注文件只有6595个而图片数据有51848张。我们可以通过以下策略解决保留所有正样本6595张有标注的图片随机选择部分负样本5000张无标注图片生成空标签文件为负样本创建空的TXT文件实现代码如下import random import shutil def balance_nightowls_dataset(image_dir, label_dir, output_image_dir, output_label_dir, neg_sample_count5000): 平衡NightOwls数据集的正负样本 os.makedirs(output_image_dir, exist_okTrue) os.makedirs(output_label_dir, exist_okTrue) # 获取所有有标注的图像 labeled_images {os.path.splitext(f)[0] for f in os.listdir(label_dir)} # 复制所有正样本 for img_name in os.listdir(image_dir): base_name os.path.splitext(img_name)[0] if base_name in labeled_images: # 复制图像并转换为JPG格式 img Image.open(os.path.join(image_dir, img_name)) img.save(os.path.join(output_image_dir, f{base_name}.jpg), quality95) # 复制标签文件 shutil.copy( os.path.join(label_dir, f{base_name}.txt), os.path.join(output_label_dir, f{base_name}.txt) ) # 随机选择负样本 all_images {os.path.splitext(f)[0] for f in os.listdir(image_dir)} neg_images list(all_images - labeled_images) selected_neg random.sample(neg_images, min(neg_sample_count, len(neg_images))) for base_name in selected_neg: # 复制图像并转换为JPG格式 img Image.open(os.path.join(image_dir, f{base_name}.png)) img.save(os.path.join(output_image_dir, f{base_name}.jpg), quality95) # 创建空标签文件 open(os.path.join(output_label_dir, f{base_name}.txt), w).close()3. WiderPerson数据集转换技巧3.1 原始格式解析WiderPerson的标注格式比较特殊每个TXT文件的第一行是标注数量后面每行是一个标注格式为class_id x1 y1 x2 y2其中类别ID含义如下类别ID描述1行人2骑手3部分可见人员4忽略区域5人群3.2 YOLO格式转换实现对于行人检测任务我们通常只关注前三个类别1-3并将它们合并为person类def convert_widerperson_to_yolo(anno_dir, image_dir, output_dir): 转换WiderPerson标注为YOLO格式 os.makedirs(output_dir, exist_okTrue) for anno_file in tqdm(os.listdir(anno_dir)): img_name os.path.splitext(anno_file)[0] .jpg img_path os.path.join(image_dir, img_name) # 获取图像尺寸 img Image.open(img_path) width, height img.size # 读取并处理标注文件 with open(os.path.join(anno_dir, anno_file)) as f: lines f.readlines() # 跳过第一行标注数量 lines lines[1:] if lines else [] # 准备YOLO格式标注 yolo_annos [] for line in lines: parts line.strip().split() if len(parts) 5: class_id int(parts[0]) if class_id in {1, 2, 3}: # 只保留前三个类别 x1, y1, x2, y2 map(int, parts[1:]) # 转换为YOLO格式 x_center (x1 x2) / 2 / width y_center (y1 y2) / 2 / height box_width (x2 - x1) / width box_height (y2 - y1) / height yolo_annos.append(f0 {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}) # 写入YOLO格式文件 output_file os.path.join(output_dir, os.path.splitext(anno_file)[0] .txt) with open(output_file, w) as f: f.write(\n.join(yolo_annos))3.3 数据验证与可视化转换完成后建议验证标注是否正确。以下代码可以帮助可视化标注结果def visualize_yolo_labels(image_dir, label_dir, output_dir, class_namesNone): 可视化YOLO格式标注 os.makedirs(output_dir, exist_okTrue) for label_file in tqdm(os.listdir(label_dir)): img_name os.path.splitext(label_file)[0] .jpg img_path os.path.join(image_dir, img_name) if not os.path.exists(img_path): continue img cv2.imread(img_path) height, width img.shape[:2] # 读取标注 with open(os.path.join(label_dir, label_file)) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) 5: class_id, xc, yc, w, h map(float, parts) # 转换为像素坐标 x1 int((xc - w/2) * width) y1 int((yc - h/2) * height) x2 int((xc w/2) * width) y2 int((yc h/2) * height) # 绘制边界框 color (0, 0, 255) # 红色 cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) # 添加类别标签 if class_names and int(class_id) in class_names: label class_names[int(class_id)] cv2.putText(img, label, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) # 保存可视化结果 cv2.imwrite(os.path.join(output_dir, img_name), img)4. 数据集合并与划分策略4.1 合并两个数据集合并NightOwls和WiderPerson数据集可以增加数据的多样性。以下是合并代码def merge_datasets(dataset1_dir, dataset2_dir, output_dir): 合并两个数据集 os.makedirs(os.path.join(output_dir, images), exist_okTrue) os.makedirs(os.path.join(output_dir, labels), exist_okTrue) # 复制第一个数据集 for img in os.listdir(os.path.join(dataset1_dir, images)): shutil.copy( os.path.join(dataset1_dir, images, img), os.path.join(output_dir, images, img) ) label_file os.path.splitext(img)[0] .txt shutil.copy( os.path.join(dataset1_dir, labels, label_file), os.path.join(output_dir, labels, label_file) ) # 复制第二个数据集处理可能的文件名冲突 for img in os.listdir(os.path.join(dataset2_dir, images)): base_name os.path.splitext(img)[0] ext os.path.splitext(img)[1] # 检查是否已存在同名文件 dst_img img counter 1 while os.path.exists(os.path.join(output_dir, images, dst_img)): dst_img f{base_name}_{counter}{ext} counter 1 # 复制图像和标签 shutil.copy( os.path.join(dataset2_dir, images, img), os.path.join(output_dir, images, dst_img) ) label_src os.path.splitext(img)[0] .txt label_dst os.path.splitext(dst_img)[0] .txt shutil.copy( os.path.join(dataset2_dir, labels, label_src), os.path.join(output_dir, labels, label_dst) )4.2 数据集划分最佳实践合理划分数据集对模型训练至关重要。以下代码实现了随机划分def split_dataset(data_dir, output_dir, ratios(0.8, 0.1, 0.1)): 划分数据集为训练集、验证集和测试集 train_ratio, val_ratio, test_ratio ratios # 创建输出目录 for subset in [train, val, test]: os.makedirs(os.path.join(output_dir, subset, images), exist_okTrue) os.makedirs(os.path.join(output_dir, subset, labels), exist_okTrue) # 获取所有图像文件并打乱 image_files os.listdir(os.path.join(data_dir, images)) random.shuffle(image_files) # 计算划分点 num_images len(image_files) train_end int(num_images * train_ratio) val_end train_end int(num_images * val_ratio) # 复制文件到相应目录 for i, img_file in enumerate(tqdm(image_files)): base_name os.path.splitext(img_file)[0] label_file base_name .txt if i train_end: subset train elif i val_end: subset val else: subset test # 复制图像 shutil.copy( os.path.join(data_dir, images, img_file), os.path.join(output_dir, subset, images, img_file) ) # 复制标签 shutil.copy( os.path.join(data_dir, labels, label_file), os.path.join(output_dir, subset, labels, label_file) )4.3 创建YOLO配置文件最后我们需要创建一个YAML配置文件供YOLO训练使用# person.yaml train: ../datasets/person/train/images val: ../datasets/person/val/images test: ../datasets/person/test/images nc: 1 # 类别数量 names: [person] # 类别名称这个配置文件指定了训练、验证和测试集的路径以及类别信息。在实际项目中你可能需要根据数据集的实际路径进行调整。

更多文章