1. 识别并移除重复图像

识别并移除重复图像 学习卡片

任务说明

本案例针对图像数据集中存在的完全重复或近似重复(如缩放、轻微压缩、色彩微调后的副本)问题,使用感知哈希(Perceptual Hash, pHash)算法为每张图像生成64位指纹,通过汉明距离判定重复,并自动移除重复项,确保数据集中图像的唯一性。

实现方案

核心流程分为三步:指纹生成(将图像缩放到8×8灰度图,计算DCT低频系数并二值化为64位哈希)、重复检测(两两比较汉明距离,阈值≤10判定为近似重复)、去重执行(保留每组第一张,删除其余)。代码使用OpenCV和NumPy实现,不依赖外部深度学习框架。

阶段1:指纹生成 读取图像 → 缩放8×8 灰度化 → DCT → 二值化 输出:64位哈希值 阶段2:重复检测 两两计算汉明距离 距离 ≤ 10 → 判定重复 构建重复组集合 阶段3:去重执行 每组保留第一张 删除其余重复文件 输出:唯一图像集 关键参数与数据结构 • 哈希长度:64位(8×8 DCT低频系数) • 汉明距离阈值:≤10 判定为近似重复 • 存储结构:dict[hash] = [文件路径列表] • 比较方式:O(n²) 两两比较 • 保留策略:每组第一个文件 • 删除方式:os.remove()

代码实现


import os
import cv2
import numpy as np
from collections import defaultdict

def compute_phash(image_path, hash_size=8):
    """计算图像的感知哈希值(64位整数)"""
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        raise ValueError(f"无法读取图像: {image_path}")
    # 缩放到 hash_size x hash_size
    img = cv2.resize(img, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
    # 转换为浮点并计算DCT
    img_float = np.float32(img) / 255.0
    dct = cv2.dct(img_float)
    # 取左上角低频块(不含DC分量)
    low_freq = dct[:hash_size, :hash_size]
    # 计算中值作为阈值
    median_val = np.median(low_freq)
    # 二值化生成哈希
    hash_bits = (low_freq > median_val).flatten()
    # 打包为64位整数
    hash_val = 0
    for bit in hash_bits:
        hash_val = (hash_val << 1) | int(bit)
    return hash_val

def hamming_distance(h1, h2):
    """计算两个64位哈希的汉明距离"""
    return bin(h1 ^ h2).count('1')

def find_duplicates(image_paths, threshold=10):
    """检测重复图像,返回需要删除的文件列表"""
    # 计算所有图像的哈希
    hash_map = {}  # path -> hash
    for path in image_paths:
        try:
            hash_map[path] = compute_phash(path)
        except Exception as e:
            print(f"跳过 {path}: {e}")
    
    # 两两比较,构建重复组
    paths = list(hash_map.keys())
    visited = set()
    duplicates = []
    
    for i in range(len(paths)):
        if paths[i] in visited:
            continue
        group = [paths[i]]
        for j in range(i+1, len(paths)):
            if paths[j] in visited:
                continue
            if hamming_distance(hash_map[paths[i]], hash_map[paths[j]]) <= threshold:
                group.append(paths[j])
                visited.add(paths[j])
        if len(group) > 1:
            # 保留第一个,其余加入删除列表
            duplicates.extend(group[1:])
            visited.add(paths[i])
    
    return duplicates

def remove_duplicates(image_dir, threshold=10):
    """主函数:扫描目录,检测并删除重复图像"""
    # 收集目录下所有图像文件
    valid_exts = {'.jpg', '.jpeg', '.png', '.bmp', '.webp'}
    image_paths = []
    for fname in os.listdir(image_dir):
        ext = os.path.splitext(fname)[1].lower()
        if ext in valid_exts:
            image_paths.append(os.path.join(image_dir, fname))
    
    if len(image_paths) < 2:
        print("图像数量不足,无需去重")
        return []
    
    # 检测重复
    duplicates = find_duplicates(image_paths, threshold)
    
    # 执行删除
    deleted = []
    for dup_path in duplicates:
        try:
            os.remove(dup_path)
            deleted.append(dup_path)
            print(f"已删除: {os.path.basename(dup_path)}")
        except Exception as e:
            print(f"删除失败 {dup_path}: {e}")
    
    print(f"\n扫描图像总数: {len(image_paths)}")
    print(f"判定重复并删除: {len(deleted)} 张")
    print(f"保留唯一图像: {len(image_paths) - len(deleted)} 张")
    return deleted

if __name__ == "__main__":
    # 使用示例:指定图像目录路径
    DATA_DIR = "./images"  # 请修改为实际路径
    remove_duplicates(DATA_DIR, threshold=10)

落地与验证

运行环境:需安装Python 3.7+、OpenCV(pip install opencv-python)和NumPy(pip install numpy)。

使用方法:将待清洗的图像放入同一目录(支持jpg/png/bmp/webp格式),修改代码末尾的DATA_DIR为实际目录路径,执行python dedup_images.py即可。

验证方法:准备一个包含10张图像的测试目录,其中手动复制3张完全相同的图像(不同文件名),另将1张图像缩放80%后另存。运行脚本后观察输出:应显示删除4张重复图像,剩余6张唯一图像。手动检查目录,确认重复文件已被移除,且保留的是每组第一张(即文件名排序靠前的文件)。

判定标准:脚本输出的删除数量与预期重复数一致;目录中不再存在视觉上相同或近似相同的图像;控制台无报错信息。对于完全相同的图像,汉明距离为0;对于缩放/压缩变体,汉明距离通常在5~10之间,阈值10可有效识别。