VisionScout / functional_zone_identifier.py
DawnC's picture
Upload 6 files
ba55edb verified
import logging
import traceback
from typing import Dict, List, Any, Optional
logger = logging.getLogger(__name__)
class FunctionalZoneIdentifier:
"""
作為功能區域辨識的主要窗口
整合區域評估和場景特定的區域辨識邏輯,提供統一的功能區域辨識接口
"""
def __init__(self, zone_evaluator=None, scene_zone_identifier=None, scene_viewpoint_analyzer=None, object_categories=None):
"""
初始化功能區域識別器
Args:
zone_evaluator: 區域評估器實例
scene_zone_identifier: 場景區域辨識器實例
scene_viewpoint_analyzer: 場景視角分析器
"""
try:
self.zone_evaluator = zone_evaluator
self.scene_zone_identifier = scene_zone_identifier
self.scene_viewpoint_analyzer = scene_viewpoint_analyzer
self.viewpoint_detector = scene_viewpoint_analyzer
self.OBJECT_CATEGORIES = object_categories or {}
logger.info("FunctionalZoneIdentifier initialized successfully with SceneViewpointAnalyzer")
except Exception as e:
logger.error(f"Failed to initialize FunctionalZoneIdentifier: {str(e)}")
logger.error(traceback.format_exc())
raise
def identify_functional_zones(self, detected_objects: List[Dict], scene_type: str) -> Dict:
"""
識別場景內的功能區域,具有針對不同視角和文化背景的改進檢測能力。
如果偵測到 is_landmark=True 的物件,則優先直接呼叫 identify_landmark_zones 並回傳結果。
"""
try:
# 1. 如果沒有啟用地標功能,就先把所有有 is_landmark=True 的物件過濾掉
if not getattr(self, 'enable_landmark', True):
detected_objects = [obj for obj in detected_objects if not obj.get("is_landmark", False)]
# 2. 只要檢測到任何 is_landmark=True 的物件,立即優先使用 identify_landmark_zones
landmark_objects = [obj for obj in detected_objects if obj.get("is_landmark", False)]
if landmark_objects and self.scene_zone_identifier:
lm_zones = self.scene_zone_identifier.identify_landmark_zones(landmark_objects)
return self._standardize_zone_keys_and_descriptions(lm_zones)
# 3. city_street
if scene_type in ["tourist_landmark", "natural_landmark", "historical_monument"]:
scene_type = "city_street"
# 4. 判斷與物件數量檢查
if self.zone_evaluator:
should_identify = self.zone_evaluator.evaluate_zone_identification_feasibility(
detected_objects, scene_type
)
if not should_identify:
logger.info(f"Zone identification not feasible for scene type '{scene_type}'")
return {}
else:
if len(detected_objects) < 2:
logger.info("Insufficient objects for zone identification")
return {}
# 5. 建立 category_regions
category_regions = self._build_category_regions_mapping(detected_objects)
zones = {}
# 6. 檢測場景視角
viewpoint_info = {"viewpoint": "eye_level"}
if self.scene_viewpoint_analyzer:
viewpoint_info = self.scene_viewpoint_analyzer.detect_scene_viewpoint(detected_objects)
# 7. 根據不同 scene_type 使用各種自己的區域辨識
if scene_type in ["living_room", "bedroom", "dining_area", "kitchen", "office_workspace", "meeting_room"]:
if self.scene_zone_identifier:
raw_zones = self.scene_zone_identifier.identify_indoor_zones(
category_regions, detected_objects, scene_type
)
zones.update(self._standardize_zone_keys_and_descriptions(raw_zones))
elif scene_type in ["city_street", "parking_lot", "park_area"]:
if self.scene_zone_identifier:
raw_zones = self.scene_zone_identifier.identify_outdoor_general_zones(
category_regions, detected_objects, scene_type
)
zones.update(self._standardize_zone_keys_and_descriptions(raw_zones))
elif "aerial" in scene_type or viewpoint_info.get("viewpoint") == "aerial":
if self.scene_zone_identifier:
raw_zones = self.scene_zone_identifier.identify_aerial_view_zones(
category_regions, detected_objects, scene_type
)
zones.update(self._standardize_zone_keys_and_descriptions(raw_zones))
elif "asian" in scene_type:
if self.scene_zone_identifier:
asian_zones = self.scene_zone_identifier.identify_asian_cultural_zones(
category_regions, detected_objects, scene_type
)
zones.update(self._standardize_zone_keys_and_descriptions(asian_zones))
elif scene_type == "urban_intersection":
if self.scene_zone_identifier:
raw_zones = self.scene_zone_identifier.identify_intersection_zones(
category_regions, detected_objects, viewpoint_info.get("viewpoint")
)
zones.update(self._standardize_zone_keys_and_descriptions(raw_zones))
used_tl_count_per_region = {}
for zone_info in raw_zones.values():
obj_list = zone_info.get("objects", [])
if "traffic light" in obj_list:
rg = zone_info.get("region", "")
count_in_zone = obj_list.count("traffic light")
used_tl_count_per_region[rg] = used_tl_count_per_region.get(rg, 0) + count_in_zone
signal_regions = {}
for t in [obj for obj in detected_objects if obj.get("class_id") == 9]:
region = t.get("region", "")
signal_regions.setdefault(region, []).append(t)
for idx, (region, signals) in enumerate(signal_regions.items()):
total_in_region = len(signals)
used_in_region = used_tl_count_per_region.get(region, 0)
remaining_in_region = total_in_region - used_in_region
if remaining_in_region > 0:
direction = self._get_directional_description(region)
if direction and direction != "central":
zone_key = f"{direction} traffic control area"
else:
zone_key = "primary traffic control area" if idx == 0 else "auxiliary traffic control area"
if zone_key in zones:
suffix = 1
new_key = f"{zone_key} ({suffix})"
while new_key in zones:
suffix += 1
new_key = f"{zone_key} ({suffix})"
zone_key = new_key
zones[zone_key] = {
"region": region,
"objects": ["traffic light"] * remaining_in_region,
"description": f"Traffic control area with {remaining_in_region} traffic lights in {region}"
}
for region, signals in signal_regions.items():
used = used_tl_count_per_region.get(region, 0)
total = len(signals)
remaining = total - used
# print(f"[DEBUG] Region '{region}': Total TL = {total}, Used in crossing = {used}, Remaining = {remaining}")
elif scene_type == "financial_district":
if self.scene_zone_identifier:
fd_zones = self.scene_zone_identifier.identify_financial_district_zones(
category_regions, detected_objects
)
zones.update(self._standardize_zone_keys_and_descriptions(fd_zones))
elif scene_type == "upscale_dining":
if self.scene_zone_identifier:
ud_zones = self.scene_zone_identifier.identify_upscale_dining_zones(
category_regions, detected_objects
)
zones.update(self._standardize_zone_keys_and_descriptions(ud_zones))
else:
# 如果不是上述任何一種場景,就用「預設功能區」
default_zones = self._identify_default_zones(category_regions, detected_objects)
zones.update(self._standardize_zone_keys_and_descriptions(default_zones))
# 8. 如果此時 zones 仍為空,就會變成 default → basic → fallback
if not zones:
default_zones = self._identify_default_zones(category_regions, detected_objects)
if default_zones:
zones.update(self._standardize_zone_keys_and_descriptions(default_zones))
else:
basic_zones = self._create_basic_zones_from_objects(detected_objects, scene_type)
zones.update(self._standardize_zone_keys_and_descriptions(basic_zones))
# 通用 fallback:把所有還沒被列出的 (class_name, region) 通通補進去
fallback_zones = self._generate_category_fallback_zones(detected_objects, zones)
zones.update(fallback_zones)
# Debug: 列印出各功能區的 traffic light 統計
total_tl_in_zones = 0
for zone_key, zone_info in zones.items():
if isinstance(zone_info, dict):
sub_objs = zone_info.get("objects", [])
else:
sub_objs = []
t_in_zone = [obj for obj in sub_objs if obj == "traffic light"]
# print(f"[DEBUG] identify_functional_zones - Zone '{zone_key}' has {len(t_in_zone)} traffic light(s).")
total_tl_in_zones += len(t_in_zone)
# print(f"[DEBUG] identify_functional_zones - Total traffic lights in zones: {total_tl_in_zones}")
logger.info(f"Identified {len(zones)} functional zones for scene type '{scene_type}'")
return zones
except Exception as e:
logger.error(f"Error identifying functional zones: {str(e)}")
logger.error(traceback.format_exc())
return {}
def _standardize_zone_keys_and_descriptions(self, raw_zones: Dict) -> Dict:
"""
標準化區域鍵名和描述,將內部標識符轉換為描述性名稱
Args:
raw_zones: 原始區域識別結果
Returns:
Dict: 標準化後的區域字典
"""
try:
standardized_zones = {}
for zone_key, zone_data in raw_zones.items():
# 生成描述性的區域鍵名
descriptive_key = self._generate_descriptive_zone_key(zone_key, zone_data)
# 確保區域描述也經過標準化
if isinstance(zone_data, dict) and "description" in zone_data:
zone_data["description"] = self._enhance_zone_description(zone_data["description"], zone_data)
standardized_zones[descriptive_key] = zone_data
return standardized_zones
except Exception as e:
logger.error(f"Error standardizing zone keys and descriptions: {str(e)}")
return raw_zones
def _generate_descriptive_zone_key(self, original_key: str, zone_data: Dict) -> str:
"""
基於區域內容生成描述性的鍵名
核心修改:只要該區域內有任一個 'traffic light',就優先回傳 'traffic control zone',
"""
try:
objects = zone_data.get("objects", [])
region = zone_data.get("region", "")
# 優先檢查是否含有 traffic light
if any(obj == "traffic light" or "traffic light" in obj for obj in objects):
return "traffic control zone"
# 如果沒有 traffic light,才繼續分析「主要物件」順序
primary_objects = self._analyze_primary_objects(objects)
# 依序檢查人、車、家具、紅綠燈等
if "person" in primary_objects:
if len([o for o in objects if o == "person"]) > 1:
return "pedestrian activity area"
else:
return "individual activity zone"
elif any(vehicle in primary_objects for vehicle in ["car", "truck", "bus", "motorcycle"]):
return "vehicle movement area"
elif any(furniture in primary_objects for furniture in ["chair", "table", "sofa", "bed"]):
return "furniture arrangement area"
# 若上述都不符合,改用「基於位置」做 fallback
position_descriptions = {
"top_left": "upper left area",
"top_center": "upper central area",
"top_right": "upper right area",
"middle_left": "left side area",
"middle_center": "main crossing area",
"middle_right": "right side area",
"bottom_left": "lower left area",
"bottom_center": "lower central area",
"bottom_right": "lower right area"
}
if region in position_descriptions:
return position_descriptions[region]
# 再次檢查主要物件,給出另一種 fallback 命名
if primary_objects:
if "traffic light" in primary_objects:
return "traffic control zone"
elif any(vehicle in primary_objects for vehicle in ["car", "truck", "bus"]):
return "vehicle movement area"
elif "person" in primary_objects:
return "pedestrian activity area"
# 最後最後的備用名稱
return "activity area"
except Exception as e:
logger.warning(f"Error generating descriptive key for '{original_key}': {str(e)}")
return "activity area"
def _analyze_primary_objects(self, objects: List[str]) -> List[str]:
"""
分析區域中的主要物件類型
Args:
objects: 物件名稱列表
Returns:
List[str]: 主要物件類型列表
"""
try:
# 計算物件出現頻率
object_counts = {}
for obj in objects:
normalized_obj = obj.replace('_', ' ').lower().strip()
object_counts[normalized_obj] = object_counts.get(normalized_obj, 0) + 1
# 按出現頻率排序,返回前三個主要物件
sorted_objects = sorted(object_counts.items(), key=lambda x: x[1], reverse=True)
return [obj[0] for obj in sorted_objects[:3]]
except Exception as e:
logger.warning(f"Error analyzing primary objects: {str(e)}")
return []
def _enhance_zone_description(self, original_description: str, zone_data: Dict) -> str:
"""
增強區域描述的自然性和完整性
"""
try:
if not original_description or not original_description.strip():
return self._generate_fallback_description(zone_data)
import re
enhanced = original_description.strip()
# 改善技術性表達為自然語言
enhanced = re.sub(r'\bin central direction\b', 'in the center', enhanced)
enhanced = re.sub(r'\bin west area\b', 'on the left side', enhanced)
enhanced = re.sub(r'\bin east direction\b', 'on the right side', enhanced)
enhanced = re.sub(r'\bnear traffic signals\b', 'near the traffic lights', enhanced)
enhanced = re.sub(r'\bwith (\d+) (\w+)\b', r'where \1 \2 can be seen', enhanced)
# 移除重複和冗餘表達
enhanced = re.sub(r'\barea with.*?in.*?area\b', lambda m: m.group(0).split(' in ')[0], enhanced)
enhanced = enhanced.replace('traffic area', 'area').replace('crossing area', 'crossing')
# 標準化描述結構
if enhanced.startswith('Pedestrian'):
enhanced = re.sub(r'^Pedestrian crossing area', 'The main pedestrian crossing', enhanced)
elif enhanced.startswith('Vehicle'):
enhanced = re.sub(r'^Vehicle traffic area', 'The vehicle movement area', enhanced)
elif enhanced.startswith('Traffic control'):
enhanced = re.sub(r'^Traffic control area', 'Traffic management elements', enhanced)
# 移除內部標識符格式
enhanced = re.sub(r'\b\w+_\w+(?:_\w+)*\b', lambda m: m.group(0).replace('_', ' '), enhanced)
# 確保描述的完整性
if not enhanced.endswith('.'):
enhanced += '.'
# 改善描述的自然性
enhanced = enhanced.replace('with with', 'with')
enhanced = re.sub(r'\s{2,}', ' ', enhanced)
return enhanced
except Exception as e:
logger.warning(f"Error enhancing zone description: {str(e)}")
return original_description if original_description else "A functional area within the scene."
def _generate_fallback_description(self, zone_data: Dict) -> str:
"""
為缺少描述的區域生成備用描述
Args:
zone_data: 區域數據
Returns:
str: 備用描述
"""
try:
objects = zone_data.get("objects", [])
region = zone_data.get("region", "")
if objects:
object_count = len(objects)
unique_objects = list(set(objects))
if object_count == 1:
return f"Area containing {unique_objects[0].replace('_', ' ')}."
elif len(unique_objects) <= 3:
obj_list = ", ".join([obj.replace('_', ' ') for obj in unique_objects])
return f"Area featuring {obj_list}."
else:
return f"Multi-functional area with {object_count} elements including various objects."
return "Functional area within the scene."
except Exception as e:
logger.warning(f"Error generating fallback description: {str(e)}")
return "Activity area."
def _build_category_regions_mapping(self, detected_objects: List[Dict]) -> Dict:
"""
建立物件按類別和區域的分組映射
Args:
detected_objects: 檢測到的物件列表
Returns:
按類別和區域分組的物件字典
"""
try:
category_regions = {}
for obj in detected_objects:
category = self._categorize_object(obj)
if not category:
continue
if category not in category_regions:
category_regions[category] = {}
region = obj.get("region", "center")
if region not in category_regions[category]:
category_regions[category][region] = []
category_regions[category][region].append(obj)
logger.debug(f"Built category regions mapping with {len(category_regions)} categories")
return category_regions
except Exception as e:
logger.error(f"Error building category regions mapping: {str(e)}")
logger.error(traceback.format_exc())
return {}
def _categorize_object(self, obj: Dict) -> str:
"""
將檢測到的物件分類到功能類別中,用於區域識別
確保所有返回值都使用自然語言格式,避免底線或技術性標識符
"""
try:
class_id = obj.get("class_id", -1)
class_name = obj.get("class_name", "").lower().strip()
# 優先處理 traffic light
# 只要 class_id == 9 或 class_name 包含 "traffic light",就分類為 "traffic light"
if class_id == 9 or "traffic light" in class_name:
return "traffic light"
# 如果有自訂的 OBJECT_CATEGORIES 映射,優先使用它
if hasattr(self, 'OBJECT_CATEGORIES') and self.OBJECT_CATEGORIES:
for category, ids in self.OBJECT_CATEGORIES.items():
if class_id in ids:
# 確保返回的類別名稱使用自然語言格式
return self._clean_category_name(category)
# COCO class default name
furniture_items = ["chair", "couch", "bed", "dining table", "toilet"]
plant_items = ["potted plant"]
electronic_items = ["tv", "laptop", "mouse", "remote", "keyboard", "cell phone"]
vehicle_items = ["bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat"]
person_items = ["person"]
kitchen_items = [
"bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl",
"banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog",
"pizza", "donut", "cake", "refrigerator", "oven", "toaster", "sink", "microwave"
]
sports_items = [
"frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat",
"baseball glove", "skateboard", "surfboard", "tennis racket"
]
personal_items = ["handbag", "tie", "suitcase", "umbrella", "backpack"]
# fallback natural language
if any(item in class_name for item in furniture_items):
return "furniture"
elif any(item in class_name for item in plant_items):
return "plant"
elif any(item in class_name for item in electronic_items):
return "electronics"
elif any(item in class_name for item in vehicle_items):
return "vehicle"
elif any(item in class_name for item in person_items):
return "person"
elif any(item in class_name for item in kitchen_items):
return "kitchen items" # 移除底線
elif any(item in class_name for item in sports_items):
return "sports"
elif any(item in class_name for item in personal_items):
return "personal items" # 移除底線
else:
return "misc"
except Exception as e:
logger.error(f"Error categorizing object: {str(e)}")
logger.error(traceback.format_exc())
return "misc"
def _clean_category_name(self, category: str) -> str:
"""
清理類別名稱,移除底線並轉換為較自然的格式
Args:
category: 原始類別名稱
Returns:
str: 清理後的類別名稱
"""
try:
if not category:
return "misc"
# 將底線替換為空格
cleaned = category.replace('_', ' ')
# 處理常見的技術性命名模式
replacements = {
'kitchen items': 'kitchen items',
'personal items': 'personal items',
'traffic light': 'traffic light',
'misc items': 'misc'
}
# 應用特定的替換規則
for old_term, new_term in replacements.items():
if cleaned == old_term:
return new_term
return cleaned.strip()
except Exception as e:
logger.warning(f"Error cleaning category name '{category}': {str(e)}")
return "misc"
def _identify_default_zones(self, category_regions: Dict, detected_objects: List[Dict]) -> Dict:
"""
當沒有匹配到特定場景類型時的一般功能區域識別
Args:
category_regions: 按類別和區域分組的物件字典
detected_objects: 檢測到的物件列表
Returns:
預設功能區域字典
"""
try:
zones = {}
# 按類別分組物件並找到主要集中區域
for category, regions in category_regions.items():
if not regions:
continue
# 找到此類別中物件最多的區域
main_region = max(regions.items(),
key=lambda x: len(x[1]),
default=(None, []))
if main_region[0] is None or len(main_region[1]) < 2:
continue
# 創建基於物件類別的區域
zone_objects = [obj["class_name"] for obj in main_region[1]]
# 如果物件太少,跳過
if len(zone_objects) < 2:
continue
# 根據類別創建區域名稱和描述
if category == "furniture":
zones["furniture arrangement area"] = {
"region": main_region[0],
"objects": zone_objects,
"description": f"Furniture arrangement area featuring {self._format_object_list_naturally(zone_objects[:3])}"
}
elif category == "electronics":
zones["electronics area"] = {
"region": main_region[0],
"objects": zone_objects,
"description": f"Electronics area containing {self._format_object_list_naturally(zone_objects[:3])}"
}
elif category == "kitchen_items":
zones["dining_zone"] = {
"region": main_region[0],
"objects": zone_objects,
"description": f"Dining or food area with {', '.join(zone_objects[:3])}"
}
elif category == "vehicle":
zones["vehicle_zone"] = {
"region": main_region[0],
"objects": zone_objects,
"description": f"Area with vehicles including {', '.join(zone_objects[:3])}"
}
elif category == "personal_items":
zones["personal_items_zone"] = {
"region": main_region[0],
"objects": zone_objects,
"description": f"Area with personal items including {', '.join(zone_objects[:3])}"
}
# 檢查人群聚集
people_objs = [obj for obj in detected_objects if obj["class_id"] == 0]
if len(people_objs) >= 2:
people_regions = {}
for obj in people_objs:
region = obj["region"]
if region not in people_regions:
people_regions[region] = []
people_regions[region].append(obj)
if people_regions:
main_people_region = max(people_regions.items(),
key=lambda x: len(x[1]),
default=(None, []))
if main_people_region[0] is not None:
zones["people_zone"] = {
"region": main_people_region[0],
"objects": ["person"] * len(main_people_region[1]),
"description": f"Area with {len(main_people_region[1])} people"
}
logger.debug(f"Identified {len(zones)} default zones")
return zones
except Exception as e:
logger.error(f"Error identifying default zones: {str(e)}")
logger.error(traceback.format_exc())
return {}
def _format_object_list_naturally(self, object_list: List[str]) -> str:
"""
將物件列表格式化為自然語言表達
Args:
object_list: 物件名稱列表
Returns:
str: 自然語言格式的物件列表
"""
try:
if not object_list:
return "various items"
# 標準化物件名稱
normalized_objects = []
for obj in object_list:
normalized = obj.replace('_', ' ').strip()
if normalized:
normalized_objects.append(normalized)
if not normalized_objects:
return "various items"
# 格式化列表
if len(normalized_objects) == 1:
return normalized_objects[0]
elif len(normalized_objects) == 2:
return f"{normalized_objects[0]} and {normalized_objects[1]}"
else:
return ", ".join(normalized_objects[:-1]) + f", and {normalized_objects[-1]}"
except Exception as e:
logger.warning(f"Error formatting object list naturally: {str(e)}")
return "various items"
def _create_basic_zones_from_objects(self, detected_objects: List[Dict], scene_type: str) -> Dict:
"""
從個別高置信度物件創建基本功能區域
這是標準區域識別失敗時的後備方案
Args:
detected_objects: 檢測到的物件列表
scene_type: 場景類型
Returns:
基本區域字典
"""
try:
zones = {}
# 專注於高置信度物件
high_conf_objects = [obj for obj in detected_objects if obj.get("confidence", 0) >= 0.6]
if not high_conf_objects:
high_conf_objects = detected_objects # 後備到所有物件
# 基於個別重要物件創建區域
processed_objects = set() # 避免重複處理相同類型的物件
for obj in high_conf_objects[:3]: # 限制為前3個物件
class_name = obj["class_name"]
region = obj.get("region", "center")
# 避免為同一類型物件創建多個區域
if class_name in processed_objects:
continue
processed_objects.add(class_name)
# 基於物件類型創建描述性區域
zone_description = self._get_basic_zone_description(class_name, scene_type)
descriptive_key = self._generate_object_based_zone_key(class_name, region)
if zone_description and descriptive_key:
zones[descriptive_key] = {
"region": region,
"objects": [class_name],
"description": zone_description
}
logger.debug(f"Created {len(zones)} basic zones from high confidence objects")
return zones
except Exception as e:
logger.error(f"Error creating basic zones from objects: {str(e)}")
logger.error(traceback.format_exc())
return {}
def _generate_object_based_zone_key(self, class_name: str, region: str) -> str:
"""
基於物件類型和位置生成描述性的區域鍵名
Args:
class_name: 物件類別名稱
region: 區域位置
Returns:
str: 描述性區域鍵名
"""
try:
# 標準化物件名稱
normalized_class = class_name.replace('_', ' ').lower().strip()
# 物件類型對應的區域描述
object_zone_mapping = {
'person': 'activity area',
'car': 'vehicle area',
'truck': 'vehicle area',
'bus': 'vehicle area',
'motorcycle': 'vehicle area',
'bicycle': 'cycling area',
'traffic light': 'traffic control area',
'chair': 'seating area',
'sofa': 'seating area',
'bed': 'rest area',
'dining table': 'dining area',
'tv': 'entertainment area',
'laptop': 'workspace area',
'potted plant': 'decorative area'
}
base_description = object_zone_mapping.get(normalized_class, f"{normalized_class} area")
# 添加位置信息以提供更具體的描述
position_modifiers = {
'top_left': 'upper left',
'top_center': 'upper central',
'top_right': 'upper right',
'middle_left': 'left side',
'middle_center': 'central',
'middle_right': 'right side',
'bottom_left': 'lower left',
'bottom_center': 'lower central',
'bottom_right': 'lower right'
}
if region in position_modifiers:
return f"{position_modifiers[region]} {base_description}"
return base_description
except Exception as e:
logger.warning(f"Error generating object-based zone key for '{class_name}': {str(e)}")
return "activity area"
def _get_basic_zone_description(self, class_name: str, scene_type: str) -> str:
"""
基於物件和場景類型生成基本區域描述
Args:
class_name: 物件類別名稱
scene_type: 場景類型
Returns:
區域描述字串
"""
try:
# 物件特定描述
descriptions = {
"bed": "Sleeping and rest area",
"sofa": "Seating and relaxation area",
"chair": "Seating area",
"dining table": "Dining and meal area",
"tv": "Entertainment and media area",
"laptop": "Work and computing area",
"potted plant": "Decorative and green space area",
"refrigerator": "Food storage and kitchen area",
"car": "Vehicle and transportation area",
"person": "Activity and social area"
}
return descriptions.get(class_name, f"Functional area with {class_name}")
except Exception as e:
logger.error(f"Error getting basic zone description for '{class_name}': {str(e)}")
return f"Functional area with {class_name}"
def _generate_category_fallback_zones(self, all_detected_objects: List[Dict], current_zones: Dict) -> Dict:
"""
通用 fallback:針對 all_detected_objects 裡,每一個 (class_name, region) 組合是否已經
在 current_zones 裡出現過。如果還沒,就為它們產生一個 fallback zone。
"""
general_fallback = {
0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus',
6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant',
11: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat',
16: 'dog', 17: 'horse', 18: 'sheep', 19: 'cow', 20: 'elephant', 21: 'bear',
22: 'zebra', 23: 'giraffe', 24: 'backpack', 25: 'umbrella', 26: 'handbag',
27: 'tie', 28: 'suitcase', 29: 'frisbee', 30: 'skis', 31: 'snowboard',
32: 'sports ball', 33: 'kite', 34: 'baseball bat', 35: 'baseball glove',
36: 'skateboard', 37: 'surfboard', 38: 'tennis racket', 39: 'bottle',
40: 'wine glass', 41: 'cup', 42: 'fork', 43: 'knife', 44: 'spoon', 45: 'bowl',
46: 'banana', 47: 'apple', 48: 'sandwich', 49: 'orange', 50: 'broccoli',
51: 'carrot', 52: 'hot dog', 53: 'pizza', 54: 'donut', 55: 'cake', 56: 'chair',
57: 'couch', 58: 'potted plant', 59: 'bed', 60: 'dining table', 61: 'toilet',
62: 'tv', 63: 'laptop', 64: 'mouse', 65: 'remote', 66: 'keyboard',
67: 'cell phone', 68: 'microwave', 69: 'oven', 70: 'toaster', 71: 'sink',
72: 'refrigerator', 73: 'book', 74: 'clock', 75: 'vase', 76: 'scissors',
77: 'teddy bear', 78: 'hair drier', 79: 'toothbrush'
}
# 1. 統計 current_zones 裡,已使用掉的 (class_name, region) 次數
used_count = {}
for zone_info in current_zones.values():
rg = zone_info.get("region", "")
for obj_name in zone_info.get("objects", []):
key = (obj_name, rg)
used_count[key] = used_count.get(key, 0) + 1
# 2. 統計 all_detected_objects 裡的 (class_name, region) 總次數
total_count = {}
for obj in all_detected_objects:
cname = obj.get("class_name", "")
rg = obj.get("region", "")
key = (cname, rg)
total_count[key] = total_count.get(key, 0) + 1
# 3. 把 default_classes 轉換成「class_name → fallback 區域 type」的對照表
category_to_fallback = {
# 行人與交通工具
"person": "pedestrian area",
"bicycle": "vehicle movement area",
"car": "vehicle movement area",
"motorcycle": "vehicle movement area",
"airplane": "vehicle movement area",
"bus": "vehicle movement area",
"train": "vehicle movement area",
"truck": "vehicle movement area",
"boat": "vehicle movement area",
"traffic light": "traffic control area",
"fire hydrant": "traffic control area",
"stop sign": "traffic control area",
"parking meter": "traffic control area",
"bench": "public furniture area",
# 動物類、鳥類
"bird": "animal area",
"cat": "animal area",
"dog": "animal area",
"horse": "animal area",
"sheep": "animal area",
"cow": "animal area",
"elephant": "animal area",
"bear": "animal area",
"zebra": "animal area",
"giraffe": "animal area",
# 托運與行李
"backpack": "personal items area",
"umbrella": "personal items area",
"handbag": "personal items area",
"tie": "personal items area",
"suitcase": "personal items area",
# 運動器材
"frisbee": "sports area",
"skis": "sports area",
"snowboard": "sports area",
"sports ball": "sports area",
"kite": "sports area",
"baseball bat": "sports area",
"baseball glove":"sports area",
"skateboard": "sports area",
"surfboard": "sports area",
"tennis racket": "sports area",
# 廚房與食品(Kitchen)
"bottle": "kitchen area",
"wine glass": "kitchen area",
"cup": "kitchen area",
"fork": "kitchen area",
"knife": "kitchen area",
"spoon": "kitchen area",
"bowl": "kitchen area",
"banana": "kitchen area",
"apple": "kitchen area",
"sandwich": "kitchen area",
"orange": "kitchen area",
"broccoli": "kitchen area",
"carrot": "kitchen area",
"hot dog": "kitchen area",
"pizza": "kitchen area",
"donut": "kitchen area",
"cake": "kitchen area",
"dining table": "furniture arrangement area",
"refrigerator": "kitchen area",
"oven": "kitchen area",
"microwave": "kitchen area",
"toaster": "kitchen area",
"sink": "kitchen area",
"book": "miscellaneous area",
"clock": "miscellaneous area",
"vase": "decorative area",
"scissors": "miscellaneous area",
"teddy bear": "miscellaneous area",
"hair drier": "miscellaneous area",
"toothbrush": "miscellaneous area",
# 電子產品
"tv": "electronics area",
"laptop": "electronics area",
"mouse": "electronics area",
"remote": "electronics area",
"keyboard": "electronics area",
"cell phone": "electronics area",
# 家具類
"chair": "furniture arrangement area",
"couch": "furniture arrangement area",
"bed": "furniture arrangement area",
"toilet": "furniture arrangement area",
# 植物(室內植物或戶外綠化)
"potted plant": "decorative area",
}
# 4. 計算缺少的 (class_name, region) 並建立 fallback zone
for (cname, rg), total in total_count.items():
used = used_count.get((cname, rg), 0)
missing = total - used
if missing <= 0:
continue
# (A) 決定這個 cname 在 fallback 裡屬於哪個大 class(zone_type)
zone_type = category_to_fallback.get(cname, "miscellaneous area")
# (B) 根據 region 與 zone_type 組合成 fallback_key
fallback_key = f"{rg} {zone_type}"
# (C) 如果名稱重複,就在後面加 (1),(2),… 避免掉衝突
if fallback_key in current_zones or fallback_key in general_fallback:
suffix = 1
new_key = f"{fallback_key} ({suffix})"
while new_key in current_zones or new_key in general_fallback:
suffix += 1
new_key = f"{fallback_key} ({suffix})"
fallback_key = new_key
# (D) 建立這支 fallback zone,objects 裡放 missing 個 cname
general_fallback[fallback_key] = {
"region": rg,
"objects": [cname] * missing,
"description": f"{missing} {cname}(s) placed in fallback {zone_type} for region {rg}"
}
return general_fallback