AI智能体行为风格度量与生成:从算法原理到游戏AI实践
2026/5/12 20:23:07
| 划分基准 | 推荐度 | 优点 | 缺点/风险 | 适用场景 |
|---|---|---|---|---|
| JPEGImages(图片) | ★★★★★ | 源头操作,保证每张有效样本必有图;通过相同 ID 加载标注天然同步;无图无标注可立即发现并清理;主流框架均以图片列表为准 | 需额外检查标注文件是否存在(但本身利于发现脏数据) | 通用首选,尤其适合自定义数据集、工业项目、可能存在脏数据的场景 |
| Annotations(原始 XML) | ★★★☆☆ | 传统 VOC 官方做法,老代码常见;若数据 100% 干净可直接使用 | 若存在“只有 XML 无图”或“只有图无 XML”会导致加载崩溃;易掩盖数据问题 | 仅确信数据集严格 1:1 且干净时使用(如官方 VOC2007/2012) |
VOCdevkit #举例 └── VOC2012 ├── Annotations # 每张图片对应的 XML 标注文件 │ ├── 2007_000001.xml │ ├── 2007_000002.xml │ └── … ├── ImageSets # 训练/验证/测试集的切分列表 │ ├── Action # 动作识别任务列表(可选) │ ├── Layout # 人体部位布局列表(可选) │ ├── Main # 目标检测/分类任务列表 │ │ ├── train.txt │ │ ├── val.txt │ │ └── trainval.txt │ └── Segmentation # 分割任务列表 │ ├── train.txt │ ├── val.txt │ └── trainval.txt ├── JPEGImages # 原始 JPG 图片 │ ├── 2007_000001.jpg │ ├── 2007_000002.jpg │ └── … ├── SegmentationClass # 语义分割标签图(类别级 PNG) │ ├── 2007_000001.png │ ├── 2007_000002.png │ └── … └── SegmentationObject # 实例分割标签图(实例级 PNG) ├── 2007_000001.png ├── 2007_000002.png └── …对比基于两个文件夹不同
# 你的代码逻辑解读def__getitem__(self,idx):img_id=self.ids[idx]# 1. 首先从 train.txt/val.txt 获取一个图片ID (如:2007_000032)img_path=os.path.join(self.img_dir,img_id+'.jpg')# 2. 拼接出图片路径annot_path=os.path.join(self.annot_dir,img_id+'.txt')# 3. 拼接出标注文件路径# ... 然后加载图片和对应的标注| 模型 / 框架类型 | 推荐划分依据 | 理由 |
|---|---|---|
| 经典 Faster R-CNN(py-faster-rcnn、jwyang 等老代码) | Annotations (XML) | 这些代码硬依赖 VOC 标准结构,ImageSets/Main/*.txt 通常基于 XML 生成。 |
| 现代 PyTorch 实现(bubbliiiing、AIZOOTech 等国内仓库) | JPEGImages (图片) | 它们通常用自定义 voc_annotation.py,你可以修改脚本让它遍历图片文件夹生成 train.txt。 |
| torchvision FasterRCNN、MMDetection、Detectron2 | JPEGImages (图片) | 官方都以图片路径列表(或 COCO json 中的 images 字段)为主。 |
| YOLO 系列(Ultralytics YOLOv5/v8/v10) | JPEGImages (图片) | data.yaml 中 train/val 指向图片文件夹,自动查找同名 .txt 标注。 |
| 自定义数据集(强烈建议) | JPEGImages (图片) | 最安全、最灵活。 |
#!/usr/bin/env python3# 带数据一致性检查功能:获取所有图片的ID。为每个ID查找对应的标注文件,即检查 Annotations_txt/2007_000032.txt 是否存在。报告缺失情况,告诉你哪些图片没有标注,帮你提前发现并修复数据问题。""" 数据集划分脚本:按8:1:1比例随机划分训练集、验证集、测试集 保存为ImageSets/Main/train.txt, val.txt, test.txt """importosimportrandomimportargparsefrompathlibimportPathfromtypingimportList,Tupledefsplit_dataset(data_root:str,jpeg_dir:str="JPEGImages",output_dir:str="ImageSets/Main",train_ratio:float=0.8,val_ratio:float=0.1,test_ratio:float=0.1,seed:int=42,shuffle:bool=True)->None:""" 随机划分数据集并保存划分结果 参数: data_root: VOC数据集根目录 jpeg_dir: 图片文件夹名称 output_dir: 输出文件夹名称 train_ratio: 训练集比例 val_ratio: 验证集比例 test_ratio: 测试集比例 seed: 随机种子(确保可复现) shuffle: 是否打乱数据 """# 验证比例总和为1total_ratio=train_ratio+val_ratio+test_ratioifabs(total_ratio-1.0)>0.001:raiseValueError(f"比例总和应为1.0,当前为{total_ratio}")# 设置随机种子random.seed(seed)# 构建路径jpeg_path=Path(data_root)/jpeg_dir output_path=Path(data_root)/output_dirprint("="*60)print("数据集划分工具")print("="*60)# 1. 获取所有图片文件ifnotjpeg_path.exists():raiseFileNotFoundError(f"图片文件夹不存在:{jpeg_path}")# 支持多种图片格式image_extensions={'.jpg','.jpeg','.png','.bmp'}image_files=[]forextinimage_extensions:image_files.extend(jpeg_path.glob(f'*{ext}'))image_files.extend(jpeg_path.glob(f'*{ext.upper()}'))ifnotimage_files:raiseFileNotFoundError(f"在{jpeg_path}中未找到任何图片文件")# 提取纯文件名(不带扩展名)image_ids=[]forimg_fileinimage_files:# 保留原始文件名,去除扩展名image_ids.append(img_file.stem)print(f"找到{len(image_ids)}张图片")# 2. 去重并排序(确保可复现)image_ids=list(set(image_ids))# 去重image_ids.sort()# 排序,确保每次相同的顺序# 3. 打乱顺序ifshuffle:print(f"使用随机种子{seed}打乱数据顺序...")random.shuffle(image_ids)# 4. 计算划分点total_count=len(image_ids)train_count=int(total_count*train_ratio)val_count=int(total_count*val_ratio)# 处理可能的舍入误差,确保测试集包含所有剩余图片test_count=total_count-train_count-val_count# 5. 执行划分train_ids=image_ids[:train_count]val_ids=image_ids[train_count:train_count+val_count]test_ids=image_ids[train_count+val_count:]print("\n划分结果统计:")print("-"*40)print(f"训练集:{len(train_ids)}张 ({len(train_ids)/total_count*100:.1f}%)")print(f"验证集:{len(val_ids)}张 ({len(val_ids)/total_count*100:.1f}%)")print(f"测试集:{len(test_ids)}张 ({len(test_ids)/total_count*100:.1f}%)")print(f"总计:{total_count}张")# 6. 创建输出目录output_path.mkdir(parents=True,exist_ok=True)# 7. 保存划分文件train_file=output_path/"train.txt"val_file=output_path/"val.txt"test_file=output_path/"test.txt"withopen(train_file,'w',encoding='utf-8')asf:f.write('\n'.join(train_ids))withopen(val_file,'w',encoding='utf-8')asf:f.write('\n'.join(val_ids))withopen(test_file,'w',encoding='utf-8')asf:f.write('\n'.join(test_ids))print("\n划分文件已保存:")print(f"{train_file}")print(f"{val_file}")print(f"{test_file}")# 8. 保存划分详情(可选)detail_file=output_path/"split_details.txt"withopen(detail_file,'w',encoding='utf-8')asf:f.write("数据集划分详情\n")f.write("="*40+"\n")f.write(f"数据根目录:{data_root}\n")f.write(f"随机种子:{seed}\n")f.write(f"总图片数:{total_count}\n")f.write(f"训练集:{len(train_ids)}({train_ratio*100:.1f}%)\n")f.write(f"验证集:{len(val_ids)}({val_ratio*100:.1f}%)\n")f.write(f"测试集:{len(test_ids)}({test_ratio*100:.1f}%)\n")f.write("\n划分比例: 训练集:验证集:测试集 = ")f.write(f"{train_ratio}:{val_ratio}:{test_ratio}\n")print(f"划分详情:{detail_file}")print("="*60)defcheck_annotation_compatibility(data_root:str,jpeg_dir:str="JPEGImages",annot_dir:str="Annotations_txt")->None:""" 检查图片文件和标注文件的对应关系 参数: data_root: 数据集根目录 jpeg_dir: 图片文件夹 annot_dir: 标注文件夹 """print("\n检查标注文件兼容性...")jpeg_path=Path(data_root)/jpeg_dir annot_path=Path(data_root)/annot_dir# 获取图片文件image_files=list(jpeg_path.glob('*.jpg'))image_ids=[f.stemforfinimage_files]missing_annotations=[]forimg_idinimage_ids:annot_file=annot_path/f"{img_id}.txt"ifnotannot_file.exists():missing_annotations.append(img_id)ifmissing_annotations:print(f"警告:{len(missing_annotations)}张图片缺少对应的标注文件")iflen(missing_annotations)<=10:print("缺失标注的图片ID:")forimg_idinmissing_annotations:print(f" -{img_id}")else:print("前10个缺失标注的图片ID:")forimg_idinmissing_annotations[:10]:print(f" -{img_id}")print(f" ... 共{len(missing_annotations)}个")else:print("✓ 所有图片都有对应的标注文件")defmain():"""命令行入口函数"""parser=argparse.ArgumentParser(description="数据集划分工具 (8:1:1比例)")parser.add_argument("--data_root",type=str,default="./VOC2012",help="VOC数据集根目录 (默认: ./VOC2012)")parser.add_argument("--jpeg_dir",type=str,default="JPEGImages",help="图片文件夹名称 (默认: JPEGImages)")parser.add_argument("--output_dir",type=str,default="ImageSets/Main",help="输出文件夹名称 (默认: ImageSets/Main)")parser.add_argument("--train_ratio",type=float,default=0.8,help="训练集比例 (默认: 0.8)")parser.add_argument("--val_ratio",type=float,default=0.1,help="验证集比例 (默认: 0.1)")parser.add_argument("--test_ratio",type=float,default=0.1,help="测试集比例 (默认: 0.1)")parser.add_argument("--seed",type=int,default=42,help="随机种子 (默认: 42)")parser.add_argument("--no_shuffle",action="store_true",help="不打乱数据顺序")parser.add_argument("--check_annotations",action="store_true",help="检查标注文件兼容性")args=parser.parse_args()try:# 执行划分split_dataset(data_root=args.data_root,jpeg_dir=args.jpeg_dir,output_dir=args.output_dir,train_ratio=args.train_ratio,val_ratio=args.val_ratio,test_ratio=args.test_ratio,seed=args.seed,shuffle=notargs.no_shuffle)# 如果需要,检查标注文件ifargs.check_annotations:check_annotation_compatibility(data_root=args.data_root,jpeg_dir=args.jpeg_dir,annot_dir="Annotations_txt"# 你的标注文件夹)exceptExceptionase:print(f"错误:{e}")return1return0if__name__=="__main__":exit(main())importosimportxml.etree.ElementTreeasET# VOC2012的20个类别(按顺序对应class_id 1-20)VOC_CLASSES=['aeroplane','bicycle','bird','boat','bottle','bus','car','cat','chair','cow','diningtable','dog','horse','motorbike','person','pottedplant','sheep','sofa','train','tvmonitor']defxml_to_txt(xml_dir,txt_dir):""" 将VOC XML标注转换为TXT格式 :param xml_dir: XML标注文件所在目录(如VOCdevkit/VOC2007/Annotations) :param txt_dir: 输出TXT文件的保存目录 """# 创建输出目录(如果不存在)os.makedirs(txt_dir,exist_ok=True)# 遍历所有XML文件forxml_filenameinos.listdir(xml_dir):ifnotxml_filename.endswith('.xml'):continue# 只处理.xml文件# 解析XMLxml_path=os.path.join(xml_dir,xml_filename)tree=ET.parse(xml_path)root=tree.getroot()# 获取图像尺寸(可选,用于验证坐标是否合理)size=root.find('size')width=int(size.find('width').text)height=int(size.find('height').text)# 提取所有目标的标注txt_content=[]forobjinroot.iter('object'):# 获取类别名称cls_name=obj.find('name').text.strip().lower()ifcls_namenotinVOC_CLASSES:continue# 跳过不在VOC类别中的目标# 转换类别名称为class_id(1-20)cls_id=VOC_CLASSES.index(cls_name)+1# 索引+1,确保从1开始# 获取边界框坐标(xmin, ymin, xmax, ymax)bbox=obj.find('bndbox')x1=float(bbox.find('xmin').text)y1=float(bbox.find('ymin').text)x2=float(bbox.find('xmax').text)y2=float(bbox.find('ymax').text)# 确保坐标在图像范围内(可选,防止越界)x1=max(0,min(x1,width))y1=max(0,min(y1,height))x2=max(x1,min(x2,width))y2=max(y1,min(y2,height))# 写入TXT内容(格式:x1 y1 x2 y2 class_id)txt_content.append(f"{x1}{y1}{x2}{y2}{cls_id}")# 保存为TXT文件(与XML同名,后缀改为.txt)txt_filename=xml_filename.replace('.xml','.txt')txt_path=os.path.join(txt_dir,txt_filename)withopen(txt_path,'w')asf:f.write('\n'.join(txt_content))# 打印进度if(len(os.listdir(txt_dir))%100==0):print(f"已转换{len(os.listdir(txt_dir))}个文件...")print(f"转换完成!共处理{len(os.listdir(txt_dir))}个XML文件,保存至{txt_dir}")# -------------------------- 运行转换脚本 --------------------------if__name__=="__main__":# 请替换为你的实际路径xml_dir="./VOC2012/Annotations"# VOC原始XML标注目录txt_dir="./VOC2012/Annotations_txt"# 输出TXT目录(需与RCNN代码中的annot_dir一致)# 执行转换xml_to_txt(xml_dir,txt_dir)