diff --git a/apps/worker/video_processing/dedup.py b/apps/worker/video_processing/dedup.py index da17d22af..d5f389095 100755 --- a/apps/worker/video_processing/dedup.py +++ b/apps/worker/video_processing/dedup.py @@ -367,11 +367,14 @@ class VideoDeduplicator: # Step 4: 加权融合 phash_similarity = 1.0 - (median_distance / 64) - hist_similarity = ( - self._compute_histogram_similarity(fingerprint.color_histograms, existing_histograms) - if existing_histograms - else 0.5 # 无直方图数据时给中间值(向后兼容) - ) + + # 直方图相似度:任一方无数据时统一返回 0.0(无法判定),避免不对称 + if existing_histograms and fingerprint.color_histograms: + hist_similarity = self._compute_histogram_similarity( + fingerprint.color_histograms, existing_histograms + ) + else: + hist_similarity = 0.0 combined_score = self.PHASH_WEIGHT * phash_similarity + self.HISTOGRAM_WEIGHT * hist_similarity return { @@ -461,7 +464,7 @@ class VideoDeduplicator: chunk_data = self._get_existing_chunks(existing.id, session) if chunk_data: existing_phashes = [c["phash_binary"] for c in chunk_data] - existing_histograms = [c["color_histogram"] for c in chunk_data] + existing_histograms = [c.get("color_histogram", []) for c in chunk_data] else: # 回退:从 JSON 字段读取(存量旧视频) existing_phashes = ef.get("keyframe_phashes", []) @@ -526,7 +529,7 @@ class VideoDeduplicator: chunk_data = self._get_existing_chunks(existing.id, session) if chunk_data: existing_phashes = [c["phash_binary"] for c in chunk_data] - existing_histograms = [c["color_histogram"] for c in chunk_data] + existing_histograms = [c.get("color_histogram", []) for c in chunk_data] else: existing_phashes = ef.get("keyframe_phashes", []) existing_histograms = ef.get("color_histograms", []) @@ -616,7 +619,7 @@ class VideoDeduplicator: chunk_data = self._get_existing_chunks(existing.id, session) if chunk_data: existing_phashes = [c["phash_binary"] for c in chunk_data] - existing_histograms = [c["color_histogram"] for c in chunk_data] + existing_histograms = [c.get("color_histogram", []) for c in chunk_data] else: existing_phashes = ef.get("keyframe_phashes", []) existing_histograms = ef.get("color_histograms", []) diff --git a/tests/unit/test_dedup_v2.py b/tests/unit/test_dedup_v2.py index 520c76dac..2b2ee8b8f 100644 --- a/tests/unit/test_dedup_v2.py +++ b/tests/unit/test_dedup_v2.py @@ -352,24 +352,24 @@ class TestBackwardCompatibility: """无直方图数据时不崩溃。""" def test_no_histogram_fallback(self): - """已有视频无分片直方图 → hist_similarity 回退到 0.5,不崩溃。""" + """已有视频无分片直方图 → hist_similarity 回退到 0.0,不崩溃。""" d = VideoDeduplicator() phashes = ["0" * 16] * 10 fp = _make_fingerprint(phashes, [[0.5] * 96] * 10) # existing_histograms 为空列表 result = d._check_fusion_duplicate(fp, {}, phashes, []) - # 应该不崩溃,hist_similarity=0.5 + # 应该不崩溃,hist_similarity=0.0(无数据时统一为0) if result: - assert result["_debug"]["hist_similarity"] == 0.5 + assert result["_debug"]["hist_similarity"] == 0.0 def test_no_histogram_combined_score(self): - """无直方图时的 combined_score = 0.7 * phash + 0.3 * 0.5。""" + """无直方图时的 combined_score = 0.7 * phash + 0.3 * 0.0。""" d = VideoDeduplicator() phashes = ["0" * 16] * 10 # 完全相同 fp = _make_fingerprint(phashes, [[0.5] * 96] * 10) result = d._check_fusion_duplicate(fp, {}, phashes, []) if result: - expected = 0.7 * 1.0 + 0.3 * 0.5 # = 0.85 + expected = 0.7 * 1.0 + 0.3 * 0.0 # = 0.7 assert result["similarity"] == pytest.approx(expected)