fix(dedup): 修复查重率恒为0%——指纹绕开降重裁剪+局部片段复用+阈值校准+3个单位bug (#1702) #1703

Merged
auto-approve-bot merged 1 commits from fix/dedup-zero-rate-1702 into develop 2026-09-05 07:57:24 +08:00
Owner

修复 Issue #1702:查重率恒为 0%

工单:#1702

两条明显同源成片(20s vs 11s,同镜头同人物)查重率显示 0.0%。

根因(staging 真实数据回归确认)

在 staging worker 容器内用证据视频做离线校准实验,确认三层原因叠加:

  1. 抽帧策略导致时序错位(主因):动态场景检测抽帧让两个同源视频各自取到不同时刻的帧,配对时序错乱(B@7.5s 配到 A@16.5s),全量 45 个帧对汉明距离最小 12。改为固定 1s 间隔密集均匀采样后,复用片段帧时刻天然对齐:同源对 min=8、<=12 命中 10/31 帧。
  2. 降重裁剪污染指纹:成片强制 random_edge_crop 随机裁 2-5%(generation.py:719)。自对照实验:同帧两次随机裁剪 phash 距离 410(crop=2% 时 46)。阈值 8 必漏。指纹改用中心 90% 区域计算 pHash/直方图(降重只服务外部平台)。
  3. 阈值过严且未校准:#1658 收紧到 <8。真实分布:同源 min=8、同帧不同裁剪 4~10、异源 4 个真实视频 min=24。阈值定为 12(统一模块常量 PHASH_THRESHOLD),与异源间隔 12bit,无误报空间。

改动清单

P0

  • 新增 sample_fingerprint_timestamps():固定 1s 密集均匀采样(长视频 >30 帧自动放宽间隔),compute_fingerprint 改用;动态场景检测函数保留
  • 新增 center_crop_frame():中心 90% 裁剪,pHash/直方图基于裁剪后画面,MD5 仍基于原始帧
  • find_duplicate_segments 重构:全量距离矩阵 + 时序一致贪心对齐 → 支持局部片段复用(复用片段可出现在任意时序位置,各成独立 run);±1 邻接窗口对齐(NEIGHBOR_WINDOW=1);连续匹配门槛短视频自适应 min(5, max(2, n//2));阈值比较统一 <=
  • 去掉 frame_match_rate<0.3 整条硬跳过frame_match_rate 分母改 min(两视频分片数)temporal_coverage 为主指标,无连续片段时按匹配帧占比回退
  • 三个比对入口(check_duplicate / check_batch / compute_duplicate_rate)遍历所有候选取融合分最高者(旧逻辑首个过阈即返回,局部复用场景可能返回次优)

P1 确定性 bug

  • 时长预过滤 fingerprint.duration/1000 单位错误(duration 本身是秒)→ dedup.py + dedup_helpers.py 两处删除 /1000
  • 颜色直方图每通道改 NORM_L1_bhattacharyya_coefficientΣ√(ab)/√(Σa·Σb) 概率分布归一(旧 L2 三通道拼接算出 ~14.9,上限应为 1;单通道旧语义不变)
  • temporal_coverage 量纲修正:duration_sec * 1000(毫秒)
  • 帧匹配与片段匹配阈值比较取齐

P2

  • 每候选结果落 debug 日志(min_distances / frame_match_rate / temporal_coverage / median / fusion / segments),0 匹配落 info 日志

配套

  • _save_fingerprint_chunks 语义从「有数据跳过」改为「先删旧分片再写入」——否则算法升级对存量视频永不生效
  • POST /videos/recompute-dedupforce: bool=false 参数,force=true 时已有查重数据也重新入队
  • _is_bad_fingerprint 修正:旧逻辑「所有 phash 相同即黑屏」误杀短视频(11s/5 片内容连续时 phash 天然相同)→ 要求 ≥8 帧且相同占比 ≥80% 才判坏

校准数据(2026-09-05 staging worker 容器,cv2 4.10.0)

场景 最小汉明距离 <=12 命中
同源 A(20s)×B(11s),1s 密集采样 8 10/31 帧(B→A 4/11)
同帧两次 2-5% 随机裁剪 4(crop=2%)/ 10(crop=5%)
异源 4 个真实视频互比 24 0 帧(<=16 也 0)

测试

  • 新增 tests/unit/test_dedup_1702_zero_rate_fix.py(15 用例):同源不同裁剪检出非 0、局部复用(结尾2s≈中间2s)检出、异源不误报、N=1 不回归、时长单位 bug、直方图归一化、coverage 量纲、自适应门槛、±1 邻接、force recompute、0 匹配日志
  • 同步更新受行为变化影响的旧测试(test_dedup_v2/test_dedup_engine/test_dedup_pure/test_fingerprint_chunks/test_bad_fingerprint_filter/test_phash_threshold_calibration_1658)
  • 全量查重相关单测 282 passed;全量单测仅剩 7 个与本次无关的既有失败(test_api_settings/test_tts_*,在干净 develop 上同样失败,本地环境 h2 依赖问题)
  • black / isort / ruff 全部通过

查重请求 / 响应数据结构

重算接口POST /videos/recompute-dedup

{ "video_ids": ["22f06b0c...", "37d76677..."], "force": true }

响应:{ "enqueued": 2, "skipped": 0 }

查重结果(写入 generated_videos):duplicate_rate(0-1,融合分,全片重复判定 = fusion≥0.70 且帧比例≥0.7 或 coverage≥0.5);check_duplicate 返回 duplicate_segments(query/target 起止毫秒 + avg_distance)。

部署后验证

PR 合并部署 staging 后,对工单两个证据视频触发 force 重算,确认 duplicate_rate 非 0(部署发布专家跟进验证)。

Closes #1702

## 修复 Issue #1702:查重率恒为 0% 工单:https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/issues/1702 两条明显同源成片(20s vs 11s,同镜头同人物)查重率显示 0.0%。 ## 根因(staging 真实数据回归确认) 在 staging worker 容器内用证据视频做离线校准实验,确认三层原因叠加: 1. **抽帧策略导致时序错位(主因)**:动态场景检测抽帧让两个同源视频各自取到不同时刻的帧,配对时序错乱(B@7.5s 配到 A@16.5s),全量 45 个帧对汉明距离最小 12。改为**固定 1s 间隔密集均匀采样**后,复用片段帧时刻天然对齐:同源对 min=8、<=12 命中 10/31 帧。 2. **降重裁剪污染指纹**:成片强制 `random_edge_crop` 随机裁 2-5%(generation.py:719)。自对照实验:同帧两次随机裁剪 phash 距离 4~10(crop=2% 时 4~6)。阈值 8 必漏。指纹改用**中心 90% 区域**计算 pHash/直方图(降重只服务外部平台)。 3. **阈值过严且未校准**:#1658 收紧到 `<8`。真实分布:同源 min=8、同帧不同裁剪 4~10、异源 4 个真实视频 min=24。**阈值定为 12**(统一模块常量 `PHASH_THRESHOLD`),与异源间隔 12bit,无误报空间。 ## 改动清单 ### P0 - 新增 `sample_fingerprint_timestamps()`:固定 1s 密集均匀采样(长视频 >30 帧自动放宽间隔),`compute_fingerprint` 改用;动态场景检测函数保留 - 新增 `center_crop_frame()`:中心 90% 裁剪,pHash/直方图基于裁剪后画面,MD5 仍基于原始帧 - `find_duplicate_segments` 重构:全量距离矩阵 + 时序一致贪心对齐 → **支持局部片段复用**(复用片段可出现在任意时序位置,各成独立 run);±1 邻接窗口对齐(`NEIGHBOR_WINDOW=1`);连续匹配门槛短视频自适应 `min(5, max(2, n//2))`;阈值比较统一 `<=` - 去掉 `frame_match_rate<0.3 整条硬跳过`;`frame_match_rate` 分母改 `min(两视频分片数)`;**`temporal_coverage` 为主指标**,无连续片段时按匹配帧占比回退 - 三个比对入口(check_duplicate / check_batch / compute_duplicate_rate)遍历所有候选取融合分最高者(旧逻辑首个过阈即返回,局部复用场景可能返回次优) ### P1 确定性 bug - 时长预过滤 `fingerprint.duration/1000` 单位错误(duration 本身是秒)→ dedup.py + dedup_helpers.py 两处删除 `/1000` - 颜色直方图每通道改 `NORM_L1`;`_bhattacharyya_coefficient` 改 `Σ√(ab)/√(Σa·Σb)` 概率分布归一(旧 L2 三通道拼接算出 ~14.9,上限应为 1;单通道旧语义不变) - `temporal_coverage` 量纲修正:`duration_sec * 1000`(毫秒) - 帧匹配与片段匹配阈值比较取齐 ### P2 - 每候选结果落 debug 日志(min_distances / frame_match_rate / temporal_coverage / median / fusion / segments),0 匹配落 info 日志 ### 配套 - `_save_fingerprint_chunks` 语义从「有数据跳过」改为「**先删旧分片再写入**」——否则算法升级对存量视频永不生效 - `POST /videos/recompute-dedup` 加 `force: bool=false` 参数,force=true 时已有查重数据也重新入队 - `_is_bad_fingerprint` 修正:旧逻辑「所有 phash 相同即黑屏」误杀短视频(11s/5 片内容连续时 phash 天然相同)→ 要求 ≥8 帧且相同占比 ≥80% 才判坏 ## 校准数据(2026-09-05 staging worker 容器,cv2 4.10.0) | 场景 | 最小汉明距离 | <=12 命中 | |---|---|---| | 同源 A(20s)×B(11s),1s 密集采样 | **8** | 10/31 帧(B→A 4/11) | | 同帧两次 2-5% 随机裁剪 | 4(crop=2%)/ 10(crop=5%) | — | | 异源 4 个真实视频互比 | **24** | 0 帧(<=16 也 0) | ## 测试 - 新增 `tests/unit/test_dedup_1702_zero_rate_fix.py`(15 用例):同源不同裁剪检出非 0、局部复用(结尾2s≈中间2s)检出、异源不误报、N=1 不回归、时长单位 bug、直方图归一化、coverage 量纲、自适应门槛、±1 邻接、force recompute、0 匹配日志 - 同步更新受行为变化影响的旧测试(test_dedup_v2/test_dedup_engine/test_dedup_pure/test_fingerprint_chunks/test_bad_fingerprint_filter/test_phash_threshold_calibration_1658) - 全量查重相关单测 **282 passed**;全量单测仅剩 7 个与本次无关的既有失败(test_api_settings/test_tts_*,在干净 develop 上同样失败,本地环境 h2 依赖问题) - black / isort / ruff 全部通过 ## 查重请求 / 响应数据结构 **重算接口**:`POST /videos/recompute-dedup` ```json { "video_ids": ["22f06b0c...", "37d76677..."], "force": true } ``` 响应:`{ "enqueued": 2, "skipped": 0 }` **查重结果**(写入 generated_videos):`duplicate_rate`(0-1,融合分,全片重复判定 = fusion≥0.70 且帧比例≥0.7 或 coverage≥0.5);check_duplicate 返回 `duplicate_segments`(query/target 起止毫秒 + avg_distance)。 ## 部署后验证 PR 合并部署 staging 后,对工单两个证据视频触发 force 重算,确认 duplicate_rate 非 0(部署发布专家跟进验证)。 Closes #1702
xiaoxia added 1 commit 2026-09-05 07:52:54 +08:00
fix(dedup): 修复查重率恒为0%——指纹绕开降重裁剪+局部片段复用+阈值校准+3个单位bug (Issue #1702)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 31s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m39s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m54s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m54s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m57s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m57s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m14s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m49s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 3m46s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 4m17s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m36s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 10s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 28s
5fbe29b553
P0:
- 指纹改用固定1s间隔密集均匀采样(sample_fingerprint_timestamps)替代动态
  场景检测抽帧:staging真实数据回归证明动态抽帧让同源两视频取帧时刻错位、
  配对时序错乱是0检出主因;固定间隔采样后复用片段帧时刻天然对齐
- 指纹pHash/颜色直方图基于中心90%区域(center_crop_frame),绕开成片强制
  random_edge_crop(2-5%随机裁边)对指纹的污染;MD5仍基于原始帧
- find_duplicate_segments重构:全量距离矩阵+时序一致贪心对齐,支持局部
  片段复用(各复用片段独立成run);±1邻接窗口(NEIGHBOR_WINDOW)容忍切点
  不一致;连续匹配门槛短视频自适应 min(5, max(2, n//2))
- pHash阈值经staging真实同源/异源指纹回归校准为12(同源密集采样min=8、
  同帧两次随机裁剪距离4~10、异源min=24),阈值常量统一模块级PHASH_THRESHOLD
- 去掉frame_match_rate<0.3硬跳过;frame_match_rate分母改min(两视频分片数);
  temporal_coverage为主指标,无连续片段时按匹配帧占比回退
- check_duplicate/check_batch/compute_duplicate_rate遍历所有候选取融合分
  最高者(旧逻辑首个过阈即返回)

P1(确定性bug):
- 时长预过滤 fingerprint.duration/1000 单位错误(duration本身是秒),
  dedup.py与dedup_helpers.py两处修复
- 颜色直方图每通道NORM_L1归一化;Bhattacharyya系数改为
  Σ√(ab)/√(Σa·Σb)概率分布归一(旧L2三通道拼接算出~14.9,上限应1)
- temporal_coverage量纲修正(duration_sec*1000)
- 帧匹配与片段匹配阈值比较统一为<=PHASH_THRESHOLD

P2:
- 三个比对入口每候选落debug日志(min_distances/frame_match_rate/coverage/
  median/fusion/segments),0匹配落info日志

配套:
- _save_fingerprint_chunks改为先删旧分片再写入(旧'有数据跳过'语义导致
  recompute对存量视频不生效)
- recompute-dedup接口加force参数(已有查重数据也可强制重算)
- _is_bad_fingerprint修正: 旧'所有phash相同即黑屏'误杀短视频(<8帧不判坏)
- 新增15个验收单测(同源裁剪检出/局部复用检出/异源不误报/N=1不回归等),
  同步更新受行为变化影响的旧测试

🚀 预览环境已部署

项目 详情
PR号 #1703
预览链接 https://pr-1703.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1703 | | 预览链接 | [https://pr-1703.preview.xiaoxiajianji.com](https://pr-1703.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:否
  • 阻塞级问题数量:0 个

📊 审查概览

  • 整体评价:通过
  • 建议级问题数量:1 个

🔴 阻塞级问题(必须修复)

💡 改进建议(不阻塞合并)

  1. [apps/worker/video_processing/dedup.py: check_duplicate 方法] 旧数据回退逻辑中的时间戳处理
    • 具体内容:在 check_duplicate 方法中,当 chunk_data 为空时,回退逻辑构造的 existing_chunk_objects 使用了固定的 start_time_ms: 0end_time_ms: 0。如果存量数据只有 phash 列表而没有分片时间信息,find_duplicate_segments 计算出的重复片段(DuplicateSegment)的目标时间范围将全部为 0。这会导致 API 返回的重复片段时间戳不准确(显示为 0ms-0ms)。建议确认这是否符合预期,或者考虑在回退时根据视频总时长进行简单的均分估算,以提供更有意义的时间范围信息。

良好实践

  • 算法重构清晰find_duplicate_segments 的重构逻辑非常清晰,通过构建候选列表和贪心对齐算法,有效解决了时序错位问题。
  • 注释详尽:代码中对常量定义、算法变更原因(Issue #1702)、边界条件处理都有非常详细的注释,便于后续维护。
  • 防御性编程:在 center_crop_frame_bhattacharyya_coefficient 等函数中,对空值、零除、负数开方等异常情况做了妥善处理。
  • 向下兼容:在 API 层面增加了 force 参数,并在算法层面保留了旧数据(JSON 字段)的回退读取逻辑,确保了平滑升级。

格式检查通过 | 逻辑审查通过 | 性能良好


🤖 由 AI 代码审查机器人自动生成 | 2026-09-04 23:57:11 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:否 - 阻塞级问题数量:0 个 ### 📊 审查概览 - 整体评价:通过 - 建议级问题数量:1 个 ### 🔴 阻塞级问题(必须修复) 无 ### 💡 改进建议(不阻塞合并) 1. **[apps/worker/video_processing/dedup.py: check_duplicate 方法] 旧数据回退逻辑中的时间戳处理** - 具体内容:在 `check_duplicate` 方法中,当 `chunk_data` 为空时,回退逻辑构造的 `existing_chunk_objects` 使用了固定的 `start_time_ms: 0` 和 `end_time_ms: 0`。如果存量数据只有 phash 列表而没有分片时间信息,`find_duplicate_segments` 计算出的重复片段(`DuplicateSegment`)的目标时间范围将全部为 0。这会导致 API 返回的重复片段时间戳不准确(显示为 0ms-0ms)。建议确认这是否符合预期,或者考虑在回退时根据视频总时长进行简单的均分估算,以提供更有意义的时间范围信息。 ### ✅ 良好实践 - **算法重构清晰**:`find_duplicate_segments` 的重构逻辑非常清晰,通过构建候选列表和贪心对齐算法,有效解决了时序错位问题。 - **注释详尽**:代码中对常量定义、算法变更原因(Issue #1702)、边界条件处理都有非常详细的注释,便于后续维护。 - **防御性编程**:在 `center_crop_frame` 和 `_bhattacharyya_coefficient` 等函数中,对空值、零除、负数开方等异常情况做了妥善处理。 - **向下兼容**:在 API 层面增加了 `force` 参数,并在算法层面保留了旧数据(JSON 字段)的回退读取逻辑,确保了平滑升级。 --- ✅ 格式检查通过 | ✅ 逻辑审查通过 | ✅ 性能良好 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-09-04 23:57:11 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
auto-approve-bot merged commit 28b3010668 into develop 2026-09-05 07:57:24 +08:00
auto-approve-bot deleted branch fix/dedup-zero-rate-1702 2026-09-05 07:57:24 +08:00

🗑️ 预览环境已清理

PR #1703 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1703 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.