本文目录导读:

我来帮你写一个图片去重脚本,这里提供几种不同的方案,从简单到复杂:
基于文件哈希的简单去重(最快)
import os
import hashlib
from collections import defaultdict
def get_file_hash(filepath, chunk_size=8192):
"""计算文件哈希值"""
hash_md5 = hashlib.md5()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(chunk_size), b''):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def find_duplicate_files(folder_path):
"""查找重复文件"""
hash_dict = defaultdict(list)
# 遍历文件夹
for root, dirs, files in os.walk(folder_path):
for filename in files:
filepath = os.path.join(root, filename)
if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')):
try:
file_hash = get_file_hash(filepath)
hash_dict[file_hash].append(filepath)
except Exception as e:
print(f"Error processing {filepath}: {e}")
# 找出重复文件
duplicates = {hash_val: paths for hash_val, paths in hash_dict.items() if len(paths) > 1}
return duplicates
def delete_duplicates(duplicates, keep_first=True):
"""删除重复文件"""
deleted_count = 0
for hash_val, paths in duplicates.items():
if keep_first:
# 保留第一个,删除其余
for filepath in paths[1:]:
try:
os.remove(filepath)
print(f"Deleted: {filepath}")
deleted_count += 1
except Exception as e:
print(f"Error deleting {filepath}: {e}")
else:
# 全部删除
for filepath in paths:
try:
os.remove(filepath)
print(f"Deleted: {filepath}")
deleted_count += 1
except Exception as e:
print(f"Error deleting {filepath}: {e}")
print(f"Total deleted: {deleted_count}")
# 使用示例
if __name__ == "__main__":
target_folder = "path/to/your/images"
duplicates = find_duplicate_files(target_folder)
if duplicates:
print("Found duplicates:")
for hash_val, paths in duplicates.items():
print(f"Hash: {hash_val}")
for path in paths:
print(f" - {path}")
# 询问是否删除
choice = input("Delete duplicates? (y/n): ").lower()
if choice == 'y':
delete_duplicates(duplicates)
else:
print("No duplicates found!")
基于图像特征的感知哈希(能检测相似图片)
import os
import hashlib
from PIL import Image
import numpy as np
from collections import defaultdict
def average_hash(image_path, hash_size=16):
"""计算图片的平均哈希值"""
try:
img = Image.open(image_path)
img = img.resize((hash_size, hash_size), Image.LANCZOS)
img = img.convert('L') # 转为灰度
pixels = np.array(img)
avg = pixels.mean()
# 生成哈希
diff = pixels > avg
hash_str = ''.join(str(int(x)) for x in diff.flatten())
return hash_str
except Exception as e:
print(f"Error processing {image_path}: {e}")
return None
def find_similar_images(folder_path, threshold=20):
"""查找相似图片"""
image_hashes = []
for root, dirs, files in os.walk(folder_path):
for filename in files:
if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')):
filepath = os.path.join(root, filename)
hash_str = average_hash(filepath)
if hash_str:
image_hashes.append((filepath, hash_str))
# 找出相似的图片
duplicates = []
processed = set()
for i, (filepath1, hash1) in enumerate(image_hashes):
if i in processed:
continue
similar_group = [filepath1]
for j, (filepath2, hash2) in enumerate(image_hashes):
if i != j and j not in processed:
# 计算汉明距离
hamming_dist = sum(c1 != c2 for c1, c2 in zip(hash1, hash2))
if hamming_dist < threshold:
similar_group.append(filepath2)
processed.add(j)
if len(similar_group) > 1:
duplicates.append(similar_group)
return duplicates
# 使用示例
if __name__ == "__main__":
target_folder = "path/to/your/images"
print("Finding similar images...")
similar_groups = find_similar_images(target_folder)
if similar_groups:
print("Found similar image groups:")
for group in similar_groups:
print("Group:")
for path in group:
print(f" - {path}")
choice = input("Remove similar images (keep first of each group)? (y/n): ").lower()
if choice == 'y':
for group in similar_groups:
for path in group[1:]:
try:
os.remove(path)
print(f"Deleted: {path}")
except Exception as e:
print(f"Error deleting {path}: {e}")
else:
print("No similar images found!")
完整的去重工具(带GUI界面)
import os
import hashlib
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
import threading
class ImageDedupTool:
def __init__(self, root):
self.root = root
self.root.title("图片去重工具")
self.root.geometry("800x600")
self.folder_path = None
self.duplicates = {}
# 创建界面元素
self.setup_ui()
def setup_ui(self):
# 顶部按钮框架
top_frame = tk.Frame(self.root)
top_frame.pack(pady=10)
tk.Button(top_frame, text="选择文件夹", command=self.select_folder).pack(side=tk.LEFT, padx=5)
tk.Button(top_frame, text="查找重复", command=self.find_duplicates).pack(side=tk.LEFT, padx=5)
tk.Button(top_frame, text="删除重复", command=self.delete_duplicates).pack(side=tk.LEFT, padx=5)
# 进度条
self.progress = ttk.Progressbar(self.root, length=500, mode='indeterminate')
self.progress.pack(pady=10)
# 结果列表
self.tree = ttk.Treeview(self.root, columns=('type', 'path'), show="tree")
self.tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 状态栏
self.status_label = tk.Label(self.root, text="Ready")
self.status_label.pack(side=tk.BOTTOM, pady=5)
def select_folder(self):
self.folder_path = filedialog.askdirectory()
if self.folder_path:
self.status_label.config(text=f"Selected folder: {self.folder_path}")
def find_duplicates(self):
if not self.folder_path:
messagebox.showerror("Error", "请先选择文件夹!")
return
self.progress.start()
threading.Thread(target=self.find_duplicates_worker).start()
def find_duplicates_worker(self):
hash_dict = {}
for root, dirs, files in os.walk(self.folder_path):
for filename in files:
if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')):
filepath = os.path.join(root, filename)
try:
file_hash = self.calculate_md5(filepath)
if file_hash in hash_dict:
hash_dict[file_hash].append(filepath)
else:
hash_dict[file_hash] = [filepath]
except Exception as e:
self.root.after(0, self.update_status, f"Error: {filepath}")
self.duplicates = {h: paths for h, paths in hash_dict.items() if len(paths) > 1}
self.root.after(0, self.display_results)
self.root.after(0, self.progress.stop)
def calculate_md5(self, filepath):
hash_md5 = hashlib.md5()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def display_results(self):
# 清空列表
for item in self.tree.get_children():
self.tree.delete(item)
if not self.duplicates:
messagebox.showinfo("Info", "没有找到重复文件!")
return
for hash_val, paths in self.duplicates.items():
parent = self.tree.insert('', 'end', text=f"重复组 (Hash: {hash_val[:8]}...)", open=True)
for path in paths:
self.tree.insert(parent, 'end', text=path)
self.update_status(f"找到 {len(self.duplicates)} 组重复文件")
def delete_duplicates(self):
if not self.duplicates:
messagebox.showinfo("Info", "没有找到重复文件!")
return
deleted_count = 0
for hash_val, paths in self.duplicates.items():
# 保留每组中的第一个,删除其余
for path in paths[1:]:
try:
os.remove(path)
deleted_count += 1
except Exception as e:
self.update_status(f"删除失败: {path} - {e}")
self.update_status(f"Successfully deleted {deleted_count} files")
messagebox.showinfo("Info", f"成功删除 {deleted_count} 个重复文件")
self.find_duplicates() # 刷新显示
def update_status(self, msg):
self.status_label.config(text=msg)
# 运行应用
if __name__ == "__main__":
root = tk.Tk()
app = ImageDedupTool(root)
root.mainloop()
使用说明
- 方案一是最简单的基于MD5的完全重复检测
- 方案二使用感知哈希,可以检测经过轻微修改(如旋转、缩放)的相似图片
- 方案三提供GUI界面,更加友好
安装依赖
# 基础版本 pip install os-sys # 如果需要感知哈希或GUI功能 pip install pillow numpy tkinter
注意事项
- 使用前建议先备份重要图片
- 删除操作不可逆,请谨慎操作
- 可以根据需要调整哈希阈值和文件类型
选择适合你需求的方案,如果有特殊要求可以进一步调整代码!
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播