Skip to content

feat: add lane detection deployment pipeline#787

Open
Biltes wants to merge 2 commits into
masterfrom
feat/680-lane-detection-script-and-configuration
Open

feat: add lane detection deployment pipeline#787
Biltes wants to merge 2 commits into
masterfrom
feat/680-lane-detection-script-and-configuration

Conversation

@Biltes

@Biltes Biltes commented Jun 19, 2026

Copy link
Copy Markdown
Member

Pull Request

Related issue(s): #680

Type of change

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Formatting, missing semicolons, etc (no code change)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding or updating tests
  • ci: CI configuration changes
  • chore: Maintenance tasks
  • spike: Investigation / research

Summary

Adds the lane-detection deployment path for the Pi/Hailo pipeline, including the headless runtime, compiled model assets, and supporting documentation for install, compilation, and usage.

How to test / Validation

Run python3 ADAS/lane-detection/deploy_lanes_headless.py --source clip.mp4 on a Pi/Hailo setup, or use --debug to inspect masks and fitted lane lines.

Checklist

  • I have run the project tests locally and they pass
  • CI checks are green for this branch (or I will fix any failures)
  • I have added or updated tests where applicable
  • I have updated documentation (if applicable)
  • I have added/updated any migration notes (if database or protocol changes)
  • I have requested appropriate reviewers and added labels if needed
  • This PR contains no secrets or sensitive data

Risks and backward compatibility

None

Related / dependent PRs

None


Approval: Requires a minimum of 2 approvals.
Action: The feature branch MUST be deleted upon successful merge.

@Biltes Biltes self-assigned this Jun 19, 2026
@Biltes Biltes linked an issue Jun 19, 2026 that may be closed by this pull request
3 tasks
@Biltes Biltes requested review from berestv and melaniereis June 19, 2026 14:24
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

✅ STM32 CI: firmware build succeeded

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

🔍 TSF Validation Results

Check Status
trudag lint ✅ PASSED
trudag score ✅ PASSED
trudag publish ✅ PASSED

📋 Lint Output

WARNING: Unreviewed Item: HLTC-INT-PS3_DECODE_VALIDATE
WARNING: Unreviewed Item: LLTC-DC_MOTOR_UNIT
WARNING: Unreviewed Item: LLTC-SERVO_MOTOR_UNIT
WARNING: Unreviewed Item: LLTC-SPEED_SENSOR_UNIT
WARNING: Unreviewed Item: SWD-DC_MOTOR_DRIVER
WARNING: Unreviewed Item: SWD-SERVO_DRIVER
WARNING: Unreviewed Item: SWD-SPEED_CALC
WARNING: Suspect Link: SRD-ACT-MOTOR_DRIVE -> SWD-DC_MOTOR_DRIVER
WARNING: Suspect Link: SRD-ACT-SERVO_STEER -> SWD-SERVO_DRIVER
WARNING: Suspect Link: SRD-SENS-ENCODER_SPD 

📊 Traceability Graph

✅ Graph generated successfully

Download artifacts to view: TSF Validation Results

  • Traceability graph: tsf-validation-artifacts/traceability_graph.svg
  • Full TSF reports in artifact archive

🧪 Unit Test Results

Overall Status: ✅ PASSED

  • DC Motor Tests: PASSED (11/11)
  • Servo Motor Tests: PASSED (5/5)
  • Speed Sensor Tests: PASSED (16/16)
  • Total: 32 tests

📊 Coverage

Coverage report available in artifacts.

🔍 Coverage Change Validation

  • dc-motor: score=1.00 issues=[]
  • servo-motor: score=1.00 issues=[]
  • speed-sensor: score=1.00 issues=[]

Full coverage reports available in workflow artifacts


🔒 CodeQL (filtered SARIF summary)

Severity Count
High/Critical (error) 0
Warnings 0
Notes 0

🧪 Unit Test Results

Overall Status: ✅ PASSED

  • DC Motor Tests: PASSED (11/11)
  • Servo Motor Tests: PASSED (5/5)
  • Speed Sensor Tests: PASSED (16/16)
  • Total: 32 tests

📊 Coverage

Coverage report available in artifacts.

🔍 Coverage Change Validation

  • dc-motor: score=1.00 issues=[]
  • servo-motor: score=1.00 issues=[]
  • speed-sensor: score=1.00 issues=[]

Full coverage reports available in workflow artifacts

Run: https://github.com/SEAME-pt/Team04_DrivaPi/actions/runs/27964898361

@melaniereis melaniereis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this is a strong contribution. The runtime is well structured, and the README explains the deployment flow clearly.

I like that the pipeline separates Hailo inference from CPU post-processing, and that the main output is exposed as lane_lines, which is the right abstraction for a future steering controller.

I would request a few changes before merging, mainly to make the runtime safer and the documentation more aligned with the code.

Main code points:

  • preprocess() modifies the original frame in-place, which can affect debug or recorded output.
  • decode() should validate all expected Hailo output groups before indexing them.
  • In compute_steering(), I have one question: is it intentional that np.interp() still returns a value when the line does not reach the lookahead row? If yes, the docstring should mention it. If not, we should skip those lines.
  • --record should probably require --debug, otherwise it silently does nothing.
  • Camera subprocess cleanup could be more robust by waiting for the process to terminate.

For the README:

  • “smoothing” should probably be described as temporal persistence;
  • the code can fit one or more line segments per lane class, not always one line per lane;
  • debug overlays are only useful when recorded, since the script does not display them;
  • steering should be described as an experimental starter signal, not final control logic;
  • crosswalk is detected by the model but excluded from lane_lines.

The perception pipeline is solid and close to merge. I would just adjust these points first to avoid confusion and keep a clear separation between lane geometry extraction and future vehicle control.

Comment thread ADAS/lane-detection/deploy_lanes_headless.py Outdated

def decode(outputs):
cv2_list, cv3_list, cv4_list, proto = sort_outputs(outputs)
if proto is None or len(cv2_list) != 3:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should validate all the expected output groups before indexing them.

Right now we only check proto and cv2_list, but later the code also accesses cv3_list[i] and cv4_list[i] inside the loop. If one of those lists is incomplete, the runtime could fail with an IndexError.

Maybe this check should include all three output groups:

if (
    proto is None
    or len(cv2_list) != 3
    or len(cv3_list) != 3
    or len(cv4_list) != 3
):
    return [], proto

This would make the decoder more robust if the HEF output structure changes or if an inference result is incomplete.

for tensor in outputs.values():
t = np.squeeze(tensor).transpose(2, 0, 1)
c, h, w = t.shape
if h == MODEL_SIZE // 4 and c == 32:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This output sorting works, but it depends heavily on tensor shapes.

That is practical for this deployment, but it could be fragile if the model or HEF output structure changes later. In that case, a tensor could be classified into the wrong group without an obvious error.

Could we add optional debug logging of the output names and shapes, at least on the first inference when --debug is enabled?

For example:

if debug:
    for name, tensor in raw.items():
        print(name, tensor.shape)

It does not need to print every frame, but having this available would make future model updates easier to debug.

order = np.argsort(ys)
# interpolate x at the lookahead row; if the line doesn't reach it,
# np.interp clamps to the nearest endpoint (so we still get a value)
xs.append(float(np.interp(y_look, ys[order], pts[order, 0])))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question/doubt: is it intentional that we still compute a steering point when the line does not actually reach the lookahead row?

The docstring says this function returns None if no line reaches the lookahead row. However, np.interp() clamps to the nearest endpoint when y_look is outside the detected line range. So we may still get a value from a line that does not really cover the lookahead area.

If the intended behavior is to only use lines that reach the lookahead row, maybe we should skip lines outside that range:

if y_look < ys.min() or y_look > ys.max():
    continue

xs.append(float(np.interp(y_look, ys[order], pts[order, 0])))

If the clamping behavior is intentional, then I think the docstring should be updated to make that explicit.

class_masks = build_class_masks(detections, proto)
class_masks = memory.update(class_masks) # temporal
lane_lines = extract_lane_lines(class_masks, w, h) # geometry
steering = compute_steering(lane_lines, w, h)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, compute_steering() averages all detected lane line positions together. That is useful to expose a basic lateral error, but the actual control strategy still needs to decide which lane classes define the drivable corridor, how to behave when only one lane line is visible, how to handle stale temporal detections, and how the final controller will consume this signal. So I would keep this as experimental/helper output for now and make sure the README also describes steering as a starter signal, not as final control logic.

source .venv/bin/activate
python main.py --config training_config.yaml
python3 deploy_lanes_headless.py # driving (headless, ~30 FPS)
python3 deploy_lanes_headless.py --debug # also draw masks/lines + steering overlay

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the debug command may be slightly misleading as written.

The code creates the debug overlay, but it does not display a window with cv2.imshow(). The overlay only becomes visible/useful if we save it with --record.

Maybe the README should show:

python3 deploy_lanes_headless.py --debug --record dbg.avi
# save annotated debug video with masks, fitted lines, and steering overlay

This would be clearer for a headless Pi workflow.

python3 deploy_lanes_headless.py # driving (headless, ~30 FPS)
python3 deploy_lanes_headless.py --debug # also draw masks/lines + steering overlay
python3 deploy_lanes_headless.py --source clip.mp4 # test on a video
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the script supports --record, I think we should document it in the run examples.

Suggested addition:

python3 deploy_lanes_headless.py --debug --record dbg.avi
# save annotated debug video

This is probably the most useful way to inspect the debug output when running headless on the Pi.

Each frame the loop produces:
- `lane_lines` — `{class_name: [ (N,2) float arrays in frame pixels ]}` — the
fitted lane geometry (this is what a steering controller consumes).
- `steering` — `(target_x, err)` starter signal (`err ∈ [-1 left, +1 right]`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would describe steering more carefully here so it is not confused with final control logic.

The current runtime exposes a starter/debug signal, not a complete steering controller. Also, the code does not explicitly clamp err to [-1, +1].

Maybe this could be reworded as:

- `steering``(target_x, err)` experimental starter signal. `err` is the
  normalized lateral offset of the detected target from the image center:
  negative = target left of center, positive = target right of center.
  This is not a final steering controller.

This keeps the distinction clear between perception output and future control logic.

`LOOKAHEAD_FRAC`.

## Model facts
- YOLOv8n-seg, 5 classes: `center_continuous_lane, center_dashed_lane,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we mention that crosswalk is detected by the model but excluded from lane_lines? The model has a crosswalk class, but the runtime only extracts lane geometry from the lane classes. That distinction may be useful for future contributors.

Suggested addition:

`crosswalk` is detected as a segmentation class for scene awareness/debug, but it is excluded from the lane-line geometry consumed by the future steering controller.

compile). The training weights and dataset are kept in the team's model/data
store, not in this repo.

> **Why v8 and not v11?** The module was originally planned around YOLO11n-seg.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This explanation is useful and I think it should stay, because it explains an important model decision.

Small suggestion: the README could keep a shorter version, while the full DFC error and troubleshooting details live in compilation.md.

For example:

YOLO11n-seg was tested but did not produce a deployable Hailo-8 `.hef` with the
standard DFC flow because of the `C2PSA` attention block. YOLOv8n-seg is
pure-convolutional, compiles cleanly, and met the runtime target.

This keeps the README readable while preserving the technical details elsewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lane-Detection Training Script and Configuration

2 participants