使用CLIP对NAS的图片进行分类整理
问题
nas中有大量图片, 如何快速分类整理, 方便查阅?
方案选选择
- Python + CLIP
- 完全掌控:python实现, 每一步都可修改, 文件物理结构清晰,不依赖任何数据库
- 扩展性强, 可进一步根据人脸细分(DBSCAN), 区分自己和朋友
- 成品平台方案 (Immich)
- 不用写代码
- 自动识别人脸: 极其准确,能让你给某人命名,然后自动归类。
- 自动识别物体: 搜索 "cat" 或 "car",它能立马把所有相关图片搜出来(基于 CLIP)。
- 地图模式: 如果照片有 GPS,能在地图上展示。
- docker部署
背景知识
Labelme
Labelme 是一个开源、跨平台的图像标注工具,由 Kentaro Wada(和东京大学/丰田研究院相关) 开发并维护,广泛应用于计算机视觉领域的数据标注任务。它支持多种标注类型,界面简洁,使用灵活,特别适合研究人员、开发者和小团队快速构建高质量的图像数据集。
用法:
- 直接整理: 将手工标注过的图片,按标签自动归档到对应文件夹
- 训练素材: 将这些标注转化为 YOLO 格式,用来训练自己的私有模型(例如识别你家的猫、特定的车,而不仅仅是通用的“猫”或“车”)。
YOLO 模型
YOLO(You Only Look Once)是一类实时目标检测(Object Detection)模型,因其速度快、精度高、端到端训练而广受欢迎。自 2016 年首次提出以来,YOLO 系列不断演进,已成为工业界和学术界最主流的目标检测框架之一。
CLIP 模型
CLIP(Contrastive Language–Image Pretraining) 是由 OpenAI 于 2021 年提出的一种多模态预训练模型,它能够将图像和文本映射到同一个语义向量空间中,从而实现“用自然语言理解图像”的能力。
无需训练,直接用自然语言描述类别进行分类 , 给一张狗的照片,输入候选标签 ["猫", "狗", "汽车"],CLIP 自动选出“狗”
离线处理(模型下载后无需联网)
DBSCAN 聚类
DBSCAN(Density-Based Spatial Clustering of Applications with Noise) 是一种经典的基于密度的聚类算法,由 Martin Ester 等人在 1996 年提出。它特别适合处理任意形状的簇、自动发现簇数量,并能有效识别噪声点(离群点)。
应用场景: 人脸聚类
将大量人脸图像按身份(自己, 朋友A, 朋友B...)分组(NAS 相册整理)
为什么是 CLIP 而不是 YOLO?
- YOLO 擅长“找框”:它能告诉你“这里有一只猫,坐标是 x,y”。
- CLIP 擅长“理解”:你直接给它文字 ["猫", "狗", "风景", "车", "人"],它能告诉你这张图最符合哪个描述。
对于分类归档任务,CLIP 比 YOLO 更直观、准确,且无需训练。
照片分类实现原理(python)
-
人脸优先判定: 先用 face_recognition 快速扫描。如果有人脸,归入 People 目录(后续可用之前的聚类脚本细分“自己”和“朋友”)。
-
场景物体分类: 如果没脸,丢给 CLIP 模型,让它在 ["猫", "狗", "车辆", "风景", "截屏"] 中选一个最匹配的。
-
物理移动: shutil.move 到对应文件夹。
python 代码实现
import os
import shutil
import face_recognition
from PIL import Image
import torch
from transformers import CLIPProcessor, CLIPModel
# --- 配置 ---
SOURCE_DIR = "/Volumes/MyNAS/Phones2025" # 原始照片目录
TARGET_ROOT = "/Volumes/MyNAS/Phones2025_Organized" # 整理后的目录
CATEGORIES = ["猫", "狗", "车辆", "风景", "截屏"] # CLIP 的候选词
# "人" 单独用 face_recognition 处理,更加准确
# 设置设备
device = "mps" if torch.backends.mps.is_available() else "cpu"
model_name = "openai/clip-vit-base-patch32"
print(f"正在加载模型: {model_name}...")
try:
# 尝试直接从本地缓存加载 (完全不联网)
print("尝试从本地缓存加载...")
model = CLIPModel.from_pretrained(model_name, local_files_only=True).to(device)
processor = CLIPProcessor.from_pretrained(model_name, local_files_only=True)
print("本地模型加载成功!")
except Exception as e:
# 如果本地没有,会抛出 OSError 或类似错误,此时捕获异常并联网下载
print(f"本地未找到模型或加载失败 ({e}),正在从 Hugging Face 下载...")
model = CLIPModel.from_pretrained(model_name).to(device)
processor = CLIPProcessor.from_pretrained(model_name)
print("模型下载并加载成功!")
def classify_and_move():
# 创建目标文件夹
for cat in CATEGORIES + ["people", "others"]:
os.makedirs(os.path.join(TARGET_ROOT, cat), exist_ok=True)
for root, _, files in os.walk(SOURCE_DIR):
for file in files:
if not file.lower().endswith(('.jpg', '.jpeg', '.png')):
continue
file_path = os.path.join(root, file)
print(f"处理: {file} ...", end="")
try:
# 1. 优先检测人脸 (Face First)
# 使用 face_recognition 快速筛查,因为 CLIP 对远景小人脸可能不敏感
# 加载图片 (不做全解析,只做人脸检测)
img_cv = face_recognition.load_image_file(file_path)
# 降采样检测加速
face_locs = face_recognition.face_locations(img_cv, model="hog")
if len(face_locs) > 0:
target_folder = "people"
print(f" -> [人像] -> 移动")
move_file(file_path, target_folder)
continue
# 2. 如果没人脸,使用 CLIP 进行场景分类
image = Image.open(file_path)
# 准备文字描述 (Prompt Engineering)
text_inputs = [f"a photo of a {c}" for c in CATEGORIES]
inputs = processor(
text=text_inputs,
images=image,
return_tensors="pt",
padding=True
).to(device)
with torch.no_grad():
outputs = model(**inputs)
# 获取概率最高的类别
probs = outputs.logits_per_image.softmax(dim=1)
max_idx = probs.argmax().item()
confidence = probs[0][max_idx].item()
pred_label = CATEGORIES[max_idx]
# 3. 移动文件
if confidence > 0.6: # 设定置信度阈值
print(f" -> [{pred_label}] ({confidence:.2f}) -> 移动")
move_file(file_path, pred_label)
else:
print(f" -> [不确定] -> 归入 others")
move_file(file_path, "others")
except Exception as e:
print(f"Error: {e}")
def move_file(src, category):
dst_dir = os.path.join(TARGET_ROOT, category)
# 处理重名文件
filename = os.path.basename(src)
dst_path = os.path.join(dst_dir, filename)
if os.path.exists(dst_path):
base, ext = os.path.splitext(filename)
dst_path = os.path.join(dst_dir, f"{base}_copy{ext}")
shutil.move(src, dst_path)
if __name__ == "__main__":
classify_and_move()

浙公网安备 33010602011771号