238 lines
9.2 KiB
Python
238 lines
9.2 KiB
Python
import cv2
|
|
import numpy as np
|
|
import os
|
|
from PIL import Image
|
|
import logging
|
|
from .adapters.object_detection import ObjectDetectionAdapterFactory
|
|
from .adapters.logo_detection import LogoDetectionAdapterFactory
|
|
from .adapters.text_detection import TextDetectionAdapterFactory
|
|
from .adapters.motion_analysis import MotionAnalysisAdapterFactory
|
|
from .execution_strategies.base import ExecutionStrategyFactory
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AnalysisEngine:
|
|
"""Main analysis engine that orchestrates capability-specific adapters with execution strategies"""
|
|
|
|
_strategy_logged = False
|
|
|
|
def __init__(self):
|
|
self.object_detector = None
|
|
self.logo_detector = None
|
|
self.text_detector = None
|
|
self.motion_analyzer = None
|
|
self.execution_strategy = None
|
|
self._configure_execution_strategy()
|
|
|
|
def configure_providers(self, provider_config):
|
|
"""Configure adapters based on provider settings"""
|
|
if 'object_detection' in provider_config:
|
|
self.object_detector = ObjectDetectionAdapterFactory.create(
|
|
provider_config['object_detection']
|
|
)
|
|
|
|
if 'logo_detection' in provider_config:
|
|
self.logo_detector = LogoDetectionAdapterFactory.create(
|
|
provider_config['logo_detection']
|
|
)
|
|
|
|
if 'text_detection' in provider_config:
|
|
self.text_detector = TextDetectionAdapterFactory.create(
|
|
provider_config['text_detection']
|
|
)
|
|
|
|
if 'motion_analysis' in provider_config:
|
|
self.motion_analyzer = MotionAnalysisAdapterFactory.create(
|
|
provider_config['motion_analysis']
|
|
)
|
|
|
|
def _configure_execution_strategy(self):
|
|
"""Configure execution strategy from environment"""
|
|
strategy_type = os.getenv('AI_PROCESSING_MODE', 'local')
|
|
|
|
strategy_configs = {
|
|
'local': lambda: ExecutionStrategyFactory.create('local'),
|
|
'remote_lan': lambda: ExecutionStrategyFactory.create(
|
|
'remote_lan',
|
|
worker_host=os.getenv('AI_WORKER_HOST'),
|
|
timeout=int(os.getenv('AI_WORKER_TIMEOUT', '30'))
|
|
),
|
|
'cloud': lambda: ExecutionStrategyFactory.create('cloud')
|
|
}
|
|
|
|
try:
|
|
if strategy_type in strategy_configs:
|
|
self.execution_strategy = strategy_configs[strategy_type]()
|
|
else:
|
|
logger.warning(f"Unknown strategy type {strategy_type}, falling back to local")
|
|
self.execution_strategy = strategy_configs['local']()
|
|
|
|
if not AnalysisEngine._strategy_logged:
|
|
logger.info(f"Configured execution strategy: {strategy_type}")
|
|
AnalysisEngine._strategy_logged = True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to configure execution strategy: {e}")
|
|
# Fallback to local
|
|
self.execution_strategy = strategy_configs['local']()
|
|
|
|
def extract_frame_from_segment(self, segment_path, timestamp=None):
|
|
"""Extract frame from video segment"""
|
|
try:
|
|
import os
|
|
logger.debug(f"Attempting to extract frame from: {segment_path}")
|
|
|
|
if not os.path.exists(segment_path):
|
|
logger.error(f"Segment file does not exist: {segment_path}")
|
|
return None
|
|
|
|
cap = cv2.VideoCapture(segment_path)
|
|
|
|
if not cap.isOpened():
|
|
logger.error(f"OpenCV failed to open: {segment_path}")
|
|
return None
|
|
|
|
# For TS segments, seeking is problematic, just read first frame
|
|
# This is suitable for real-time analysis where any frame is representative
|
|
ret, frame = cap.read()
|
|
cap.release()
|
|
|
|
if ret:
|
|
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
return Image.fromarray(frame_rgb)
|
|
else:
|
|
logger.error(f"Failed to read frame from {segment_path}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error extracting frame: {e}")
|
|
return None
|
|
|
|
def analyze_frame(self, image, requested_analysis, confidence_threshold=0.5):
|
|
"""Analyze a single frame using configured adapters and execution strategy"""
|
|
results = {}
|
|
|
|
try:
|
|
# Adapter execution map
|
|
adapter_map = {
|
|
'object_detection': self.object_detector,
|
|
'logo_detection': self.logo_detector,
|
|
'text_detection': self.text_detector
|
|
}
|
|
|
|
# Execute detection using strategy
|
|
for analysis_type in requested_analysis:
|
|
if analysis_type in adapter_map and adapter_map[analysis_type]:
|
|
detections = self.execution_strategy.execute_detection(
|
|
adapter_map[analysis_type],
|
|
image,
|
|
confidence_threshold
|
|
)
|
|
|
|
# Map to expected result format
|
|
result_key = {
|
|
'object_detection': 'objects',
|
|
'logo_detection': 'logos',
|
|
'text_detection': 'text'
|
|
}.get(analysis_type, analysis_type)
|
|
|
|
results[result_key] = detections
|
|
|
|
# Visual properties (always computed locally)
|
|
if 'visual_analysis' in requested_analysis:
|
|
results['visual'] = self._analyze_visual_properties(image)
|
|
|
|
return results
|
|
finally:
|
|
# Clean up models after each analysis to prevent memory leaks
|
|
self.cleanup()
|
|
|
|
def cleanup(self):
|
|
"""Clean up all models and release memory"""
|
|
try:
|
|
if self.logo_detector and hasattr(self.logo_detector, 'cleanup'):
|
|
self.logo_detector.cleanup()
|
|
if self.object_detector and hasattr(self.object_detector, 'cleanup'):
|
|
self.object_detector.cleanup()
|
|
if self.text_detector and hasattr(self.text_detector, 'cleanup'):
|
|
self.text_detector.cleanup()
|
|
if self.motion_analyzer and hasattr(self.motion_analyzer, 'cleanup'):
|
|
self.motion_analyzer.cleanup()
|
|
|
|
# Force garbage collection
|
|
import gc
|
|
gc.collect()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Cleanup error: {e}")
|
|
|
|
def health_check(self):
|
|
"""Check health of execution strategy and configured adapters"""
|
|
try:
|
|
strategy_info = self.execution_strategy.get_info()
|
|
|
|
adapter_check = {
|
|
'object_detection': self.object_detector,
|
|
'logo_detection': self.logo_detector,
|
|
'text_detection': self.text_detector,
|
|
'motion_analysis': self.motion_analyzer
|
|
}
|
|
|
|
configured_adapters = [name for name, adapter in adapter_check.items() if adapter]
|
|
|
|
return {
|
|
'execution_strategy': strategy_info,
|
|
'adapters_configured': configured_adapters,
|
|
'strategy_available': self.execution_strategy.is_available()
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
'error': str(e),
|
|
'execution_strategy': None,
|
|
'adapters_configured': [],
|
|
'strategy_available': False
|
|
}
|
|
|
|
def analyze_video_segment(self, segment_path, requested_analysis):
|
|
"""Analyze video segment for temporal features"""
|
|
results = {}
|
|
|
|
# Motion analysis
|
|
if 'motion_analysis' in requested_analysis and self.motion_analyzer:
|
|
results['motion'] = self.motion_analyzer.analyze(segment_path)
|
|
|
|
return results
|
|
|
|
def _analyze_visual_properties(self, image):
|
|
"""Local visual property analysis"""
|
|
img_array = np.array(image)
|
|
|
|
# Dominant colors
|
|
dominant_colors = self._get_dominant_colors(img_array)
|
|
|
|
# Visual metrics
|
|
brightness = float(np.mean(img_array)) / 255.0
|
|
gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
|
|
contrast = float(np.std(gray)) / 255.0
|
|
hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV)
|
|
saturation = float(np.mean(hsv[:,:,1])) / 255.0
|
|
|
|
return {
|
|
'dominant_colors': dominant_colors,
|
|
'brightness_level': brightness,
|
|
'contrast_level': contrast,
|
|
'saturation_level': saturation
|
|
}
|
|
|
|
def _get_dominant_colors(self, image_array, k=3):
|
|
try:
|
|
data = image_array.reshape((-1, 3))
|
|
data = np.float32(data)
|
|
|
|
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
|
|
_, labels, centers = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
|
|
|
|
return centers.astype(int).tolist()
|
|
except:
|
|
return [[128, 128, 128]] |