From d96d0237461bb7e797ebc1690eaaec50706f5bf7 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Thu, 5 Nov 2026 00:51:32 -0600 Subject: [PATCH] Improve night stop detection and reject advisory speed signs --- .../speed_limits/vision/assets/PROVENANCE.md | 86 ++++++++++++++-- .../speed_limit_us_value_classifier.onnx | Bin 6229930 -> 6240936 bytes .../assets/traffic_signs_us_detector.onnx | Bin 9721639 -> 9725014 bytes .../starpilot/speed_limits/vision/model.py | 40 +++++++- .../speed_limits/vision/observation.py | 2 +- .../vision/tests/test_advisory_rejection.py | 94 ++++++++++++++++++ .../speed_limits/vision/traffic_signs.py | 2 +- prebuilt.json | 2 +- 8 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 openpilot/starpilot/speed_limits/vision/tests/test_advisory_rejection.py diff --git a/openpilot/starpilot/speed_limits/vision/assets/PROVENANCE.md b/openpilot/starpilot/speed_limits/vision/assets/PROVENANCE.md index 701b3ef62e..cd3a5c2b03 100644 --- a/openpilot/starpilot/speed_limits/vision/assets/PROVENANCE.md +++ b/openpilot/starpilot/speed_limits/vision/assets/PROVENANCE.md @@ -1,12 +1,12 @@ The joint traffic-sign detector contains the original StarPilot US speed graph plus a separate STOP head. The vision producer uses one joint detector forward. Original speed graph weights, -speed decoding, numeric classifier and resource scheduling are preserved. +speed decoding, numeric digit logits and resource scheduling are preserved. The classifier adds a learned rejection-only residual. | File | Bytes | SHA-256 | | --- | ---: | --- | -| `traffic_signs_us_detector.onnx` | 9721639 | `e94f0ef60ce6c631fb69e449b86d50f07518d0031d7d4ec84db912618697d3ed` | +| `traffic_signs_us_detector.onnx` | 9725014 | `ac735518a3d32108dee9a112ec2f740c2ed573daac22798525ecbbbbc0ed6606` | | `speed_limit_us_detector.onnx` | 9,605,948 | `82408b68c79c269296f0af942130c5383cace4ee06c78e2a4690e8488720116a` | -| `speed_limit_us_value_classifier.onnx` | 6,229,930 | `07c6696e530eb940d2757d5849b4bc0f1d785cda704e5296e18c0a94959f30a5` | +| `speed_limit_us_value_classifier.onnx` | 6,240,936 | `5d861067ab8f50986488f983cbc24f78527775d37af0b9740c5545d5f3173902` | The joint detector accepts the existing 256×256 RGB letterbox. `output0` remains the original `(1, 5, 1344)` speed output. `stop/output0` is `(1, 5, 4096)`, with four @@ -28,16 +28,16 @@ Training used the recovered YOLO26n speed checkpoint with all backbone/speed weights frozen. LISA, GLARE, COCO and manually reviewed comma training scenes supplied STOP/negative examples. Original recording splits were retained for LISA/GLARE; augmented and low-light views inherited their source -split. COCO splits were assigned by image identity. Real driving approaches used -for evaluation were excluded from training. Public datasets overlap the pretrained +split. COCO splits were assigned by image identity. Evaluation recordings were kept separate except for the development scenes +explicitly promoted into training below. Public datasets overlap the pretrained backbone's ancestry and do not establish whole-model unseen-scene generalization. The STOP branch was trained for 30 epochs, selected epoch 20, then fine-tuned with mobile Hardswish activations; selected fine-tuning epoch 10. -A final frozen-backbone hard-negative fine-tune selected epoch 4 of 12. Three +The initial frozen-backbone hard-negative fine-tune selected epoch 4 of 12. Three previous development false positives (Do Not Enter / road closure) were explicitly promoted into training, with 195 augmented views; they are no longer held-out -evidence. The final STOP checkpoint SHA-256 is +evidence. That STOP checkpoint SHA-256 is `4fa350a2e7d177eb9b3f48cbeafd1d406560f7a7c77482eb3f61e90716f6f73c`, using Ultralytics 8.4.32, PyTorch 2.15.0, opset 12. Original Ultralytics author/license metadata is retained in the detector. Dataset source/license receipts and original recordings are retained with the training @@ -78,3 +78,75 @@ another device timing pass. A declared-cost replay showed small latency changes can shift speed-sign sampling (73 to 69 correct reads in a 100-scene +5% cost scenario). Frozen speed outputs preserve per-frame accuracy but do not establish identical sampling, loaded-device performance or onroad stopping reliability. + + +## Night and advisory rejection follow-up + +The STOP head was fine-tuned with frozen backbone/speed weights and frozen batch +normalization statistics; selected epoch 6. Six reviewed night images and eight +reviewed construction-warning negatives were explicitly assigned to training. +Those recordings are development/training evidence, not independent test data. +All existing source splits were retained, and original recordings were unchanged. + +A 313-parameter RGB rejection branch was added to the same detector forward: +4x4 average pooling, a 3-to-8 channel 3x3 convolution, Hardswish, an 8-channel +3x3 depthwise convolution, Hardswish, and an 8-to-1 pointwise convolution with +Softplus. Its nonnegative output is subtracted from STOP logits before their +sigmoid. The fine-tuned STOP head and box coordinates stay frozen. The rejection +branch used 3,000 initial updates followed by 2,000 updates with positive-score +margin preservation; no detector or confirmation threshold was relaxed. +The original 402 speed nodes and 204 initializers remain byte-for-byte identical. + +In development replay at one source frame per second, five sampling phases and +400ms assumed completion latency, the previously missed dark approach confirmed +in all five phases. Existing approach confirmation was preserved. Two reviewed +warning-sign sequences produced no confirmed STOP observations; isolated raw +warning proposals can remain. The selected model retained all 108 reviewed strong +STOP detections in an additional backlog sample and improved a teacher-selected +difficult sample from 3 to 14 detections among 46 signs inside the existing crop. +These are selected development samples, not exhaustive road recall estimates. +The retained GLARE test split remained 36/46 IoU50 hits, one false detection, +and no detections on its 50 negative images. Public pretraining ancestry limits +whole-model generalization claims. + +The numeric classifier preserves all 147 original nodes preceding the final +softmax and all 80 original initializers from classifier +`07c6696e530eb940d2757d5849b4bc0f1d785cda704e5296e18c0a94959f30a5`. +A 2,204-parameter residual uses frozen classifier features and original logits: +a 256-to-8 pointwise convolution, Hardswish, concatenated original logits, a +linear output and Softplus. The residual, scaled by 0.9, is added only to the +reject logit. Original numeric logits and their ordering are unchanged for the +same crop; numeric probabilities can decrease. Full-frame proposal selection +and classifier workload are therefore evaluated separately. + +Classifier training retained the original numeric weights and used reviewed +advisory negatives plus an immutable semantic-label overlay. Yellow advisory +numerals previously labeled as regulatory speeds were corrected by visual sign +identity, while warm-white regulatory and school signs were retained. Original +images and train/validation assignments were not changed; validation examples +were not used for gradient updates. The selected rejection checkpoint is update +1,800. Additional validation annotation review was used for model selection. + +The 1,486-frame development replay retained all 395 previously correct reads +among 472 regulatory frames and the same three wrong-value reads. Advisory false +reads dropped from 137 to 43 among 167 advisory frames. No new numeric reads were +introduced in the 845 reviewed reject-frame sample. All 1,858 previously accepted +regulatory crops in the reviewed classifier validation set were retained. +Classifier forwards were 6,108 before and 6,148 after in the full-frame replay; +static accuracy preservation does not establish unchanged processing cadence. +Resource placement, processing-cost cooldown, source freshness, confirmation, +STOP expiry and braking policy remain unchanged. Closed-loop road behavior and +co-load performance are not established by offline replay. + + +The runtime retrieves the original classifier logits in the same classifier +forward. A learned advisory veto can stop further crop expansions only for a +non-regulatory-looking class-0 proposal whose original numeric probability is +at least 0.98 and new reject probability at least 0.90. Previous tentative reads +for that object are discarded. Duplicate class-0 proposals with IoU at least +0.75 are skipped within that frame; other objects, school proposals and the next +camera frame remain eligible. White regulatory crops keep their existing +expansion fallback. Five focused tests cover the real classifier ABI, same-forward logits, invalid/generic +rejections, duplicate-object scope and regulatory fallback. This reduces repeated +classifier work after a confident advisory rejection; it does not change +acceptance, confirmation, expiry or processing-cost cooldown thresholds. diff --git a/openpilot/starpilot/speed_limits/vision/assets/speed_limit_us_value_classifier.onnx b/openpilot/starpilot/speed_limits/vision/assets/speed_limit_us_value_classifier.onnx index c3b2eb51ed8b29bcc928e3d0600f89491dc666e4..3090c34d0c26e1a012e784815cc2aa3e73a066c5 100644 GIT binary patch delta 11096 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 HpAz&RrBu-+ delta 410 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 delta 21621 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 biNWjQj1)Gl-R`?}{l+!HGZW9Ri!1mqCAPrM diff --git a/openpilot/starpilot/speed_limits/vision/model.py b/openpilot/starpilot/speed_limits/vision/model.py index 7d1a749e41..784385a39d 100644 --- a/openpilot/starpilot/speed_limits/vision/model.py +++ b/openpilot/starpilot/speed_limits/vision/model.py @@ -272,7 +272,7 @@ class VisionModelCore: ASSET_DIR = Path(__file__).resolve().parent / "assets" DETECTOR_SHA256 = "82408b68c79c269296f0af942130c5383cace4ee06c78e2a4690e8488720116a" - CLASSIFIER_SHA256 = "07c6696e530eb940d2757d5849b4bc0f1d785cda704e5296e18c0a94959f30a5" + CLASSIFIER_SHA256 = "5d861067ab8f50986488f983cbc24f78527775d37af0b9740c5545d5f3173902" DETECTOR_SIZE = 256 CLASSIFIER_SIZE = 128 @@ -283,12 +283,15 @@ class VisionModelCore: self.net = self._load_net("speed_limit_us_detector.onnx", self.DETECTOR_SHA256) self.classifier_net = self._load_net("speed_limit_us_value_classifier.onnx", self.CLASSIFIER_SHA256) self._validate_model_abi() + self.classifier_logits_layer = ("onnx_node!/model.10/linear/Gemm" + if self.classifier_net.getLayerId("onnx_node!advisory/AddReject") > 0 else None) self.detector_input_size = self.DETECTOR_SIZE self.classifier_input_size = self.CLASSIFIER_SIZE self.last_detector_forward_count = 0 self.last_detector_forward_duration_s = 0.0 self.last_classifier_forward_count = 0 self.last_classifier_forward_duration_s = 0.0 + self.last_classifier_advisory_rejected = False self.temporal_tracking_enabled = False self.latest_detector_proposal = None self.history: deque[HistoryEntry] = deque() @@ -304,6 +307,7 @@ class VisionModelCore: def reset(self) -> None: """Discard evidence from a previous camera, stream or sign-unit setting.""" self.history.clear() + self.last_classifier_advisory_rejected = False self.latest_detector_proposal = None self.published_speed_limit_mph = 0 self.published_confidence = 0.0 @@ -667,6 +671,7 @@ class VisionModelCore: def _classify_speed_limit_from_model(self, sign_crop): + self.last_classifier_advisory_rejected = False if self.classifier_net is None or sign_crop.size == 0: return None @@ -678,7 +683,17 @@ class VisionModelCore: self.classifier_net.setInput(blob) forward_started_at = time.monotonic() - scores = np.array(self.classifier_net.forward()).reshape(-1) + logits_layer = getattr(self, "classifier_logits_layer", None) + original_probabilities = None + if logits_layer is not None: + output, original_logits = self.classifier_net.forward(['output0', logits_layer]) + original_logits = np.asarray(original_logits).reshape(-1) + if original_logits.size != speed_class_count + 1 or not np.isfinite(original_logits).all(): + raise ValueError("Invalid original classifier logits") + original_probabilities = self._normalize_classifier_output(original_logits) + else: + output = self.classifier_net.forward() + scores = np.asarray(output).reshape(-1) self.last_classifier_forward_count += 1 self.last_classifier_forward_duration_s += time.monotonic() - forward_started_at has_reject_class = scores.size == speed_class_count + 1 @@ -691,6 +706,10 @@ class VisionModelCore: confidence = float(speed_probabilities[class_index]) speed_limit = US_CLASSIFIER_SPEED_VALUES[class_index] if has_reject_class and float(probabilities[speed_class_count]) >= max(confidence, US_CLASSIFIER_REJECT_MIN_CONFIDENCE): + self.last_classifier_advisory_rejected = bool( + original_probabilities is not None and float(np.max(original_probabilities[:speed_class_count])) >= .98 and + float(probabilities[speed_class_count]) >= .90 + ) return None minimum_confidence = ( EXTENDED_CLASSIFIER_MIN_CONFIDENCE if speed_limit in EXTENDED_CLASSIFIER_SPEED_VALUES else US_CLASSIFIER_MIN_CONFIDENCE @@ -705,10 +724,21 @@ class VisionModelCore: frame_height, frame_width = frame_bgr.shape[:2] best_detection = None best_score = 0.0 + rejected_advisory_boxes: list[tuple[int, int, int, int]] = [] for proposal_confidence, class_id, (x1, y1, x2, y2) in self._collect_detector_classifier_proposals(frame_bgr): if class_id == 1: continue + if class_id == 0: + duplicate_advisory = False + for rx1, ry1, rx2, ry2 in rejected_advisory_boxes: + overlap = max(0, min(x2, rx2) - max(x1, rx1)) * max(0, min(y2, ry2) - max(y1, ry1)) + union = (x2 - x1) * (y2 - y1) + (rx2 - rx1) * (ry2 - ry1) - overlap + if union > 0 and overlap >= .75 * union: + duplicate_advisory = True + break + if duplicate_advisory: + continue box_width = x2 - x1 box_height = y2 - y1 @@ -814,6 +844,12 @@ class VisionModelCore: is_regulatory = True model_read = self._classify_speed_limit_from_model(sign_crop) + if (class_id == 0 and not raw_is_regulatory and model_read is None and + getattr(self, "last_classifier_advisory_rejected", False)): + # A confident advisory veto applies to this object, not another crop of it. + speed_scores.clear() + rejected_advisory_boxes.append((x1, y1, x2, y2)) + break trusted_model_read = ( class_id == 0 and model_read is not None and diff --git a/openpilot/starpilot/speed_limits/vision/observation.py b/openpilot/starpilot/speed_limits/vision/observation.py index 664281f81b..059e1d8ea2 100644 --- a/openpilot/starpilot/speed_limits/vision/observation.py +++ b/openpilot/starpilot/speed_limits/vision/observation.py @@ -13,7 +13,7 @@ MAX_FRAME_AGE_NS = 250_000_000 # Bound sign-processing latency independently of raw camera admission. MAX_RESULT_AGE_NS = 2_000_000_000 MAX_PAIR_SKEW_NS = 5_000_000 -MODEL_ID = "e94f0ef60ce6c631fb69e449b86d50f07518d0031d7d4ec84db912618697d3ed:07c6696e530eb940d2757d5849b4bc0f1d785cda704e5296e18c0a94959f30a5" +MODEL_ID = "ac735518a3d32108dee9a112ec2f740c2ed573daac22798525ecbbbbc0ed6606:5d861067ab8f50986488f983cbc24f78527775d37af0b9740c5545d5f3173902" def clock_pair_ns() -> tuple[int, int] | None: diff --git a/openpilot/starpilot/speed_limits/vision/tests/test_advisory_rejection.py b/openpilot/starpilot/speed_limits/vision/tests/test_advisory_rejection.py new file mode 100644 index 0000000000..1e375a27f3 --- /dev/null +++ b/openpilot/starpilot/speed_limits/vision/tests/test_advisory_rejection.py @@ -0,0 +1,94 @@ +import unittest +from unittest.mock import Mock, patch + +import numpy as np + +from openpilot.starpilot.speed_limits.vision import model +from openpilot.starpilot.speed_limits.vision.model import VisionModelCore + + +class TestAdvisoryRejection(unittest.TestCase): + def test_pinned_classifier_returns_original_logits_in_one_real_forward(self): + if model.cv2 is None: + self.skipTest('OpenCV DNN is not available on this host') + core = VisionModelCore(is_metric=False) + self.assertIsNotNone(core.classifier_logits_layer) + core._classify_speed_limit_from_model(np.zeros((32, 32, 3), np.uint8)) + self.assertEqual(core.last_classifier_forward_count, 1) + self.assertIsInstance(core.last_classifier_advisory_rejected, bool) + + def classifier(self, *, original_digit=True): + core = VisionModelCore.__new__(VisionModelCore) + logits = np.full((1, 19), -12., np.float32) + logits[0, 8 if original_digit else 18] = 12. + probabilities = np.zeros((1, 19), np.float32) + probabilities[0, 8], probabilities[0, 18] = .01, .99 + core.classifier_net = Mock() + core.classifier_net.forward.return_value = (probabilities, logits) + core.classifier_logits_layer = 'original_logits' + core.classifier_input_size = 128 + core.last_classifier_forward_count = 0 + core.last_classifier_forward_duration_s = 0. + return core + + def test_advisory_rejection_uses_original_logits_in_the_same_forward(self): + core = self.classifier() + self.assertIsNone(core._classify_speed_limit_from_model(np.zeros((32, 32, 3), np.uint8))) + self.assertTrue(core.last_classifier_advisory_rejected) + core.classifier_net.forward.assert_called_once_with(['output0', 'original_logits']) + self.assertEqual(core.last_classifier_forward_count, 1) + self.assertIsNone(core._classify_speed_limit_from_model(np.empty((0, 0, 3), np.uint8))) + self.assertFalse(core.last_classifier_advisory_rejected) + + def test_generic_rejection_and_invalid_logits_cannot_be_an_advisory_veto(self): + core = self.classifier(original_digit=False) + crop = np.zeros((32, 32, 3), np.uint8) + self.assertIsNone(core._classify_speed_limit_from_model(crop)) + self.assertFalse(core.last_classifier_advisory_rejected) + probabilities, logits = core.classifier_net.forward.return_value + for invalid in (logits[:, :18], np.full((1, 19), np.nan, np.float32)): + core.classifier_net.forward.return_value = probabilities, invalid + with self.assertRaisesRegex(ValueError, 'Invalid original classifier logits'): + core._classify_speed_limit_from_model(crop) + self.assertFalse(core.last_classifier_advisory_rejected) + + def detector(self, proposals): + core = VisionModelCore.__new__(VisionModelCore) + core._collect_detector_classifier_proposals = Mock(return_value=proposals) + core._remember_detector_proposal = Mock() + return core + + def test_confident_nonregulatory_rejection_stops_only_that_proposal(self): + core = self.detector([(.99, 0, (240, 30, 280, 90)), (.98, 0, (241, 31, 281, 91)), + (.97, 0, (310, 30, 350, 90))]) + calls = 0 + + def classify(_crop): + nonlocal calls + calls += 1 + core.last_classifier_advisory_rejected = calls == 2 + return None if calls == 2 else (35 if calls == 1 else 55, .99) + + with patch.object(core, '_classify_speed_limit_from_model', side_effect=classify), \ + patch.object(core, '_is_regulatory_speed_sign', side_effect=lambda _crop: calls >= 2): + result = core._detect_sign_from_detector_classifier(np.zeros((200, 400, 3), np.uint8)) + assert result is not None + self.assertEqual(result.speed_limit_mph, 55) + self.assertEqual(calls, 2 + len(model.DETECTOR_CLASSIFIER_EXPANSIONS)) + + def test_white_regulatory_crop_keeps_its_expansion_fallback(self): + core = self.detector([(.99, 0, (240, 30, 280, 90))]) + calls = 0 + + def classify(_crop): + nonlocal calls + calls += 1 + core.last_classifier_advisory_rejected = calls == 1 + return None if calls == 1 else (55, .99) + + with patch.object(core, '_classify_speed_limit_from_model', side_effect=classify), \ + patch.object(core, '_is_regulatory_speed_sign', return_value=True): + result = core._detect_sign_from_detector_classifier(np.zeros((200, 400, 3), np.uint8)) + assert result is not None + self.assertEqual(result.speed_limit_mph, 55) + self.assertEqual(calls, len(model.DETECTOR_CLASSIFIER_EXPANSIONS)) diff --git a/openpilot/starpilot/speed_limits/vision/traffic_signs.py b/openpilot/starpilot/speed_limits/vision/traffic_signs.py index 5c941c99bd..48f42eb70b 100644 --- a/openpilot/starpilot/speed_limits/vision/traffic_signs.py +++ b/openpilot/starpilot/speed_limits/vision/traffic_signs.py @@ -14,7 +14,7 @@ class SignDetections: class TrafficSignModelCore(model.VisionModelCore): - DETECTOR_SHA256 = 'e94f0ef60ce6c631fb69e449b86d50f07518d0031d7d4ec84db912618697d3ed' + DETECTOR_SHA256 = 'ac735518a3d32108dee9a112ec2f740c2ed573daac22798525ecbbbbc0ed6606' MODEL_ID = DETECTOR_SHA256 + ':' + model.VisionModelCore.CLASSIFIER_SHA256 STOP_THRESHOLD = .6 diff --git a/prebuilt.json b/prebuilt.json index 115eab6205..ce6a1d3a85 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "5870a8ec20d98140ff1f527f8f7a8d3714eef42321538d8c90fb5a04591b0c84", "schema_version": 1} +{"protected_inventory_sha256": "fcd05ae38a6a2a27e0d5e38347a880f1eb9cee25539646aede1c9cdeeabd1bdb", "schema_version": 1}