Cars In Bike Lane

I cobbled together a way to track how many cars are driving in the bikelane outside of my place using Frigate, a bit of command line / ffmpeg, and Apex charts cards.

Untitledvideo-ezgif.com-resize (3)

event_preview

Edit: Happy to share code if there is interest.
Edit 2: There is interest. Progressively adding detail.

I used Frigate add-on and integration. Here is my Frigate config;

Frigate Config:
environment_vars:
  LIBVA_DRIVER_NAME: i965 # Intel(R) Core(TM) i7-3610QM CPU @ 2.30GHz. It includes an Intel HD Graphics 4000 iGPU, which belongs to Intel Gen7 graphics.

ffmpeg:
  global_args: -hide_banner -loglevel debug -threads 4
  hwaccel_args: -hwaccel vaapi -hwaccel_device /dev/dri/renderD128
  input_args: preset-rtsp-generic
  output_args:
    detect: -threads 4 -f rawvideo -pix_fmt yuv420p
    record: preset-record-generic
  retry_interval: 5
# ffmpeg:
#   hwaccel_args: preset-vaapi # Correct for legacy Intel GPUs (Gen1–Gen7)
detectors:
  coral:
    type: edgetpu
    device: usb

logger:
  default: debug

mqtt:
  enabled: true
  host: homeassistant
  port: xxxx
  topic_prefix: frigate
  client_id: frigate
  user: XXXXX
  password: XXXXX

timestamp_style:
  position: tl   # "tl" (top left), "tr" (top right), "bl" (bottom left), "br" (bottom right)
  format: '%d/%m/%Y %H:%M:%S'
  color:
    red: 255
    green: 255
    blue: 255
  thickness: 2
  effect: solid # solid , shadow

detect:
  enabled: false
motion:
  enabled: false

record:
  enabled: true
  retain:
    days: 1
    mode: all
  alerts:
    pre_capture: 1 # secs
    post_capture: 1 # secs
    retain:
      days: 7
      mode: active_objects
  export:
    timelapse_args: -vf setpts=PTS/1440 -r 20 -vcodec libx264 -crf 28 -preset slower   # PTS/10 = 10x speed. 1440 = 24 hours of video into 1 minute

snapshots:
  enabled: true
  clean_copy: false
  timestamp: true
  bounding_box: true
  crop: false
  quality: 100  # JPEG quality  
  required_zones: [CarInBikeLane, Pigeon_Watch]
  retain:
    default: 7  # Retain snapshots for x days


go2rtc:
  log:
    exec: trace
  streams:
    bikelane: 
      ffmpeg:rtsp:xxxxxxxx#video=h264#raw=-vf crop=565:749:230:260 -r 4 -c:v libx264 -preset ultrafast -crf 32 -an

cameras:
  bikelane:
    enabled: true
    ffmpeg:
      inputs:
        - path: rtsp://xxxxx/bikelane
          input_args: preset-rtsp-restream
          roles:
            - detect
            - record
    motion:
      enabled: true
      mask: 
        0,0,0,1,0.586,1,0.581,0.934,0.554,0.851,0.362,0.864,0.214,0.542,0.136,0.353,0.45,0.354,0.589,0.603,0.696,0.758,0.534,0.767,0.59,0.924,0.595,1,0.8,0.997,1,1,1,0
      threshold: 32
      contour_area: 5
      improve_contrast: false
    zones:
      CarInBikeLane:
        coordinates: 0.463,0.678,0.645,0.673,0.548,0.528,0.391,0.528
        inertia: 1
        loitering_time: 0
        objects: car
      CarInCarLane:
        coordinates: 0.212,0.575,0.264,0.693,0.46,0.689,0.405,0.576
        loitering_time: 0
        objects: car
        inertia: 1
    detect:
      enabled: true
      min_initialized: 1   # Num of detection hits for an object to be initialized in tracker.
      max_disappeared: 4   # Num of frames with no detections before oobject considered gone.
    review:
      alerts:
        labels:
          - car
        required_zones: CarInBikeLane
      detections:
        required_zones: CarInBikeLane
    objects:
      track:
        - car   # Car detection
        - bicycle
      filters:
        car:
          min_area: 3600
          max_area: 50000
          min_ratio: 0.3
          max_ratio: 1.5
          min_score: 0.5
          threshold: 0.5
          mask: 
            0,0,0,1,1,1,1,0,0,0,0.16,0.36,0.452,0.357,0.683,0.739,0.773,0.823,0.333,0.817,0.147,0.309
    record:
      enabled: true
      retain:
        days: 1
      detections:
        pre_capture: 3
        post_capture: 3
        retain:
          days: 7
          mode: active_objects

Watch the Frigate /media/frigate/clips folder for new files and parse the latest eventID into a sensor and build public API endpoints.

Example folder watch sensor output.

event.folder_watcher_media_frigate_clips
Folder Watcher /media/frigate/clips
2025-05-18T10:33:25.390+00:00	event_types: closed, created, deleted, modified, moved
event_type: closed
path: /media/frigate/clips/bikelane-1747564401.500457-kajc9f.jpg
file: bikelane-1747564401.500457-kajc9f.jpg
folder: /media/frigate/clips
friendly_name: Folder Watcher /media/frigate/clips

That sensor is input into this one which builds paths to Frigate api endpoints

  - sensor:
      - name: "Last car in bikelane eventid" 
        unique_id: last_car_in_bikelane_eventid
        state: "{{ states.event.folder_watcher_media_frigate_clips.attributes.path.split('bikelane-')[1].split('.jpg')[0] }}"
        attributes:
          full_path_to_clip_image: "{{ states.event.folder_watcher_media_frigate_clips.attributes.path }}"
          file_name: "{{ states.event.folder_watcher_media_frigate_clips.attributes.file }}"
          api_path_to_event_preview: https://xxx.ui.nabu.casa/api/frigate/notifications/{{ this.state }}/event_preview.gif
          api_path_to_event_snapshot: https://xxx.ui.nabu.casa/api/frigate/notifications/{{ this.state }}/snapshot.jpg
          api_path_to_event_clip: https://xxx.ui.nabu.casa/api/frigate/notifications/{{ this.state }}/clip.mp4

Example sensor outputs (attributes).

full_path_to_clip_image: /media/frigate/clips/bikelane-1747564401.500457-kajc9f.jpg
file_name: bikelane-1747564401.500457-kajc9f.jpg
api_path_to_event_preview: >-
  https://xxc.ui.nabu.casa/api/frigate/notifications/1747564401.500457-kajc9f/event_preview.gif
api_path_to_event_snapshot: >-
  https://xxx.ui.nabu.casa/api/frigate/notifications/1747564401.500457-kajc9f/snapshot.jpg
api_path_to_event_clip: >-
  https://xxx.ui.nabu.casa/api/frigate/notifications/1747564401.500457-kajc9f/clip.mp4
friendly_name: Last car in bikelane eventid

Notification API

Write some shell commands to move / create files in www

shell_command:
  make_bikelane_gif: 'ffmpeg -framerate 4 -pattern_type glob -i "/media/frigate/clips/bikelane*.jpg" -c:v libx264 -crf 30 -r 10 -y /config/www/timelapses/bikelane/bikelane_bboxs.mp4'
  copy_latest_bikelane: cp "{{ file_path }}" /config/www/timelapses/bikelane/bikelane_bbox_latest.jpg

Build a dashboard:
type: sections
max_columns: 4
title: Bike Lane
path: bike-lane
sections:
  - type: grid
    cards:
      - type: custom:apexcharts-card
        apex_config:
          chart:
            height: 250px
            tickAmount: 5
            forceNiceScale: true
        graph_span: 24h
        header:
          show: true
          title: Cars in Bike Lane by Hour | Last 24hrs
        yaxis:
          - apex_config:
              tickAmount: 6
        series:
          - entity: sensor.carinbikelane_car_count
            name: Cars in Bike Lane
            type: column
            color: "#b84b4b"
            group_by:
              duration: 60min
              func: sum
      - type: markdown
        content: Latest
        text_only: true
      - type: custom:config-template-card
        entities: sensor.last_car_in_bikelane_eventid
        card:
          type: picture
          image: >-
            ${states['sensor.last_car_in_bikelane_eventid'].attributes['api_path_to_event_preview']}
          tap_action:
            action: url
            url_path: >-
              ${states['sensor.last_car_in_bikelane_eventid'].attributes['api_path_to_event_clip']}
      - type: horizontal-stack
        cards: []
  - type: grid
    cards:
      - type: custom:apexcharts-card
        apex_config:
          chart:
            height: 250px
            zoom:
              enabled: false
          xaxis:
            labels:
              format: ddd dd
        graph_span: 7d
        header:
          show: true
          title: Cars in Bike Lane by Day | Last 7 Days
        yaxis:
          - apex_config:
              tickAmount: 4
              forceNiceScale: true
        series:
          - entity: sensor.carinbikelane_car_count
            name: Cars in Bike Lane
            type: column
            color: "#b84b4b"
            show:
              datalabels: true
            group_by:
              duration: 24h
              func: sum
      - type: markdown
        content: Latest
        text_only: true
      - type: custom:config-template-card
        entities: sensor.last_car_in_bikelane_eventid
        card:
          type: picture
          image: >-
            ${states['sensor.last_car_in_bikelane_eventid'].attributes['api_path_to_event_snapshot']}
          tap_action:
            action: url
            url_path: >-
              ${states['sensor.last_car_in_bikelane_eventid'].attributes['api_path_to_event_clip']}
  - type: grid
    cards:
      - type: custom:apexcharts-card
        apex_config:
          chart:
            stacked: true
            stackType: 100%
            height: 250px
          xaxis:
            labels:
              format: ddd dd
          show:
            data_labels: true
        graph_span: 7d
        header:
          show: true
          title: Non-compliance by Day | Last 7days
        series:
          - entity: sensor.bikelane_car_count
            name: Bike Lane Count
            type: column
            color: "#6b6767"
            group_by:
              duration: 24h
              func: sum
          - entity: sensor.carinbikelane_car_count
            name: Car In Bike Lane
            type: column
            color: "#b84b4b"
            show:
              datalabels: true
            group_by:
              duration: 24h
              func: sum
      - type: markdown
        content: Last 7 Days
        text_only: true
      - type: horizontal-stack
        cards:
          - type: iframe
            url: /local/timelapses/bikelane/bikelane_bboxs.html
            aspect_ratio: 130%
  - type: grid
    cards:
      - type: custom:apexcharts-card
        apex_config:
          chart:
            stacked: true
            stackType: 100%
            height: 250px
          xaxis:
            labels:
              format: ddd dd
          show:
            data_labels: true
        graph_span: 12w
        header:
          show: true
          title: Non-compliance by week | Last 12w
        series:
          - entity: sensor.bikelane_car_count
            name: Bike Lane Count
            type: column
            color: "#6b6767"
            group_by:
              duration: 1w
              func: sum
          - entity: sensor.carinbikelane_car_count
            name: Car In Bike Lane
            type: column
            color: "#b84b4b"
            show:
              datalabels: true
            group_by:
              duration: 1w
              func: sum
      - type: markdown
        content: Latest (All Cars)
        text_only: true
      - show_state: true
        show_name: true
        camera_view: auto
        fit_mode: cover
        type: picture-entity
        entity: image.bikelane_car
        name: Latest Car (all cars)
      - type: logbook
        target:
          entity_id:
            - binary_sensor.carinbikelane_car_occupancy
        hours_to_show: 1
icon: mdi:bike
cards: []

4 Likes

What do you intend to do with the information?

Not sure yet.
Maybe report it. That’s encouraged where I am.

https://www.crimestoppersvic.com.au/current-focus/road-safety-see-it-share-it/

Up the road there is hard separation of the bike path so cars can’t drive in it. I might advocate for that treatment.

1 Like

I’ve heard that a message written in soap on the windscreen is very effective. It reappears every time it rains, apparently. Hard with moving cars, though. :grin:

2 Likes

This is more a showoff then sharing a project.
TBC: I have no problem at all that you are showing what you have created.

I’m more than happy to share the code if there is interest.

But yes, I was pretty pleased with the outputs and wanted to show them off;)

2 Likes

Please do .

I’ve now dumped like 90% of it in OP.

Will need to progressively narrate/refine it and can field queries about details on request.