Spaces:
Running
on
Zero
Running
on
Zero
File size: 28,222 Bytes
e6a18b7 525fd2b e6a18b7 525fd2b e6a18b7 525fd2b e6a18b7 525fd2b e6a18b7 525fd2b e6a18b7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
import logging
import traceback
from typing import Dict, List, Tuple, Optional, Any
from scene_type import SCENE_TYPES
class SceneScoringEngine:
"""
負責場景評分相關的所有計算邏輯,包括基於 YOLO 檢測的場景評分、
多種場景分數融合,以及最終場景類型的確定。
這邊會有YOLO, CLIP, Places365混合運用的分數計算
"""
# 日常場景,用於特殊評分
EVERYDAY_SCENE_TYPE_KEYS = [
"general_indoor_space", "generic_street_view",
"desk_area_workspace", "outdoor_gathering_spot",
"kitchen_counter_or_utility_area"
]
def __init__(self, scene_types: Dict[str, Any], enable_landmark: bool = True):
"""
初始化場景評分引擎。
Args:
scene_types: 場景類型定義字典
enable_landmark: 是否啟用地標檢測功能
"""
self.logger = logging.getLogger(__name__)
self.scene_types = scene_types
self.enable_landmark = enable_landmark
def compute_scene_scores(self, detected_objects: List[Dict],
spatial_analysis_results: Optional[Dict] = None) -> Dict[str, float]:
"""
基於檢測到的物體計算各場景類型的置信度分數。
增強了對日常場景的評分能力,並考慮物體豐富度和空間聚合性。
Args:
detected_objects: 檢測到的物體列表,包含物體詳細資訊
spatial_analysis_results: 空間分析器的輸出結果,特別是 'objects_by_region' 部分
Returns:
場景類型到置信度分數的映射字典
"""
scene_scores = {}
if not detected_objects:
for scene_type_key in self.scene_types:
scene_scores[scene_type_key] = 0.0
return scene_scores
# 準備檢測物體的數據
detected_class_ids_all = [obj["class_id"] for obj in detected_objects]
detected_classes_set_all = set(detected_class_ids_all)
class_counts_all = {}
for obj in detected_objects:
class_id = obj["class_id"]
class_counts_all[class_id] = class_counts_all.get(class_id, 0) + 1
# 評估 scene_types 中定義的每個場景類型
for scene_type, scene_def in self.scene_types.items():
required_obj_ids_defined = set(scene_def.get("required_objects", []))
optional_obj_ids_defined = set(scene_def.get("optional_objects", []))
min_required_matches_needed = scene_def.get("minimum_required", 0)
# 確定哪些實際檢測到的物體與此場景類型相關
# 這些列表將存儲實際檢測到的物體字典,而不僅僅是 class_ids
actual_required_objects_found_list = []
for req_id in required_obj_ids_defined:
if req_id in detected_classes_set_all:
# 找到此必需物體的第一個實例添加到列表中(用於後續的聚合性檢查)
for dobj in detected_objects:
if dobj['class_id'] == req_id:
actual_required_objects_found_list.append(dobj)
break
num_required_matches_found = len(actual_required_objects_found_list)
actual_optional_objects_found_list = []
for opt_id in optional_obj_ids_defined:
if opt_id in detected_classes_set_all:
for dobj in detected_objects:
if dobj['class_id'] == opt_id:
actual_optional_objects_found_list.append(dobj)
break
num_optional_matches_found = len(actual_optional_objects_found_list)
# 初始分數計算權重
# 基礎分數:55% 來自必需物體,25% 來自可選物體,10% 豐富度,10% 聚合性(最大值)
required_weight = 0.55
optional_weight = 0.25
richness_bonus_max = 0.10
cohesion_bonus_max = 0.10 # _get_object_spatial_cohesion_score 的最大獎勵是 0.1
current_scene_score = 0.0
objects_to_check_for_cohesion = [] # 用於空間聚合性評分
# 檢查 minimum_required 條件並計算基礎分數
if num_required_matches_found >= min_required_matches_needed:
if len(required_obj_ids_defined) > 0:
required_ratio = num_required_matches_found / len(required_obj_ids_defined)
else: # 沒有定義必需物體,但 min_required_matches_needed 可能為 0
required_ratio = 1.0 if min_required_matches_needed == 0 else 0.0
current_scene_score = required_ratio * required_weight
objects_to_check_for_cohesion.extend(actual_required_objects_found_list)
# 從可選物體添加分數
if len(optional_obj_ids_defined) > 0:
optional_ratio = num_optional_matches_found / len(optional_obj_ids_defined)
current_scene_score += optional_ratio * optional_weight
objects_to_check_for_cohesion.extend(actual_optional_objects_found_list)
# 日常場景的靈活處理,如果嚴格的 minimum_required(基於 'required_objects')未滿足
elif scene_type in self.EVERYDAY_SCENE_TYPE_KEYS:
# 如果日常場景有許多可選項目,它仍可能是一個弱候選
# 檢查是否存在相當比例的 'optional_objects'
if (len(optional_obj_ids_defined) > 0 and
(num_optional_matches_found / len(optional_obj_ids_defined)) >= 0.25): # 例如,至少 25% 的典型可選項目
# 對這些類型的基礎分數更多地基於可選物體的滿足度
current_scene_score = (num_optional_matches_found / len(optional_obj_ids_defined)) * (required_weight + optional_weight * 0.5) # 給予一些基礎分數
objects_to_check_for_cohesion.extend(actual_optional_objects_found_list)
else:
scene_scores[scene_type] = 0.0
continue # 跳過此場景類型
else: # 對於非日常場景,如果未滿足 minimum_required,分數為 0
scene_scores[scene_type] = 0.0
continue
# 物體豐富度/多樣性的獎勵
# 考慮找到的與場景定義相關的唯一物體類別
relevant_defined_class_ids = required_obj_ids_defined.union(optional_obj_ids_defined)
unique_relevant_detected_classes = relevant_defined_class_ids.intersection(detected_classes_set_all)
object_richness_score = 0.0
if len(relevant_defined_class_ids) > 0:
richness_ratio = len(unique_relevant_detected_classes) / len(relevant_defined_class_ids)
object_richness_score = min(richness_bonus_max, richness_ratio * 0.15) # 豐富度最大 10% 獎勵
current_scene_score += object_richness_score
# 空間聚合性的獎勵(如果提供了 spatial_analysis_results)
spatial_cohesion_bonus = 0.0
if spatial_analysis_results and objects_to_check_for_cohesion:
spatial_cohesion_bonus = self._get_object_spatial_cohesion_score(
objects_to_check_for_cohesion, # 傳遞實際檢測到的物體字典列表
spatial_analysis_results
)
current_scene_score += spatial_cohesion_bonus # 此獎勵最大 0.1
# 關鍵物體多個實例的獎勵(原始邏輯的精煉版)
multiple_instance_bonus = 0.0
# 對於多實例獎勵,專注於場景定義中心的物體
key_objects_for_multi_instance_check = required_obj_ids_defined
if scene_type in self.EVERYDAY_SCENE_TYPE_KEYS and len(optional_obj_ids_defined) > 0:
# 對於日常場景,如果某些可選物體多次出現,也可以是關鍵的
# 例如,"general_indoor_space" 中的多把椅子
key_objects_for_multi_instance_check = key_objects_for_multi_instance_check.union(
set(list(optional_obj_ids_defined)[:max(1, len(optional_obj_ids_defined)//2)]) # 考慮前半部分的可選物體
)
for class_id_check in key_objects_for_multi_instance_check:
if class_id_check in detected_classes_set_all and class_counts_all.get(class_id_check, 0) > 1:
multiple_instance_bonus += 0.025 # 每種類型稍微小一點的獎勵
current_scene_score += min(0.075, multiple_instance_bonus) # 最大 7.5% 獎勵
# 應用 SCENE_TYPES 中定義的場景特定優先級
if "priority" in scene_def:
current_scene_score *= scene_def["priority"]
scene_scores[scene_type] = min(1.0, max(0.0, current_scene_score))
# 如果通過實例屬性 self.enable_landmark 禁用地標檢測,
# 確保地標特定場景類型的分數被歸零。
if not self.enable_landmark:
landmark_scene_types = ["tourist_landmark", "natural_landmark", "historical_monument"]
for lm_scene_type in landmark_scene_types:
if lm_scene_type in scene_scores:
scene_scores[lm_scene_type] = 0.0
return scene_scores
def _get_object_spatial_cohesion_score(self, objects_for_scene: List[Dict],
spatial_analysis_results: Optional[Dict]) -> float:
"""
基於場景關鍵物體的空間聚合程度計算分數。
較高的分數意味著物體在較少的區域中更加集中。
這是一個啟發式方法,可以進一步精煉。
Args:
objects_for_scene: 與當前評估場景類型相關的檢測物體列表(至少包含 'class_id' 的字典)
spatial_analysis_results: SpatialAnalyzer._analyze_regions 的輸出
預期格式:{'objects_by_region': {'region_name': [{'class_id': id, ...}, ...]}}
Returns:
float: 聚合性分數,通常是小額獎勵(例如,0.0 到 0.1)
"""
if (not objects_for_scene or not spatial_analysis_results or
"objects_by_region" not in spatial_analysis_results or
not spatial_analysis_results["objects_by_region"]):
return 0.0
# 獲取定義當前場景類型的關鍵物體的 class_ids 集合
key_object_class_ids = {obj.get('class_id') for obj in objects_for_scene if obj.get('class_id') is not None}
if not key_object_class_ids:
return 0.0
# 找出這些關鍵物體出現在哪些區域
regions_containing_key_objects = set()
# 計算找到的關鍵物體實例數量
# 這有助於區分 1 個區域中的 1 把椅子與分佈在 5 個區域中的 5 把椅子
total_key_object_instances_found = 0
for region_name, objects_in_region_list in spatial_analysis_results["objects_by_region"].items():
region_has_key_object = False
for obj_in_region in objects_in_region_list:
if obj_in_region.get('class_id') in key_object_class_ids:
region_has_key_object = True
total_key_object_instances_found += 1 # 計算每個實例
if region_has_key_object:
regions_containing_key_objects.add(region_name)
num_distinct_key_objects_in_scene = len(key_object_class_ids) # 關鍵物體的類型數量
num_instances_of_key_objects_passed = len(objects_for_scene) # 傳遞的實例數量
if not regions_containing_key_objects or num_instances_of_key_objects_passed == 0:
return 0.0
# 簡單的啟發式方法:
if (len(regions_containing_key_objects) == 1 and
total_key_object_instances_found >= num_instances_of_key_objects_passed * 0.75):
return 0.10 # 最強聚合性:大部分/所有關鍵物體實例在單個區域中
elif (len(regions_containing_key_objects) <= 2 and
total_key_object_instances_found >= num_instances_of_key_objects_passed * 0.60):
return 0.05 # 中等聚合性:大部分/所有關鍵物體實例在最多兩個區域中
elif (len(regions_containing_key_objects) <= 3 and
total_key_object_instances_found >= num_instances_of_key_objects_passed * 0.50):
return 0.02 # 較弱聚合性
return 0.0
def determine_scene_type(self, scene_scores: Dict[str, float]) -> Tuple[str, float]:
"""
基於分數確定最可能的場景類型。如果偵測到地標分數夠高,則優先回傳 "tourist_landmark"。
Args:
scene_scores: 場景類型到置信度分數的映射字典
Returns:
(最佳場景類型, 置信度) 的元組
"""
if not scene_scores:
return "unknown", 0.0
# 檢查地標相關分數是否達到門檻,如果是,直接回傳 "tourist_landmark"
# 假設場景分數 dictionary 中,"tourist_landmark"、"historical_monument"、"natural_landmark" 三個 key
# 分別代表不同類型地標。將它們加總,若總分超過 0.3,就認定為地標場景。
print(f"DEBUG: determine_scene_type input scores: {scene_scores}")
landmark_score = (
scene_scores.get("tourist_landmark", 0.0) +
scene_scores.get("historical_monument", 0.0) +
scene_scores.get("natural_landmark", 0.0)
)
if landmark_score >= 0.3:
# 回傳地標場景類型,以及該分數總和
return "tourist_landmark", float(landmark_score)
# 找分數最高的那個場景
best_scene = max(scene_scores, key=scene_scores.get)
best_score = scene_scores[best_scene]
print(f"DEBUG: determine_scene_type result: scene={best_scene}, score={best_score}")
return best_scene, float(best_score)
def fuse_scene_scores(self, yolo_scene_scores: Dict[str, float],
clip_scene_scores: Dict[str, float],
num_yolo_detections: int = 0,
avg_yolo_confidence: float = 0.0,
lighting_info: Optional[Dict] = None,
places365_info: Optional[Dict] = None) -> Dict[str, float]:
"""
融合來自 YOLO 物體檢測、CLIP 分析和 Places365 場景分類的場景分數。
根據場景類型、YOLO 檢測的豐富度、照明資訊和 Places365 置信度調整權重。
Args:
yolo_scene_scores: 基於 YOLO 物體檢測的場景分數
clip_scene_scores: 基於 CLIP 分析的場景分數
num_yolo_detections: YOLO 檢測到的置信度足夠的非地標物體總數
avg_yolo_confidence: YOLO 檢測到的非地標物體的平均置信度
lighting_info: 可選的照明條件分析結果,預期包含 'is_indoor' (bool) 和 'confidence' (float)
places365_info: 可選的 Places365 場景分類結果,預期包含 'mapped_scene_type'、'confidence' 和 'is_indoor'
Returns:
Dict: 融合了所有三個分析來源的場景分數
"""
# 處理其中一個分數字典可能為空或所有分數實際上為零的情況
# 提取和處理 Places365 場景分數
print(f"DEBUG: fuse_scene_scores input - yolo_scores: {yolo_scene_scores}")
print(f"DEBUG: fuse_scene_scores input - clip_scores: {clip_scene_scores}")
print(f"DEBUG: fuse_scene_scores input - num_yolo_detections: {num_yolo_detections}")
print(f"DEBUG: fuse_scene_scores input - avg_yolo_confidence: {avg_yolo_confidence}")
print(f"DEBUG: fuse_scene_scores input - lighting_info: {lighting_info}")
print(f"DEBUG: fuse_scene_scores input - places365_info: {places365_info}")
places365_scene_scores_map = {} # 修改變數名稱以避免與傳入的字典衝突
if places365_info and places365_info.get('confidence', 0) > 0.1:
mapped_scene_type = places365_info.get('mapped_scene_type', 'unknown')
places365_confidence = places365_info.get('confidence', 0.0)
if mapped_scene_type in self.scene_types.keys():
places365_scene_scores_map[mapped_scene_type] = places365_confidence # 使用新的字典
self.logger.info(f"Places365 contributing: {mapped_scene_type} with confidence {places365_confidence:.3f}")
# 檢查各個數據來源是否具有有意義的分數
yolo_has_meaningful_scores = bool(yolo_scene_scores and any(s > 1e-5 for s in yolo_scene_scores.values())) # 確保是布林值
clip_has_meaningful_scores = bool(clip_scene_scores and any(s > 1e-5 for s in clip_scene_scores.values())) # 確保是布林值
places365_has_meaningful_scores = bool(places365_scene_scores_map and any(s > 1e-5 for s in places365_scene_scores_map.values()))
# 計算有意義的數據來源數量
meaningful_sources_count = sum([
yolo_has_meaningful_scores,
clip_has_meaningful_scores,
places365_has_meaningful_scores
])
# 處理特殊情況:無有效數據源或僅有單一數據源
if meaningful_sources_count == 0:
return {st: 0.0 for st in self.scene_types.keys()}
elif meaningful_sources_count == 1:
if yolo_has_meaningful_scores:
return {st: yolo_scene_scores.get(st, 0.0) for st in self.scene_types.keys()}
elif clip_has_meaningful_scores:
return {st: clip_scene_scores.get(st, 0.0) for st in self.scene_types.keys()}
elif places365_has_meaningful_scores:
return {st: places365_scene_scores_map.get(st, 0.0) for st in self.scene_types.keys()}
# 初始化融合分數結果字典
fused_scores = {}
all_relevant_scene_types = set(self.scene_types.keys())
all_possible_scene_types = all_relevant_scene_types.union(
set(yolo_scene_scores.keys()),
set(clip_scene_scores.keys()),
set(places365_scene_scores_map.keys())
)
# 基礎權重 - 調整以適應三個來源
default_yolo_weight = 0.5
default_clip_weight = 0.3
default_places365_weight = 0.2
is_lighting_indoor = None
lighting_analysis_confidence = 0.0
if lighting_info and isinstance(lighting_info, dict):
is_lighting_indoor = lighting_info.get("is_indoor")
lighting_analysis_confidence = lighting_info.get("confidence", 0.0)
for scene_type in all_possible_scene_types:
yolo_score = yolo_scene_scores.get(scene_type, 0.0)
clip_score = clip_scene_scores.get(scene_type, 0.0)
places365_score = places365_scene_scores_map.get(scene_type, 0.0)
current_yolo_weight = default_yolo_weight
current_clip_weight = default_clip_weight
current_places365_weight = default_places365_weight
print(f"DEBUG: Scene {scene_type} - yolo_score: {yolo_score}, clip_score: {clip_score}, places365_score: {places365_score}")
print(f"DEBUG: Scene {scene_type} - weights: yolo={current_yolo_weight:.3f}, clip={current_clip_weight:.3f}, places365={current_places365_weight:.3f}")
scene_definition = self.scene_types.get(scene_type, {})
# 基於場景類型性質和 YOLO 豐富度的權重調整
if scene_type in self.EVERYDAY_SCENE_TYPE_KEYS:
# Places365 在日常場景分類方面表現出色
if num_yolo_detections >= 5 and avg_yolo_confidence >= 0.45: # 豐富的 YOLO 用於日常場景
current_yolo_weight = 0.60
current_clip_weight = 0.15
current_places365_weight = 0.25
elif num_yolo_detections >= 3: # 中等 YOLO 用於日常場景
current_yolo_weight = 0.50
current_clip_weight = 0.20
current_places365_weight = 0.30
else: # 降低 YOLO 用於日常場景,更多依賴 Places365
current_yolo_weight = 0.35
current_clip_weight = 0.25
current_places365_weight = 0.40
# 對於 CLIP 的全域理解或特定訓練通常更有價值的場景
elif any(keyword in scene_type.lower() for keyword in ["asian", "cultural", "aerial", "landmark", "monument", "tourist", "natural_landmark", "historical_monument"]):
current_yolo_weight = 0.25
current_clip_weight = 0.65
current_places365_weight = 0.10 # 地標場景的較低權重
# 對於特定室內常見場景(非地標),物體檢測是關鍵,但 Places365 提供強大的場景上下文
elif any(keyword in scene_type.lower() for keyword in
["room", "kitchen", "office", "bedroom", "desk_area", "indoor_space",
"professional_kitchen", "cafe", "library", "gym", "retail_store",
"supermarket", "classroom", "conference_room", "medical_facility",
"educational_setting", "dining_area"]):
current_yolo_weight = 0.55
current_clip_weight = 0.20
current_places365_weight = 0.25
# 對於特定室外常見場景(非地標),物體仍然重要
elif any(keyword in scene_type.lower() for keyword in
["parking_lot", "park_area", "beach", "harbor", "playground", "sports_field", "bus_stop", "train_station", "airport"]):
current_yolo_weight = 0.50
current_clip_weight = 0.25
current_places365_weight = 0.25
# 如果為此次運行全域禁用地標檢測
if not self.enable_landmark:
if any(keyword in scene_type.lower() for keyword in ["landmark", "monument", "tourist"]):
yolo_score = 0.0 # 應該已經從 compute_scene_scores 中為 0
clip_score *= 0.05 # 重度懲罰
places365_score *= 0.8 if scene_type not in self.EVERYDAY_SCENE_TYPE_KEYS else 1.0 # 地標場景的輕微懲罰
elif (scene_type not in self.EVERYDAY_SCENE_TYPE_KEYS and
not any(keyword in scene_type.lower() for keyword in ["asian", "cultural", "aerial"])):
# 將權重從 CLIP 重新分配給 YOLO 和 Places365
weight_boost = 0.05
current_yolo_weight = min(0.9, current_yolo_weight + weight_boost)
current_places365_weight = min(0.9, current_places365_weight + weight_boost)
current_clip_weight = max(0.1, current_clip_weight - weight_boost * 2)
# 如果 Places365 對此特定場景類型有高置信度,則提升其權重
if places365_score > 0.0 and places365_info: # 這裡的 places365_score 已經是從 map 中獲取
places365_original_confidence = places365_info.get('confidence', 0.0) # 獲取原始的 Places365 信心度
if places365_original_confidence > 0.7:
boost_factor = min(0.2, (places365_original_confidence - 0.7) * 0.4)
current_places365_weight += boost_factor
total_other_weight = current_yolo_weight + current_clip_weight
if total_other_weight > 0:
reduction_factor = boost_factor / total_other_weight
current_yolo_weight *= (1 - reduction_factor)
current_clip_weight *= (1 - reduction_factor)
# 權重標準化處理
total_weight = current_yolo_weight + current_clip_weight + current_places365_weight
if total_weight > 0: # 避免除以零
current_yolo_weight /= total_weight
current_clip_weight /= total_weight
current_places365_weight /= total_weight
else:
current_yolo_weight = 1/3
current_clip_weight = 1/3
current_places365_weight = 1/3
# 計算融合score
fused_score = (yolo_score * current_yolo_weight) + (clip_score * current_clip_weight) + (places365_score * current_places365_weight)
# 處理室內外判斷的衝突分析
places365_is_indoor = None
places365_confidence_for_indoor = 0.0
effective_is_indoor = is_lighting_indoor
effective_confidence = lighting_analysis_confidence
if places365_info and isinstance(places365_info, dict):
places365_is_indoor = places365_info.get('is_indoor')
places365_confidence_for_indoor = places365_info.get('confidence', 0.0)
# Places365 在置信度高時覆蓋照明分析
if places365_confidence_for_indoor >= 0.8 and places365_is_indoor is not None:
effective_is_indoor = places365_is_indoor
effective_confidence = places365_confidence_for_indoor
# 只在特定場景類型首次處理時輸出調試資訊
if (scene_type == "intersection" or
(scene_type in ["urban_intersection", "street_view"] and
scene_type == sorted(all_possible_scene_types)[0])):
self.logger.debug(f"Using Places365 indoor/outdoor decision: {places365_is_indoor} (confidence: {places365_confidence_for_indoor:.3f}) over lighting analysis")
if effective_is_indoor is not None and effective_confidence >= 0.65:
# 基於其定義確定場景類型本質上是室內還是室外
is_defined_as_indoor = ("indoor" in scene_definition.get("description", "").lower() or
any(kw in scene_type.lower() for kw in ["room", "kitchen", "office", "indoor", "library", "cafe", "gym"]))
is_defined_as_outdoor = ("outdoor" in scene_definition.get("description", "").lower() or
any(kw in scene_type.lower() for kw in ["street", "park", "aerial", "beach", "harbor", "intersection", "crosswalk"]))
lighting_adjustment_strength = 0.20 # 最大調整因子(例如,20%)
# 根據分析在閾值以上的置信度來縮放調整
adjustment_scale = (effective_confidence - 0.65) / (1.0 - 0.65) # 從 0 到 1 縮放
adjustment = lighting_adjustment_strength * adjustment_scale
adjustment = min(lighting_adjustment_strength, max(0, adjustment)) # 限制調整
if effective_is_indoor and is_defined_as_outdoor:
fused_score *= (1.0 - adjustment)
elif not effective_is_indoor and is_defined_as_indoor:
fused_score *= (1.0 - adjustment)
elif effective_is_indoor and is_defined_as_indoor:
fused_score = min(1.0, fused_score * (1.0 + adjustment * 0.5))
elif not effective_is_indoor and is_defined_as_outdoor:
fused_score = min(1.0, fused_score * (1.0 + adjustment * 0.5))
fused_scores[scene_type] = min(1.0, max(0.0, fused_score))
return fused_scores
print(f"DEBUG: fuse_scene_scores final result: {fused_scores}")
def update_enable_landmark_status(self, enable_landmark: bool):
"""
更新地標檢測的啟用狀態。
Args:
enable_landmark: 是否啟用地標檢測
"""
self.enable_landmark = enable_landmark
|