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