python
复制代码
# coding: utf-8
import os
from typing import Set, List, Tuple
class DatasetChecker:
"""数据集与标注文件比对工具,用于检查图片和标注文件的匹配情况"""
def __init__(self, img_dir: str, label_dir: str):
"""
初始化检查器
:param img_dir: 图片文件夹路径
:param label_dir: 标注文件夹路径
"""
self.img_dir = img_dir
self.label_dir = label_dir
self.img_files = self._get_files(img_dir)
self.label_files = self._get_files(label_dir)
self.img_basenames = self._get_basenames(self.img_files)
self.label_basenames = self._get_basenames(self.label_files)
def _get_files(self, dir_path: str) -> List[str]:
"""获取文件夹中的所有文件名(包含扩展名)"""
if not os.path.exists(dir_path):
raise FileNotFoundError(f"文件夹不存在: {dir_path}")
return os.listdir(dir_path)
def _get_basename(self, filename: str) -> str:
"""提取文件名(去除扩展名)"""
return os.path.splitext(filename)[0]
def _get_basenames(self, files: List[str]) -> Set[str]:
"""将文件名列表转换为去除扩展名的集合"""
return {self._get_basename(f) for f in files}
def get_extra_files(self) -> Tuple[Set[str], Set[str]]:
"""
计算多余的文件
:return: 元组 (图片文件夹中多余的文件, 标注文件夹中多余的文件)
"""
extra_imgs = self.img_basenames - self.label_basenames
extra_labels = self.label_basenames - self.img_basenames
return extra_imgs, extra_labels
def find_original_filenames(self, basenames: Set[str], source_files: List[str]) -> List[str]:
"""
根据去除扩展名的文件名,查找原始带扩展名的文件名
:param basenames: 去除扩展名的文件名集合
:param source_files: 原始文件列表(带扩展名)
:return: 原始文件名列表
"""
original_files = []
for basename in basenames:
matches = [f for f in source_files if self._get_basename(f) == basename]
if matches:
original_files.append(matches[0]) # 取第一个匹配项
return original_files
def print_results(self):
"""打印比对结果"""
extra_imgs, extra_labels = self.get_extra_files()
# 处理图片文件夹多余文件
img_original = self.find_original_filenames(extra_imgs, self.img_files)
print(f"图片文件夹中多余的文件(无对应标注):{len(img_original)} 个")
for file in img_original:
print(f" - {file}")
# 处理标注文件夹多余文件
label_original = self.find_original_filenames(extra_labels, self.label_files)
print(f"\n标注文件夹中多余的文件(无对应图片):{len(label_original)} 个")
for file in label_original:
print(f" - {file}")
# 使用示例
if __name__ == "__main__":
img_directory = r'C:\Users\123\Desktop\fsdownload\images'
label_directory = r'C:\Users\123\Desktop\fsdownload\labels'
try:
checker = DatasetChecker(img_directory, label_directory)
checker.print_results()
except Exception as e:
print(f"错误: {e}")
带界面
python
复制代码
# coding: utf-8
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from typing import Set, List, Tuple
from datetime import datetime
class DatasetCheckerGUI:
"""带 TK 界面的数据集与标注文件比对工具"""
def __init__(self, root):
self.root = root
self.root.title("数据集与标注文件比对工具")
self.root.geometry("600x280")
self.root.resizable(False, False)
# 存储路径
self.img_dir = ""
self.label_dir = ""
# 初始化界面组件
self._init_ui()
def _init_ui(self):
"""构建界面"""
# 样式配置
style = ttk.Style()
style.configure("TButton", font=("微软雅黑", 10))
style.configure("TLabel", font=("微软雅黑", 10))
style.configure("TEntry", font=("微软雅黑", 10))
# 1. 图片文件夹选择区域
img_frame = ttk.Frame(self.root, padding="10")
img_frame.pack(fill=tk.X, padx=20, pady=15)
ttk.Label(img_frame, text="图片文件夹:").pack(side=tk.LEFT, padx=5)
self.img_entry = ttk.Entry(img_frame, width=40)
self.img_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
ttk.Button(img_frame, text="选择", command=self._select_img_dir).pack(side=tk.LEFT, padx=5)
# 2. 标注文件夹选择区域
label_frame = ttk.Frame(self.root, padding="10")
label_frame.pack(fill=tk.X, padx=20, pady=5)
ttk.Label(label_frame, text="标注文件夹:").pack(side=tk.LEFT, padx=5)
self.label_entry = ttk.Entry(label_frame, width=40)
self.label_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
ttk.Button(label_frame, text="选择", command=self._select_label_dir).pack(side=tk.LEFT, padx=5)
# 3. 执行按钮
btn_frame = ttk.Frame(self.root, padding="10")
btn_frame.pack(pady=20)
self.run_btn = ttk.Button(
btn_frame,
text="开始比对并保存结果",
command=self._run_check,
state=tk.DISABLED,
style="Accent.TButton"
)
self.run_btn.pack(padx=5, pady=5, ipady=3)
# 4. 状态显示区域
self.status_var = tk.StringVar(value="请选择图片和标注文件夹")
status_label = ttk.Label(
self.root,
textvariable=self.status_var,
foreground="#666",
font=("微软雅黑", 10)
)
status_label.pack(pady=10)
def _select_img_dir(self):
"""选择图片文件夹"""
dir_path = filedialog.askdirectory(title="选择图片文件夹")
if dir_path:
self.img_dir = dir_path
self.img_entry.delete(0, tk.END)
self.img_entry.insert(0, dir_path)
self._check_btn_status()
def _select_label_dir(self):
"""选择标注文件夹"""
dir_path = filedialog.askdirectory(title="选择标注文件夹")
if dir_path:
self.label_dir = dir_path
self.label_entry.delete(0, tk.END)
self.label_entry.insert(0, dir_path)
self._check_btn_status()
def _check_btn_status(self):
"""检查按钮是否可启用"""
if self.img_dir and self.label_dir:
self.run_btn.config(state=tk.NORMAL)
self.status_var.set("已选择文件夹,点击开始比对")
else:
self.run_btn.config(state=tk.DISABLED)
self.status_var.set("请选择图片和标注文件夹")
def _get_files(self, dir_path: str) -> List[str]:
"""获取文件夹中的所有文件名(包含扩展名)"""
if not os.path.exists(dir_path):
raise FileNotFoundError(f"文件夹不存在: {dir_path}")
return [f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f))]
def _get_basename(self, filename: str) -> str:
"""提取文件名(去除扩展名)"""
return os.path.splitext(filename)[0]
def _get_basenames(self, files: List[str]) -> Set[str]:
"""将文件名列表转换为去除扩展名的集合"""
return {self._get_basename(f) for f in files}
def _get_extra_files(self) -> Tuple[List[str], List[str]]:
"""计算多余的文件"""
# 获取文件列表
img_files = self._get_files(self.img_dir)
label_files = self._get_files(self.label_dir)
# 提取文件名(无扩展名)
img_basenames = self._get_basenames(img_files)
label_basenames = self._get_basenames(label_files)
# 查找多余文件
extra_img_basenames = img_basenames - label_basenames
extra_label_basenames = label_basenames - img_basenames
# 转换为原始文件名
extra_imgs = [f for f in img_files if self._get_basename(f) in extra_img_basenames]
extra_labels = [f for f in label_files if self._get_basename(f) in extra_label_basenames]
return extra_imgs, extra_labels
def _save_results_to_txt(self, extra_imgs: List[str], extra_labels: List[str]) -> bool:
"""将结果保存到 TXT 文件"""
# 生成文件名(包含时间戳)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = os.path.join(os.getcwd(), f"数据集比对结果_{timestamp}.txt")
try:
with open(save_path, "w", encoding="utf-8") as f:
f.write("=" * 50 + "\n")
f.write(f"数据集比对结果 - 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"图片文件夹路径:{self.img_dir}\n")
f.write(f"标注文件夹路径:{self.label_dir}\n")
f.write("=" * 50 + "\n\n")
# 写入图片文件夹多余文件
f.write(f"一、图片文件夹中多余的文件(无对应标注):共 {len(extra_imgs)} 个\n")
if extra_imgs:
for i, file in enumerate(extra_imgs, 1):
f.write(f"{i:2d}. {file}\n")
else:
f.write("无\n")
f.write("\n" + "-" * 30 + "\n\n")
# 写入标注文件夹多余文件
f.write(f"二、标注文件夹中多余的文件(无对应图片):共 {len(extra_labels)} 个\n")
if extra_labels:
for i, file in enumerate(extra_labels, 1):
f.write(f"{i:2d}. {file}\n")
else:
f.write("无\n")
f.write("\n" + "=" * 50 + "\n")
return save_path
except Exception as e:
messagebox.showerror("保存失败", f"结果保存出错:{str(e)}")
return False
def _run_check(self):
"""执行比对并保存结果"""
self.status_var.set("正在比对...")
self.root.update() # 刷新界面
try:
# 执行比对
extra_imgs, extra_labels = self._get_extra_files()
# 保存结果到 TXT
save_path = self._save_results_to_txt(extra_imgs, extra_labels)
if save_path:
self.status_var.set(f"比对完成!结果已保存到当前目录")
messagebox.showinfo(
"成功",
f"比对完成!\n\n统计结果:\n图片文件夹多余文件:{len(extra_imgs)} 个\n标注文件夹多余文件:{len(extra_labels)} 个\n\n结果文件路径:\n{save_path}"
)
else:
self.status_var.set("比对完成,但保存失败")
except Exception as e:
error_msg = f"比对出错:{str(e)}"
self.status_var.set(error_msg)
messagebox.showerror("错误", error_msg)
if __name__ == "__main__":
root = tk.Tk()
app = DatasetCheckerGUI(root)
root.mainloop()
比对同一个文件夹内jpg/png 和 xml的不同
python
复制代码
# coding: utf-8
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from typing import Set, List, Tuple
from datetime import datetime
class DatasetCheckerGUI:
"""同一文件夹下 图片与XML标注文件名比对工具"""
def __init__(self, root):
self.root = root
self.root.title("图片与XML标注文件名比对工具")
self.root.geometry("600x250")
self.root.resizable(False, False)
# 存储文件夹路径
self.target_dir = ""
# 初始化界面组件
self._init_ui()
def _init_ui(self):
"""构建界面"""
# 样式配置
style = ttk.Style()
style.configure("TButton", font=("微软雅黑", 10))
style.configure("TLabel", font=("微软雅黑", 10))
style.configure("TEntry", font=("微软雅黑", 10))
# 1. 文件夹选择区域
dir_frame = ttk.Frame(self.root, padding="10")
dir_frame.pack(fill=tk.X, padx=20, pady=30)
ttk.Label(dir_frame, text="目标文件夹:").pack(side=tk.LEFT, padx=5)
self.dir_entry = ttk.Entry(dir_frame, width=45)
self.dir_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
ttk.Button(dir_frame, text="选择", command=self._select_dir).pack(side=tk.LEFT, padx=5)
# 2. 执行按钮
btn_frame = ttk.Frame(self.root, padding="10")
btn_frame.pack(pady=10)
self.run_btn = ttk.Button(
btn_frame,
text="开始比对并保存结果",
command=self._run_check,
state=tk.DISABLED,
style="Accent.TButton"
)
self.run_btn.pack(padx=5, pady=5, ipady=3)
# 3. 状态显示区域
self.status_var = tk.StringVar(value="请选择包含图片和XML的文件夹")
status_label = ttk.Label(
self.root,
textvariable=self.status_var,
foreground="#666",
font=("微软雅黑", 10)
)
status_label.pack(pady=10)
def _select_dir(self):
"""选择目标文件夹(图片和XML在同一目录)"""
dir_path = filedialog.askdirectory(title="选择包含图片和XML标注的文件夹")
if dir_path:
self.target_dir = dir_path
self.dir_entry.delete(0, tk.END)
self.dir_entry.insert(0, dir_path)
self.run_btn.config(state=tk.NORMAL)
self.status_var.set("已选择文件夹,点击开始比对(仅检查JPG/PNG与XML)")
else:
self.run_btn.config(state=tk.DISABLED)
self.status_var.set("请选择包含图片和XML的文件夹")
def _get_files_by_ext(self, dir_path: str) -> Tuple[List[str], List[str]]:
"""
获取文件夹中所有图片文件(JPG/PNG)和XML标注文件
:return: (图片文件列表, XML文件列表)
"""
if not os.path.exists(dir_path):
raise FileNotFoundError(f"文件夹不存在: {dir_path}")
img_files = []
xml_files = []
for filename in os.listdir(dir_path):
file_path = os.path.join(dir_path, filename)
if not os.path.isfile(file_path):
continue # 跳过子目录
ext = os.path.splitext(filename)[1].lower()
# 收集图片文件(JPG/PNG)
if ext in [".jpg", ".jpeg", ".png"]:
img_files.append(filename)
# 收集XML标注文件
elif ext == ".xml":
xml_files.append(filename)
return img_files, xml_files
def _check_matching(self, img_files: List[str], xml_files: List[str]) -> Tuple[List[str], List[str]]:
"""
检查文件名一致性(忽略扩展名)
:return: (无对应XML的图片文件, 无对应图片的XML文件)
"""
# 提取文件名(去除扩展名)
img_basenames = {os.path.splitext(f)[0] for f in img_files}
xml_basenames = {os.path.splitext(f)[0] for f in xml_files}
# 找出不匹配的文件
img_without_xml = [f for f in img_files if os.path.splitext(f)[0] not in xml_basenames]
xml_without_img = [f for f in xml_files if os.path.splitext(f)[0] not in img_basenames]
return img_without_xml, xml_without_img
def _save_results_to_txt(self, img_without_xml: List[str], xml_without_img: List[str]) -> bool:
"""将比对结果保存到TXT文件"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = os.path.join(self.target_dir, f"文件名比对结果_{timestamp}.txt")
try:
with open(save_path, "w", encoding="utf-8") as f:
f.write("=" * 60 + "\n")
f.write(f"图片与XML标注文件名比对结果\n")
f.write(f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"目标文件夹:{self.target_dir}\n")
f.write(f"图片格式:JPG/JPEG/PNG | 标注格式:XML\n")
f.write("=" * 60 + "\n\n")
# 无对应XML的图片
f.write(f"一、无对应XML标注的图片文件:共 {len(img_without_xml)} 个\n")
if img_without_xml:
for i, file in enumerate(img_without_xml, 1):
f.write(f"{i:2d}. {file}\n")
else:
f.write("无\n")
f.write("\n" + "-" * 40 + "\n\n")
# 无对应图片的XML
f.write(f"二、无对应图片的XML标注文件:共 {len(xml_without_img)} 个\n")
if xml_without_img:
for i, file in enumerate(xml_without_img, 1):
f.write(f"{i:2d}. {file}\n")
else:
f.write("无\n")
f.write("\n" + "=" * 60 + "\n")
return save_path
except Exception as e:
messagebox.showerror("保存失败", f"结果保存出错:{str(e)}")
return False
def _run_check(self):
"""执行比对逻辑"""
self.status_var.set("正在比对...")
self.root.update() # 刷新界面
try:
# 获取目标文件
img_files, xml_files = self._get_files_by_ext(self.target_dir)
if not img_files and not xml_files:
messagebox.showwarning("无文件", "文件夹中未找到JPG/PNG图片或XML标注文件")
self.status_var.set("比对完成:未找到目标文件")
return
# 检查文件名匹配
img_without_xml, xml_without_img = self._check_matching(img_files, xml_files)
# 保存结果
save_path = self._save_results_to_txt(img_without_xml, xml_without_img)
if save_path:
self.status_var.set("比对完成!结果已保存到目标文件夹")
# 弹窗显示统计信息
msg = (
f"比对完成!\n\n"
f"统计结果:\n"
f"文件夹中图片总数(JPG/PNG):{len(img_files)} 个\n"
f"文件夹中XML标注总数:{len(xml_files)} 个\n"
f"无对应XML的图片:{len(img_without_xml)} 个\n"
f"无对应图片的XML:{len(xml_without_img)} 个\n\n"
f"结果文件已保存到:\n{save_path}"
)
messagebox.showinfo("成功", msg)
except Exception as e:
error_msg = f"比对出错:{str(e)}"
self.status_var.set(error_msg)
messagebox.showerror("错误", error_msg)
if __name__ == "__main__":
root = tk.Tk()
# 优化界面样式(适配Windows系统)
try:
root.tk.call("source", "azure.tcl")
root.tk.call("set_theme", "light")
except:
pass # 无样式文件时不影响功能
app = DatasetCheckerGUI(root)
root.mainloop()