Python 实现 VOC格式目标检测数据集的按类别分拣脚本
flyfish
从多层级的混合标注数据集中,筛选并拆分出 dog(狗)和 cat(猫)两类的图片数据
-
多层目录自动扫描识别
递归遍历源根目录下的所有子文件夹,自动识别出同时包含
jpg图片文件夹和xml标注文件夹的有效数据目录;处理过程会完整保留源数据的目录层级结构,不会打乱原有文件组织方式。 -
VOC标注文件解析与类别筛选
解析标准VOC格式的XML标注文件,提取每个标注目标的类别名称;仅保留
dog和cat两类目标。单张图片包含多个同类别目标时会自动去重,同一张图只会被归类一次;
若单张图片同时标注了狗和猫,则这张图片会同时被分到
dog和cat两个类别目录中。 -
按类别分类拷贝图片
根据XML标注的类别结果,将同名jpg图片拷贝到目标根目录下的对应类别文件夹;使用
shutil.copy2拷贝文件,会保留图片的原始创建时间、修改时间等元数据;目标路径中缺失的目录会自动创建,无需手动提前建立文件夹。
得到的结果
分类好的图片数据集
在配置的目标根目录(TARGET_ROOT)下,会生成 dog 和 cat 两个独立的分类文件夹;每个文件夹内都会完整复刻源数据的目录层级结构,分别存放标注中包含对应类别的jpg图片。
python
import os
import shutil
import xml.etree.ElementTree as ET
# ===================== 路径配置 =====================
# 源文件夹根目录(存放多层目录数据的顶层文件夹)
SOURCE_ROOT = r"E:\data"
# 目标输出根目录(程序会自动创建 dog / cat 子文件夹)
TARGET_ROOT = r"E:\animal\"
# =====================================================
def parse_voc_xml(xml_file_path):
"""
解析VOC格式XML,提取目标类别(dog / cat)
返回:去重后的类别列表
"""
target_classes = []
try:
tree = ET.parse(xml_file_path)
root = tree.getroot()
# 遍历所有标注目标
for obj in root.findall("object"):
class_name = obj.find("name").text.strip()
# 匹配目标类别
if class_name == "dog":
target_classes.append("dog")
elif class_name == "cat":
target_classes.append("cat")
# 去重:单张图多个同类别目标,只归类一次
return list(set(target_classes))
except Exception as e:
print(f"XML解析失败:{xml_file_path},错误:{str(e)}")
return []
def copy_image_with_structure(src_jpg, rel_path, target_class):
"""
按照原目录层级结构拷贝图片到对应类别目录
:param src_jpg: 源图片完整路径
:param rel_path: 源目录相对根路径,用于复刻目录结构
:param target_class: 目标类别文件夹名
"""
# 拼接目标完整路径:目标根目录/类别/原目录层级/图片名
target_dir = os.path.join(TARGET_ROOT, target_class, rel_path)
target_jpg = os.path.join(target_dir, os.path.basename(src_jpg))
# 自动创建不存在的目录
os.makedirs(target_dir, exist_ok=True)
# 拷贝文件并保留原始元数据(创建/修改时间等)
shutil.copy2(src_jpg, target_jpg)
print(f"已拷贝:{os.path.basename(src_jpg)} → {target_class}/{rel_path}")
def main():
print("========== 开始扫描并处理文件 ==========\n")
# 递归遍历源根目录下所有文件夹
for root_dir, sub_dirs, files in os.walk(SOURCE_ROOT):
# 核心判断:当前目录下同时存在 jpg 和 xml 两个子文件夹,才作为有效数据目录
if "jpg" in sub_dirs and "xml" in sub_dirs:
# 计算当前目录相对于源根目录的路径,用于保留层级结构
relative_path = os.path.relpath(root_dir, SOURCE_ROOT)
jpg_folder = os.path.join(root_dir, "jpg")
xml_folder = os.path.join(root_dir, "xml")
print(f"\n找到目标目录:{relative_path}")
print(f" JPG文件夹:{jpg_folder}")
print(f" XML文件夹:{xml_folder}")
# 遍历当前目录下所有XML标注文件
for xml_name in os.listdir(xml_folder):
if not xml_name.lower().endswith(".xml"):
continue # 跳过非XML文件
xml_path = os.path.join(xml_folder, xml_name)
file_stem = os.path.splitext(xml_name)[0] # 提取文件名前缀
jpg_path = os.path.join(jpg_folder, file_stem + ".jpg") # 匹配同名图片
# 校验对应图片是否存在,不存在则跳过
if not os.path.exists(jpg_path):
print(f"跳过:{xml_name} 无匹配JPG文件")
continue
# 解析XML获取标注类别
detected_classes = parse_voc_xml(xml_path)
# 按类别拷贝图片到对应目录
for cls in detected_classes:
copy_image_with_structure(jpg_path, relative_path, cls)
print("\n========== 任务全部完成 ==========")
if __name__ == "__main__":
main()