hakandinger commited on
Commit
3d7d0e2
·
1 Parent(s): b0609ef

feat: add video comparison module to find common faces

Browse files
Files changed (2) hide show
  1. app.py +210 -1
  2. core/comparator.py +423 -0
app.py CHANGED
@@ -19,6 +19,7 @@ import requests
19
  import tempfile
20
  from urllib.parse import urlparse
21
  import logging
 
22
 
23
  logging.basicConfig(
24
  level=logging.INFO,
@@ -291,7 +292,7 @@ class FaceDetector:
291
 
292
  def create_output_directory(self, video_path: str, is_temp: bool = False) -> str:
293
  logger.info(f"burası {self},{video_path},{is_temp}")
294
- """Çıktı dizinini oluşturur - Gradio uyumlu"""
295
  if is_temp:
296
  # URL/YouTube için temp dizini kullan
297
  temp_dir = tempfile.gettempdir()
@@ -528,6 +529,7 @@ class FaceDetector:
528
  self.cleanup_temp_files()
529
 
530
  detector_instance = None
 
531
 
532
  def initialize_detector(frame_skip, face_threshold, clustering_eps, use_gpu):
533
  global detector_instance
@@ -540,6 +542,21 @@ def initialize_detector(frame_skip, face_threshold, clustering_eps, use_gpu):
540
  detector_instance = FaceDetector(config)
541
  return "✅ Ayarlar kaydedildi!"
542
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  def process_video_gradio(video_file, video_url, progress=gr.Progress()):
544
  global detector_instance
545
 
@@ -666,6 +683,78 @@ def compare_two_faces(face1, face2):
666
  except Exception as e:
667
  return f"❌ Hata: {str(e)}"
668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  with gr.Blocks(title="Yüz Tanıma Sistemi", theme=gr.themes.Soft()) as demo:
670
  gr.Markdown("""
671
  # 🎭 Video Yüz Tanıma Sistemi
@@ -747,6 +836,126 @@ with gr.Blocks(title="Yüz Tanıma Sistemi", theme=gr.themes.Soft()) as demo:
747
  outputs=compare_result
748
  )
749
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
  with gr.Tab("⚙️ Ayarlar"):
751
  gr.Markdown("### Gelişmiş Ayarlar")
752
 
 
19
  import tempfile
20
  from urllib.parse import urlparse
21
  import logging
22
+ from core.comparator import VideoComparator, ComparisonResult
23
 
24
  logging.basicConfig(
25
  level=logging.INFO,
 
292
 
293
  def create_output_directory(self, video_path: str, is_temp: bool = False) -> str:
294
  logger.info(f"burası {self},{video_path},{is_temp}")
295
+
296
  if is_temp:
297
  # URL/YouTube için temp dizini kullan
298
  temp_dir = tempfile.gettempdir()
 
529
  self.cleanup_temp_files()
530
 
531
  detector_instance = None
532
+ comparator_instance = None
533
 
534
  def initialize_detector(frame_skip, face_threshold, clustering_eps, use_gpu):
535
  global detector_instance
 
542
  detector_instance = FaceDetector(config)
543
  return "✅ Ayarlar kaydedildi!"
544
 
545
+ def initialize_comparator():
546
+ """Video karşılaştırıcı instance'ını başlat"""
547
+ global comparator_instance, detector_instance
548
+
549
+ if detector_instance is None:
550
+ detector_instance = FaceDetector(FaceDetectionConfig())
551
+
552
+ if comparator_instance is None:
553
+ comparator_instance = VideoComparator(
554
+ face_detector=detector_instance,
555
+ similarity_threshold=0.6 # %60 benzerlik eşiği
556
+ )
557
+
558
+ return comparator_instance
559
+
560
  def process_video_gradio(video_file, video_url, progress=gr.Progress()):
561
  global detector_instance
562
 
 
683
  except Exception as e:
684
  return f"❌ Hata: {str(e)}"
685
 
686
+ def compare_videos_gradio(video1, video2, url1, url2, similarity_threshold, progress=gr.Progress()):
687
+ """
688
+ Gradio callback: İki videoyu karşılaştır
689
+ """
690
+ global comparator_instance
691
+
692
+ # Comparator'ı başlat
693
+ comparator = initialize_comparator()
694
+ comparator.similarity_threshold = similarity_threshold
695
+
696
+ def update_progress(value, message):
697
+ progress(value / 100, desc=message)
698
+
699
+ comparator.set_progress_callback(update_progress)
700
+
701
+ try:
702
+ progress(0, desc="Videolar hazırlanıyor...")
703
+
704
+ # Video 1 kaynağını belirle
705
+ if url1 and url1.strip() and not IS_HUGGINGFACE:
706
+ video1_source = url1.strip()
707
+ is_v1_url = True
708
+ elif video1:
709
+ video1_source = video1
710
+ is_v1_url = False
711
+ else:
712
+ return [], "❌ Lütfen Video 1 yükleyin!", "❌ Video 1 eksik"
713
+
714
+ # Video 2 kaynağını belirle
715
+ if url2 and url2.strip() and not IS_HUGGINGFACE:
716
+ video2_source = url2.strip()
717
+ is_v2_url = True
718
+ elif video2:
719
+ video2_source = video2
720
+ is_v2_url = False
721
+ else:
722
+ return [], "❌ Lütfen Video 2 yükleyin!", "❌ Video 2 eksik"
723
+
724
+ # Hugging Face kontrolü
725
+ if IS_HUGGINGFACE and (is_v1_url or is_v2_url):
726
+ return [], "❌ Hugging Face Spaces'te URL desteği yok!", "❌ URL desteği yok"
727
+
728
+ # Karşılaştırmayı yap
729
+ result, output_dir, saved_images = comparator.compare_videos(
730
+ video1_source,
731
+ video2_source,
732
+ is_video1_url=is_v1_url,
733
+ is_video2_url=is_v2_url
734
+ )
735
+
736
+ # Rapor oluştur
737
+ report = comparator.generate_report(result)
738
+
739
+ # Özet mesaj
740
+ summary = f"""
741
+ ✅ **Karşılaştırma Tamamlandı!**
742
+
743
+ 🎯 **Sonuçlar:**
744
+ - Ortak Kişi: **{result.common_count}**
745
+ - Sadece Video 1: **{len(result.only_video1)}**
746
+ - Sadece Video 2: **{len(result.only_video2)}**
747
+
748
+ 📁 Çıktı: {output_dir}
749
+ """
750
+
751
+ return saved_images, report, summary
752
+
753
+ except Exception as e:
754
+ error_msg = f"❌ Hata: {str(e)}"
755
+ logger.error(error_msg, exc_info=True)
756
+ return [], error_msg, error_msg
757
+
758
  with gr.Blocks(title="Yüz Tanıma Sistemi", theme=gr.themes.Soft()) as demo:
759
  gr.Markdown("""
760
  # 🎭 Video Yüz Tanıma Sistemi
 
836
  outputs=compare_result
837
  )
838
 
839
+ with gr.Tab("🎬 Video Karşılaştır"):
840
+ gr.Markdown("""
841
+ ## İki Videoyu Karşılaştır
842
+ İki farklı videodan yüz tespiti yaparak ortak kişileri bulur
843
+ """)
844
+
845
+ # Hugging Face uyarısı
846
+ if IS_HUGGINGFACE:
847
+ gr.Markdown("""
848
+ ⚠️ **Hugging Face Spaces Modunda**: URL/YouTube desteği kapalı.
849
+ Sadece dosya yükleme kullanılabilir.
850
+ """)
851
+
852
+ with gr.Row():
853
+ with gr.Column(scale=1):
854
+ gr.Markdown("### 📹 Video 1")
855
+ video1_input = gr.Video(
856
+ label="Video 1 Yükle",
857
+ height=250,
858
+ show_label=True
859
+ )
860
+
861
+ if not IS_HUGGINGFACE:
862
+ gr.Markdown("**VEYA**")
863
+ url1_input = gr.Textbox(
864
+ label="Video 1 URL",
865
+ placeholder="https://example.com/video1.mp4",
866
+ lines=1,
867
+ interactive=True
868
+ )
869
+ else:
870
+ url1_input = gr.Textbox(
871
+ label="Video 1 URL (Devre Dışı)",
872
+ placeholder="HF Spaces'te URL desteği yok",
873
+ lines=1,
874
+ interactive=False
875
+ )
876
+
877
+ with gr.Column(scale=1):
878
+ gr.Markdown("### 📹 Video 2")
879
+ video2_input = gr.Video(
880
+ label="Video 2 Yükle",
881
+ height=250,
882
+ show_label=True
883
+ )
884
+
885
+ if not IS_HUGGINGFACE:
886
+ gr.Markdown("**VEYA**")
887
+ url2_input = gr.Textbox(
888
+ label="Video 2 URL",
889
+ placeholder="https://example.com/video2.mp4",
890
+ lines=1,
891
+ interactive=True
892
+ )
893
+ else:
894
+ url2_input = gr.Textbox(
895
+ label="Video 2 URL (Devre Dışı)",
896
+ placeholder="HF Spaces'te URL desteği yok",
897
+ lines=1,
898
+ interactive=False
899
+ )
900
+
901
+ # Ayarlar
902
+ with gr.Row():
903
+ similarity_slider = gr.Slider(
904
+ minimum=0.4,
905
+ maximum=0.9,
906
+ value=0.6,
907
+ step=0.05,
908
+ label="🎯 Benzerlik Eşiği (Düşük = Daha fazla eşleşme)",
909
+ info="İki yüzün aynı kişi olarak kabul edilmesi için minimum benzerlik oranı"
910
+ )
911
+
912
+ compare_videos_btn = gr.Button("🔍 Videoları Karşılaştır", variant="primary", size="lg")
913
+ status_compare = gr.Textbox(label="Durum", interactive=False, lines=3)
914
+
915
+ gr.Markdown("### 📊 Sonuçlar")
916
+
917
+ with gr.Row():
918
+ with gr.Column(scale=2):
919
+ gallery_compare = gr.Gallery(
920
+ label="Ortak Yüzler (Sol: Video 1, Sağ: Video 2)",
921
+ columns=2,
922
+ height=500,
923
+ object_fit="contain"
924
+ )
925
+
926
+ with gr.Column(scale=1):
927
+ report_compare = gr.Markdown(label="Detaylı Rapor")
928
+
929
+ gr.Markdown("""
930
+ ### 💡 Nasıl Çalışır?
931
+ 1. **İki video yükleyin** (veya URL girin)
932
+ 2. **Benzerlik eşiğini ayarlayın** (varsayılan %60)
933
+ 3. **Karşılaştır** butonuna tıklayın
934
+ 4. Sistem her iki videodan yüz tespiti yapar ve ortak kişileri bulur
935
+
936
+ ### 📈 Sonuç Kategorileri
937
+ - **Ortak Yüzler**: Her iki videoda da görünen kişiler (yan yana gösterilir)
938
+ - **Sadece Video 1**: Yalnızca ilk videoda görünen kişiler
939
+ - **Sadece Video 2**: Yalnızca ikinci videoda görünen kişiler
940
+ """)
941
+
942
+ # Event handler
943
+ compare_videos_btn.click(
944
+ fn=compare_videos_gradio,
945
+ inputs=[
946
+ video1_input,
947
+ video2_input,
948
+ url1_input,
949
+ url2_input,
950
+ similarity_slider
951
+ ],
952
+ outputs=[
953
+ gallery_compare,
954
+ report_compare,
955
+ status_compare
956
+ ]
957
+ )
958
+
959
  with gr.Tab("⚙️ Ayarlar"):
960
  gr.Markdown("### Gelişmiş Ayarlar")
961
 
core/comparator.py ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Video Comparator Module
3
+ İki videodan yüz tespiti yaparak ortak kişileri bulur
4
+ """
5
+
6
+ import os
7
+ import cv2
8
+ import numpy as np
9
+ from typing import List, Dict, Tuple, Optional
10
+ from dataclasses import dataclass
11
+ from sklearn.metrics.pairwise import cosine_similarity
12
+ import logging
13
+ from datetime import datetime
14
+ import json
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class ComparisonResult:
21
+ """Karşılaştırma sonuç verisi"""
22
+ common_faces: List[Dict] # Her iki videoda da bulunan yüzler
23
+ only_video1: List[Dict] # Sadece video 1'de bulunan yüzler
24
+ only_video2: List[Dict] # Sadece video 2'de bulunan yüzler
25
+ total_unique_video1: int
26
+ total_unique_video2: int
27
+ common_count: int
28
+ similarity_threshold: float
29
+ metadata: Dict
30
+
31
+
32
+ class VideoComparator:
33
+ """
34
+ İki videoyu karşılaştırarak ortak yüzleri bulur
35
+ Mevcut FaceDetector'ı kullanır
36
+ """
37
+
38
+ def __init__(self, face_detector, similarity_threshold: float = 0.6):
39
+ """
40
+ Args:
41
+ face_detector: FaceDetector instance
42
+ similarity_threshold: Yüz benzerlik eşiği (0-1 arası, varsayılan 0.6)
43
+ """
44
+ self.detector = face_detector
45
+ self.similarity_threshold = similarity_threshold
46
+ self.progress_callback = None
47
+
48
+ def set_progress_callback(self, callback):
49
+ """Progress callback fonksiyonu ayarla"""
50
+ self.progress_callback = callback
51
+
52
+ def _update_progress(self, value: float, message: str):
53
+ """Progress güncelleme"""
54
+ if self.progress_callback:
55
+ self.progress_callback(value, message)
56
+
57
+ def compare_videos(
58
+ self,
59
+ video1_path: str,
60
+ video2_path: str,
61
+ is_video1_url: bool = False,
62
+ is_video2_url: bool = False
63
+ ) -> Tuple[ComparisonResult, str, List[str]]:
64
+ """
65
+ İki videoyu karşılaştır ve ortak yüzleri bul
66
+
67
+ Args:
68
+ video1_path: İlk video yolu veya URL
69
+ video2_path: İkinci video yolu veya URL
70
+ is_video1_url: İlk video URL mi?
71
+ is_video2_url: İkinci video URL mi?
72
+
73
+ Returns:
74
+ Tuple[ComparisonResult, output_dir, saved_images]
75
+ """
76
+ try:
77
+ logger.info("Video karşılaştırma başlatılıyor...")
78
+ start_time = datetime.now()
79
+
80
+ # Video 1'i işle
81
+ self._update_progress(0, "Video 1 işleniyor...")
82
+ logger.info(f"Video 1 işleniyor: {video1_path}")
83
+
84
+ output_dir1, faces1, metadata1 = self.detector.detect_faces(
85
+ video1_path,
86
+ is_url=is_video1_url
87
+ )
88
+
89
+ video1_faces = self._load_face_data(output_dir1, metadata1)
90
+ logger.info(f"Video 1: {len(video1_faces)} benzersiz yüz bulundu")
91
+
92
+ # Video 2'yi işle
93
+ self._update_progress(50, "Video 2 işleniyor...")
94
+ logger.info(f"Video 2 işleniyor: {video2_path}")
95
+
96
+ output_dir2, faces2, metadata2 = self.detector.detect_faces(
97
+ video2_path,
98
+ is_url=is_video2_url
99
+ )
100
+
101
+ video2_faces = self._load_face_data(output_dir2, metadata2)
102
+ logger.info(f"Video 2: {len(video2_faces)} benzersiz yüz bulundu")
103
+
104
+ # Yüzleri karşılaştır
105
+ self._update_progress(80, "Yüzler karşılaştırılıyor...")
106
+ comparison_result = self._compare_face_sets(
107
+ video1_faces,
108
+ video2_faces,
109
+ metadata1,
110
+ metadata2
111
+ )
112
+
113
+ # Sonuçları kaydet
114
+ self._update_progress(90, "Sonuçlar kaydediliyor...")
115
+ output_dir, saved_images = self._save_comparison_results(
116
+ comparison_result,
117
+ output_dir1,
118
+ output_dir2
119
+ )
120
+
121
+ elapsed_time = (datetime.now() - start_time).total_seconds()
122
+ logger.info(f"Karşılaştırma tamamlandı: {elapsed_time:.1f} saniye")
123
+
124
+ self._update_progress(100, f"✅ Tamamlandı! {comparison_result.common_count} ortak yüz bulundu")
125
+
126
+ return comparison_result, output_dir, saved_images
127
+
128
+ except Exception as e:
129
+ logger.error(f"Video karşılaştırma hatası: {e}", exc_info=True)
130
+ raise
131
+
132
+ def _load_face_data(self, output_dir: str, metadata: Dict) -> List[Dict]:
133
+ """
134
+ Bir videodan tespit edilen yüzleri yükle
135
+
136
+ Returns:
137
+ List of dicts with 'embedding', 'face_path', 'cluster_id', 'quality_score'
138
+ """
139
+ faces = []
140
+
141
+ for face_info in metadata['faces']:
142
+ face_path = os.path.join(output_dir, face_info['face_file'])
143
+
144
+ if not os.path.exists(face_path):
145
+ logger.warning(f"Yüz dosyası bulunamadı: {face_path}")
146
+ continue
147
+
148
+ # Yüz görselini oku ve embedding çıkar
149
+ face_img = cv2.imread(face_path)
150
+ embedding, _ = self.detector.extract_embeddings(face_img)
151
+
152
+ if embedding is not None:
153
+ faces.append({
154
+ 'embedding': embedding,
155
+ 'face_path': face_path,
156
+ 'cluster_id': face_info['cluster_id'],
157
+ 'quality_score': face_info['quality_score'],
158
+ 'cluster_size': face_info.get('cluster_size', 1),
159
+ 'bbox': face_info.get('bbox', [])
160
+ })
161
+
162
+ return faces
163
+
164
+ def _compare_face_sets(
165
+ self,
166
+ video1_faces: List[Dict],
167
+ video2_faces: List[Dict],
168
+ metadata1: Dict,
169
+ metadata2: Dict
170
+ ) -> ComparisonResult:
171
+ """
172
+ İki video yüz setini karşılaştır
173
+ """
174
+ common_faces = []
175
+ only_video1 = []
176
+ only_video2 = []
177
+
178
+ # Video 1'deki her yüz için Video 2'de eşleşme ara
179
+ matched_video2_indices = set()
180
+
181
+ for v1_face in video1_faces:
182
+ best_match = None
183
+ best_similarity = 0
184
+ best_v2_idx = -1
185
+
186
+ # Video 2'deki tüm yüzlerle karşılaştır
187
+ for v2_idx, v2_face in enumerate(video2_faces):
188
+ if v2_idx in matched_video2_indices:
189
+ continue # Zaten eşleşmiş
190
+
191
+ similarity = cosine_similarity(
192
+ [v1_face['embedding']],
193
+ [v2_face['embedding']]
194
+ )[0][0]
195
+
196
+ if similarity > best_similarity:
197
+ best_similarity = similarity
198
+ best_match = v2_face
199
+ best_v2_idx = v2_idx
200
+
201
+ # Eşik üzerinde eşleşme var mı?
202
+ if best_similarity >= self.similarity_threshold and best_match:
203
+ matched_video2_indices.add(best_v2_idx)
204
+ common_faces.append({
205
+ 'video1_face': v1_face,
206
+ 'video2_face': best_match,
207
+ 'similarity': float(best_similarity),
208
+ 'match_id': len(common_faces)
209
+ })
210
+ else:
211
+ only_video1.append({
212
+ 'face': v1_face,
213
+ 'person_id': v1_face['cluster_id']
214
+ })
215
+
216
+ # Video 2'de eşleşmeyen yüzler
217
+ for v2_idx, v2_face in enumerate(video2_faces):
218
+ if v2_idx not in matched_video2_indices:
219
+ only_video2.append({
220
+ 'face': v2_face,
221
+ 'person_id': v2_face['cluster_id']
222
+ })
223
+
224
+ return ComparisonResult(
225
+ common_faces=common_faces,
226
+ only_video1=only_video1,
227
+ only_video2=only_video2,
228
+ total_unique_video1=len(video1_faces),
229
+ total_unique_video2=len(video2_faces),
230
+ common_count=len(common_faces),
231
+ similarity_threshold=self.similarity_threshold,
232
+ metadata={
233
+ 'video1': {
234
+ 'path': metadata1['video_path'],
235
+ 'duration': metadata1['duration'],
236
+ 'fps': metadata1['fps']
237
+ },
238
+ 'video2': {
239
+ 'path': metadata2['video_path'],
240
+ 'duration': metadata2['duration'],
241
+ 'fps': metadata2['fps']
242
+ },
243
+ 'comparison_time': datetime.now().isoformat()
244
+ }
245
+ )
246
+
247
+ def _save_comparison_results(
248
+ self,
249
+ result: ComparisonResult,
250
+ output_dir1: str,
251
+ output_dir2: str
252
+ ) -> Tuple[str, List[str]]:
253
+ """
254
+ Karşılaştırma sonuçlarını kaydet
255
+
256
+ Returns:
257
+ Tuple[output_directory, list_of_saved_image_paths]
258
+ """
259
+ # Ana çıktı dizini oluştur
260
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
261
+ output_dir = os.path.join(
262
+ os.path.dirname(output_dir1),
263
+ f"comparison_{timestamp}"
264
+ )
265
+ os.makedirs(output_dir, exist_ok=True)
266
+
267
+ # Alt dizinler
268
+ common_dir = os.path.join(output_dir, "common_faces")
269
+ only_v1_dir = os.path.join(output_dir, "only_video1")
270
+ only_v2_dir = os.path.join(output_dir, "only_video2")
271
+
272
+ os.makedirs(common_dir, exist_ok=True)
273
+ os.makedirs(only_v1_dir, exist_ok=True)
274
+ os.makedirs(only_v2_dir, exist_ok=True)
275
+
276
+ saved_images = []
277
+
278
+ # Ortak yüzleri kaydet (yan yana)
279
+ for idx, match in enumerate(result.common_faces):
280
+ v1_img = cv2.imread(match['video1_face']['face_path'])
281
+ v2_img = cv2.imread(match['video2_face']['face_path'])
282
+
283
+ # İki resmi yan yana birleştir
284
+ combined = np.hstack([v1_img, v2_img])
285
+
286
+ # Benzerlik skorunu ekle
287
+ similarity = match['similarity']
288
+ cv2.putText(
289
+ combined,
290
+ f"Similarity: {similarity:.2%}",
291
+ (10, 30),
292
+ cv2.FONT_HERSHEY_SIMPLEX,
293
+ 0.7,
294
+ (0, 255, 0),
295
+ 2
296
+ )
297
+
298
+ output_path = os.path.join(common_dir, f"match_{idx:03d}.jpg")
299
+ cv2.imwrite(output_path, combined, [cv2.IMWRITE_JPEG_QUALITY, 95])
300
+ saved_images.append(output_path)
301
+
302
+ # Sadece Video 1'de olan yüzler
303
+ for idx, item in enumerate(result.only_video1):
304
+ face_img = cv2.imread(item['face']['face_path'])
305
+ output_path = os.path.join(only_v1_dir, f"person_{idx:03d}.jpg")
306
+ cv2.imwrite(output_path, face_img, [cv2.IMWRITE_JPEG_QUALITY, 95])
307
+
308
+ # Sadece Video 2'de olan yüzler
309
+ for idx, item in enumerate(result.only_video2):
310
+ face_img = cv2.imread(item['face']['face_path'])
311
+ output_path = os.path.join(only_v2_dir, f"person_{idx:03d}.jpg")
312
+ cv2.imwrite(output_path, face_img, [cv2.IMWRITE_JPEG_QUALITY, 95])
313
+
314
+ # Metadata kaydet
315
+ metadata = {
316
+ 'comparison_results': {
317
+ 'common_count': result.common_count,
318
+ 'only_video1_count': len(result.only_video1),
319
+ 'only_video2_count': len(result.only_video2),
320
+ 'total_video1': result.total_unique_video1,
321
+ 'total_video2': result.total_unique_video2,
322
+ 'similarity_threshold': result.similarity_threshold
323
+ },
324
+ 'common_faces': [
325
+ {
326
+ 'match_id': m['match_id'],
327
+ 'similarity': m['similarity'],
328
+ 'video1_cluster_id': m['video1_face']['cluster_id'],
329
+ 'video2_cluster_id': m['video2_face']['cluster_id']
330
+ }
331
+ for m in result.common_faces
332
+ ],
333
+ 'metadata': result.metadata
334
+ }
335
+
336
+ metadata_path = os.path.join(output_dir, 'comparison_metadata.json')
337
+ with open(metadata_path, 'w', encoding='utf-8') as f:
338
+ json.dump(metadata, f, indent=2, ensure_ascii=False)
339
+
340
+ logger.info(f"Karşılaştırma sonuçları kaydedildi: {output_dir}")
341
+ return output_dir, saved_images
342
+
343
+ def generate_report(self, result: ComparisonResult) -> str:
344
+ """
345
+ Karşılaştırma raporu oluştur (Markdown formatında)
346
+ """
347
+ report = f"""
348
+ # 🔍 Video Karşılaştırma Raporu
349
+
350
+ ## 📊 Özet Bilgiler
351
+
352
+ | Metrik | Video 1 | Video 2 | Ortak |
353
+ |--------|---------|---------|-------|
354
+ | **Benzersiz Kişi** | {result.total_unique_video1} | {result.total_unique_video2} | {result.common_count} |
355
+ | **Sadece Bu Videoda** | {len(result.only_video1)} | {len(result.only_video2)} | - |
356
+
357
+ ### 🎯 Karşılaştırma Parametreleri
358
+ - **Benzerlik Eşiği**: {result.similarity_threshold:.0%}
359
+ - **Ortak Kişi Oranı**: {(result.common_count / max(result.total_unique_video1, result.total_unique_video2) * 100):.1f}%
360
+
361
+ ---
362
+
363
+ ## 👥 Ortak Yüzler ({result.common_count} kişi)
364
+
365
+ Her iki videoda da bulunan kişiler:
366
+
367
+ """
368
+
369
+ for idx, match in enumerate(result.common_faces, 1):
370
+ similarity = match['similarity']
371
+ report += f"""
372
+ **Kişi {idx}**
373
+ - 🎯 Benzerlik Skoru: {similarity:.1%}
374
+ - 📹 Video 1 Cluster ID: {match['video1_face']['cluster_id']}
375
+ - 📹 Video 2 Cluster ID: {match['video2_face']['cluster_id']}
376
+ """
377
+
378
+ if result.only_video1:
379
+ report += f"""
380
+
381
+ ---
382
+
383
+ ## 📹 Sadece Video 1'de Bulunanlar ({len(result.only_video1)} kişi)
384
+
385
+ """
386
+ for idx, item in enumerate(result.only_video1, 1):
387
+ report += f"- Kişi {idx} (Cluster ID: {item['person_id']})\n"
388
+
389
+ if result.only_video2:
390
+ report += f"""
391
+
392
+ ---
393
+
394
+ ## 📹 Sadece Video 2'de Bulunanlar ({len(result.only_video2)} kişi)
395
+
396
+ """
397
+ for idx, item in enumerate(result.only_video2, 1):
398
+ report += f"- Kişi {idx} (Cluster ID: {item['person_id']})\n"
399
+
400
+ report += """
401
+
402
+ ---
403
+
404
+ ## ℹ️ Video Bilgileri
405
+
406
+ """
407
+
408
+ report += f"""
409
+ ### Video 1
410
+ - **Yol**: {result.metadata['video1']['path']}
411
+ - **Süre**: {result.metadata['video1']['duration']:.1f} saniye
412
+ - **FPS**: {result.metadata['video1']['fps']}
413
+
414
+ ### Video 2
415
+ - **Yol**: {result.metadata['video2']['path']}
416
+ - **Süre**: {result.metadata['video2']['duration']:.1f} saniye
417
+ - **FPS**: {result.metadata['video2']['fps']}
418
+
419
+ ---
420
+ *Rapor Oluşturulma: {result.metadata['comparison_time']}*
421
+ """
422
+
423
+ return report