VisionScout / region_analyzer.py
DawnC's picture
Upload 59 files
e6a18b7 verified
import logging
import traceback
from typing import Dict, List, Any
logger = logging.getLogger(__name__)
class RegionAnalyzer:
"""
負責處理圖像區域劃分和基礎空間分析功能
專注於3x3網格的區域劃分、物件分布分析和空間多樣性計算
"""
def __init__(self):
"""初始化區域分析器,定義3x3網格區域"""
try:
# 定義圖像的3x3網格區域
self.regions = {
"top_left": (0, 0, 1/3, 1/3),
"top_center": (1/3, 0, 2/3, 1/3),
"top_right": (2/3, 0, 1, 1/3),
"middle_left": (0, 1/3, 1/3, 2/3),
"middle_center": (1/3, 1/3, 2/3, 2/3),
"middle_right": (2/3, 1/3, 1, 2/3),
"bottom_left": (0, 2/3, 1/3, 1),
"bottom_center": (1/3, 2/3, 2/3, 1),
"bottom_right": (2/3, 2/3, 1, 1)
}
logger.info("RegionAnalyzer initialized successfully with 3x3 grid regions")
except Exception as e:
logger.error(f"Failed to initialize RegionAnalyzer: {str(e)}")
logger.error(traceback.format_exc())
raise
def determine_region(self, x: float, y: float) -> str:
"""
判斷點位於哪個區域
Args:
x: 標準化x座標 (0-1)
y: 標準化y座標 (0-1)
Returns:
區域名稱
"""
try:
for region_name, (x1, y1, x2, y2) in self.regions.items():
if x1 <= x < x2 and y1 <= y < y2:
return region_name
logger.warning(f"Point ({x}, {y}) does not fall into any defined region")
return "unknown"
except Exception as e:
logger.error(f"Error determining region for point ({x}, {y}): {str(e)}")
logger.error(traceback.format_exc())
return "unknown"
def get_spatial_description_phrase(self, region: str) -> str:
"""
將region ID轉換為完整的空間描述短語,包含適當的介詞結構
Args:
region: 區域標識符(如 "middle_center", "top_left")
Returns:
str: 完整的空間描述短語,空值時返回空字串
"""
try:
# 處理空值或無效輸入
if not region or region.strip() == "" or region == "unknown":
return "within the visible area"
# 清理region格式,移除底線
clean_region = region.replace('_', ' ').strip().lower()
# 根據區域位置生成自然語言描述
region_mappings = {
"top left": "in the upper left area",
"top center": "in the upper area",
"top right": "in the upper right area",
"middle left": "on the left side",
"middle center": "in the center",
"center": "in the center",
"middle right": "on the right side",
"bottom left": "in the lower left area",
"bottom center": "in the lower area",
"bottom right": "in the lower right area"
}
# 直接映射匹配
if clean_region in region_mappings:
return region_mappings[clean_region]
# 模糊匹配方位的處理
if "top" in clean_region and "left" in clean_region:
return "in the upper left area"
elif "top" in clean_region and "right" in clean_region:
return "in the upper right area"
elif "bottom" in clean_region and "left" in clean_region:
return "in the lower left area"
elif "bottom" in clean_region and "right" in clean_region:
return "in the lower right area"
elif "top" in clean_region:
return "in the upper area"
elif "bottom" in clean_region:
return "in the lower area"
elif "left" in clean_region:
return "on the left side"
elif "right" in clean_region:
return "on the right side"
elif "center" in clean_region or "middle" in clean_region:
return "in the center"
else:
# 對於無法辨識的區域,返回通用描述
return f"in the {clean_region} area"
except Exception as e:
logger.warning(f"Error generating spatial description for region '{region}': {str(e)}")
return ""
def get_contextual_spatial_description(self, region: str, object_type: str = "") -> str:
"""
根據物件類型提供更具情境的空間描述
Args:
region: 區域標識符
object_type: 物件類型,用於優化描述語境
Returns:
str: 情境化的空間描述短語
"""
try:
# 獲取基礎空間描述
base_description = self.get_spatial_description_phrase(region)
if not base_description:
return ""
# 根據物件類型調整描述語境
if object_type:
object_type_lower = object_type.lower()
# 對於辨識到人相關,用更自然的位置描述
if "person" in object_type_lower or "people" in object_type_lower:
if "center" in base_description:
return "in the central area"
elif "upper" in base_description:
return "in the background"
elif "lower" in base_description:
return "in the foreground"
# 對於車輛,強調道路位置
elif any(vehicle in object_type_lower for vehicle in ["car", "vehicle", "truck", "bus"]):
if "left" in base_description:
return "on the left side of the scene"
elif "right" in base_description:
return "on the right side of the scene"
elif "center" in base_description:
return "in the central area"
# 對於交通設施,使用更具體的位置描述
elif "traffic" in object_type_lower:
if "upper" in base_description:
return "positioned in the upper portion"
elif "center" in base_description:
return "centrally positioned"
else:
return base_description.replace("in the", "positioned in the")
return base_description
except Exception as e:
logger.warning(f"Error generating contextual spatial description: {str(e)}")
return self.get_spatial_description_phrase(region)
def validate_region_input(self, region: str) -> bool:
"""
驗證region輸入是否有效
Args:
region: 待驗證的區域標識符
Returns:
bool: 是否為有效的region
"""
try:
if not region or region.strip() == "":
return False
# 清理並檢查是否為已知區域
clean_region = region.replace('_', ' ').strip().lower()
known_regions = [
"top left", "top center", "top right",
"middle left", "middle center", "middle right",
"bottom left", "bottom center", "bottom right",
"center", "unknown"
]
# 直接匹配或包含關鍵詞匹配
if clean_region in known_regions:
return True
# 檢查是否包含有效的位置關鍵詞組合
position_keywords = ["top", "bottom", "left", "right", "center", "middle"]
has_valid_keyword = any(keyword in clean_region for keyword in position_keywords)
return has_valid_keyword
except Exception as e:
logger.warning(f"Error validating region input '{region}': {str(e)}")
return False
def get_enhanced_directional_description(self, region: str) -> str:
"""
增強版的方位描述生成,提供更豐富的方位資訊
擴展原有的get_directional_description方法功能
Args:
region: 區域名稱
Returns:
str: 增強的方位描述字串
"""
try:
if not self.validate_region_input(region):
return "central"
region_lower = region.replace('_', ' ').strip().lower()
# 用比較準確的方位映射
direction_mappings = {
"top left": "northwest",
"top center": "north",
"top right": "northeast",
"middle left": "west",
"middle center": "central",
"center": "central",
"middle right": "east",
"bottom left": "southwest",
"bottom center": "south",
"bottom right": "southeast"
}
if region_lower in direction_mappings:
return direction_mappings[region_lower]
# 模糊匹配邏輯保持與原方法相同
if "top" in region_lower and "left" in region_lower:
return "northwest"
elif "top" in region_lower and "right" in region_lower:
return "northeast"
elif "bottom" in region_lower and "left" in region_lower:
return "southwest"
elif "bottom" in region_lower and "right" in region_lower:
return "southeast"
elif "top" in region_lower:
return "north"
elif "bottom" in region_lower:
return "south"
elif "left" in region_lower:
return "west"
elif "right" in region_lower:
return "east"
else:
return "central"
except Exception as e:
logger.error(f"Error getting enhanced directional description for region '{region}': {str(e)}")
return "central"
def analyze_regions(self, detected_objects: List[Dict]) -> Dict:
"""
分析物件在各區域的分布情況
Args:
detected_objects: 包含位置資訊的檢測物件列表
Returns:
包含區域分析結果的字典
"""
try:
if not detected_objects:
logger.warning("No detected objects provided for region analysis")
return {
"counts": {region: 0 for region in self.regions.keys()},
"main_focus": [],
"objects_by_region": {region: [] for region in self.regions.keys()}
}
# 計算每個區域的物件數量
region_counts = {region: 0 for region in self.regions.keys()}
region_objects = {region: [] for region in self.regions.keys()}
for obj in detected_objects:
try:
region = obj.get("region", "unknown")
if region in region_counts:
region_counts[region] += 1
region_objects[region].append({
"class_id": obj.get("class_id"),
"class_name": obj.get("class_name")
})
else:
logger.warning(f"Unknown region '{region}' found in object")
except Exception as e:
logger.error(f"Error processing object in region analysis: {str(e)}")
continue
# 確定主要焦點區域(按物件數量排序的前1-2個區域)
sorted_regions = sorted(region_counts.items(), key=lambda x: x[1], reverse=True)
main_regions = [region for region, count in sorted_regions if count > 0][:2]
result = {
"counts": region_counts,
"main_focus": main_regions,
"objects_by_region": region_objects
}
logger.info(f"Region analysis completed. Main focus areas: {main_regions}")
return result
except Exception as e:
logger.error(f"Error in region analysis: {str(e)}")
logger.error(traceback.format_exc())
# 返回空的結果結構而不是拋出異常
return {
"counts": {region: 0 for region in self.regions.keys()},
"main_focus": [],
"objects_by_region": {region: [] for region in self.regions.keys()}
}
def create_distribution_map(self, detected_objects: List[Dict]) -> Dict:
"""
創建物件在各區域分布的詳細地圖,用於空間分析
Args:
detected_objects: 檢測到的物件列表
Returns:
包含各區域分布詳情的字典
"""
try:
if not detected_objects:
logger.warning("No detected objects provided for distribution map creation")
return self._get_empty_distribution_map()
distribution = {}
# 初始化所有區域
for region in self.regions.keys():
distribution[region] = {
"total": 0,
"objects": {},
"density": 0
}
# 填充分布資料
for obj in detected_objects:
try:
region = obj.get("region", "unknown")
class_id = obj.get("class_id")
class_name = obj.get("class_name", "unknown")
if region not in distribution:
logger.warning(f"Unknown region '{region}' found, skipping object")
continue
distribution[region]["total"] += 1
if class_id not in distribution[region]["objects"]:
distribution[region]["objects"][class_id] = {
"name": class_name,
"count": 0,
"positions": []
}
distribution[region]["objects"][class_id]["count"] += 1
# 儲存位置資訊用於空間關係分析
normalized_center = obj.get("normalized_center")
if normalized_center:
distribution[region]["objects"][class_id]["positions"].append(normalized_center)
except Exception as e:
logger.error(f"Error processing object in distribution map: {str(e)}")
continue
# 計算每個區域的物件密度
for region, data in distribution.items():
# 假設所有區域在網格中大小相等
data["density"] = data["total"] / 1
logger.info("Distribution map created successfully")
return distribution
except Exception as e:
logger.error(f"Error creating distribution map: {str(e)}")
logger.error(traceback.format_exc())
return self._get_empty_distribution_map()
def calculate_spatial_diversity(self, detected_objects: List[Dict]) -> float:
"""
計算物件空間分布的多樣性
評估物件是否分散在不同區域,避免所有物件集中在單一區域
Args:
detected_objects: 檢測到的物件列表
Returns:
空間多樣性評分 (0.0-1.0)
"""
try:
if not detected_objects:
logger.warning("No detected objects provided for spatial diversity calculation")
return 0.0
regions = set()
for obj in detected_objects:
region = obj.get("region", "center")
regions.add(region)
unique_regions = len(regions)
diversity_score = min(unique_regions / 2.0, 1.0)
logger.info(f"Spatial diversity calculated: {diversity_score:.3f} (regions: {unique_regions})")
return diversity_score
except Exception as e:
logger.error(f"Error calculating spatial diversity: {str(e)}")
logger.error(traceback.format_exc())
return 0.0
def get_directional_description(self, region: str) -> str:
"""
將區域名稱轉換為方位描述(東西南北)
Args:
region: 區域名稱
Returns:
方位描述字串
"""
try:
region_lower = region.lower()
if "top" in region_lower and "left" in region_lower:
return "northwest"
elif "top" in region_lower and "right" in region_lower:
return "northeast"
elif "bottom" in region_lower and "left" in region_lower:
return "southwest"
elif "bottom" in region_lower and "right" in region_lower:
return "southeast"
elif "top" in region_lower:
return "north"
elif "bottom" in region_lower:
return "south"
elif "left" in region_lower:
return "west"
elif "right" in region_lower:
return "east"
else:
return "central"
except Exception as e:
logger.error(f"Error getting directional description for region '{region}': {str(e)}")
return "central"
def _get_empty_distribution_map(self) -> Dict:
"""
返回空的分布地圖結構
Returns:
空的分布地圖字典
"""
distribution = {}
for region in self.regions.keys():
distribution[region] = {
"total": 0,
"objects": {},
"density": 0
}
return distribution