-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPhotoSimRemove.py
More file actions
159 lines (143 loc) · 5.69 KB
/
Copy pathPhotoSimRemove.py
File metadata and controls
159 lines (143 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import os
import shutil
from imagededup.methods import PHash
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import time
def get_image_info(img_path):
try:
stat = os.stat(img_path)
img = Image.open(img_path)
info = {
'文件名': os.path.basename(img_path),
'创建时间': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_ctime)),
'修改时间': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime)),
'文件大小': f'{stat.st_size / 1024:.1f} KB',
'像素': f'{img.width}x{img.height}',
'模式': img.mode,
'格式': img.format,
}
img.close()
return info
except Exception as e:
return {'文件名': os.path.basename(img_path), '错误': str(e)}
def show_images_with_checkboxes(image_paths, progress_text=''):
root = tk.Tk()
root.title(f'请选择要保留的图片 {progress_text}')
selected = [tk.BooleanVar(master=root, value=True) for _ in image_paths]
photos = []
panels = []
labels = []
min_sizes = []
# 创建Canvas和滚动条
canvas = tk.Canvas(root, width=1200, height=500)
h_scroll = tk.Scrollbar(root, orient='horizontal', command=canvas.xview)
canvas.configure(xscrollcommand=h_scroll.set)
canvas.grid(row=0, column=0, sticky='nsew')
h_scroll.grid(row=1, column=0, sticky='ew')
# 创建Frame放到Canvas里
frame = tk.Frame(canvas)
canvas.create_window((0, 0), window=frame, anchor='nw')
# 支持缩放
def zoom(event, idx, factor):
img = Image.open(image_paths[idx])
size = panels[idx].image.width(), panels[idx].image.height()
min_w, min_h = min_sizes[idx]
new_w = max(int(size[0] * factor), min_w)
new_h = max(int(size[1] * factor), min_h)
try:
resample = Image.Resampling.LANCZOS
except AttributeError:
resample = Image.LANCZOS
img = img.resize((new_w, new_h), resample)
photo = ImageTk.PhotoImage(img)
panels[idx].configure(image=photo)
panels[idx].image = photo
img.close()
# 更新canvas scrollregion
frame.update_idletasks()
canvas.config(scrollregion=canvas.bbox("all"))
for i, img_path in enumerate(image_paths):
try:
img = Image.open(img_path)
img.thumbnail((300, 300))
min_sizes.append(img.size)
photo = ImageTk.PhotoImage(img)
photos.append(photo)
panel = tk.Label(frame, image=photo)
panel.image = photo
panel.grid(row=0, column=i, padx=10, pady=5)
panel.bind('<MouseWheel>', lambda e, idx=i: zoom(e, idx, 1.2 if e.delta > 0 else 0.8))
panels.append(panel)
info = get_image_info(img_path)
info_text = '\n'.join([f'{k}: {v}' for k, v in info.items()])
label = tk.Label(frame, text=info_text, justify='left', font=('Consolas', 9))
label.grid(row=1, column=i)
labels.append(label)
chk = tk.Checkbutton(frame, text='保留', variable=selected[i])
chk.grid(row=2, column=i)
img.close()
except Exception as e:
print(f'无法打开图片: {img_path}, 错误: {e}')
# 确定按钮放在主窗口下方
btn = ttk.Button(root, text='确定', command=root.quit)
btn.grid(row=2, column=0, pady=10)
# 更新scrollregion
frame.update_idletasks()
canvas.config(scrollregion=canvas.bbox("all"))
root.mainloop()
root.destroy()
return [i for i, var in enumerate(selected) if var.get()]
def get_all_image_paths(root_dir, exts={'.jpg', '.jpeg', '.png', '.bmp', '.gif'}):
image_paths = []
for dirpath, _, filenames in os.walk(root_dir):
for fname in filenames:
if os.path.splitext(fname)[1].lower() in exts:
image_paths.append(os.path.join(dirpath, fname))
return image_paths
def build_groups(duplicates, root_dir):
# 构建无向图
from collections import defaultdict, deque
graph = defaultdict(set)
for key, dupes in duplicates.items():
for d in dupes:
graph[key].add(d)
graph[d].add(key)
visited = set()
groups = []
for node in graph:
if node not in visited:
group = []
queue = deque([node])
visited.add(node)
while queue:
curr = queue.popleft()
group.append(os.path.join(root_dir, curr))
for neighbor in graph[curr]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
if len(group) > 1:
groups.append(group)
return groups
if __name__ == '__main__':
root_dir = r'K:\photo'
to_delete_dir = os.path.join(root_dir, '待删除')
os.makedirs(to_delete_dir, exist_ok=True)
phasher = PHash()
encodings = phasher.encode_images(image_dir=root_dir)
print(f'编码图片数量: {len(encodings)}')
duplicates = phasher.find_duplicates(encoding_map=encodings)
groups = build_groups(duplicates, root_dir)
total = len(groups)
for idx, group in enumerate(groups, 1):
progress_text = f'({idx}/{total})'
keep_indices = show_images_with_checkboxes(group, progress_text)
for i, img_path in enumerate(group):
if i not in keep_indices:
try:
shutil.move(img_path, os.path.join(to_delete_dir, os.path.basename(img_path)))
print(f'已移动: {img_path}')
except Exception as e:
print(f'移动失败: {img_path}, 错误: {e}')