ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于CNN特征提取的本地图片视频重复检测与整理工具

2026/9/28 13:42:59 拓冰建站 浏览量
基于CNN特征提取的本地图片视频重复检测与整理工具 很多人在整理本地照片和视频素材时都会被一个问题折磨文件越攒越多重复内容占了大量磁盘空间手动翻目录找重复项又慢又容易漏。最早我写脚本用MD5比对结果同一张照片换个尺寸、换种格式、加个水印MD5就完全不一样了这玩意儿根本抓不到“内容重复但文件不同”的情况。所以我索性做了一个基于CNN特征提取的本地图片视频重复检测与整理工具核心思路是让卷积神经网络把每一张图、每一帧视频都变成一串特征向量再用余弦相似度去衡量内容是否重复。实测下来跨格式、跨尺寸的重复图片能被很稳定地识别出来视频查重也基本不用人工复核。这篇内容适合所有本地素材积压严重、想找一套靠谱去重方案的读者也适合想了解CNN特征提取工程落地细节的开发者。1. 项目背景与整体思路1.1 本地素材重复为什么这么难查本地文件一多重复检测的难度不是线性上升而是爆炸式上升。我自己的状况是累计存了大概两万多张图片、上千段视频分散在好几个硬盘和网盘同步目录里。这些素材的来源五花八门手机自动备份、相机导出、同事发来的二次转发文件、从聊天记录里保存的缓存图每一类都存在大量内容相同或高度相似的副本。传统方案里文件级MD5/SHA1哈希只能处理“字节级完全一致”的情况。同一张照片从iPhone导到Windows元数据变了MD5就变了同一个视频被QQ、微信分别压缩转发文件哈希完全不同但画面内容是同一段。再往上一步是感知哈希算法对图片计算aHash、pHash、dHash这能捕捉一些全局视觉特征但对旋转、裁剪、加文字水印、添加滤镜这类常见操作依然很脆弱。视频方面就更麻烦单纯压缩转码之后感知哈希基本失效因为关键帧的像素完全被重排了。我需要的不是一个“文件比对工具”而是一个“内容语义比对工具”——哪怕两个文件在外面包装上天差地别只要画面主体、构图、场景是同一段就要能识别出来并允许我把重复项挑出来整理归档。这就把方案引向了深度学习的特征提取方向。CNN的中间层输出本身就是一种高层次的图像语义表示它不像像素比较那样在乎局部细节的改动而是抓住物体的形状、纹理、场景结构这类本质信息天然适合做这种鲁棒性要求很高的重复检测。1.2 为什么选CNN特征提取而不是其他方案选CNN做特征提取核心原因有三层。第一层是它具备极强的抗干扰能力。预训练模型见过的图像模式非常多对亮度变化、几何缩放、轻微裁剪、水印覆盖都有一定的不变性。第二层是CNN把图像压缩成几百上千维的浮点向量之后相似度计算变得非常简单特征向量之间的余弦距离可以直接作为一个稳定的量化指标这比像素级的各种手工特征要可靠得多。第三层是生态成熟开源的预训练模型非常多比如EfficientNet、ResNet、MobileNet系列都能直接拿来当特征提取器不用从头训练。那为什么不考虑直接用CLIP这类多模态模型CLIP在语义匹配上确实更强但它的侧重点是“图文对齐”也就是文字与图像之间的语义关联而不是细粒度的图像相似度。在图片重复检测这个任务里两张内容几乎一样只是分辨率不同的图CLIP提取的特征差异反而可能比CNN更大因为它更关注整体语义差异。另外CLIP模型体积和推理耗时也比纯CNN特征提取器大不少跑全量素材库会很吃力。CNN这边我最后选的是EfficientNet-B0预训练模型去掉分类层输出的1280维向量直接作为特征。这个维度做余弦相似度计算完全够用而且单张图在CPU上推理也只花几十毫秒。还有一点容易被忽略选型时要考虑特征向量的稳定性。同一张照片用同一个模型重复提取两次输出的向量必须完全一致推理模式下结果是确定性的。模型参数固定后这个条件是满足的这样才能保证重复检测是在做静态比对而不是每次算出来的结果都不一样。2. 工具设计与技术选型2.1 整体架构与模块划分这个工具的设计我拆成了四个模块素材扫描、特征提取、相似度分组、文件整理。素材扫描负责遍历指定的本地目录过滤出图片文件jpg、png、webp、bmp、heic等和视频文件mp4、mov、avi、mkv等把路径和文件大小记录到内存或SQLite里。特征提取模块对图片直接提取CNN特征对视频则需要先在固定间隔抽帧再对帧特征做聚合。相似度分组模块维护一个全局相似度阈值遍历所有特征向量的两两组合把相似度超过阈值的文件归入同一个候选组。文件整理模块负责执行实际操作比如把重复的副本移入指定目录、重命名、或者直接删除可选。这里有一个很关键的设计决策检测和整理必须分离。因为一旦整理出错文件被删掉或强制移动造成的损失是不可逆的。我实际的做法是检测阶段只输出一份“重复候选清单”带相似度分数和文件路径人工或脚本审核这份清单之后再进入整理阶段。这个分离带来的好处是可以在已检测出的结果上反复调整整理策略而不用重新跑一遍全量匹配。2.2 阈值与相似度指标的确定特征向量之间的相似度我采用余弦相似度取值范围从-1到1在图像特征空间中基本都在0到1之间波动。同一个内容的不同压缩版本相似度通常能到0.95以上内容相似但构图有局部裁剪的大概在0.85到0.95完全不相关的图一般落在0.2到0.6之间。相似度区间含义处理建议0.95 - 1.0高度重复基本同一文件的不同版本直接归档/删除副本0.85 - 0.95相似内容可能有裁剪、滤镜、水印归入候选组人工确认0.70 - 0.85弱相似可能只是同一场景不同角度不自动处理仅标记0.70以下不相关忽略这个阈值不是拍脑袋定的我分别跑过一组精确重复样本和一组不相关随机样本观察两者的分数分布之后定下来的。实际使用中大家的素材差异很大所以我建议把阈值做成可配置参数第一次跑完先看一眼相似度统计分布再决定分流阈值不要一上来就设到0.99或者0.5这种极端值。2.3 预训练模型与运行环境的取舍模型层面我对比过三个方向。ResNet50的特征维度是2048稍高一些模型参数也大特征提取效果中等。MobileNetV3的特征提取速度非常快移动端优化好但在细粒度图像区分上不如EfficientNet。EfficientNet-B0是隐式缩放过的网络在ImageNet分类任务里模型体积和精度平衡得很好最后一层平均池化输出的1280维特征信息密度已经足够表示一张图的“相貌”。运行环境上如果我本地有GPU就优先CUDA推理但没有GPU也不用太担心因为特征提取本来就是逐张处理数据量到5万张图片时纯CPU批量跑大概需要四十分钟到一小时。可以接受毕竟这是一次性的工作——特征提取完之后后续所有整理操作都基于本地缓存的特征向量不需要重复计算。我把特征向量统一存成npy格式文件名与原始路径做映射下次工具启动时直接load缓存几秒钟就能恢复全素材库的特征索引。3. 核心实现图片重复检测与整理3.1 图片特征提取的工程实现图片特征提取是整个工具的地基我直接对每个图片文件做了一次标准化的预处理流水线。第一步读取图片统一转成RGB格式这一步非常必要因为很多目录里混杂着灰度图、CMYK图或者带Alpha通道的PNGPIL直接打开转RGB可以避免后续模型输入通道不匹配的报错。第二步resize到224x224这一步会丢失一些细节信息但CNN模型本身是在这个尺寸下训练的喂更大尺寸反而会影响特征分布的一致性。第三步把像素从0到255归一化到模型期望的均值方差区间使用ImageNet的统计参数0.4850.4560.406和0.2290.2240.225。核心提取代码如下我用的是PyTorch生态模型结构直接走torchvision的预训练权重import torch import torch.nn as nn from torchvision import models, transforms from PIL import Image import numpy as np model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1) model.classifier nn.Identity() model.eval() if torch.cuda.is_available(): model model.cuda() preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def extract_image_feature(img_path): try: img Image.open(img_path).convert(RGB) x preprocess(img).unsqueeze(0) if torch.cuda.is_available(): x x.cuda() with torch.no_grad(): feat model(x).cpu().numpy().flatten() return feat.astype(np.float32) except Exception as e: return None这一段代码有几个细节值得注意。model.classifier nn.Identity()这一步是精髓等于砍掉了模型末尾的分类头让模型输出的不再是某个图片类别的概率而是进入分类层之前那个1280维的高层语义向量。另外torch.no_grad()不能漏漏了的话推理阶段会累计梯度内存占用会随图片数量线性增加处理几千张图可能直接OOM。特征类型我强制转成float32因为这个精度在余弦相似度计算里完全够用却能比float64省一半内存和计算开销。3.2 批量扫描与特征缓存机制面对全量素材逐张调用上面的函数还不够必须配合一个扫描驱动逻辑。我这边按扩展名过滤出候选图片再跳过文件大小小于1KB的损坏文件以及隐藏目录和回收站相关目录。扫描完成之后把所有有效文件的路径、大小、修改时间、目标特征路径写入一个清单文件然后逐条执行特征提取。提取完的特征不要直接丢内存里因为图片量一大几万个1280维的float32向量也有几百MB。我采用按目录分块存储的策略每个根目录生成一个独立的npy特征矩阵同时保存一个对应的路径列表。这样在增量扫描时只需要对新增文件做特征提取旧文件直接从磁盘加载向量矩阵避免全量重算。这里我踩过一个很实际的坑有些图片在读取阶段能正常打开但模型推理时会抛出异常原因是图片本身是伪造的扩展名实际内容不是有效图像数据。所以我给每个文件提取加了一层try-except把失败的文件单独记录到一个error_log里而不是中断整个流程。这个日志在后续清理文件时也非常有用那些打不开的图片本身就是潜在残缺文件建议结合人工判断再决定是否处理。3.3 两两相似度比对与重复分组有了特征矩阵接下来就进入核心去重逻辑。最直接的做法是两层循环算所有特征两两之间的余弦相似度复杂度是O(n²)n为图片数量。当n达到两万时两两组合数就是将近两亿纯Python双层循环跑一趟至少二十分钟起步。这里必须用向量化技巧把特征矩阵按L2范数做归一化之后相似度计算直接变成矩阵乘法feat_matrix feat_matrix.T一次就能得到全量的余弦相似度矩阵。我在实现时没有把整个矩阵保存在内存中因为两万张图的全量相似度矩阵是两万乘两万的float32大概需要1.6GB内存勉强能抗但五万张图就吃不消了。更合理的做法是分块计算例如每次取1000行特征与全量特征矩阵做矩阵乘法只保留每一行中超过阈值的索引和分数输出成稀疏的“候选对”列表。这样内存占用始终是可控的而且分段结果可以增量保存到磁盘方便断点续跑。下面是分组逻辑的示意代码import numpy as np def find_duplicate_groups(feat_matrix, path_list, threshold0.90): norms np.linalg.norm(feat_matrix, axis1, keepdimsTrue) mat_norm feat_matrix / norms groups [] visited set() for i in range(len(mat_norm)): if i in visited: continue sims mat_norm[i:i1] mat_norm.T sims sims.flatten() dup_idx np.where(sims threshold)[0] dup_idx [idx for idx in dup_idx if idx ! i] if dup_idx: group [path_list[i]] [path_list[idx] for idx in dup_idx] groups.append(group) visited.update(dup_idx) visited.add(i) return groups这个分组的策略是典型的“以第一个未被处理过的样本为中心建立重复簇”逻辑简单结果够用。有一个需要留意的地方相似关系不一定满足传递性A和B相似、B和C相似不代表A和C也一定相似。但实际在重复检测场景里这种传递性不成立的情况影响很小因为同一个内容的多个副本通常会围绕原始文件形成紧密的相似簇。如果追求更严谨的聚类效果可以换成DBSCAN或者连通分量查找但对于文件整理目的来说上面的贪心分组已经足够。分组之后输出结果时要附带每组内部的相似度矩阵摘要比如该组内最低相似度、平均相似度、典型文件大小。这些信息在整理阶段非常有用比如同样的画面4K原图有20MB压缩缓存版只有500KB就可以根据文件大小智能选择保留大文件还是保留小文件。3.4 文件整理策略与安全执行检测和整理必须分段防止出现不可逆的风险。我实际的整理策略是先在每组候选重复项里选出一个“锚点文件”默认挑选文件大小最大、分辨率最高或者目录层级最规范的那个作为保留对象其余副本统一移动到/duplicates/日期/目录下不直接删除。移动而不是删除是为了给自己留一个缓冲期跑一周确认没问题之后再批量清空重复目录。这里对视频文件要特别小心。视频存在转码、截断、倍速播放等复杂情况CNN图像特征只能捕捉单帧画面如果整段视频内容只有部分相同直接按整体特征判定为重复很容易误删。所以视频整理我永远保持只归档、不自动删除的状态而且每一组候选都必须输出前几帧的缩略图拼接图供人工快速确认。图片的话风险相对小一些但依然建议先移动到重复目录而不是直接进回收站。4. 视频重复检测从抽帧到特征聚合4.1 视频抽帧策略与关键帧选取视频没有天然的单张图像不能直接把视频文件喂给CNN图片特征提取器必须先抽帧。抽帧策略直接影响重复检测的质量和耗时我踩过一个很明显的坑固定抽第1秒、第5秒、第10秒的画面结果发现很多重复视频的片头时长不一致有的有片头logo有的直接进正片固定时间点完全错过重叠画面。后来我改成按固定间隔抽帧加限时策略比如每2秒抽一帧最多抽30帧。为什么选2秒因为重复视频即使有轻微裁剪或倍速差2秒间隔下的相邻帧特征序列仍然能保持局部一致。30帧上限是为了控制计算量一个半小时的电影抽满30帧足够覆盖代表性画面500帧也没必要。如果视频本身小于30秒就会抽不到几个帧这时候我会把间隔缩到0.5秒确保至少抽取8帧。抽帧用的工具是OpenCV具体代码模式是这样的import cv2 def sample_frames(video_path, interval_sec2.0, max_frames30): cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) if fps 0 or fps 240: fps 25.0 interval_frames max(1, int(round(fps * interval_sec))) frames [] frame_count 0 while True: ret, frame cap.read() if not ret: break if frame_count % interval_frames 0: frames.append(frame) if len(frames) max_frames: break frame_count 1 cap.release() return frames这个实现里有个细节值得注意我强制对fps做了合法性校验。有些视频文件元数据损坏读出来的fps是0或者离谱的大数直接用它算间隔会导致误采样。设置上限到240如果超出就回退到25帧的通用值宁可采样间隔略有偏差也不能让程序崩溃或者直接退出。4.2 帧特征序列的聚合表示抽出来的帧不能直接拼个平均向量就完事。我试过对全部帧特征做简单平均效果在“整段视频完全重复”的情况下尚可但一旦两个视频只是中间段落重叠比如同一个素材剪出了一个30秒版和一个60秒版简单平均会把不重叠部分的特征也混进去导致相似度被严重稀释。参考多模态特征提取与时序对齐里的常见做法我这里设计了两个聚合层。第一层是帧级的局部比较先对所有帧提取CNN特征然后两个视频的特征序列之间做逐帧匹配找到最大相似度匹配对。这个步骤能找出最相似的一帧作为“内容重叠的证据”。第二层是在最大相似帧附近取一个窗口比如前后各5帧共11帧把这一小段局部序列的特征做均值池化作为整个视频的代表特征。这样即使视频有倍速差局部窗口内也能稳定对齐。def video_representative_feature(frames): frame_feats [extract_image_feature(frame_to_pil(f)) for f in frames] # 取中间段的平均特征作为一个保守的代表特征 mid len(frame_feats) // 2 return np.mean(frame_feats[max(0, mid-5):mid6], axis0)简化版代码里取中间窗口只是个近似策略。实际工程里我是把视频A的全帧序列与视频B的全帧序列做一次轻量级的动态时间规整匹配DTW确定对齐路径后再取对齐区间内特征的平均值。这个过程计算量大一些但只对抽帧后的几十帧做开销完全可以接受。跑全库视频查重时所有视频预先抽帧并提取完特征两两比较阶段基本就是空间换时间的矩阵操作。4.3 视频重复检测的判定阈值视频判定阈值和图片要有区分。图片重复检测的阈值我设置在0.90因为图片单张特征本身就比较稳定。视频经过抽帧、聚合、归一化这一通操作特征信息被压缩相似度普遍比图片低一点。我实测下来同一个内容不同码率版本的两个MP4聚合特征相似度大概在0.80到0.90之间完全不同的视频相似度普遍低于0.55。所以我给视频单独设置一个阈值参数默认0.78左右同时要求两个视频的时长差不能超过3倍。这条时长约束是个很实用的防误判手段因为两个画面相似但长度完全不同的视频很可能只是拍摄了同一个场景而不是重复文件。检测类型默认阈值额外约束处理方式图片重复0.90无自动归档副本视频重复0.78时长差≤3倍只标记候选需人工复核视频局部重复0.70局部片段匹配只输出警告不自动移动5. 工程化细节与性能优化5.1 多线程与流水线并行全量素材库的特征提取环节是I/O密集和计算密集的混合体纯单线程跑太浪费时间。我用了Python的concurrent.futures.ThreadPoolExecutor实现图片读取与预处理的并行再用一个进程池专门做模型推理。之所以不直接用多线程跑PyTorch模型是因为PyTorch的推理在Python线程里受GIL影响比较大多个线程同时调模型反而会互相阻塞不如拆成两段流水线多个线程负责读图、resize、Normalize把张量准备好放进队列一个独立进程从队列取张量做批量推理。这里的核心优化思想是把预处理和推理解耦。预处理线程可以开8到16个因为PIL的open和resize会释放GIL并行度很高推理进程内用PyTorch的batch推理把张量堆叠成batch一次性走一次前向传播batch size设为32可以减少Python调用模型的次数和GPU核函数的launch开销。我只用一个推理进程再加一个CPU下的OpenMP多线程实测下来CPU推理吞吐量可以提升三倍左右。5.2 相似度矩阵的分块计算前文提过两两比对是O(n²)复杂度在工程化时我把它做了更细的优化。二次检查阶段我把特征矩阵按文件大小做预剪枝如果两个文件大小相差超过一定比例比如大文件是小文件的5倍以上那么它们被判定为重复的概率很低可以直接跳过不用参与相似度计算。这个基于文件大小的剪枝策略不是绝对准确但能过滤掉相当一部分明明不可能是同一个文件的组合把真正的计算集缩小到同级别文件范围内。这一步的收益非常大。两万张图里有大量小体积的图标、截图和超大体积的原片它们之间根本不存在重复关系。我做了一次统计启用文件大小预剪枝后真正需要计算的两两组合数量降到原先的40%并且没有出现任何因为剪枝而漏检的情况。剪枝是硬规则阈值设得保守一点比如大小差超过6倍才跳过宁可多算一些也不漏检。5.3 增量更新与索引持久化素材库不是一成不变的今天导入了新拍摄的照片明天可能又加了一堆下载的视频。如果每次都用全量重扫一遍耗时不可接受。所以我在工具里做了一个基于文件路径加修改时间的增量索引本地维护一个JSON或者SQLite表记录每个文件的路径、大小、mtime以及是否已提取特征。扫描时先对比文件系统的状态与索引状态只有新增文件或mtime变化的文件才需要重新提取特征。已存在的特征向量直接按路径映射加载。这个机制看似不起眼但对真实使用体验的提升是决定性的。打个比方五万张图的库全量提取一次耗时约一小时之后每天只新增一两百张图增量扫描只需要一两分钟完全不影响日常工作流。增量更新有一个需要注意的坑如果文件被移动了位置但没有修改内容它的mtime可能已经变化导致重复提取特征。这个问题可以通过快速hash文件大小加mtime的组合判断如果大小没变且mtime变化极小往往只是文件复制操作导致的时间戳更新这种情况可以跳过重算。我在索引表里额外存了文件大小三者同时不一致才判定为文件已变更。6. 常见问题与排查经验实录6.1 相似度很高的两张图其实是不同内容这个情况我遇到过好几次两个完全不相关的风景照片相似度居然能到0.82。原因是图片中有大面积天空、草地或纯色背景这类无纹理区域会让CNN的特征分布偏向特定方向。如果素材里大量存在这种以简单背景为主的图片那阈值就要适当上调比如调到0.92。另外还有一种情况两张图的内容主体相同但构图差异很大CNN提取出的特征相似度反而只有0.6。这说明特征提取是在理解语义而不是在比对像素画幅。遇到这种情况建议结合感知哈希做二级过滤先用CNN粗筛出相似度高于0.75的候选再用pHash对比能纠正一部分误判。6.2 视频抽帧结果全是黑屏或花屏视频查重时最烦的就是抽出来几十帧全是黑屏或者花屏带异常色块。造成这个现象的原因通常是视频编码格式解码不完整比如HDR10视频在OpenCV默认解码下颜色空间映射错误或者损坏的MP4只有索引没有数据。排查时先得判断是视频源损坏还是抽帧逻辑问题。我建议用FFmpeg命令去验证解码状态OpenCV的VideoCapture对某些编码标准支持不够稳定。如果确认是编码兼容性问题工程上的解法是切换到FFmpeg作为抽帧后端通过管道直接把帧数据传给Python处理。用subprocess调用ffmpeg -i input.mp4 -vf fps1/2 -f image2pipe -vcodec rawvideo -pix_fmt bgr24 -这样直接从FFmpeg解码出来的帧就是干净的BGR数组绕开OpenCV的兼容性问题。视频素材越杂越值得在一开始就引入FFmpeg抽帧方案。6.3 特征提取速度太慢慢不慢是相对的CPU推理环境下EfficientNet-B0的处理速度大概是每秒4到8张批量素材几万张确实要跑一阵子。这里有几个提升速度的顺序按收益从高到低排列第一优先是安装CPU版本的OpenVINO或ONNX Runtime把PyTorch模型导出为ONNX后用ONNX Runtime推理速度能提升七八倍而且无需GPU第二优先是调大batch size在ONNX Runtime里一次性输入64张图能进一步榨干CPU向量化指令的性能第三是减少不必要的预处理比如原图明确小于224x224的直接缩放反而更慢可以跳过部分resize逻辑。我实际把PyTorch模型导出成ONNX之后五万张图的特征提取时间从两个小时压缩到了二十分钟以内这个提升是立竿见影的。导出过程也很简单用torch.onnx.export传一个示例输入即可但记得把模型设为eval模式再导出否则模型的batch norm层会因依赖运行统计结果而出错。6.4 重复文件归档后软件打不开原目录有一种相对隐蔽的坑整理工具把重复文件移动到新目录后原目录下某些软件还在引用这些文件路径导致打开工程时提示找不到素材。这是因为很多工具比如剪辑软件的工程文件、笔记软件的附件库保存的是绝对路径文件一移动引用就断了。这个问题的缓解办法是不要做物理移动而是对重复文件创建相对路径引用清单。换句话说把检测结果保存成一个CSV报告里面标明“保留文件路径”和“重复文件路径”由业务侧的软件或脚本去处理这些副本而不是直接粗暴地把文件移走。我用这个方案之后再也没有出现过整理完素材导致其他软件报错的情况。诚然保留重复文件会占用磁盘空间但真实整理场景中磁盘空间的安全性比空间本身更重要把移动操作留给人去执行和理解本身就是工具设计的一部分。6.5 特征缓存文件损坏导致索引失效npy缓存文件如果因为磁盘写入中断或版本更新导致形状不匹配加载时就会崩。我的规避措施是缓存文件头写入一行固定的元数据JSON包括模型名称、特征维度、文件总数、提取时间。加载时先校验这几个字段如果维度不对或者模型名称对不上就直接废弃该缓存并重新提取。加这层校验花不了多少代码量却能避免非常难以排查的隐性bug——想象一下特征向量串位之后相似度分组结果完全错乱但程序不报任何错误那种状况是最难追查的。现在这个工具已经稳定运行了挺长时间我个人的体会是CNN特征提取方案在本地素材管理上的确比传统哈希和感知哈希高出一个维度。所谓“重复”的定义从“字节相同”升级成了“语义相同”这种视角的转换让原本很难处理的场景变得异常清晰。几个实践下来值得再次强调的经验检测和整理务必分离阈值必须根据自己素材的分布去调视频处理一定要留人工复核口子。后续如果再扩展我想给这个工具加上简单的相似素材聚类浏览界面把“重复检测”进一步升级成“素材智能归类”毕竟CNN特征向量都算出来了多维度聚类不过是多跑一段聚类的功夫。