alvises

alvises

Raw camera frames on Scenic with Ports and OpenCV

In my journey of playing with Ports, OpenCV python wrapper and real-time object detection, I’ve finally ended up playing (for the first time) with Scenic… what a joy (thanks @boydm!) :blush:!!

What I’m trying to do is to render raw camera frames on a window with Scenic (then I’ll render object detection labels and bounding boxes). At the moment I’m using a Python script with OpenCV, which reads the frames from the camera, coverts the numpy array to a binary with 3 uint8 bytes (rgb) per pixel and pushes the frames to Elixir via port.

The example below works well, but since I’ve just started with Scenic, I’m just wondering if there is a better/easier pattern, maybe a ready to use library that reads camera frames !?!?

What impressed me is that the result with Scenic + Port is far smoother than rendering the frames using OpenCV with cv2.imshow("Frame", arr) on Python! For what I see there is almost no perceptible delay.
I’ll try to post a quick video showing the comparison.

#camera.py

import os, sys
from struct import unpack, pack
import cv2
import time

def setup_io():
  return os.fdopen(3,"rb"), os.fdopen(4,"wb")

def write_frame(output, message):
  header = pack("!I", len(message))
  output.write(header)
  output.write(message)
  output.flush()

def open_camera(source=0):
  cap = cv2.VideoCapture(source)
  cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
  cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
  return cap

def get_and_write_frame(cap, output_f):
  start = time.time()
  _, arr = cap.read()
  stop = time.time()
  
  arr = cv2.cvtColor(arr, cv2.COLOR_BGR2RGB)
  data = arr.tobytes()
  write_frame(output_f, data)


def run():
  input_f, output_f = setup_io()
  cap = open_camera(0)

  while True:
    get_and_write_frame(cap, output_f)

run()

Camera Scene on the Elixir side

defmodule Cv.Scene.Camera do

  use Scenic.Scene
  require Logger

  alias Scenic.Graph
  alias Scenic.ViewPort

  import Scenic.Primitives

  def init(_, opts) do
    {:ok, %ViewPort.Status{size: {width, height}}} = ViewPort.info(opts[:viewport])
    scenic_ver = Application.spec(:scenic, :vsn) |> to_string()
    glfw_ver = Application.spec(:scenic, :vsn) |> to_string()


    graph =
      Graph.build(font: :roboto)
      |> rect({1280, 720}, id: :white, fill: {:dynamic, "camera_frame"})

    _port = Port.open({:spawn, "python camera.py"}, [:binary, {:packet, 4}, :nouse_stdio])

    {:ok, graph, push: graph}
  end

  def handle_info({_port, {:data, raw_frame}}, graph) do

    Elixir.Scenic.Cache.Dynamic.Texture.put("camera_frame", {:rgb, 1280, 720, raw_frame, []})
   
   {:noreply, graph}
  end
end

On the scenic side there is no need of decoding, handle_info receives the raw frame bytes that can be directly put in the camera_frame dynamic texture (I still have to better understand how textures work in Scenic…)

At the beginning I was concerned about performance, thinking that port could be a bottleneck, but hopefully I was wrong! A 720p (1280x720) RGB raw frame is ~3mb and I measured that a round-trip-time of this message via ports is ~0.6ms - the maximum throughput I could get with a port is ~1.7Gbyte/s on a macbook pro.

Some snapshots of the observers!

640x480 frames

1280x720 frames

Most Liked

alvises

alvises

thank you @xlphs for the help with ffmpeg, I’m still having some issues but I’ll try again to make it work :blush:
At the moment, I’m going ahead with python/opencv + ports, especially because my next action is to run Yolo on frames, so reading camera frames directly in python + opencv can be pretty handy.

About performance, Scenic beats OpenCV hands down on rendering the frames, which is surprising considering that Scenic receives raw frames from python via ports, and there is almost no lag (a 3mb message is sent and received via port in less than 1ms).

Short comparison (video of myself :sweat_smile:saying :waving_hand:and drinking :hot_beverage:) trying to show the difference between the two

The Python version is pretty simple, while the Scenic version is the one in the first post.

import cv2

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

while True:
	_, arr = cap.read()
	cv2.imshow("OpenCV imshow 1280x720", arr)
	cv2.waitKey(1)
xlphs

xlphs

FFmpeg can read from camera device (/dev/video0) but it probably gives you yuv, you can tell it to convert to rgb with pixel format conversion. Combined with pipe I think you can use a single ffmpeg command to push the bytes to elixir.

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement