""" app.py - Sokak Köpeği İzleme Sistemi Yapay zeka ile sokak köpeği takibi, sağlık değerlendirmesi ve harita görselleştirme """ import os os.environ["OMP_NUM_THREADS"] = "1" import zipfile import tempfile import gradio as gr import cv2 import numpy as np import torch from typing import Dict, List, Optional import gc import base64 from io import BytesIO from PIL import Image from pathlib import Path import json from datetime import datetime import threading from detection import DogDetector from pose_detection import DogPoseDetector from tracking import DeepSORTTracker from reid import SimplifiedReID from health_assessment import DogHealthAssessor from database import DogDatabase from map_visualization import DogMapVisualizer from config import Config class DogMonitoringApp: """Sokak köpeği izleme ve sağlık değerlendirme uygulaması""" def __init__(self): self.device = 'cuda' if torch.cuda.is_available() else 'cpu' print("="*60) print("SOKAK KÖPEĞİ İZLEME SİSTEMİ BAŞLATILIYOR") print("="*60) # Config sistemi self.config = Config() detection_cfg = self.config.get_detection_config() pose_cfg = self.config.get_pose_config() tracking_cfg = self.config.get_tracking_config() reid_cfg = self.config.get_reid_config() health_cfg = self.config.get_health_config() map_cfg = self.config.get_map_config() db_cfg = self.config.get_database_config() # Modülleri başlat print("\n1. Tespit modülü yükleniyor...") self.detector = DogDetector( confidence_threshold=detection_cfg['confidence_threshold'], device=self.device ) print("\n2. Poz algılama modülü yükleniyor...") self.pose_detector = DogPoseDetector( model_path=pose_cfg['model'], device=self.device ) print("\n3. Takip modülü yükleniyor...") self.tracker = DeepSORTTracker( max_iou_distance=tracking_cfg['max_iou_distance'], max_age=tracking_cfg['max_age'], n_init=tracking_cfg['n_init'], use_appearance=tracking_cfg['use_appearance'] ) print("\n4. ReID modülü yükleniyor...") self.reid = SimplifiedReID(device=self.device) self.reid.set_threshold(reid_cfg['threshold']) print("\n5. Sağlık değerlendirme modülü yükleniyor...") self.health_assessor = DogHealthAssessor( temporal_window=health_cfg['temporal_window'] ) print("\n6. Veritabanı modülü yükleniyor...") self.db = DogDatabase(db_cfg['path']) print("\n7. Harita görselleştirme modülü yükleniyor...") self.map_visualizer = DogMapVisualizer( default_location=map_cfg['default_location'] ) # Oturum verileri self.temp_session = {} self.current_video_path = None self.is_processing = threading.Event() self.processing_lock = threading.Lock() self.validation_data = {} print("\n" + "="*60) print("✅ SİSTEM BAŞARIYLA BAŞLATILDI") print(f" Veritabanı: {len(self.db.get_all_dogs())} köpek") print(f" Cihaz: {self.device}") print("="*60 + "\n") def process_video(self, video_path: str, reid_threshold: float, sample_rate: int, create_viz_video: bool = True): """Video işle ve geçici oturuma kaydet""" if not video_path: return None, "Lütfen bir video yükleyin", "", gr.update(visible=False), None if not self.processing_lock.acquire(blocking=False): return None, "İşlem zaten devam ediyor", "", gr.update(visible=False), None try: self.is_processing.set() self.current_video_path = video_path self.temp_session.clear() self.validation_data = {} self.reid.set_threshold(reid_threshold) self.reid.set_video_source(video_path) self.tracker.reset() self.reid.reset_session() cap = cv2.VideoCapture(video_path) if not cap.isOpened(): return None, "Video açılamıyor", "", gr.update(visible=False), None total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = cap.get(cv2.CAP_PROP_FPS) or 30 frame_num = 0 processed = 0 temp_dogs = {} dataset_cfg = self.config.get_dataset_config() health_cfg = self.config.get_health_config() min_frame_gap = max(dataset_cfg['min_frame_gap'], total_frames // 45) blur_threshold = health_cfg['blur_threshold'] blur_rejected = 0 pose_failed = 0 health_assessed = 0 print(f"\n{'='*60}") print(f"VİDEO İŞLENİYOR") print(f"{'='*60}") print(f"Toplam kare: {total_frames}") print(f"FPS: {fps}") print(f"Örnekleme hızı: {sample_rate}") print(f"Min kare aralığı: {min_frame_gap}") print(f"ReID eşiği: {reid_threshold:.2f}") print(f"{'='*60}\n") while cap.isOpened() and self.is_processing.is_set(): ret, frame = cap.read() if not ret: break if frame_num % sample_rate == 0: detections = self.detector.detect(frame) tracks = self.tracker.update(detections) for track in tracks: if not self.is_processing.is_set(): break result = self.reid.match_or_register(track) temp_id = result['temp_id'] if temp_id == 0: continue if temp_id not in temp_dogs: temp_dogs[temp_id] = { 'images': [], 'keypoints_24': [], 'keypoints_8': [], 'health_scores': [], 'timestamps': [], 'confidences': [], 'bboxes': [], 'frame_numbers': [], 'last_captured_frame': -1 } if len(temp_dogs[temp_id]['images']) >= dataset_cfg['max_images_per_dog']: continue frames_since_last = frame_num - temp_dogs[temp_id]['last_captured_frame'] if frames_since_last < min_frame_gap: continue image_crop = None for det in reversed(track.detections[-3:]): if det.image_crop is not None: image_crop = det.image_crop break if image_crop is None: continue # Bulanıklık kontrolü gray = cv2.cvtColor(image_crop, cv2.COLOR_BGR2GRAY) laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var() if laplacian_var < blur_threshold: blur_rejected += 1 continue # Poz algılama keypoints_24 = self.pose_detector.detect_pose(image_crop) if keypoints_24 is None: pose_failed += 1 continue # 8 nokta iskelet keypoints_8 = self.pose_detector.extract_8_keypoints(keypoints_24) # Sağlık değerlendirmesi health_assessment = self.health_assessor.assess_health(keypoints_24) if health_assessment is not None: health_assessed += 1 # Kaydet temp_dogs[temp_id]['images'].append(image_crop.copy()) temp_dogs[temp_id]['keypoints_24'].append(keypoints_24) temp_dogs[temp_id]['keypoints_8'].append(keypoints_8) temp_dogs[temp_id]['health_scores'].append(health_assessment) temp_dogs[temp_id]['timestamps'].append(frame_num / fps) det = track.detections[-1] temp_dogs[temp_id]['confidences'].append(det.confidence) temp_dogs[temp_id]['bboxes'].append(det.bbox) temp_dogs[temp_id]['frame_numbers'].append(frame_num) temp_dogs[temp_id]['last_captured_frame'] = frame_num processed += 1 frame_num += 1 if frame_num % 30 == 0: progress = int((frame_num / total_frames) * 100) print(f"İlerleme: {progress}% ({frame_num}/{total_frames} kare)") cap.release() # Temizlik for temp_id in list(temp_dogs.keys()): if 'last_captured_frame' in temp_dogs[temp_id]: del temp_dogs[temp_id]['last_captured_frame'] # Filtreleme original_count = len(temp_dogs) discarded_ids = [] for temp_id in list(temp_dogs.keys()): if len(temp_dogs[temp_id]['images']) < dataset_cfg['min_images_per_dog']: discarded_ids.append(temp_id) del temp_dogs[temp_id] discarded_count = len(discarded_ids) self.temp_session = temp_dogs # Checkbox durumları başlat for temp_id in temp_dogs.keys(): self.validation_data[temp_id] = [True] * len(temp_dogs[temp_id]['images']) # Özet summary = f"✅ İşlem tamamlandı!\n\n" summary += f"📊 Tespit özeti:\n" summary += f" Başlangıçta tespit: {original_count} köpek\n" if discarded_count > 0: summary += f" İptal edildi (< {dataset_cfg['min_images_per_dog']} resim): {discarded_count} köpek\n" summary += f" ID'ler: {discarded_ids}\n" summary += f" Tutuldu ({dataset_cfg['min_images_per_dog']}+ resim): {len(temp_dogs)} köpek\n\n" summary += f"📈 İşlem istatistikleri:\n" summary += f" İşlenen kare: {processed}\n" summary += f" Kare aralığı: {min_frame_gap} minimum\n" summary += f" Bulanık reddedilen: {blur_rejected}\n" summary += f" Poz algılama başarısız: {pose_failed}\n" summary += f" Sağlık değerlendirmesi: {health_assessed}\n\n" if len(temp_dogs) == 0: summary += "❌ Hiçbir köpek minimum resim gereksinimini karşılamadı.\n" summary += "ReID eşiğini ayarlamayı veya daha uzun video kullanmayı deneyin." show_validation = False else: summary += "✅ Sonuçlar GEÇİCİ oturumda saklandı\n" summary += "Resimleri incelemek ve kaydetmek için Sekme 2'ye gidin" show_validation = True gallery_html = self._create_temp_gallery() # Görselleştirme videosu oluştur viz_video_path = None if create_viz_video and len(temp_dogs) > 0: print("\n" + "="*60) print("GÖRSELLEŞTİRME VİDEOSU OLUŞTURULUYOR") print("="*60) viz_video_path = self._create_visualization_video(video_path, sample_rate) if viz_video_path: summary += f"\n\n🎬 Görselleştirme videosu oluşturuldu!" print(f"✅ Görselleştirme kaydedildi: {viz_video_path}") print("="*60 + "\n") gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() status = "Doğrulama için hazır" if len(temp_dogs) > 0 else "Geçerli köpek yok" return ( gallery_html, summary, status, gr.update(visible=show_validation), viz_video_path ) except Exception as e: import traceback error = f"❌ Hata: {str(e)}\n\n{traceback.format_exc()}" print(f"\n{'='*60}") print("İŞLEM SIRASINDA HATA") print("="*60) print(error) print("="*60 + "\n") return None, error, "", gr.update(visible=False), None finally: self.is_processing.clear() self.processing_lock.release() def _create_visualization_video(self, video_path: str, sample_rate: int) -> Optional[str]: """Robust visualization: Detects and tracks LIVE to ensure perfect sync""" try: cap = cv2.VideoCapture(video_path) if not cap.isOpened(): print("Error: Cannot open video") return None fps = cap.get(cv2.CAP_PROP_FPS) or 30 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # Use absolute path for output to avoid Gradio finding issues output_path = os.path.abspath(f"viz_{datetime.now().strftime('%H%M%S')}.mp4") # Try avc1 (H.264) for browser compatibility, fallback to mp4v fourcc = cv2.VideoWriter_fourcc(*'avc1') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) if not out.isOpened(): fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) # Create a FRESH tracker for visualization to avoid state conflicts viz_tracker = DeepSORTTracker( max_iou_distance=0.7, max_age=30, n_init=1, # Lower init to show tracks immediately use_appearance=False # Faster for viz ) frame_num = 0 colors = [ (0, 255, 0), (255, 0, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255), (128, 0, 128), (255, 128, 0) ] print(f"Generating visualization video (Live Mode)...") while cap.isOpened(): ret, frame = cap.read() if not ret: break # Only process frames according to sample rate if frame_num % sample_rate == 0: # 1. Detect LIVE detections = self.detector.detect(frame) # 2. Track LIVE tracks = viz_tracker.update(detections) # 3. Draw LIVE for idx, track in enumerate(tracks): # Get ReID temp_id result = self.reid.match_or_register(track) temp_id = result['temp_id'] color = colors[temp_id % len(colors)] # ← Use temp_id for color # Draw BBox x1, y1, x2, y2 = map(int, track.bbox) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 6) # Draw ID cv2.putText(frame, f"ID: {temp_id}", (x1, y1-10), # ← Use temp_id for label cv2.FONT_HERSHEY_SIMPLEX, 1.5, color, 5) # Draw Skeleton # Find the detection corresponding to this track detection = None # Search recent detections for the one matching this track for det in reversed(track.detections[-5:]): if det.image_crop is not None: detection = det break if detection and detection.image_crop is not None: # Re-run pose on the specific crop to ensure alignment keypoints_24 = self.pose_detector.detect_pose(detection.image_crop) if keypoints_24 is not None: keypoints_8 = self.pose_detector.extract_8_keypoints(keypoints_24) # Scale back to full frame keypoints_8_scaled = keypoints_8.copy() keypoints_8_scaled[:, 0] += x1 keypoints_8_scaled[:, 1] += y1 self.pose_detector.visualize_8_keypoints( frame, keypoints_8_scaled, draw_skeleton=True, draw_points=True ) out.write(frame) frame_num += 1 if frame_num % 30 == 0: print(f"Viz Progress: {frame_num}/{total_frames}") cap.release() out.release() if os.path.exists(output_path) and os.path.getsize(output_path) > 1000: return output_path return None except Exception as e: print(f"Visualization Error: {e}") import traceback traceback.print_exc() return None def stop_processing(self): """Video işlemeyi durdur""" if self.is_processing.is_set(): self.is_processing.clear() return "İşlem kullanıcı tarafından durduruldu", "Durduruldu", None else: return "Durduralacak işlem yok", "İşlem yapılmıyor", None def clear_reset(self): """Tüm geçici verileri temizle ve UI'yi sıfırla""" self.temp_session.clear() self.tracker.reset() self.reid.reset_session() self.current_video_path = None self.validation_data = {} gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() return ( None, # video_display "
Oturum temizlendi. Başlamak için yeni bir video yükleyin.
", # gallery "", # summary "", # status gr.update(visible=False) # validation container ) def discard_session(self): """Geçici oturumu tamamen iptal et""" count = len(self.temp_session) self.temp_session.clear() self.tracker.reset() self.reid.reset_session() self.validation_data = {} gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() return ( gr.update(visible=False), # validation container f"{count} geçici köpek iptal edildi. Farklı bir eşik deneyin.", # summary gr.update(visible=False) # database display ) def _create_temp_gallery(self) -> str: """Geçici oturumdan galeri oluştur""" if not self.temp_session: return "Henüz köpek tespit edilmedi
" html = "Tespit edilen köpekler: {len(self.temp_session)}
" for temp_id, data in sorted(self.temp_session.items()): num_images = len(data['images']) avg_conf = np.mean(data['confidences']) if data['confidences'] else 0 # Sağlık durumu valid_health = [h for h in data['health_scores'] if h is not None] if valid_health: avg_health = np.mean([h['overall'] for h in valid_health]) health_status = valid_health[-1]['status'] status_colors = { 'Healthy': '#51cf66', 'Sağlıklı': '#51cf66', 'Monitor': '#ffd43b', 'İzlenmeli': '#ffd43b', 'Concern': '#ff6b6b', 'Risk altında': '#ff6b6b' } status_color = status_colors.get(health_status, '#868e96') # Türkçe durum status_turkish = { 'Healthy': 'Sağlıklı', 'Monitor': 'İzlenmeli', 'Concern': 'Risk altında' }.get(health_status, health_status) else: avg_health = None status_turkish = "Bilinmiyor" status_color = '#868e96' html += f"""Toplam resim: {num_images}
Ortalama güven: {avg_conf:.2f}
""" if avg_health is not None: html += f"Sağlık puanı: {avg_health:.1f}/10
" html += f"Durum: {status_turkish}
" html += """Veritabanında henüz köpek yok
" html = "İlk görülme: {dog['first_seen']}
Son görülme: {dog['last_seen']}
Toplam görüntülenme: {dog['total_sightings']}
""" if dog['avg_health_score'] is not None: html += f"Ortalama sağlık puanı: {dog['avg_health_score']:.1f}/10
" if last_status: html += f"Sağlık durumu: {status_turkish}
" html += """Sağlık puanı: {avg_health:.1f}/10
Sağlık durumu: {status_turkish}
""" header_html += "