Advent 2023 banner

Connecting the Web Camera Interface with Docker Container

Photo of Raul Muñoz

Posted on Dec 8, 2023 by Raul Muñoz

9 min read

Welcome to part eight of our AI Powered Christmas Tree Series! The goal of this series is to serve as a guide for you to build your own AI powered Machine Learning Christmas Tree, thanks to the Arduino Portenta X8 SoM and Edge Impulse AI Platform.

In this tutorial, we will be guiding you though the process of interfacing your webcam with a Docker container. We will cover two methods of connecting a camera to a running container, and how to save these configurations in a Docker-compose app.

Before jumping into this entry, be sure to check out the previous blog entries. You can find a summary of each tutorial and a sneak peek of what’s coming next on our project overview page.

It can be tricky to interface an external device with a Docker container running on an embedded Linux device. In order for your Christmas Tree to detect a human presence, you need to connect a webcam—so that it can “see”—to our Arduino Portenta X8, which will be running our ML algorithm in a container. Typically, this communication between an external device and a host operating system can be difficult, but we are going to guide you through every step, including deploying the finished application on your Factory.

Prerequisites

This is part eight of our AI Powered Christmas Tree Series. Please ensure you’ve completed the earlier parts before continuing.

You will also need:

  • a USB Camera

Connecting the USB Camera to the SoM or Embedded Device

First, you are going to connect the webcam to the Arduino Portenta X8 (or other embedded device).

Once you have done that, log in, open a terminal, and run a dmesg command to check the system messages log to see if the camera has been recognized:

device:~$ dmesg

[73651.352075] input: GENERAL WEBCAM: GENERAL WEBCAM as /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/input/input3

After the camera has been successfully recognized, a /dev/video[X] interface should appear on your device. Run the following command to identify it:

device:~$ ls -l /dev/video*

crw-rw---- 1 root video 81, 8 Oct 31 23:10 /dev/video0
crw-rw---- 1 root video 81, 9 Oct 31 23:10 /dev/video1
crw-rw---- 1 root video 81, 0 Apr 28  2022 /dev/video13
crw-rw---- 1 root video 81, 1 Apr 28  2022 /dev/video14
crw-rw---- 1 root video 81, 2 Apr 28  2022 /dev/video15
crw-rw---- 1 root video 81, 3 Apr 28  2022 /dev/video16
crw-rw---- 1 root video 81, 4 Apr 28  2022 /dev/video20
crw-rw---- 1 root video 81, 5 Apr 28  2022 /dev/video21
crw-rw---- 1 root video 81, 6 Apr 28  2022 /dev/video22
crw-rw---- 1 root video 81, 7 Apr 28  2022 /dev/video23

In this case, the camera shows up as /dev/video0.

Now that we have identified the correct node in /dev, we will be able to interact directly with the camera.

Building Your Docker Image

For testing your application, instead of using software on the host (LmP), you will install everything in a Docker Container and test it with Docker, live on the device.

Docker is included in the Linux microPlatform, but you will need to add some additional packages to the basic image in order to access the camera and take a test picture.

To do this, let’s create a Dockerfile with the instructions to create our Docker image with everything you need.

On your embedded device, create a new Dockerfile:

vim Dockerfile
FROM debian:bullseye-slim
RUN

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
    fswebcam \
    v4l-utils

Build this Dockerfile in the Docker Image:

device:~$ docker build -t camera-test .

Running the Docker Image

The next challenge is to expose the camera connected to your embedded device to the running Docker container. There are a few ways to do this; the simplest of which is to allow the container full access to the /dev directory. This is not ideal, as it may expose various interfaces to the container which you may not want.

For now, it is a quick and easy way for you to test the camera.

Run the following command to launch the container:

device:~$ docker run -it --privileged -v /var/rootdirs/home/fio/image/:/image/ camera-test

This launches the container with full root privileges (--privileged) which, among many other things, gives it access to the /dev directory. It also creates a shared volume (-v) between the host directory /var/rootdirs/home/fio/image/, and the container's /image/ directory, allowing data exchange and manipulation between the host and the container.

The container also launches into an interactive session (-it flag), so you will immediately be able to issue commands and navigate directories inside the container.

Navigate to /image and run the following command to take a picture:

device-docker:~$ cd /image/
device-docker:~$ fswebcam -r 1920x1080 --jpeg 95 -D 1 web-cam-shot.jpg

--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
Delaying 1 seconds.
--- Capturing frame...
Captured frame in 0.00 seconds.
--- Processing captured image...
Setting output format to JPEG, quality 95
Writing JPEG image to 'web-cam-shot.jpg'.

Now open a second terminal on the embedded device, navigate to the shared volume location, and use scpto copy the picture to your host machine:

device:~$ cd /var/rootdirs/home/fio/image
device:~$ scp web-cam-shot.jpg <host-user>@<host-ip>:~

This will send the picture to your host machine! The above command will place it in the home directory, but you may specify any location to save the picture.

Connecting the Webcam the Right Way with cgroup

As mentioned, privileged is not the best approach, as it gives too much power to the container. This elevated privilege level essentially removes the majority of security barriers and isolations that Docker usually applies to containers.

The right way to share the camera is to specify the camera device cgroup, sharing just the camera device.

First, find the device cgroup by running ls -l to find the camera:

device:~$ ls -l /dev/video0

crw-rw---- 1 root video 81, 8 Oct 31 23:10 /dev/video0

The 81, 8 is the camera's major and minor device number. With this we can alter the cgroup permissions pertaining to the camera.

The new command to run the container is as follows:

device:~$ docker run -it \
           --device-cgroup-rule='c 81:* rmw' \
           -v /dev/video0:/dev/video0 \
           -v /var/rootdirs/home/fio/image/:/image/ \
           camera-test

Rather than elevating the Docker container's permissions with the --privileged flag, you instead grant only the necessary permissions to interact with the camera via the --device-cgroup-rule flag.

  • c: Denotes the device type. In this case, a character device.
  • 81:*: Specifies the major device number as 81, and allows any minor device number (*). It defines which device or range of devices the rule applies to.
  • rmw:
    • r: Grants read access to the specified device(s) within the cgroup.
    • m: Allows device modification or management permissions.
    • w: Provides write access to the device(s).
  • -v /dev/video0:/dev/video0: share just the video0 device — which the container now has relevant permissions to access.
  • -v /var/rootdirs/home/fio/image/:/image/: creates the shared volume to save the pictures.

Let’s try the new version of our run command:

device:~$ docker run -it --device-cgroup-rule='c 81:* rmw' -v /dev/video0:/dev/video0 -v /var/rootdirs/home/fio/image/:/image/ camera-test

device-docker:~$ cd /image/
device-docker:~$ fswebcam -r 1920x1080 --jpeg 95 -D 1 web-cam-shot.jpg

--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
Delaying 1 seconds.
--- Capturing frame...
Captured frame in 0.00 seconds.
--- Processing captured image...
Setting output format to JPEG, quality 95
Writing JPEG image to 'web-cam-shot.jpg'.

On a second terminal, copy the picture again:

device:~$ cd /var/rootdirs/home/fio/image
device:~$ scp web-cam-shot.jpg <host-user>@<host-ip>:~

That will send the picture to your host machine.

Docker Compose Application

These configurations for running the build command can be included in docker-compose.yaml. This ensures the container will always be built in the same manner and with adequate permissions.

Below is what docker-compose.yaml needs in order to build the same container as our Docker run command.

Create the following docker-compose.yaml file in the same directory as your Dockerfile:

device:~$ vim docker-compose.yaml

version: '3.6'

services:
  camera-test:
    image: camera-test
    volumes:
      - /dev/video0:/dev/video0
      - /var/rootdirs/home/fio/image/:/image/
    device_cgroup_rules:
      - 'c 81:* rmw'

With this, you can build the same container as before, but instead of running the old long build command:

device:~$ docker run -it \
           --device-cgroup-rule='c 81:* rmw' \
           -v /dev/video0:/dev/video0 \
           -v /var/rootdirs/home/fio/image/:/image/ \
           camera-test

You can now simply run:

device:~$ docker-compose up

Recap and Conclusions

In this tutorial, we have successfully interfaced a webcam with a Docker container running on our embedded device. We covered a quick and dirty version using the --privileged flag, and the more secure cgroups method. We also discussed using docker-compose.yaml to easily recreate our container.

This was a prototype of the kind of configuration we will use to connect a webcam to a container running our ML algorithm to detect a human presence for our Christmas Tree.

Continue Building Ai-Powered Christmas Tree

If you are interested in our entire 14-part series on how to build an AI powdered Christmas tree, browse through each section below. Each part brings you closer to mastering your build:

Related posts

Keep up to date with Foundries.io