alvises

alvises

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

Showing Posts 1 to 6

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.

alvises

alvises OP

Thanks! I’m now able to get camera frames with ffmpeg, but I can’t prepend a size header to each frame. I need the header to let the port split messages (frames) for me.

My current ffmpeg command is

ffmpeg -f avfoundation -framerate 30 -i "0" -s 640x480 -pix_fmt 0rgb

Any idea how to pipe the output to fd 4?

xlphs

xlphs

Try pipe:4 as the output file name.

As for the size, if you know width and height and format is rgb, each frame is always width*height*3 number of bytes. I think it is not difficult to add a bit of read logic into Cv.Scene.Camera to read exactly that many bytes per frame and hand off the frame to Scenic.

alvises

alvises OP

As far as I know to let Port read, split and deliver immediately the frames of width*height*3 fixed size to the process, it needs to read a size header prepended to the payload.

xlphs

xlphs

Try open_port with stream, the doc says

Output messages are sent without packet lengths. A user-defined protocol must be used between the Erlang process and the external object.

alvises

alvises OP

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)
— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews