[P0] 查重率恒为0%:降重裁剪污染pHash指纹 + 无局部片段复用检出 + 时长/直方图/coverage单位bug #1702

Closed
opened 2026-09-05 00:38:11 +08:00 by xiaoxia · 1 comment
Owner

现象

两条明显同源的成片(generated-a6700c39.mp4 20s 与 generated-41a49f15.mp4 11s,同一镜头/人物做腿部护理),查重率都显示 0.0%。

根因(已实查 staging 数据库+真实指纹离线验证)

查重跑了、分片指纹齐全(5片/9片),但第一层帧匹配就 0 命中:

  • 45 个分片对汉明距离最小 12,代码要求严格 < PHASH_THRESHOLD(8) → 0 帧匹配 → match_ratio / 时序覆盖 / 融合分全部短路为 0,visual_similarity/duplicate_rate 写入字面值 0。
  • 距离被推高的主因:每条成片都强制随机 2-5% 边缘裁剪降重(generation.py:719 random_edge_crop,注释明写"用于改变 pHash 指纹")。两次裁剪随机不同,同源镜头 pHash 位翻转 12-16。降重功能与自查重自相矛盾
  • 次因:场景切割导致分片边界不对齐,取帧时刻错位。
  • 结构性问题:算法按"整条视频重复"设计(match_ratio 分母是新视频全部帧数,frame_match_rate<0.3 直接 continue 不计分),没有局部片段复用检出能力;MIN_CONSECUTIVE_MATCHES=5 让 11s/5片短视频天然无法报片段。

确定性 Bug(顺带实查发现)

  1. check_duplicate 时长预过滤单位错误duration_sec = fingerprint.duration / 1000,但 fingerprint.duration 单位是秒 → duration_max≈0.0135s,list_by_user(duration_min/duration_max) 把所有真实视频过滤掉,scope="user" 的 is_duplicate 跨项目判定永远返回 None(跨项目重复标记实际全失效)。位置:dedup.py check_duplicate_task 与 dedup_helpers 对应处。
  2. 颜色直方图归一化错误:compute_color_histogram 用 cv2.normalize 默认 L2,而 _bhattacharyya_coefficient 按概率分布(Σ=1)设计,实测系数 ~14.9(理论上限1),放松 pHash 阈值后会误判。应改 NORM_L1。
  3. temporal_coverage 单位错误(dedup.py:901/962):total_duration_ms = fingerprint.duration(实为秒),covered_ms/秒 量纲错,命中一段即 min(1.0) 判 100% 覆盖。应 * 1000
  4. 阈值比较不一致:帧匹配严格 <8,find_duplicate_segments 用 <=8,应统一。

修复要求

P0 — 让同源/局部复用可检出

  1. 查重指纹必须基于降重裁剪前的画面:用原始渲染产物算 pHash,或算指纹前对成片居中裁除边缘(如中心 90% 区域),使 random_edge_crop 不再污染自查重指纹。降重只服务外部平台,不影响自查。
  2. 支持局部片段复用:duplicate_rate/visual_similarity 以命中片段时长占比(temporal_coverage)为主指标,去掉"frame_match_rate<0.3 全片才计分"的硬跳过;match_ratio 分母改为 min(两视频分片数) 或按命中片段统计。
  3. 分片对齐:比对时允许 ±1 邻接窗口 / 时序单调偏移对齐,缓解切点不一致。
  4. 短视频连续匹配门槛自适应:MIN_CONSECUTIVE_MATCHES = min(5, max(2, len(query_chunks)//2))。
  5. pHash 阈值校准:用 staging 真实同源/异源指纹数据回归确定阈值(同源片段距离分布 vs 异源分布),防止放松后误报;阈值常量统一来源。

P1 — 修 4 个确定性 bug
6. 删除时长 /1000(恢复 ±15% 时长预过滤)。
7. 直方图改 NORM_L1 归一化。
8. temporal_coverage total_duration 统一毫秒(*1000)。
9. 统一 < / <= 阈值比较。

P2 — 可观测性
10. 每次比对的每候选结果(min_distances、frame_match_rate、median、fusion)落 debug 日志或写比对记录表,0 匹配也要有痕迹可查。

验收

  • 单测:同源不同裁剪的两个视频能检出非 0 相似度;局部片段复用(B 结尾2s ≈ A 中间2s)能检出;异源视频不误报(相似度接近0);N=1 现有流程不回归。
  • 修复后对 staging 已有成片提供 recompute-dedup 重算,这两个视频查重率应非 0。
  • CI 全绿,PR 到 develop。

参考证据视频

  • 22f06b0c87964fdaa2f2266dd555aab0 (generated-a6700c39, 20s)
  • 37d76677086449aca4dbdf99fde3e871 (generated-41a49f15, 11s)
## 现象 两条明显同源的成片(generated-a6700c39.mp4 20s 与 generated-41a49f15.mp4 11s,同一镜头/人物做腿部护理),查重率都显示 0.0%。 ## 根因(已实查 staging 数据库+真实指纹离线验证) 查重跑了、分片指纹齐全(5片/9片),但第一层帧匹配就 0 命中: - 45 个分片对汉明距离**最小 12**,代码要求严格 `< PHASH_THRESHOLD(8)` → 0 帧匹配 → match_ratio / 时序覆盖 / 融合分全部短路为 0,visual_similarity/duplicate_rate 写入字面值 0。 - 距离被推高的主因:**每条成片都强制随机 2-5% 边缘裁剪降重**(generation.py:719 `random_edge_crop`,注释明写"用于改变 pHash 指纹")。两次裁剪随机不同,同源镜头 pHash 位翻转 12-16。**降重功能与自查重自相矛盾**。 - 次因:场景切割导致分片边界不对齐,取帧时刻错位。 - 结构性问题:算法按"整条视频重复"设计(match_ratio 分母是新视频全部帧数,frame_match_rate<0.3 直接 continue 不计分),**没有局部片段复用检出能力**;MIN_CONSECUTIVE_MATCHES=5 让 11s/5片短视频天然无法报片段。 ## 确定性 Bug(顺带实查发现) 1. **check_duplicate 时长预过滤单位错误**:`duration_sec = fingerprint.duration / 1000`,但 fingerprint.duration 单位是秒 → duration_max≈0.0135s,`list_by_user(duration_min/duration_max)` 把所有真实视频过滤掉,scope="user" 的 is_duplicate 跨项目判定永远返回 None(跨项目重复标记实际全失效)。位置:dedup.py check_duplicate_task 与 dedup_helpers 对应处。 2. **颜色直方图归一化错误**:compute_color_histogram 用 cv2.normalize 默认 L2,而 _bhattacharyya_coefficient 按概率分布(Σ=1)设计,实测系数 ~14.9(理论上限1),放松 pHash 阈值后会误判。应改 NORM_L1。 3. **temporal_coverage 单位错误**(dedup.py:901/962):`total_duration_ms = fingerprint.duration`(实为秒),covered_ms/秒 量纲错,命中一段即 min(1.0) 判 100% 覆盖。应 `* 1000`。 4. 阈值比较不一致:帧匹配严格 `<8`,find_duplicate_segments 用 `<=8`,应统一。 ## 修复要求 **P0 — 让同源/局部复用可检出** 1. 查重指纹必须基于**降重裁剪前**的画面:用原始渲染产物算 pHash,或算指纹前对成片居中裁除边缘(如中心 90% 区域),使 random_edge_crop 不再污染自查重指纹。降重只服务外部平台,不影响自查。 2. 支持局部片段复用:duplicate_rate/visual_similarity 以命中片段时长占比(temporal_coverage)为主指标,去掉"frame_match_rate<0.3 全片才计分"的硬跳过;match_ratio 分母改为 min(两视频分片数) 或按命中片段统计。 3. 分片对齐:比对时允许 ±1 邻接窗口 / 时序单调偏移对齐,缓解切点不一致。 4. 短视频连续匹配门槛自适应:MIN_CONSECUTIVE_MATCHES = min(5, max(2, len(query_chunks)//2))。 5. pHash 阈值校准:用 staging 真实同源/异源指纹数据回归确定阈值(同源片段距离分布 vs 异源分布),防止放松后误报;阈值常量统一来源。 **P1 — 修 4 个确定性 bug** 6. 删除时长 /1000(恢复 ±15% 时长预过滤)。 7. 直方图改 NORM_L1 归一化。 8. temporal_coverage total_duration 统一毫秒(*1000)。 9. 统一 `<` / `<=` 阈值比较。 **P2 — 可观测性** 10. 每次比对的每候选结果(min_distances、frame_match_rate、median、fusion)落 debug 日志或写比对记录表,0 匹配也要有痕迹可查。 ## 验收 - 单测:同源不同裁剪的两个视频能检出非 0 相似度;局部片段复用(B 结尾2s ≈ A 中间2s)能检出;异源视频不误报(相似度接近0);N=1 现有流程不回归。 - 修复后对 staging 已有成片提供 recompute-dedup 重算,这两个视频查重率应非 0。 - CI 全绿,PR 到 develop。 ## 参考证据视频 - 22f06b0c87964fdaa2f2266dd555aab0 (generated-a6700c39, 20s) - 37d76677086449aca4dbdf99fde3e871 (generated-41a49f15, 11s)
Author
Owner

修复完成 & staging 验收通过

共 5 个 PR 合并到 develop(全部 CI 全绿):

PR 内容
#1703 主修复:指纹绕开降重裁剪(密集1s采样+中心裁剪)、局部片段复用(temporal_coverage 为主指标、去掉 frame_match_rate<0.3 硬跳过、match_ratio 分母改 min)、时序一致贪心对齐(±1邻接/时序偏移)、短视频自适应门槛 min(5,max(2,n//2))、pHash 阈值真实数据校准、3 个单位 bug(时长 /1000、cv2.normalize、coverage 量纲)、每候选落日志
#1705 连带:recompute 任务 OSS 下载 key 从 video.file_url 派生(旧硬编码 key 从不存在导致重算 404)
#1706 连带:check_duplicate 加 exclude_video_id(recompute 自匹配导致 duplicate_of 指向自己)
#1708 连带:scope=user 去掉 ±15% 时长预过滤(局部复用视频时长必然不同,同源视频互相不可见)
#1709 连带:pHash 阈值二次校准 12→16 + 时序对齐允许 ±1 反向抖动

staging 真实数据验收(两个证据视频,同镜头同人物、20s vs 11.75s)

  • A(20s):duplicate_rate 72.73%、visual_similarity 0.80、is_duplicate=True
  • B(11.75s):duplicate_rate 63.77%、visual_similarity 0.84、is_duplicate=True、duplicate_of=A ✓
  • 同用户另外检出 2 对重复(A↔C 11.75s 54.55%、一对 7.85s 71.43%,均为真实同源)
  • 异源零误报:阈值 16 的校准依据——该用户 15 个真实成片中 13 个异源候选,每帧全局最近邻最小距离 18、≤16 命中帧数全部为 0,仍有 ≥2bit 安全裕度

测试

  • 查重相关单测 290 passed(新增 23 个回归用例:指纹绕裁剪、局部片段复用、阈值边界 16检出/18零误报、反向时序抖动、排除自身、下载 key 派生、scope 不过滤时长等)
  • black/isort/ruff 全绿;staging worker 镜像已部署验证

阈值说明:12→16 是第四轮验证时用该用户全部 15 个真实成片指纹离线回归校准的结果——降重滤镜/字幕/画面扰动把同源帧距离从 8 推到 1416,12 会漏检降重对,16 对同源命中 0.73+ 且异源 ≤16 命中为 0。

## ✅ 修复完成 & staging 验收通过 共 5 个 PR 合并到 develop(全部 CI 全绿): | PR | 内容 | |---|---| | #1703 | 主修复:指纹绕开降重裁剪(密集1s采样+中心裁剪)、局部片段复用(temporal_coverage 为主指标、去掉 frame_match_rate<0.3 硬跳过、match_ratio 分母改 min)、时序一致贪心对齐(±1邻接/时序偏移)、短视频自适应门槛 min(5,max(2,n//2))、pHash 阈值真实数据校准、3 个单位 bug(时长 /1000、cv2.normalize、coverage 量纲)、每候选落日志 | | #1705 | 连带:recompute 任务 OSS 下载 key 从 video.file_url 派生(旧硬编码 key 从不存在导致重算 404)| | #1706 | 连带:check_duplicate 加 exclude_video_id(recompute 自匹配导致 duplicate_of 指向自己)| | #1708 | 连带:scope=user 去掉 ±15% 时长预过滤(局部复用视频时长必然不同,同源视频互相不可见)| | #1709 | 连带:pHash 阈值二次校准 12→16 + 时序对齐允许 ±1 **反向**抖动 | ### staging 真实数据验收(两个证据视频,同镜头同人物、20s vs 11.75s) - **A(20s)**:duplicate_rate **72.73%**、visual_similarity 0.80、is_duplicate=**True** - **B(11.75s)**:duplicate_rate **63.77%**、visual_similarity 0.84、is_duplicate=**True**、duplicate_of=A ✓ - 同用户另外检出 2 对重复(A↔C 11.75s 54.55%、一对 7.85s 71.43%,均为真实同源) - **异源零误报**:阈值 16 的校准依据——该用户 15 个真实成片中 13 个异源候选,每帧全局最近邻最小距离 18、≤16 命中帧数全部为 0,仍有 ≥2bit 安全裕度 ### 测试 - 查重相关单测 **290 passed**(新增 23 个回归用例:指纹绕裁剪、局部片段复用、阈值边界 16检出/18零误报、反向时序抖动、排除自身、下载 key 派生、scope 不过滤时长等) - black/isort/ruff 全绿;staging worker 镜像已部署验证 阈值说明:12→16 是第四轮验证时用该用户**全部 15 个真实成片指纹**离线回归校准的结果——降重滤镜/字幕/画面扰动把同源帧距离从 ~8 推到 14~16,12 会漏检降重对,16 对同源命中 0.73+ 且异源 ≤16 命中为 0。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: xiaoxia/xiaoxia-saas#1702