使用CLIP对NAS的图片进行分类整理

问题

nas中有大量图片, 如何快速分类整理, 方便查阅?

方案选选择

  1. Python + CLIP
  • 完全掌控:python实现, 每一步都可修改, 文件物理结构清晰,不依赖任何数据库
  • 扩展性强, 可进一步根据人脸细分(DBSCAN), 区分自己和朋友
  1. 成品平台方案 (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)

  1. 人脸优先判定: 先用 face_recognition 快速扫描。如果有人脸,归入 People 目录(后续可用之前的聚类脚本细分“自己”和“朋友”)。

  2. 场景物体分类: 如果没脸,丢给 CLIP 模型,让它在 ["猫", "狗", "车辆", "风景", "截屏"] 中选一个最匹配的。

  3. 物理移动: 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()
posted @ 2026-01-04 00:44  fx-wiki  阅读(5)  评论(0)    收藏  举报