Преглед изворни кода

fix: improve mapping quality with tighter thresholds and monotonic tilt lookup

wenhongquan пре 3 дана
родитељ
комит
caf7013a70

+ 2 - 2
calibration_scan_180_360/build_calibration_mapping.py

@@ -57,8 +57,8 @@ def fuse_results(tm_result, fm_result) -> Optional[Dict]:
     fm_x, fm_y, fm_inliers, _ = fm_result
 
     diff = abs(tm_x - fm_x) + abs(tm_y - fm_y)
-    if diff > 0.30:  # 差异过大,取更可信的一方
-        if tm_score > 0.7 and fm_inliers < 15:
+    if diff > 0.30:  # 差异过大,优先信任特征匹配
+        if tm_score >= 0.8 and fm_inliers < 10:
             x, y = tm_x, tm_y
             conf = 'template_high_disagree'
         else:

+ 29 - 27
calibration_scan_180_360/calibration_group1.json

@@ -1,25 +1,35 @@
 {
-  "pan_offset": 357.44247301578946,
-  "pan_scale_x": -181.64337569328407,
-  "pan_scale_y": -2.9504249165785583,
-  "tilt_offset": 50.846523020260086,
-  "tilt_scale_x": -79.50569184235249,
-  "tilt_scale_y": 66.72987658055763,
-  "rms_error": 8.486858359513489,
+  "pan_offset": 248.8302491601405,
+  "pan_scale_x": -64.12778386149799,
+  "pan_scale_y": -0.47041454925068676,
+  "tilt_offset": -51.50312804157889,
+  "tilt_scale_x": 6.099094519451438,
+  "tilt_scale_y": 95.74942535008448,
+  "rms_error": 15.176371887959284,
+  "pan_rms_error": 8.995640703581632,
+  "tilt_rms_error": 12.222958398591816,
   "pan_lookup": [
     [
-      0.4,
-      280.0
+      0.2,
+      240.0
     ],
     [
-      0.5,
-      260.0
+      0.30000000000000004,
+      220.0
     ],
     [
-      0.75,
+      0.45,
+      220.0
+    ],
+    [
+      0.5,
       220.0
     ],
     [
+      0.55,
+      210.0
+    ],
+    [
       0.8500000000000001,
       200.0
     ],
@@ -34,28 +44,20 @@
   ],
   "tilt_lookup": [
     [
-      0.25,
-      25.0
-    ],
-    [
-      0.30000000000000004,
-      15.0
+      0.2,
+      -25.0
     ],
     [
       0.35000000000000003,
-      5.0
-    ],
-    [
-      0.4,
       -5.0
     ],
     [
-      0.45,
-      45.0
+      0.6000000000000001,
+      5.0
     ],
     [
-      0.6000000000000001,
-      15.0
+      0.65,
+      5.0
     ],
     [
       0.7000000000000001,
@@ -63,7 +65,7 @@
     ],
     [
       0.75,
-      35.0
+      25.0
     ],
     [
       0.8,

+ 110 - 383
calibration_scan_180_360/mapping_fused.json

@@ -25,12 +25,7 @@
       "pan": 180.0,
       "tilt": 15.0,
       "matched": true,
-      "tm": [
-        0.8828125,
-        0.18703703703703703,
-        0.4995708167552948,
-        0.15
-      ],
+      "tm": null,
       "fm": [
         0.9668191075325012,
         0.6862925291061401,
@@ -40,8 +35,8 @@
       "fused": {
         "x_ratio": 0.9668191075325012,
         "y_ratio": 0.6862925291061401,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.4995708167552948,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 10
       }
     },
@@ -75,12 +70,7 @@
       "pan": 180.0,
       "tilt": 35.0,
       "matched": true,
-      "tm": [
-        0.953515625,
-        0.5777777777777777,
-        0.47561177611351013,
-        0.18
-      ],
+      "tm": null,
       "fm": [
         0.9513329267501831,
         0.8797857165336609,
@@ -90,8 +80,8 @@
       "fused": {
         "x_ratio": 0.9513329267501831,
         "y_ratio": 0.8797857165336609,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.47561177611351013,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 13
       }
     },
@@ -100,12 +90,7 @@
       "pan": 180.0,
       "tilt": 45.0,
       "matched": true,
-      "tm": [
-        0.5044270833333333,
-        0.4222222222222222,
-        0.40934979915618896,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.8915870785713196,
         0.9116201996803284,
@@ -115,8 +100,8 @@
       "fused": {
         "x_ratio": 0.8915870785713196,
         "y_ratio": 0.9116201996803284,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.40934979915618896,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 8
       }
     },
@@ -183,9 +168,9 @@
         16
       ],
       "fused": {
-        "x_ratio": 0.4109375,
-        "y_ratio": 0.5194444444444445,
-        "confidence": "template_high_disagree",
+        "x_ratio": 0.8053718209266663,
+        "y_ratio": 0.37971019744873047,
+        "confidence": "feature_high_disagree",
         "tm_score": 0.7255898714065552,
         "fm_inliers": 7
       }
@@ -389,21 +374,10 @@
       "filename": "ptz_p200_t-25.jpg",
       "pan": 200.0,
       "tilt": -25.0,
-      "matched": true,
-      "tm": [
-        0.8809895833333333,
-        0.6824074074074075,
-        0.4969852864742279,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.8809895833333333,
-        "y_ratio": 0.6824074074074075,
-        "confidence": "template_only",
-        "tm_score": 0.4969852864742279,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p200_t-35.jpg",
@@ -455,12 +429,7 @@
       "pan": 220.0,
       "tilt": 15.0,
       "matched": true,
-      "tm": [
-        0.5453125,
-        0.5675925925925925,
-        0.35455626249313354,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.7489804029464722,
         0.28599217534065247,
@@ -470,8 +439,8 @@
       "fused": {
         "x_ratio": 0.7489804029464722,
         "y_ratio": 0.28599217534065247,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.35455626249313354,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 5
       }
     },
@@ -480,12 +449,7 @@
       "pan": 220.0,
       "tilt": 25.0,
       "matched": true,
-      "tm": [
-        0.5700520833333333,
-        0.8847222222222222,
-        0.42090359330177307,
-        0.12
-      ],
+      "tm": null,
       "fm": [
         0.46505847573280334,
         0.4401363134384155,
@@ -495,8 +459,8 @@
       "fused": {
         "x_ratio": 0.46505847573280334,
         "y_ratio": 0.4401363134384155,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.42090359330177307,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 5
       }
     },
@@ -504,33 +468,17 @@
       "filename": "ptz_p220_t+35.jpg",
       "pan": 220.0,
       "tilt": 35.0,
-      "matched": true,
-      "tm": [
-        0.87109375,
-        0.7055555555555556,
-        0.32259267568588257,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.87109375,
-        "y_ratio": 0.7055555555555556,
-        "confidence": "template_only",
-        "tm_score": 0.32259267568588257,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p220_t+45.jpg",
       "pan": 220.0,
       "tilt": 45.0,
       "matched": true,
-      "tm": [
-        0.8729166666666667,
-        0.7277777777777777,
-        0.37161633372306824,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.704368531703949,
         0.7706988453865051,
@@ -538,10 +486,10 @@
         25
       ],
       "fused": {
-        "x_ratio": 0.7656784754696726,
-        "y_ratio": 0.7550861645221457,
-        "confidence": "fused",
-        "tm_score": 0.37161633372306824,
+        "x_ratio": 0.704368531703949,
+        "y_ratio": 0.7706988453865051,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 13
       }
     },
@@ -574,33 +522,17 @@
       "filename": "ptz_p220_t-15.jpg",
       "pan": 220.0,
       "tilt": -15.0,
-      "matched": true,
-      "tm": [
-        0.90546875,
-        0.20092592592592592,
-        0.43825963139533997,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.90546875,
-        "y_ratio": 0.20092592592592592,
-        "confidence": "template_only",
-        "tm_score": 0.43825963139533997,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p220_t-25.jpg",
       "pan": 220.0,
       "tilt": -25.0,
       "matched": true,
-      "tm": [
-        0.9059895833333333,
-        0.20092592592592592,
-        0.43156757950782776,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.2772420048713684,
         0.5203272700309753,
@@ -610,8 +542,8 @@
       "fused": {
         "x_ratio": 0.2772420048713684,
         "y_ratio": 0.5203272700309753,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.43156757950782776,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 7
       }
     },
@@ -620,12 +552,7 @@
       "pan": 220.0,
       "tilt": -35.0,
       "matched": true,
-      "tm": [
-        0.8815104166666666,
-        0.17592592592592593,
-        0.4316449463367462,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.7836732864379883,
         0.7531222105026245,
@@ -635,8 +562,8 @@
       "fused": {
         "x_ratio": 0.7836732864379883,
         "y_ratio": 0.7531222105026245,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.4316449463367462,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 7
       }
     },
@@ -665,12 +592,7 @@
       "pan": 240.0,
       "tilt": 15.0,
       "matched": true,
-      "tm": [
-        0.03463541666666667,
-        0.060648148148148145,
-        0.43243858218193054,
-        0.12
-      ],
+      "tm": null,
       "fm": [
         0.5630189776420593,
         0.5253088474273682,
@@ -680,8 +602,8 @@
       "fused": {
         "x_ratio": 0.5630189776420593,
         "y_ratio": 0.5253088474273682,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.43243858218193054,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 6
       }
     },
@@ -690,12 +612,7 @@
       "pan": 240.0,
       "tilt": 25.0,
       "matched": true,
-      "tm": [
-        0.94453125,
-        0.2662037037037037,
-        0.39011240005493164,
-        0.12
-      ],
+      "tm": null,
       "fm": [
         0.4368559718132019,
         0.43448901176452637,
@@ -705,8 +622,8 @@
       "fused": {
         "x_ratio": 0.4368559718132019,
         "y_ratio": 0.43448901176452637,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.39011240005493164,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 6
       }
     },
@@ -714,33 +631,17 @@
       "filename": "ptz_p240_t+35.jpg",
       "pan": 240.0,
       "tilt": 35.0,
-      "matched": true,
-      "tm": [
-        0.41380208333333335,
-        0.46574074074074073,
-        0.315958172082901,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.41380208333333335,
-        "y_ratio": 0.46574074074074073,
-        "confidence": "template_only",
-        "tm_score": 0.315958172082901,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p240_t+45.jpg",
       "pan": 240.0,
       "tilt": 45.0,
       "matched": true,
-      "tm": [
-        0.9463541666666667,
-        0.5861111111111111,
-        0.4986889064311981,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.7036330699920654,
         0.43191203474998474,
@@ -750,8 +651,8 @@
       "fused": {
         "x_ratio": 0.7036330699920654,
         "y_ratio": 0.43191203474998474,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.4986889064311981,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 5
       }
     },
@@ -773,9 +674,9 @@
         18
       ],
       "fused": {
-        "x_ratio": 0.9471354166666667,
-        "y_ratio": 0.2740740740740741,
-        "confidence": "template_high_disagree",
+        "x_ratio": 0.36470919847488403,
+        "y_ratio": 0.8172793388366699,
+        "confidence": "feature_high_disagree",
         "tm_score": 0.7120202779769897,
         "fm_inliers": 6
       }
@@ -798,9 +699,9 @@
         14
       ],
       "fused": {
-        "x_ratio": 0.975,
-        "y_ratio": 0.30648148148148147,
-        "confidence": "template_high_disagree",
+        "x_ratio": 0.2194342315196991,
+        "y_ratio": 0.19871291518211365,
+        "confidence": "feature_high_disagree",
         "tm_score": 0.7819044589996338,
         "fm_inliers": 5
       }
@@ -829,21 +730,10 @@
       "filename": "ptz_p240_t-35.jpg",
       "pan": 240.0,
       "tilt": -35.0,
-      "matched": true,
-      "tm": [
-        0.28515625,
-        0.7851851851851852,
-        0.3167070746421814,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.28515625,
-        "y_ratio": 0.7851851851851852,
-        "confidence": "template_only",
-        "tm_score": 0.3167070746421814,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p260_t+05.jpg",
@@ -869,33 +759,17 @@
       "filename": "ptz_p260_t+15.jpg",
       "pan": 260.0,
       "tilt": 15.0,
-      "matched": true,
-      "tm": [
-        0.96953125,
-        0.262037037037037,
-        0.3315727114677429,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.96953125,
-        "y_ratio": 0.262037037037037,
-        "confidence": "template_only",
-        "tm_score": 0.3315727114677429,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p260_t+25.jpg",
       "pan": 260.0,
       "tilt": 25.0,
       "matched": true,
-      "tm": [
-        0.95625,
-        0.3175925925925926,
-        0.3758372664451599,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.5196965336799622,
         0.24702516198158264,
@@ -905,8 +779,8 @@
       "fused": {
         "x_ratio": 0.5196965336799622,
         "y_ratio": 0.24702516198158264,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.3758372664451599,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 5
       }
     },
@@ -914,41 +788,19 @@
       "filename": "ptz_p260_t+35.jpg",
       "pan": 260.0,
       "tilt": 35.0,
-      "matched": true,
-      "tm": [
-        0.9395833333333333,
-        0.6157407407407407,
-        0.3484783470630646,
-        0.2
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.9395833333333333,
-        "y_ratio": 0.6157407407407407,
-        "confidence": "template_only",
-        "tm_score": 0.3484783470630646,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p260_t+45.jpg",
       "pan": 260.0,
       "tilt": 45.0,
-      "matched": true,
-      "tm": [
-        0.8572916666666667,
-        0.48518518518518516,
-        0.4707513749599457,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.8572916666666667,
-        "y_ratio": 0.48518518518518516,
-        "confidence": "template_only",
-        "tm_score": 0.4707513749599457,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p260_t-05.jpg",
@@ -1059,21 +911,10 @@
       "filename": "ptz_p280_t+15.jpg",
       "pan": 280.0,
       "tilt": 15.0,
-      "matched": true,
-      "tm": [
-        0.059895833333333336,
-        0.32037037037037036,
-        0.42696642875671387,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.059895833333333336,
-        "y_ratio": 0.32037037037037036,
-        "confidence": "template_only",
-        "tm_score": 0.42696642875671387,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p280_t+25.jpg",
@@ -1272,33 +1113,17 @@
       "filename": "ptz_p300_t+35.jpg",
       "pan": 300.0,
       "tilt": 35.0,
-      "matched": true,
-      "tm": [
-        0.5645833333333333,
-        0.7787037037037037,
-        0.3152913451194763,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.5645833333333333,
-        "y_ratio": 0.7787037037037037,
-        "confidence": "template_only",
-        "tm_score": 0.3152913451194763,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p300_t+45.jpg",
       "pan": 300.0,
       "tilt": 45.0,
       "matched": true,
-      "tm": [
-        0.06614583333333333,
-        0.3523148148148148,
-        0.3445965349674225,
-        0.12
-      ],
+      "tm": null,
       "fm": [
         0.46529990434646606,
         0.3875478506088257,
@@ -1308,8 +1133,8 @@
       "fused": {
         "x_ratio": 0.46529990434646606,
         "y_ratio": 0.3875478506088257,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.3445965349674225,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 5
       }
     },
@@ -1382,61 +1207,28 @@
       "filename": "ptz_p300_t-35.jpg",
       "pan": 300.0,
       "tilt": -35.0,
-      "matched": true,
-      "tm": [
-        0.5427083333333333,
-        0.8194444444444444,
-        0.42739081382751465,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.5427083333333333,
-        "y_ratio": 0.8194444444444444,
-        "confidence": "template_only",
-        "tm_score": 0.42739081382751465,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p320_t+05.jpg",
       "pan": 320.0,
       "tilt": 5.0,
-      "matched": true,
-      "tm": [
-        0.8575520833333333,
-        0.48194444444444445,
-        0.33757466077804565,
-        0.12
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.8575520833333333,
-        "y_ratio": 0.48194444444444445,
-        "confidence": "template_only",
-        "tm_score": 0.33757466077804565,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p320_t+15.jpg",
       "pan": 320.0,
       "tilt": 15.0,
-      "matched": true,
-      "tm": [
-        0.5408854166666667,
-        0.5166666666666667,
-        0.41650739312171936,
-        0.15
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.5408854166666667,
-        "y_ratio": 0.5166666666666667,
-        "confidence": "template_only",
-        "tm_score": 0.41650739312171936,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p320_t+25.jpg",
@@ -1451,41 +1243,19 @@
       "filename": "ptz_p320_t+35.jpg",
       "pan": 320.0,
       "tilt": 35.0,
-      "matched": true,
-      "tm": [
-        0.53671875,
-        0.5101851851851852,
-        0.35990479588508606,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.53671875,
-        "y_ratio": 0.5101851851851852,
-        "confidence": "template_only",
-        "tm_score": 0.35990479588508606,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p320_t+45.jpg",
       "pan": 320.0,
       "tilt": 45.0,
-      "matched": true,
-      "tm": [
-        0.6421875,
-        0.6888888888888889,
-        0.4986323416233063,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.6421875,
-        "y_ratio": 0.6888888888888889,
-        "confidence": "template_only",
-        "tm_score": 0.4986323416233063,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p320_t-05.jpg",
@@ -1617,12 +1387,7 @@
       "pan": 340.0,
       "tilt": 15.0,
       "matched": true,
-      "tm": [
-        0.39036458333333335,
-        0.44074074074074077,
-        0.4313136041164398,
-        0.25
-      ],
+      "tm": null,
       "fm": [
         0.8530533313751221,
         0.40425822138786316,
@@ -1632,8 +1397,8 @@
       "fused": {
         "x_ratio": 0.8530533313751221,
         "y_ratio": 0.40425822138786316,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.4313136041164398,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 5
       }
     },
@@ -1641,53 +1406,26 @@
       "filename": "ptz_p340_t+25.jpg",
       "pan": 340.0,
       "tilt": 25.0,
-      "matched": true,
-      "tm": [
-        0.033854166666666664,
-        0.30833333333333335,
-        0.4244994819164276,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.033854166666666664,
-        "y_ratio": 0.30833333333333335,
-        "confidence": "template_only",
-        "tm_score": 0.4244994819164276,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p340_t+35.jpg",
       "pan": 340.0,
       "tilt": 35.0,
-      "matched": true,
-      "tm": [
-        0.9479166666666666,
-        0.5833333333333334,
-        0.31273624300956726,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.9479166666666666,
-        "y_ratio": 0.5833333333333334,
-        "confidence": "template_only",
-        "tm_score": 0.31273624300956726,
-        "fm_inliers": null
-      }
+      "fused": null
     },
     {
       "filename": "ptz_p340_t+45.jpg",
       "pan": 340.0,
       "tilt": 45.0,
       "matched": true,
-      "tm": [
-        0.6901041666666666,
-        0.7546296296296297,
-        0.3221656084060669,
-        0.1
-      ],
+      "tm": null,
       "fm": [
         0.9713004231452942,
         0.5200591683387756,
@@ -1697,8 +1435,8 @@
       "fused": {
         "x_ratio": 0.9713004231452942,
         "y_ratio": 0.5200591683387756,
-        "confidence": "feature_high_disagree",
-        "tm_score": 0.3221656084060669,
+        "confidence": "feature_only",
+        "tm_score": null,
         "fm_inliers": 10
       }
     },
@@ -1776,21 +1514,10 @@
       "filename": "ptz_p340_t-35.jpg",
       "pan": 340.0,
       "tilt": -35.0,
-      "matched": true,
-      "tm": [
-        0.8927083333333333,
-        0.05648148148148148,
-        0.3681495487689972,
-        0.1
-      ],
+      "matched": false,
+      "tm": null,
       "fm": null,
-      "fused": {
-        "x_ratio": 0.8927083333333333,
-        "y_ratio": 0.05648148148148148,
-        "confidence": "template_only",
-        "tm_score": 0.3681495487689972,
-        "fm_inliers": null
-      }
+      "fused": null
     }
   ],
   "panorama_size": {

+ 37 - 6
calibration_scan_180_360/mapping_model.py

@@ -12,6 +12,8 @@ class MappingModel:
         self.tilt_scale_x = 0.0
         self.tilt_scale_y = 0.0
         self.rms_error = 0.0
+        self.pan_rms_error = 0.0
+        self.tilt_rms_error = 0.0
         self.pan_lookup: List[Tuple[float, float]] = []
         self.tilt_lookup: List[Tuple[float, float]] = []
         self.panorama_width = 3840
@@ -26,6 +28,14 @@ class MappingModel:
             diff += 360
         return diff
 
+    def _residual_error(self, record: Dict) -> float:
+        pred_pan, pred_tilt = self._predict_from_ratios(
+            float(record['x_ratio']), float(record['y_ratio'])
+        )
+        pan_err = self._angular_diff(pred_pan, record['pan'])
+        tilt_err = pred_tilt - record['tilt']
+        return math.sqrt(pan_err ** 2 + tilt_err ** 2)
+
     @staticmethod
     def _unwrap_pan_angles(pan_values: np.ndarray,
                            ref: Optional[float] = None) -> np.ndarray:
@@ -64,6 +74,14 @@ class MappingModel:
 
         # 计算 RMS
         self.rms_error = self._calculate_rms_error(inliers)
+
+        # 后拟合残差过滤:剔除残差 > 30° 的离群点并重新拟合
+        filtered = [r for r in valid if self._residual_error(r) <= 30.0]
+        if len(filtered) >= 4 and len(filtered) < len(valid):
+            self._fit_linear(filtered)
+            self._build_lookups(filtered)
+            self.rms_error = self._calculate_rms_error(filtered)
+
         return True
 
     def _ransac_filter(self, records: List[Dict],
@@ -169,12 +187,19 @@ class MappingModel:
             if any(abs(r['x_ratio'] - vx) <= pan_tolerance for vx in pan_valid_x):
                 valid_for_tilt.append(r)
 
-        y_buckets: Dict[float, List[float]] = {}
+        y_buckets: Dict[float, List[Tuple[float, float]]] = {}
         for r in valid_for_tilt:
             y_key = round(r['y_ratio'] / grid_size) * grid_size
-            y_buckets.setdefault(y_key, []).append(r['tilt'])
+            y_buckets.setdefault(y_key, []).append((r['tilt'], 1.0))
+
+        raw_tilt = []
+        for y_key in sorted(y_buckets.keys()):
+            tilts = [t for t, _ in y_buckets[y_key]]
+            median_tilt = float(np.median(np.array(tilts, dtype=float)))
+            raw_tilt.append((y_key, median_tilt, len(tilts)))
 
-        self.tilt_lookup = [(y_key, float(np.median(tilts))) for y_key, tilts in sorted(y_buckets.items())]
+        filtered_tilt = self._filter_continuous_monotonic(raw_tilt)
+        self.tilt_lookup = [(y, tilt) for y, tilt, _ in filtered_tilt]
 
     def _filter_continuous_monotonic(self, entries: List[Tuple[float, float, float]],
                                      max_step: float = 60.0) -> List[Tuple[float, float, float]]:
@@ -270,15 +295,19 @@ class MappingModel:
         return result
 
     def _calculate_rms_error(self, records: List[Dict]) -> float:
-        total = 0.0
+        pan_total = 0.0
+        tilt_total = 0.0
         for r in records:
             pred_pan, pred_tilt = self._predict_from_ratios(
                 float(r['x_ratio']), float(r['y_ratio'])
             )
             pan_err = self._angular_diff(pred_pan, r['pan'])
             tilt_err = pred_tilt - r['tilt']
-            total += pan_err ** 2 + tilt_err ** 2
-        return math.sqrt(total / len(records))
+            pan_total += pan_err ** 2
+            tilt_total += tilt_err ** 2
+        self.pan_rms_error = math.sqrt(pan_total / len(records))
+        self.tilt_rms_error = math.sqrt(tilt_total / len(records))
+        return math.sqrt((pan_total + tilt_total) / len(records))
 
     def _predict_from_ratios(self, x_ratio: float, y_ratio: float) -> Tuple[float, float]:
         if self.pan_lookup:
@@ -334,6 +363,8 @@ class MappingModel:
             'tilt_scale_x': self.tilt_scale_x,
             'tilt_scale_y': self.tilt_scale_y,
             'rms_error': self.rms_error,
+            'pan_rms_error': self.pan_rms_error,
+            'tilt_rms_error': self.tilt_rms_error,
             'pan_lookup': self.pan_lookup,
             'tilt_lookup': self.tilt_lookup,
             'overlap_ranges': [{

+ 1 - 1
calibration_scan_180_360/matchers.py

@@ -7,7 +7,7 @@ class TemplateMatcher:
     def __init__(self,
                  roi_ratio: float = 0.5,
                  scales: Tuple[float, ...] = (0.10, 0.12, 0.15, 0.18, 0.20, 0.25, 0.30),
-                 score_threshold: float = 0.3):
+                 score_threshold: float = 0.5):
         self.roi_ratio = roi_ratio
         self.scales = scales
         self.score_threshold = score_threshold

BIN
calibration_scan_180_360/matches_fused/match_ptz_p180_t-25.jpg


BIN
calibration_scan_180_360/matches_fused/match_ptz_p220_t+45.jpg


BIN
calibration_scan_180_360/matches_fused/match_ptz_p240_t-05.jpg


BIN
calibration_scan_180_360/matches_fused/match_ptz_p240_t-15.jpg