14 KiB
Train Speed-Limit Vision
This flow is for replacing the current imported ayoubsa_best checkpoint with a U.S.-focused detector plus a separate posted-speed classifier.
The intended recipe is:
- bootstrap from public traffic-sign data
- fine-tune on comma-specific bookmarked drives
- export ONNX models
- copy them onto the device for runtime testing
Why Two Models
The detector and the value reader solve different problems.
- detector: find the sign and decide whether it is a regulatory speed-limit sign
- classifier: read the posted value from the cropped sign
This is a better fit for U.S. roads than a single detector with baked-in classes like Speed Limit 10, Speed Limit 20, Speed Limit 30, and so on.
Suggested Public Data
LISAfor U.S. roadside sign geometry and sign styleARTSfor U.S. MUTCD-style sign annotations and valuesGLAREfor U.S. glare/lighting failures
Use public data to get the model into the right regime, then fine-tune on comma bookmarks and replay clips.
Install Training Deps
From the repo root:
uv sync --extra speedvision
The runtime device does not need these packages. Only the training machine does.
Initialize a Workspace
.venv/bin/python scripts/speed_limit_vision/init_workspace.py
This creates .tmp/speed_limit_training with:
- detector image/label folders
- classifier crop folders
- review/bookmark manifests
- raw-source manifests
- export and run directories
To keep the raw datasets off the internal disk, point --workspace at the SSD-backed workspace, for example:
.venv/bin/python scripts/speed_limit_vision/init_workspace.py \
--workspace /Volumes/T5/starpilot_speed_limit/workspace/speed_limit_training_clean
The workspace now also tracks:
manifests/raw_sources.csvmanifests/public_detector_samples.csvmanifests/public_classifier_samples.csv
Those manifests are the provenance record for every imported public sample.
Import Public Datasets
ARTS challenging subset:
.venv/bin/python scripts/speed_limit_vision/import_arts_challenging.py \
--workspace /Volumes/T5/starpilot_speed_limit/workspace/speed_limit_training_clean
This imports mapped speed-limit signs from the raw challenging-dev.tar.gz archive and appends detector/classifier provenance rows into the manifest CSVs.
GLARE and LISA should be downloaded into the SSD raw tree first:
/Volumes/T5/starpilot_speed_limit/raw/glare_official/Volumes/T5/starpilot_speed_limit/raw/lisa_official
Then import them into the same workspace so the detector/classifier datasets stay source-traceable.
For GLARE, do not pull the whole Drive tree blindly. Use the filtered raw downloader so only the Images/ and optional Tracks/ files land on disk:
.venv/bin/python scripts/speed_limit_vision/download_glare_raw.py \
--workspace /Volumes/T5/starpilot_speed_limit/workspace/speed_limit_training_clean \
--output-root /Volumes/T5/starpilot_speed_limit/raw/glare_raw \
--prefix Images/ \
--resume
Then import the completed image tree:
.venv/bin/python scripts/speed_limit_vision/import_glare_images.py \
--workspace /Volumes/T5/starpilot_speed_limit/workspace/speed_limit_training_clean \
--overwrite
Import Bookmarked Debug Sessions
After a drive, copy or mount the debug session directory locally, then import it:
.venv/bin/python scripts/speed_limit_vision/import_debug_sessions.py
Or point at specific sessions:
.venv/bin/python scripts/speed_limit_vision/import_debug_sessions.py 20260330_220102 20260330_223355
This writes:
review/bookmarks.csv- snapshot images under
review/images
That manifest is the shortlist for labeling.
If the route only lives on comma connect, fetch it directly into the same clip layout the bookmark tools expect:
.venv/bin/python scripts/speed_limit_vision/download_connect_routes.py \
<dongle_id>/<route_log_id> \
--streams fcamera,qlog
This reads the JWT from ~/.comma/auth.json, downloads the requested route files from comma connect, and updates:
/Volumes/T5/starpilot_speed_limit/live_route_clips/bookmark_windows/data/media/0/realdata/...when the SSD is mounted, otherwise the same.tmp/...paths under the repo/Volumes/T5/starpilot_speed_limit/live_routes_meta/qlog_mtimes.txtwhen the SSD is mounted, otherwise.tmp/live_routes_meta/qlog_mtimes.txt/Volumes/T5/starpilot_speed_limit/live_routes_meta/files.txtwhen the SSD is mounted, otherwise.tmp/live_routes_meta/files.txt
For multiple routes, either pass several ids on the command line or use --routes-file.
Evaluate Real Sign Lead-Ins
Bookmark stills are often too late. To score what matters, replay the real fcamera.hevc footage from the 5 seconds before each bookmark:
.venv/bin/python scripts/speed_limit_vision/evaluate_bookmark_leadins.py \
--json-out .tmp/live_route_clips/bookmark_windows_report.json
This produces a per-bookmark report of whether the current runtime saw anything in the usable sign approach window.
The evaluator now reads a local session-to-route map from session_route_map.json under the same live_routes_meta root. Keep that file local or on the SSD so personal route ids never need to live in the repo.
Import Missed Lead-Ins for Labeling
Turn those lead-in misses into review frames and contact sheets:
.venv/bin/python scripts/speed_limit_vision/import_bookmark_leadins.py \
--mode misses
This writes:
review/bookmark_leadins.csv- sampled frames under
review/leadins/frames - contact sheets under
review/leadins/contact_sheets
That review set is the right source for labeling missed 55 mph, night, and town-sequence failures.
The bookmark/lead-in importers also accept source metadata fields such as region, device, and driver. Use those when importing debug sessions from multiple users so the comma-specific fine-tune can be sliced by contributor or geography instead of becoming one opaque pool.
To shrink that review set to the most promising frames per missed sign window:
.venv/bin/python scripts/speed_limit_vision/rank_bookmark_leadin_frames.py
This writes review/bookmark_leadin_shortlist.csv with the top-ranked sampled frames per bookmark.
Build the Detector Dataset
Take the imported review images and move or copy the ones you want into:
detector/images/traindetector/images/val
Label them in YOLO detect format into:
detector/labels/traindetector/labels/val
Recommended classes:
regulatory_speed_limitadvisory_speed_limitschool_zone_speed_limit
The dataset YAML is already generated at:
detector/dataset.yaml
Build the Value Classifier Dataset
Fill out:
classifier/value_labels.csv
Columns:
image_path: source image filesplit:trainorvalspeed_limit_mph: posted value such as25,35, or55bbox_index: which YOLO box to crop if an image has multiple labeled signspadding: optional crop padding ratiolabel_path: optional explicit path to the YOLO label file
Then generate the classifier crop folders:
.venv/bin/python scripts/speed_limit_vision/build_value_dataset.py
This writes cropped sign images into:
classifier/train/<value>/...classifier/val/<value>/...
Train
Detector:
.venv/bin/python scripts/speed_limit_vision/train_detector.py --device mps
Classifier:
.venv/bin/python scripts/speed_limit_vision/train_value_classifier.py --device mps
Use --device cpu, --device mps, or a CUDA device string depending on the training host.
Rebalance Toward Real Comma Data
If the detector starts overfitting to synthetic/public data, build a lighter rebalanced dataset that keeps all real_* detector images and samples the rest:
.venv/bin/python scripts/speed_limit_vision/rebalance_detector_dataset.py \
--workspace .tmp/speed_limit_training \
--max-other-train 3200
Then point the detector trainer at the generated YAML:
.venv/bin/python scripts/speed_limit_vision/train_detector.py \
--workspace .tmp/speed_limit_training \
--data .tmp/speed_limit_training/detector_rebalanced/dataset.yaml \
--device mps
This keeps validation unchanged while making retrains faster and more comma-biased.
Export ONNX
.venv/bin/python scripts/speed_limit_vision/export_models.py \
--detector-weights .tmp/speed_limit_training/runs/detector/yolo11n-speed-limit-us/weights/best.pt \
--classifier-weights .tmp/speed_limit_training/runs/classifier/yolo11n-cls-speed-limit-us/weights/best.pt \
--install-repo-assets
That writes:
.tmp/speed_limit_training/exports/speed_limit_us_detector.onnx.tmp/speed_limit_training/exports/speed_limit_us_value_classifier.onnx
And optionally copies them into:
starpilot/assets/vision_models
Copy to the Device
.venv/bin/python scripts/speed_limit_vision/install_models.py --host comma@192.168.3.110
The runtime already prefers speed_limit_us_detector.onnx plus speed_limit_us_value_classifier.onnx when both files exist in starpilot/assets/vision_models.
Evaluate the Runtime Path
Run the real StarPilot runtime path, using the installed ONNX pair, against the known saved-frame cases:
.venv/bin/python scripts/speed_limit_vision/evaluate_runtime_cases.py --strict
For temporal behavior on a saved frame directory or route extract, replay the runtime directly:
.venv/bin/python scripts/replay_speed_limit_vision.py .tmp/vision_iter/seg10_5fps --frames-fps 5
The detector/classifier runtime is model-only by default. Use --crop-ocr with
evaluate_runtime_manifest.py or replay_route_runtime.py only for an explicit
legacy comparison. A model-only release must match reviewed-manifest accuracy
and pass representative route replays at measured on-device cadence. Evaluate
candidate recognition and temporal publish behavior separately: a correct
single-frame candidate can still be suppressed by the history and speed-change
confirmation policy.
Ignored review rows label the proposed crop, not the entire camera frame.
Consequently, negative-window candidate and publish counts from
evaluate_reviewed_route_events.py are an upper bound until the full frame is
audited; another valid sign can be present outside the rejected crop. Use the
per-row output and frame image to audit any regression delta before treating it
as a runtime false positive.
Promotion Gate
Do not promote a checkpoint from classifier validation accuracy alone. Export it to an isolated model directory and run the complete runtime pipeline against the reviewed positive, hard-negative, and failed-drive manifests. A candidate must preserve exact-value recall, avoid new wrong-value reads, and remain within the accepted false-positive budget before route replay.
Mine detector proposals that fool an integrated-reject classifier into a new reject class before retraining:
.venv/bin/python scripts/speed_limit_vision/mine_classifier_reject_crops.py \
--models-dir /path/to/candidate/models \
--dataset /path/to/versioned/classifier \
--manifest /path/to/reviewed-negative-manifest.csv
Keep the resulting dataset version separate from the current training set. If a hard-negative retrain lowers reviewed recall, reject the checkpoint even when it improves aggregate validation accuracy or removes a known false positive.
Active-Learning Review Pass
Keep parallel miners in separate directories and merge them only when their model and mining fingerprints match:
.venv/bin/python scripts/speed_limit_vision/merge_manual_review_queues.py \
/path/to/shard0 /path/to/shard1 /path/to/shard2 /path/to/shard3 \
--output-dir /path/to/merged
When rescanning with a new model, compare the fingerprinted queues before selecting another batch. The optional review output retains the full queue schema so it can be passed directly to the selector and review server:
.venv/bin/python scripts/speed_limit_vision/compare_manual_review_queues.py \
--before /path/to/baseline/manual_review_queue.csv \
--after /path/to/candidate/manual_review_queue.csv \
--output-csv /path/to/comparison.csv \
--review-output /path/to/disagreements/manual_review_queue.csv
.venv/bin/python scripts/speed_limit_vision/select_manual_review_queue.py \
--input /path/to/disagreements/manual_review_queue.csv \
--output /path/to/review/manual_review_queue.csv \
--max-rows 1200 \
--min-seconds-per-route-speed 3
The selector prioritizes value changes and gained/lost reads, balances routes and speed classes, and removes adjacent same-speed frames from one scene. Start the reviewer and import its labels without moving route media off the training volume:
.venv/bin/python scripts/speed_limit_vision/serve_manual_review_queue.py \
--manifest /path/to/review/manual_review_queue.csv \
--port 8765
.venv/bin/python scripts/speed_limit_vision/import_manual_review_queue.py \
--queue /path/to/review/manual_review_queue.csv
Re-mine the Route Backlog
Re-run the backlog after a candidate passes the reviewed-manifest and route replay gates. Use a model fingerprinted run so new pseudo-labels are staged next to, rather than merged into, the original route-mining data:
.venv/bin/python scripts/speed_limit_vision/mine_route_training_samples.py \
--workspace /Volumes/T5/starpilot_speed_limit/workspace/speed_limit_training_clean \
--models-dir /path/to/promoted/models \
--model-only \
--run-id auto \
--sample-every 2.0 \
--transition-step 0.5 \
--max-frames-per-route 720 \
--max-positives-per-route 120 \
--max-negatives-per-route 200
The output is written under
staging/route_mining/model_<model-fingerprint>_run_<mining-fingerprint>/ with
its own detector images, classifier labels, review manifest, and per-route
completion state. The mining fingerprint includes the model-only mode,
thresholds, sampling configuration, and relevant source code. Review and
deduplicate that staged run before merging it into a training dataset. Never
overwrite the canonical route samples or automatically train on every mined
positive; map agreement and human review remain required because a stronger
model can still reproduce its own mistakes at larger scale.