Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
97c8cb6
Write YOLO Live and Offline filters
FelonEkonom Nov 12, 2025
d9f4b10
Add demo for YOLO on computer camera
FelonEkonom Nov 20, 2025
620f546
Prepare demos WiP
FelonEkonom Nov 24, 2025
5020dd4
Improve examples
FelonEkonom Nov 24, 2025
9cacb8b
Improve readme
FelonEkonom Nov 24, 2025
ed4e72a
Fix mix install
FelonEkonom Nov 24, 2025
eec0fec
Adjust to changes in raw video
FelonEkonom Nov 25, 2025
8398c33
Fix setting log level
FelonEkonom Nov 25, 2025
e1f7f5a
Fix typo
FelonEkonom Nov 26, 2025
9c4b8fd
Implement CR suggestions WiP
FelonEkonom Dec 2, 2025
07de53c
Implement CR suggestions WiP
FelonEkonom Dec 2, 2025
4826aa3
Improve demos WiP
FelonEkonom Dec 2, 2025
d4a5c82
Update raw video dep in examples
FelonEkonom Dec 2, 2025
5399eb6
Rewrite almost everything WiP
FelonEkonom Dec 3, 2025
ea18ce4
Update Mix.exs
FelonEkonom Dec 3, 2025
741e5dd
Rewrite plugin
FelonEkonom Dec 3, 2025
5e03306
Fix livebook examples
FelonEkonom Dec 3, 2025
db48b51
Merge remote-tracking branch 'origin/master' into implementation
FelonEkonom Dec 3, 2025
2dae611
Update docs
FelonEkonom Dec 3, 2025
fcc6dc7
Improve detector docs
FelonEkonom Dec 3, 2025
0ca52ee
Add lacking handle end of stream
FelonEkonom Dec 3, 2025
a6f72d0
Bump version to clear cache in CircleCI
FelonEkonom Dec 4, 2025
253763e
Maybe fix Circle
FelonEkonom Dec 4, 2025
bbc681a
CI doesn't work
FelonEkonom Dec 4, 2025
d1854fe
Mix credo
FelonEkonom Dec 4, 2025
8aa58ec
Fix dialyzer and warnings
FelonEkonom Dec 4, 2025
6ba0a06
Fix docs warn
FelonEkonom Dec 4, 2025
9ecb3e9
Unlock unused dep
FelonEkonom Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ orbs:
elixir: membraneframework/elixir@1

workflows:
version: 2
version: 3
build:
jobs:
- elixir/build_test:
Expand Down
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
inputs: [
"{lib,test,config}/**/*.{ex,exs}",
"{lib,test,config,examples}/**/*.{ex,exs}",
".formatter.exs",
"*.exs"
],
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
examples/models/*.onnx
examples/outputs/*

compile_commands.json
.gdb_history
bundlex.sh
Expand Down
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
# Membrane Template Plugin
# Membrane YOLO Plugin

[![Hex.pm](https://img.shields.io/hexpm/v/membrane_template_plugin.svg)](https://hex.pm/packages/membrane_template_plugin)
[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_template_plugin)
[![CircleCI](https://circleci.com/gh/membraneframework/membrane_template_plugin.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_template_plugin)
[![Hex.pm](https://img.shields.io/hexpm/v/membrane_yolo_plugin.svg)](https://hex.pm/packages/membrane_yolo_plugin)
[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_yolo_plugin)
[![CircleCI](https://circleci.com/gh/membraneframework/membrane_yolo_plugin.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_yolo§ §_plugin)

This repository contains a template for new plugins.
Contains 2 Membrane Filters:
- `Membrane.YOLO.Detector` - for running object detection on a video stream.
- `Membrane.YOLO.Drawer` - for drawing object detection results generated by `Membrane.YOLO.Detector`.

Check out different branches for other flavors of this template.
Uses under the hood [yolo_elixir](https://github.com/poeticoding/yolo_elixir).

It's a part of the [Membrane Framework](https://membrane.stream).

## Installation

The package can be installed by adding `membrane_template_plugin` to your list of dependencies in `mix.exs`:
The package can be installed by adding `membrane_yolo_plugin` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:membrane_template_plugin, "~> 0.1.0"}
{:membrane_yolo_plugin, "~> 0.1.0"}
]
end
```

## Usage
## Examples

TODO
Open a Livebook `examples/yolo.livemd` or run `$ elixir examples/live_camera_capture.exs`, `$ elixir examples/live_mp4_processing.exs` and `$ elixir examples/offline_mp4_processing.exs`

## Copyright and License

Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_template_plugin)
Copyright 2025, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_yolo_plugin)

[![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_template_plugin)
[![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_yolo_plugin)

Licensed under the [Apache License, Version 2.0](LICENSE)
73 changes: 73 additions & 0 deletions examples/live_camera_capture.exs
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should only keep examples in the livebook. It's basically duplicated in this and other .exs scripts, not great for maintenance

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The advantage of .exs examples is that the video player is visible right away, at least on my computer

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
hardware_acceleration =
case :os.type() do
{:unix, :darwin} -> :coreml
{:unix, :linux} -> :cuda
end

Mix.install(
[
{:membrane_yolo_plugin, path: Path.join(__DIR__, "..")},
{:membrane_core, "~> 1.0"},
{:membrane_camera_capture_plugin, "~> 0.7.4"},
{:membrane_ffmpeg_swscale_plugin, "~> 0.16.3"},
{:boombox, "~> 0.2.8"},
{:exla, "~> 0.10"}
],
config: [
ortex: [
{Ortex.Native, [features: [hardware_acceleration]]}
],
nx: [
default_backend: EXLA.Backend
]
]
)

Logger.configure(level: :info)

model_name = "yolox_l.onnx"
model_path = Path.join("examples/models", model_name)

if not File.exists?(model_path) do
model_url =
"https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/#{model_name}"

%{body: data} = Req.get!(model_url)
File.write!(model_path, data)
end

defmodule YOLO.CameraCapture.Pipeline do
use Membrane.Pipeline

@impl true
def handle_init(_ctx, _opts) do
spec =
child(:camera_capture, Membrane.CameraCapture)
|> child(:swscale_converter, %Membrane.FFmpeg.SWScale.Converter{
format: :RGB,
output_width: 640
})
|> child(:yolo_detector, %Membrane.YOLO.Detector{
mode: :live_low_latency,
yolo_model:
YOLO.load(
model_impl: YOLO.Models.YOLOX,
model_path: "examples/models/yolox_l.onnx",
classes_path: "examples/models/coco_classes.json",
eps: [unquote(hardware_acceleration)]
)
})
|> child(:yolo_drawer, Membrane.YOLO.Drawer)
|> via_in(:input, options: [kind: :video])
|> child(:boombox_sink, %Boombox.Bin{output: :player})

{[spec: spec], %{}}
end
end

{:ok, _supervisor, pipeline} = Membrane.Pipeline.start_link(YOLO.CameraCapture.Pipeline, [])
Process.monitor(pipeline)

receive do
{:DOWN, _ref, :process, _pid, _reason} -> :ok
end
82 changes: 82 additions & 0 deletions examples/live_mp4_processing.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
hardware_acceleration =
case :os.type() do
{:unix, :darwin} -> :coreml
{:unix, :linux} -> :cuda
end

Mix.install(
[
{:membrane_yolo_plugin, path: Path.join(__DIR__, "..")},
{:membrane_core, "~> 1.0"},
{:membrane_camera_capture_plugin, "~> 0.7.4"},
{:membrane_ffmpeg_swscale_plugin, "~> 0.16.3"},
{:boombox, "~> 0.2.8"},
{:exla, "~> 0.10"}
],
config: [
ortex: [
{Ortex.Native, [features: [hardware_acceleration]]}
],
nx: [
default_backend: EXLA.Backend
]
]
)

Logger.configure(level: :info)

model_name = "yolox_l.onnx"
model_path = Path.join("examples/models", model_name)

if not File.exists?(model_path) do
model_url =
"https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/#{model_name}"

%{body: data} = Req.get!(model_url)
File.write!(model_path, data)
end

defmodule YOLO.MP4.LivePipeline do
use Membrane.Pipeline

@impl true
def handle_init(_ctx, _opts) do
spec =
child(:mp4_source, %Boombox.Bin{input: "examples/fixtures/street.mp4"})
|> via_out(:output, options: [kind: :video])
|> child(:transcoder, %Membrane.Transcoder{output_stream_format: Membrane.RawVideo})
|> child(:realtimer, Membrane.Realtimer)
|> child(:swscale_converter, %Membrane.FFmpeg.SWScale.Converter{
format: :RGB,
output_width: 640
})
|> child(:yolo_detector, %Membrane.YOLO.Detector{
mode: :live,
yolo_model:
YOLO.load(
model_impl: YOLO.Models.YOLOX,
model_path: "examples/models/yolox_l.onnx",
classes_path: "examples/models/coco_classes.json",
eps: [unquote(hardware_acceleration)]
),
additional_latency: Membrane.Time.milliseconds(500)
})
|> child(:yolo_drawer, Membrane.YOLO.Drawer)
|> via_in(:input, options: [kind: :video])
|> child(:boombox_sink, %Boombox.Bin{output: :player})

{[spec: spec], %{}}
end

@impl true
def handle_child_notification(:processing_finished, :boombox_sink, _ctx, state) do
{[terminate: :normal], state}
end
end

{:ok, supervisor, _pipeline} = Membrane.Pipeline.start_link(YOLO.MP4.LivePipeline, [])
Process.monitor(supervisor)

receive do
{:DOWN, _ref, :process, _pid, _reason} -> :ok
end
97 changes: 97 additions & 0 deletions examples/offline_mp4_processing.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
hardware_acceleration =
case :os.type() do
{:unix, :darwin} -> :coreml
{:unix, :linux} -> :cuda
end

Mix.install(
[
{:membrane_yolo_plugin, path: Path.join(__DIR__, "..")},
{:membrane_core, "~> 1.0"},
{:membrane_camera_capture_plugin, "~> 0.7.4"},
{:membrane_ffmpeg_swscale_plugin, "~> 0.16.3"},
{:boombox, "~> 0.2.8"},
{:exla, "~> 0.10"}
],
config: [
ortex: [
{Ortex.Native, [features: [hardware_acceleration]]}
],
nx: [
default_backend: EXLA.Backend
]
]
)

Logger.configure(level: :info)

model_name = "yolox_l.onnx"
model_path = Path.join("examples/models", model_name)

if not File.exists?(model_path) do
model_url =
"https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/#{model_name}"

%{body: data} = Req.get!(model_url)
File.write!(model_path, data)
end

result_file_path = "examples/outputs/street_bounding_boxes.mp4"

defmodule YOLO.MP4.OfflinePipeline do
use Membrane.Pipeline
require Membrane.Logger

@impl true
def handle_init(_ctx, _opts) do
spec =
child(:mp4_source, %Boombox.Bin{input: "examples/fixtures/street_short.mp4"})
|> via_out(:output, options: [kind: :video])
|> child(:transcoder, %Membrane.Transcoder{output_stream_format: Membrane.RawVideo})
|> child(:rgb_converter, %Membrane.FFmpeg.SWScale.Converter{
format: :RGB,
output_width: 640
})
|> child(:yolo_detector, %Membrane.YOLO.Detector{
mode: :offline,
yolo_model:
YOLO.load(
model_impl: YOLO.Models.YOLOX,
model_path: "examples/models/yolox_l.onnx",
classes_path: "examples/models/coco_classes.json",
eps: [unquote(hardware_acceleration)]
)
})
|> child(:yolo_drawer, Membrane.YOLO.Drawer)
|> child(:debug_logger, %Membrane.Debug.Filter{
handle_buffer: fn buffer ->
pts_ms = Membrane.Time.as_milliseconds(buffer.pts, :round)

Membrane.Logger.info("""
Processed #{inspect(pts_ms)} ms of 10_000 ms of fixture video
""")
end
})
|> child(:i420_converter, %Membrane.FFmpeg.SWScale.Converter{
format: :I420
})
|> via_in(:input, options: [kind: :video])
|> child(:boombox_sink, %Boombox.Bin{output: unquote(result_file_path)})

{[spec: spec], %{}}
end

@impl true
def handle_child_notification(:processing_finished, :boombox_sink, _ctx, state) do
{[terminate: :normal], state}
end
end

{:ok, supervisor, _pipeline} = Membrane.Pipeline.start_link(YOLO.MP4.OfflinePipeline, [])
Process.monitor(supervisor)

receive do
{:DOWN, _ref, :process, _pid, :normal} -> :ok
end

Boombox.play(result_file_path)
Loading