2026/9/13 13:23:33

短视频相似性检测:多哈希召回与双神经网络精排架构解析

短视频相似性检测:多哈希召回与双神经网络精排架构解析 简介面向短视频平台内容审核、版权保护与个性化推荐等场景本项目提出并实现了一套结合多哈希算法与孪生神经网络的相似性检测完整方案。方案将视频关键帧特征转换为哈希码再通过共享权重的双神经子网络学习度量兼顾检索速度与精度能有效应对大规模视频库的相似内容匹配问题。压缩包共277个文件以py源代码、PDF设计文档、Vue前端页面和HTML交互模板为核心辅以jpg/png算法流程图、js脚本、XML配置、mp4效果演示及SQLite数据库整体约77.09MB。内容覆盖从特征提取、哈希编码到相似度度量的完整代码实现并配有登录注册、视频上传等可运行界面与部署说明便于读者理解原理并直接复现或扩展。已有64人学习浏览适合具备Python基础并从事计算机视觉、多媒体检索方向研究的学生、算法工程师及课题开发人员参考。1. 视频相似度检测不是“相似图片”的简单复用短视频服务平台每天涌入大量搬运、混剪和二次创作内容单靠人工审核或依赖视频文件名、MD5 去重早已失效。真正的相似性检测要解决的是“同一个视频被裁剪、加滤镜、调整播放速度、翻转甚至重新配音后系统仍能判断它和原始视频相关”的问题。常见做法是先用哈希算法把视频降维成指纹快速排除绝大多数不相关内容再用神经网络对候选集合做精细比对。这套“多哈希召回 双神经网络精排”的架构既能控制计算成本又能拿到比单一方法高得多的准确率。本文面向需要处理视频去重、版权识别或推荐场景去重的工程师重点讲清多哈希如何设计、双神经网络各自承担什么任务以及从抽帧到输出相似度分数的完整落地路径。2. 多哈希为什么一套指纹不够以及指纹怎么设计2.1 感知哈希三件套aHash / pHash / dHash 各自捕捉了什么视频相似性检测的第一层是“粗筛”目标不是把相似视频全找出来而是把明显不相关的视频快速扔掉。粗筛最常用的工具是感知哈希Perceptual Hash。与 MD5 或 SHA-1 这类加密哈希不同感知哈希把视觉内容的“结构特征”压缩成一组比特串两张图片只要人眼看相似它们的感知哈希就会接近汉明距离小。业内常见的感知哈希算法有三个aHashAverage Hash将图像缩放到固定尺寸通常 8x8 或 16x16转成灰度图后计算所有像素的均值每个像素与均值比较大于记为 1小于记为 0组成哈希值。它的计算量最小速度极快但对亮度变化、加字幕框这类改动较敏感。pHashPerceptual HashDCT 变体缩放后先做离散余弦变换DCT保留低频分量再对低频部分取均值生成哈希。它对亮度和轻微几何形变的鲁棒性比 aHash 强是短视频封面等价检测中最常用的方案。dHashDifference Hash比较相邻像素的灰度差值方向记录差值的符号。它对亮度变化和轻微的缩放最不敏感擅长捕捉“构图轮廓”相似的内容。在短视频场景里我不推荐只使用其中任何一个。原因是短视频的变换太复杂竖屏转横屏会裁边加滤镜会改变色彩分布加字幕会破坏局部 DCT 系数。单一哈希很容易被某种变换击中导致同一个视频的指纹跑偏。2.2 多哈希投票用“多个弱信号”堆出稳定召回多哈希的核心设计理念是每个哈希算法单独看都是弱分类器但把它们组合起来做投票能显著提升稳定性。常见的做法是“抽帧后逐帧计算多哈希分桶索引再按桶内命中次数投票”。一个标准的最小实现大致是这样的import cv2 import numpy as np from PIL import Image def resize_and_gray(frame, size(16, 16)): # 统一缩放到 16x16转灰度减少后续计算压力 img cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) return cv2.resize(img, size, interpolationcv2.INTER_AREA) def a_hash(frame): img resize_and_gray(frame, (16, 16)) avg np.mean(img) # 与均值比较生成 256 位哈希 return (img avg).flatten().astype(np.uint8) def d_hash(frame): img resize_and_gray(frame, (17, 16)) diff img[:, 1:] img[:, :-1] return diff.flatten().astype(np.uint8) def phash(frame, hash_size16): # 用 DCT 低频分量生成感知哈希 img resize_and_gray(frame, (32, 32)) dct cv2.dct(np.float32(img)) low_freq dct[:hash_size, :hash_size] avg np.mean(low_freq) return (low_freq avg).flatten().astype(np.uint8)代码说明a_hash把 16x16 灰度图与均值比较生成 256 位哈希d_hash通过相邻列像素差的方向生成 16x16256 位pHash先缩放至 32x32 做 DCT再取出 16x16 低频块与均值比较。三个哈希各捕捉不同维度的视觉差异组合后单独一个算法被干扰时另外两个还能兜住。实际工程中每 0.5 秒抽一帧每帧计算三种哈希后拼成一个 768 位的“指纹串”再按哈希类型分别写入不同的索引。查询时把待检测视频的帧同样计算三种哈希分桶取出候选哈希计算汉明距离小于阈值的帧然后按视频 ID 聚合投票。只有当多帧、多哈希同时命中时才判定属于候选集。2.3 从指纹到索引汉明距离的边界与近似检索多哈希方案解决了“怎么算指纹”的问题但视频库数量上来后线性比对汉明距离是不可行的。常见的做法是用**局部敏感哈希LSH**对每帧的指纹分桶或直接用支持汉明距离的近似近邻库如 Faiss 的 IndexBinaryFlat / IndexBinaryIVF。IndexBinaryFlat 适合数据量在千万级以内的场景查询时直接暴力计算汉明距离准确率 100%但内存占用较高。IndexBinaryIVF 适合数据量更大或要求低延迟的场景通过先聚类再查询的方式把召回范围缩小到少数几个桶速度提升明显代价是召回率略微下降。需要注意一个边界问题感知哈希对“较大幅度的改动”容忍度有限。比如同一个视频被插入了 10 秒无关片段后重新发布逐帧对比时中间那一整段都会是低匹配结果。此时依赖“整段视频指纹一致”的思路会直接漏检需要通过“滑动窗口 帧级投票”把匹配帧数量抬高后再做聚合判断。哈希层只负责召回候选不负责真正判定相似——判定交给后面的神经网络。3. 双神经网络哈希筛完后的第二道精密筛选3.1 第一路网络从关键帧中学习视觉内容表示多哈希召回的候选集里可能混着一些“哈希上接近但内容并不相同”的视频例如同一部电影的两个不同片段或同一帧画面的不同字幕版本。这一步需要神经网络来做精细化判断输出一个可比较的内容表示向量。第一路网络处理的对象是单帧图像。它通常会用一个在图像分类或图像检索任务上预训练的卷积网络如 ResNet50、EfficientNet作为骨干去掉最后的全连接分类层把倒数第二层的特征向量作为该帧的 embedding。但只做单帧特征提取还不够因为短视频的“同一帧”往往被施加了缩放、裁剪、加速等变换直接比对单帧特征会产生大量误判。工程上更有效的做法是关键帧聚合先从视频中按均匀间隔抽取关键帧逐帧提取 embedding再对这些 embedding 做平均池化或最大池化得到一个代表整个视频的全局内容表示。这里有一个经验值均匀抽 816 帧通常就够了帧数再多并不会显著提升准确率反而增加计算耗时。池化方式的选择很关键平均池化对“整条视频风格一致”的情况更稳健最大池化对“只希望抓到几个核心镜头”更敏感。两段视频的全局内容向量之间的相似度常用余弦相似度或内积来衡量。实际测试中阈值设在 0.820.9 之间时能过滤掉大多数不相关视频。但仅靠这一个向量仍然区分不了“结构不同但画面相似”的情况。3.2 第二路网络捕获时序结构与动作节奏很多被搬运、翻拍或混剪的短视频在视觉内容上有一定变化但时序结构高度相似例如同一套运镜顺序、同一段音乐卡点节奏、同一组动作编排顺序。第一路网络只做帧级内容表示它不会关注帧之间的先后关系和整体节奏。因此需要一个专门的时序网络来处理“顺序”和“动作模式”。第二路网络可以选用以下几类结构时间序列模型将第一路网络提取每一帧的特征按时序组成特征序列送入 GRU / LSTM 或 Transformer 编码器输出一个视频级别的时序表示。视频动作识别网络直接使用 3D-CNN如 C3D、I3D、SlowFast从连续帧序列中同时提取空间和时序特征输出维度稍高的动作表示。光流差分网络对相邻帧计算光流训练一个分类/匹配网络让模型学习“动作模式”而非“画面内容”。实际落地上我建议把第一路网络当作“提取器”第二路网络当作“编码器”。具体流程是多哈希召回候选视频后先用第一路网络过滤内容不相关的项节省第二路网络的计算量再对候选做时序特征提取与比对。这里有一个隐藏的关键点。如果两段视频内容完全一样但时间方向被打乱例如原视频被从头到尾倒放第一路网络的帧级表示不会察觉问题但第二路网络必须能识别出这种倒序特征。训练时可以加入“时序顺序是否一致”的二分类损失也可以使用时序对比学习例如把帧序列打乱作为负样本让网络学到真正的时序语义而不只是画面的静态相似性。3.3 双网络如何配合以及相似度得分怎样合成单用第一路网络可能出现画面相似但内容完全不相关的情况例如大量美食教程开头都有一幕“食材摆盘”镜头、大量游戏短视频都截取同一个“胜利结算”画面。单用第二路网络则可能把节奏一致但内容不同的视频误判为相似。两个网络配合就是要同时满足“画面相似”和“结构相似”。典型的配合方式是串行级联第一路网络先计算候选视频与查询视频的全局内容余弦相似度低于阈值 T1 的直接剔除通过第一路阈值过滤后的候选集进入第二路网络提取时序 embedding计算时序相似度 S2最终相似度得分由两个得分加权合成S 0.65 * S1 0.35 * S2或S S1 * S2乘法形式惩罚任何一个维度相似度低的情况。需要注意的是加权系数不要拍脑袋定死。应根据业务场景来定如果目标是打击“直接搬运”S1 权重要大如果目标是抓“翻拍抄袭”S2 权重应该提高。实际操作中先随机抽取 300500 个标记好的相似/不相似视频对在验证集上穷举几组权重组合选出 F1 最高的那组作为默认值。训练层面双神经网络的常见策略是使用对比学习Contrastive Learning构造正样本对同一个视频经过视觉变换后的两个版本和负样本对不同视频分别经过两个网络分支使用 Triplet Loss 或 InfoNCE Loss 来拉近正样本对距离、推远负样本对距离。训练时要注意 Batch Size 不要太小经验上 Batch Size 至少 128否则对比学习的收敛速度和稳定性都比较差。4. 搭建一个可复现的短视频相似性检测系统4.1 最小系统架构与运行链路多哈希和双神经网络的完整系统本质上是一条“三段式”流水线。第一段负责把视频转成指纹库第二段负责把入库视频的帧 embedding 和时序 embedding 提取出来第三段在查询时对候选集做精排。完整链路如下视频输入统一抽帧策略采样率、最大帧数、关键帧选择对每一帧做多哈希写入二进制指纹索引对同一段视频的帧跑双神经网络提特征特征写入向量数据库查询视频同样经过上述三步先走哈希索引召回候选集候选集过双神经网络精排得到最终相似度得分命中高相似度的视频进入人工审核或自动下架流程。4.2 查询端关键代码从哈希召回过渡到神经网络精排这里给出查询侧的半伪代码实现重点是展示“哈希召回什么时机交给神经网络”的衔接逻辑import numpy as np from sklearn.preprocessing import normalize def extract_video_frames(video_path, skip_interval_secs0.5): cap cv2.VideoCapture(video_path) fps int(cap.get(cv2.CAP_PROP_FPS)) frames [] cur_frame 0 step int(fps * skip_interval_secs) while True: ret, frame cap.read() if not ret: break if cur_frame % step 0: frames.append(frame) cur_frame 1 cap.release() # 限制最多取 32 帧防止长视频拖垮后续计算 if len(frames) 32: indices np.linspace(0, len(frames)-1, 32, dtypeint) frames [frames[i] for i in indices] return frames def query_short_video(video_path, hasher_index, nets, top_k50): frames extract_video_frames(video_path) hash_bits [combine_hashes(f) for f in frames] # 用多个哈希桶的命中数决定是否进入下一层 candidate_ids hasher_index.search(hash_bits, threshold_hamming10) if len(candidate_ids) 1: return [] # 第一路网络帧级内容过滤 frame_embeds [nets[content_net].predict(f) for f in frames] video_embed np.mean(frame_embeds, axis0) content_scores faiss_index_content.search(normalize(video_embed)[None, :], top_k) candidates_keep [c for c, s in zip(content_scores[1][0], content_scores[0][0]) if s 0.82] if len(candidates_keep) 0: return [] # 第二路网络时序编码精排 seq_embed nets[temporal_net].predict(frame_embeds) final_scores faiss_index_temporal.search(normalize(seq_embed)[None, :], len(candidates_keep)) return final_scores代码说明这里的sleep_interval_secs0.5表示每半秒抽一帧16 或 32 帧是特征聚合时的常用上限再多的帧会让神经网络的推理时间线性增长但准确率增益有限。hasher_index.search内部对每帧哈希做汉明距离匹配threshold_hamming10表示如果两帧指纹超过 10 bit 不同就不算匹配。帧级内容过滤阈值 0.82 来自余弦相似度实际需要按预训练模型微调。4.3 内存索引与向量库的参数选择系统跑起来后搜索性能瓶颈通常在向量库和哈希索引的底层参数上。以 Faiss 为例存储对象索引类型关键参数适用规模说明二进制哈希IndexBinaryIVFnlist聚类中心数千万级以上nlist 设为 sqrt(数据量) 效果较好二进制哈希IndexBinaryFlat无百万至千万级暴力搜索准确率最高内容 embeddingIndexIVFFlatnlist、nprobe十万至百万级nprobe 越大召回越高延迟越高时序 embeddingIndexHNSWFlatM、efConstruction、efSearch十万级以内M32 时检索速度与准确率平衡较好这里有一个容易被忽略的细节IndexIVFFlat 需要把训练集聚类后生成量化器。训练数据必须来自真实业务视频的 embedding 分布不能拿随机图片的 embedding 来训练否则聚类中心严重偏移查询时会大面积漏召回。实践中训练集至少采样不少于 10 万个视频的 embedding 做聚类聚类后检查每一类的样本数如果某些类样本过少要增大 nlist 或重新采样。5. 参数调节与排错影响召回率和误检率的几个关键旋钮5.1 关键参数的推荐初始值整个系统的精确度并不只由神经网络决定抽帧策略和哈希阈值对最终效果的影响同样不可忽视。以短视频场景的常见配置作为参考抽帧间隔0.5 秒一帧适合大多数情况。如果视频时长很短10 秒以内应改为固定抽取 8 帧避免长视频和短视频之间因为帧数差异造成匹配率偏低。哈希位长256 位16x16是常用折中。位长太短如 64 位会让不好区分的视频落入同一个桶误召回率飙升太长如 1024 位则对扰动过于敏感稍加滤镜就匹配不上。汉明距离阈值256 位哈希下阈值设在 812 位比较合理。经验上有两种情况纯搬运无改动时汉明距离通常小于 4加了水印和字幕但主体未动距离通常在 510改动较大时距离超过 15。内容网络相似度阈值余弦 0.820.88 之间先跑一个月记录线上命中视频的人工审核通过率再反向调整。时序网络相似度阈值建议初始设为 0.75。因为时序特征的分布比内容特征更“挤”阈值稍微降低一点召回率的提升会很显著误检率则需要配合人工抽检来控制。5.2 误检与漏检的排查路径误检和漏检是这个系统上线后最常被投诉的问题排查方向有固定的优先级顺序。误检高发时先看哈希层是不是太“宽”。把某一条误检记录对应的两段视频抽帧后分别打印每个哈希算法的汉明距离。如果距离在 10 以上仍然被召回说明阈值太宽应该提高。如果两个算法各自由一张恰好相似的帧造成投票命中则需要把投票规则从“任一帧命中即算”改成“连续 3 帧以上命中才进入候选”。漏检高发时优先排查抽帧密度。短视频常见的变体是原视频被压缩到很短的时长按 0.5 秒间隔抽帧后可能只抽出寥寥几帧加上哈希和神经网络都需要多帧信息漏检就发生了。另一种常见原因是两个视频分辨率差异巨大例如一个 1080p 一个 240p。此时在哈希层极易错过因为高分辨率视频经过缩放后丢失了部分低频结构。应对办法是在提取哈希之前先把两段视频的控制帧统一缩放到 64x64 的同一尺寸再计算。关于神经网络的干扰还有一种情况要特别说明双神经网络在推理时不接受任意尺寸输入训练时通常把输入帧统一 resize 到 224x224。如果线上代码把帧直接送入网络而忘了 resize模型会拿到不符合预期尺寸的数据多个版本的推理结果会出现抖动。这类问题不会直接报错但会让同一视频二次查询时输出完全不同的相似度分数。建议在特征提取处加一个尺寸断言确保每个 batch 的输入都是相同尺寸。另外生产环境的推理最好把图像归一化参数固定下来。帧被 resize 后还要执行/ 255.0再用 ImageNet 预训练模型的均值和标准差对通道做标准化。漏写这一步骤会导致特征分布偏移使相似度整体偏高或偏低。6. 用冷启动验证替代盲目调参让阈值自己找到位置系统搭建完成后不要急着用网上的 demo 视频测试也不要只依赖一两个人工标注样本调阈值。正确起始做法是构建一个“冷启动验证集”规模不必太大但结构要能代表真实的线上分布。验证集应该包含以下三类样本正样本对同一个源视频经过裁剪、加滤镜、调速、加字幕、翻转等单一或组合变换后的成对视频至少 200 对难负样本对内容来自同一部电影、同一档直播、同一主题但不同片段的两段视频至少 200 对随机负样本对完全无关的视频对 500 对。在这套验证集上计算不同阈值组合下的召回率、准确率和 F1。记录一组数据哈希阈值10, 内容阈值0.82, 时序阈值0.75时的精确率和召回率然后每次只调整其中一个阈值观察曲线。最终选择的阈值组合应该是在保持 95% 以上召回率的前提下取精确率最高的那组。验证通过后把同一套验证集固化为回归测试集每次改动抽帧策略、哈希位数、网络结构参数后重新跑一遍确保改动没有破坏原有精度。冷启动完成后再逐步用线上真实数据扩充难负样本集让系统在持续迭代中更贴近业务分布。值得最后强调的是相似性检测系统本质上是一个需要持续维护的系统哈希算法和神经网络版本都会随业务变化而演进但验证集的标尺作用不会变回归测试反而是保证系统长期可靠最划得来的投资。本文还有配套的精品资源点击获取