"这是我在整理训练数据时被逼出来的脚本,分享给和我一样痛苦的你。"
import os
import shutil
from pathlib import Path
def find_extra_image_by_labels(images_folder, labels_folder, backup_folder=None, label_extension=".txt"):
"""
根据标签数据找出多余的图片文件
Args:
images_folder: 图片文件夹路径
labels_folder: 标签文件夹路径
backup_folder: 备份文件夹路径(用于存放多余的图片)
label_extension: 标签文件扩展名
"""
# 设置默认备份文件夹
if backup_folder is None:
backup_folder = Path(images_folder).parent / "extra_images_backup"
# 确保备份文件夹存在
Path(backup_folder).mkdir(parents=True, exist_ok=True)
print("开始根据标签数据查找多余的图片...")
print(f"图片文件夹: {images_folder}")
print(f"标签文件夹: {labels_folder}")
print(f"备份文件夹: {backup_folder}")
print("-" * 60)
# 获取所有标签文件名(不含扩展名)
label_names = set()
print("收集标签文件名...")
for label_path in Path(labels_folder).rglob(f'*{label_extension}'):
label_names.add(label_path.stem)
print(f"✓ 找到 {len(label_names)} 个标签文件")
# 获取所有图片文件
image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp']
image_files = []
print("收集图片文件...")
for ext in image_extensions:
for img_path in Path(images_folder).rglob(f'*{ext}'):
image_files.append(img_path)
for img_path in Path(images_folder).rglob(f'*{ext.upper()}'):
image_files.append(img_path)
print(f"✓ 找到 {len(image_files)} 个图片文件")
# 找出没有对应标签的图片文件
extra_images = []
for img_path in image_files:
if img_path.stem not in label_names:
extra_images.append(img_path)
print(f"⚠ 发现 {len(extra_images)} 个多余的图片文件")
# 验证数量
expected_extra = len(image_files) - len(label_names)
if len(extra_images) != expected_extra:
print(f"⚠ 警告: 实际发现的多余图片数量({len(extra_images)})与预期({expected_extra})不符!")
if not extra_images:
print("没有发现多余的图片文件!")
return []
# 显示多余的图片文件
print(f"\n多余的图片文件列表:")
print("-" * 50)
for i, img_path in enumerate(extra_images):
print(f" {i + 1}. {img_path.relative_to(images_folder)}")
# 生成详细报告
report_path = generate_extra_images_report(
images_folder, labels_folder, backup_folder,
len(image_files), len(label_names), extra_images
)
# 提供处理选项
handle_extra_images(extra_images, backup_folder, images_folder)
return extra_images
def generate_extra_images_report(images_folder, labels_folder, backup_folder,
image_count, label_count, extra_images):
"""
生成多余图片文件的详细报告
"""
report_path = Path(backup_folder) / "多余图片文件报告.txt"
with open(report_path, 'w', encoding='utf-8') as f:
f.write("多余图片文件报告\n")
f.write("=" * 50 + "\n")
f.write(f"生成时间: {get_current_time()}\n")
f.write(f"图片文件夹: {images_folder}\n")
f.write(f"标签文件夹: {labels_folder}\n")
f.write(f"备份文件夹: {backup_folder}\n")
f.write(f"图片文件总数: {image_count}\n")
f.write(f"标签文件总数: {label_count}\n")
f.write(f"理论多余图片数量: {image_count - label_count}\n")
f.write(f"实际发现多余图片数量: {len(extra_images)}\n")
f.write("\n多余图片文件列表:\n")
f.write("-" * 40 + "\n")
for img_path in extra_images:
f.write(f"{img_path.relative_to(images_folder)}\n")
# 添加统计信息
folder_stats = {}
for img_path in extra_images:
folder = img_path.parent.relative_to(images_folder)
folder_stats[folder] = folder_stats.get(folder, 0) + 1
f.write("\n按文件夹分布:\n")
for folder, count in sorted(folder_stats.items()):
f.write(f" {folder}: {count} 个\n")
print(f"📊 详细报告已保存到: {report_path}")
return report_path
def handle_extra_images(extra_images, backup_folder, images_folder):
"""
处理多余的图片文件
"""
if not extra_images:
return
print(f"\n🔧 处理选项:")
print("1. 移动多余的图片到备份文件夹")
print("2. 删除多余的图片")
print("3. 仅查看报告,不进行任何操作")
choice = input("请选择操作 (1/2/3): ").strip()
if choice == "1":
move_extra_images(extra_images, backup_folder, images_folder)
elif choice == "2":
delete_extra_images(extra_images, backup_folder, images_folder)
elif choice == "3":
print("已选择不进行任何操作")
else:
print("无效选择,已取消操作")
def move_extra_images(extra_images, backup_folder, images_folder):
"""
移动多余的图片到备份文件夹
"""
print(f"准备移动 {len(extra_images)} 个多余的图片文件到备份文件夹...")
confirm = input("确认移动? (y/n): ").strip().lower()
if confirm != 'y':
print("操作已取消")
return
moved_count = 0
for img_path in extra_images:
try:
# 保持原有的目录结构
relative_path = img_path.relative_to(images_folder)
target_path = Path(backup_folder) / relative_path
# 确保目标目录存在
target_path.parent.mkdir(parents=True, exist_ok=True)
# 移动文件
shutil.move(str(img_path), str(target_path))
moved_count += 1
if moved_count % 10 == 0:
print(f"已移动 {moved_count} 个文件...")
except Exception as e:
print(f"移动文件失败: {img_path} -> {e}")
print(f"✅ 已移动 {moved_count} 个多余的图片文件到备份文件夹")
print(f"📁 备份文件夹: {backup_folder}")
def delete_extra_images(extra_images, backup_folder, images_folder):
"""
删除多余的图片文件(先备份)
"""
print(f"准备删除 {len(extra_images)} 个多余的图片文件...")
print("⚠ 警告: 此操作将永久删除文件!")
confirm = input("确认删除? (输入 'DELETE' 确认): ").strip()
if confirm != 'DELETE':
print("操作已取消")
return
deleted_count = 0
for img_path in extra_images:
try:
# 先备份文件
relative_path = img_path.relative_to(images_folder)
backup_path = Path(backup_folder) / "deleted_images" / relative_path
backup_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(str(img_path), str(backup_path))
# 删除原文件
img_path.unlink()
deleted_count += 1
if deleted_count % 10 == 0:
print(f"已删除 {deleted_count} 个文件...")
except Exception as e:
print(f"删除文件失败: {img_path} -> {e}")
print(f"✅ 已删除 {deleted_count} 个多余的图片文件")
print(f"📁 备份文件保存在: {Path(backup_folder) / 'deleted_images'}")
def get_current_time():
"""获取当前时间字符串"""
from datetime import datetime
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
def verify_image_label_match(images_folder, labels_folder, label_extension=".txt"):
"""
验证图片和标签的匹配情况
"""
print("验证图片和标签的匹配情况...")
# 获取所有标签文件名
label_names = set()
for label_path in Path(labels_folder).rglob(f'*{label_extension}'):
label_names.add(label_path.stem)
# 获取所有图片文件名
image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp']
image_names = set()
for ext in image_extensions:
for img_path in Path(images_folder).rglob(f'*{ext}'):
image_names.add(img_path.stem)
# 计算匹配情况
matched = image_names & label_names
images_without_labels = image_names - label_names
labels_without_images = label_names - image_names
print(f"匹配统计:")
print(f" 图片文件总数: {len(image_names)}")
print(f" 标签文件总数: {len(label_names)}")
print(f" 完全匹配的文件对: {len(matched)}")
print(f" 有图片但无标签: {len(images_without_labels)}")
print(f" 有标签但无图片: {len(labels_without_images)}")
if images_without_labels:
print(f"\n有图片但无标签的文件 (前10个):")
for i, name in enumerate(list(images_without_labels)[:10]):
print(f" {i + 1}. {name}")
return len(matched), len(images_without_labels), len(labels_without_images)
def find_single_extra_image(images_folder, labels_folder, label_extension=".txt"):
"""
专门找出单个多余的图片(适用于你的情况)
"""
print("查找单个多余的图片文件...")
# 获取所有标签文件名
label_names = set()
for label_path in Path(labels_folder).rglob(f'*{label_extension}'):
label_names.add(label_path.stem)
# 获取所有图片文件
image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp']
image_files = []
for ext in image_extensions:
for img_path in Path(images_folder).rglob(f'*{ext}'):
image_files.append(img_path)
# 找出没有对应标签的图片文件
extra_images = []
for img_path in image_files:
if img_path.stem not in label_names:
extra_images.append(img_path)
if len(extra_images) == 1:
print(f"✅ 找到唯一的多余图片文件:")
print(f" {extra_images[0].relative_to(images_folder)}")
return extra_images[0]
elif len(extra_images) > 1:
print(f"⚠ 发现 {len(extra_images)} 个多余的图片文件:")
for img_path in extra_images:
print(f" {img_path.relative_to(images_folder)}")
return extra_images
else:
print("❌ 没有发现多余的图片文件")
return None
def interactive_extra_image_finder():
"""
交互式多余图片查找工具
"""
print("=== 多余图片查找工具 ===")
# 获取用户输入
images_folder = input("请输入图片文件夹路径: ").strip()
labels_folder = input("请输入标签文件夹路径: ").strip()
# 选择标签文件扩展名
print("\n请选择标签文件扩展名:")
print("1. .txt (默认)")
print("2. .json")
print("3. .xml")
print("4. 自定义")
ext_choice = input("请选择 (1/2/3/4): ").strip()
if ext_choice == "1":
label_extension = ".txt"
elif ext_choice == "2":
label_extension = ".json"
elif ext_choice == "3":
label_extension = ".xml"
elif ext_choice == "4":
label_extension = input("请输入自定义扩展名 (例如 .txt): ").strip()
else:
label_extension = ".txt"
print(f"使用标签扩展名: {label_extension}")
# 选择操作
print("\n请选择操作:")
print("1. 仅验证匹配情况")
print("2. 查找单个多余的图片")
print("3. 查找并处理所有多余的图片")
choice = input("请选择 (1/2/3): ").strip()
if choice == "1":
verify_image_label_match(images_folder, labels_folder, label_extension)
elif choice == "2":
extra_image = find_single_extra_image(images_folder, labels_folder, label_extension)
if extra_image:
print(f"\n找到的多余图片: {extra_image}")
elif choice == "3":
backup_folder = input("请输入备份文件夹路径 (留空使用默认路径): ").strip()
if not backup_folder:
backup_folder = None
find_extra_image_by_labels(images_folder, labels_folder, backup_folder, label_extension)
else:
print("无效选择")
使用示例
if name == "main ":
方法1: 直接调用(查找并处理所有多余的图片)
find_extra_image_by_labels(
images_folder=r"E:\data(修改调整的数据-模型迭代用)\imags\val",
labels_folder=r"E:\data(修改调整的数据-模型迭代用)\labels\val",
backup_folder="E:/extra_images_backup"
)
# 方法2: 仅验证匹配情况
# verify_image_label_match("E:/images", "E:/labels")
# 方法3: 专门查找单个多余的图片
# find_single_extra_image("E:/images", "E:/labels")
# 方法4: 交互式使用
interactive_extra_image_finder()