Spaces:
Running
on
Zero
Running
on
Zero
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 | |