Using Canon EOS 70D DSLR as a USB web cam in Xubuntu Linux 19.04

For a while now, I’ve been interested in using my Canon EOS 70D DSLR as a USB camera so that I can record (and potentially live stream) better quality tutorial videos. The quality of video from the 70D is super and it has a USB connector, so I thought this should be possible. However, although I think Canon provides some kind of software utility to support this kind of thing in Windows and Mac OS, it wasn’t immediately obvious how to do it in Linux. Anyway, I spent a while digging around to figure it, but the solution which finally worked for me (shown below) is basically straight from this post.

One drawback to using the 70D this way is that it doesn’t seem to be possible to power it via the USB connector, so you either need to periodically recharge the battery or buy some kind of AC adapter that plugs into the battery compartment (I think devices like this are available, but I haven’t tried any of them).

The solution is basically:

  • Connect the Canon 70D to the laptop via USB cable.
  • Use gphoto2 to initiate video capture on the camera and receive the data in real time from USB and pipe it onwards via stdout.
  • The output of gphoto2 is piped into gstreamer which does some data format conversion
  • The output of gstreamer is sent to a V4L2 loopback device (e.g. /dev/video2), so that other applications can access it the same way as a normal USB cam.
  • I’ve been using OBS to capture videos from the 70D (including audio), which seems to work extremely well, but in principle any V4L2 compatible software should now be able to access the camera.

First, I had to install a couple of packages:

sudo apt-get install gphoto2 v4l2loopback-utils

Then, the v4l2loopback kernel module needs to be inserted, since that will be used to make the video stream appear as a camera device (e.g. /dev/video2).

sudo modprobe v4l2loopback

Edit (25-7-2019): For some reason, to make the loopback video capture device visible to the Chrome browser, I need to insert the v4l2loopback module with the following additional option specified:

modprobe v4l2loopback exclusive_caps=1

To begin streaming video data:

gphoto2 --stdout --capture-movie | gst-launch-1.0 fdsrc ! decodebin3 name=dec ! queue ! videoconvert ! v4l2sink device=/dev/video2

Incidentally, the following command can be used to capture a still image to a file on the laptop without storing it on the camera’s memory card):

gphoto2 --capture-image-and-download --no-keep
Advertisements
Posted in Uncategorized | Tagged , , , , , , , , , , , , , | Leave a comment

TU Dubuntu – a customised live USB version of Xubuntu Linux 19.04 for TU Dublin

This post documents the steps I followed to create a customised version of the Xubuntu 19.04 live CD. I called the modified image “TU Dubuntu” (after my university, TU Dublin), but it’s really just Xubuntu with some additional software packages installed that aren’t included in the default image. The steps I followed are based on the “LiveCDCustomization” article on the Ubuntu Community Wiki.

https://help.ubuntu.com/community/LiveCDCustomization

That article isn’t specific to this version of Xubuntu and it includes a lot of optional customisations that I didn’t use, so I wrote this post to capture the specific sequence of steps that produced my working image. Also, I added a step at the end to turn the iso file into a “hybrid” image. That step wasn’t included in the article on the Ubuntu Community Image, but my image wouldn’t boot without it.

Preparing to Create the Custom Image

First, we install some required packages:

sudo apt install squashfs-tools genisoimage syslinux-utils

First, create a directory and download the original Xubuntu iso image into it:

cd ~
mkdir livecdtmp
cd livecdtmp
wget http://cdimages.ubuntu.com/xubuntu/releases/19.04/release/xubuntu-19.04-desktop-amd64.iso

Create a sub-directory “mnt” and mount the downloaded iso image as a loopback filesystem.

mkdir mnt
sudo mount -o loop xubuntu-19.04-desktop-amd64.iso mnt

Extract the contents of the installation CD from the downloaded iso image:

mkdir extract-cd
sudo rsync --exclude=/casper/filesystem.squashfs -a mnt/ extract-cd

Extract the SquashFS filesystem into the current directory, then rename the root folder as “edit”:

sudo unsquashfs mnt/casper/filesystem.squashfs
sudo mv squashfs-root edit

The “edit” folder will be the root directory of the chroot environment. Before chrooting into it, we copy some network configuration files into it and clone a couple of special directories as sub-directories of “edit”.

sudo cp /etc/resolv.conf edit/etc/
sudo mount -o bind /run/ edit/run
sudo cp /etc/hosts edit/etc/
sudo mount --bind /dev/ edit/dev

Now, we chroot into the edit folder, mount some special filesystems, and set a couple of environment variables.

sudo chroot edit
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devpts none /dev/pts
export HOME=/root
export LC_ALL=C

I actually don’t really understand what the following commands do, but I ran than because they were in the original instructions and everything worked out ok!

dbus-uuidgen > /var/lib/dbus/machine-id
dpkg-divert --local --rename --add /sbin/initctl
ln -s /bin/true /sbin/initctl

Customising the Image

The following command runs the timezone configuration. I selected “Europe” and “Dublin”.

dpkg-reconfigure tzdata

Now install whatever packages are to be added using apt-get.

apt-get -y install inkscape octave geany hexedit mplayer ffmpeg blender python-scipy python-matplotlib python-serial obs-studio php texlive texstudio audacity

Note: I intended to add the Arduino IDE to the chroot filesystem at this point, but I forgot so I had to add it in later. The required commands are included later in the process.

Cleaning Up

The following commands basically undo those commands above that I didn’t understand.

rm /var/lib/dbus/machine-id
rm /sbin/initctl
dpkg-divert --rename --remove /sbin/initctl

The following commands unmount the special filesystems and exit chroot.

umount /proc
umount /sys
umount /dev/pts
exit
sudo umount edit/dev
sudo umount edit/run
sudo umount mnt

Assembling the Modified Filesystem

Regenerate manifest:

sudo chmod +w extract-cd/casper/filesystem.manifest
sudo su
chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' > extract-cd/casper/filesystem.manifest
exit
sudo cp extract-cd/casper/filesystem.manifest extract-cd/casper/filesystem.manifest-desktop
sudo sed -i '/ubiquity/d' extract-cd/casper/filesystem.manifest-desktop
sudo sed -i '/casper/d' extract-cd/casper/filesystem.manifest-desktop

When I was installing pacakges earlier in the process using apt-get, I forgot to also download the Arduino IDE and unpack it into the /opt directory of the chroot environment so I did it at this point instead:

wget https://downloads.arduino.cc/arduino-1.8.9-linux64.tar.xz
tar xf arduino-1.8.9-linux64.tar.xz
rm arduino-1.8.9-linux64.tar.xz
sudo mv arduino-1.8.9 edit/opt/

The following command compresses the modified filesystem:

sudo mksquashfs edit extract-cd/casper/filesystem.squashfs

This recalculates the filesystem size:

sudo su
printf $(du -sx --block-size=1 edit | cut -f1) > extract-cd/casper/filesystem.size
exit

To change the image name to 'TUDubuntu 19.04 "Learned Lecturer" - Release amd64', we edit the first line of the file “extract-cd/README.diskdefines” using nano:

sudo nano extract-cd/README.diskdefines

The following command recalculates the md5sum:

cd extract-cd
sudo rm md5sum.txt
find -type f -print0 | sudo xargs -0 md5sum | grep -v isolinux/boot.cat | sudo tee md5sum.txt

Create the Bootable Image

This creates the iso image:

sudo mkisofs -D -r -V "$IMAGE_NAME" -cache-inodes -J -l -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -o ../TUDubuntu-19.04-desktop-amd64.iso .

This command runs isohybrid on the iso file to turn it into a “hybrid” image that will boot from a USB drive:

cd ..
sudo isohybrid TUDubuntu-19.04-desktop-amd64.iso

Finally, the image needs to be copied onto a USB drive. In my case, when I plugged in my USB key it appeared as “/dev/sda”, but you must check the correct device before running the following command!!! The entire contents of the device you specify (hopefully your USB key) will be erased.

WARNING: RUNNING THE NEXT COMMAND WITHOUT CHECKING WHICH DEVICE FILE IS THE USB DRIVE MAY WIPE YOUR ENTIRE OPERATING SYSTEM. MINE IS /dev/sda BUT THERE IS NO GUARANTEE THAT WILL BE THE CASE ON OTHER SYSTEMS.

sudo dd if=TUDubuntu-19.04-desktop-amd64.iso of=/dev/sda

The previous command may take a few minutes to complete because it has to copy approximately 2.7 GB of data to the USB drive.

That’s it! The USB drive should now be ready to boot.

Posted in Uncategorized | Tagged , , , , , , , | Leave a comment

Using the PHP command line web server to transfer files between devices on a local network

When you install PHP, you get a simple built-in webserver as a bonus. This is very handy for testing web pages you’re writing, but I also sometimes use it as a simple way to transfer files between devices on my local wifi network. In particular, it’s a convenient way of getting files from my laptop onto my phone.

The basic idea is that I run the PHP web server in the folder that contains the files I want to transfer onto my phone, then use the browser on my phone to download the files. Let’s say that the file I’m transferring is called “myfile.txt” and it’s in the directory “/home/xubuntu/Downloads/“. I would type the following commands in a terminal to run the PHP web server in that directory:

cd /home/xubuntu/Downloads
php -S 0.0.0.0:8000

The “0.0.0.0” part is a dummy IP address that tells the PHP web server to listen on all network interfaces (e.g. 127.0.0.1 and 192.168.0.16 on my machine). I’ve also specified 8000 as the port number for the web server. To download the file onto my phone, the URL would be:

http//192.168.0.16:8000/myfile.txt

The phone needs to be connected to the same local network as the computer that the web server is running on. I have both connected to my wifi router.

If I have a few files to transfer, or if the filenames are long, it can be useful to make a little PHP script in the directory to generate a simple web page with links to each file. This is what I’m currently using (saved in the same directory as “index.php“):

<!DOCTYPE html>

<head>
    <style>
        body {font-size:200%;}
    </style>
</head>

<body>
    <br>

    <?php
    $i = 1;
    $g = glob("*");
    foreach ($g as $f)
    {
        if ($f == "index.php") continue;
        echo $i . ". " . "<a href=\"" . $f . "\">" . $f . "</a><br><br>";
        $i = $i + 1;
    }
    ?>
</body>

Here are the files stored in “/home/xubuntu/Downloads/”:

And here’s how it looks in the phone browser (the address is “192.168.0.16:8000”):

When I’m finished transferring files, I just press Ctrl-C in the terminal to close down the web server.

Posted in Uncategorized | Tagged , , , | Leave a comment

€5 PPG – photoplethysmogram amplifier / Arduino circuit

The photoplethysmogram (PPG) is a signal that measures changes in blood volume in some part of the body (e.g. the fingertip) by shining light into the skin and detecting small changes in the level of light absorption that occur due to the blood vessels enlarging and contracting. One common application is heart rate measurement. When the heart beats, blood vessels around the body swell slightly due to the increased blood pressure. This results is variable light absorption over the course of each cardiac cycle.

This circuit is a €5 PPG system that uses a TCRT5000 reflective infrared sensor, an LM358 opamp and an Arduino Nano.

The following Arduino code samples the analog voltage on pin A7 (the sample rate is approximately 100 Hz) and prints the values via the serial connection. The signal can therefore be plotted using the Arduino development environment’s Serial Plotter tool (located under the Tools menu). Normally, the Serial Plotter dynamically scales the vertical axis to fit the displayed signal. To prevent this (and maintain constant scaling), this program actually outputs two additional dummy signals – one which is always 0 and another which is always 1023. These hold the vertical axis limits at constant values.

//
// Photoplethysmogram (PPG) example
// Written by Ted Burke, 3-4-2019
//

void setup()
{
  pinMode(2, OUTPUT);
  Serial.begin(9600);
}

void loop()
{
  int du;

  du = analogRead(7);

  Serial.print("0 1023 ");
  Serial.println(du);

  delay(10);
}

This is an example PPG signal recorded using the above circuit (I rested my fingertip directly on top of the TCRT5000) and displayed in the Serial Plotter:

Since the PPG is a very low frequency signal, you may wish to reduce the gain of the amplifier at higher frequencies, which will tend to reduce the visible interference and smooth out the signal. This can be achieved by placing a 100nF capacitor in parallel with the 100kΩ resistor. The following signal was recorded with that capacitor in the circuit.

Posted in Uncategorized | Tagged , , , , , , , , , , | Leave a comment

Clap detector circuit / AirSpell typing system

This circuit combines a simple audio amplifier (based on an LM358 opamp) with an Arduino Nano to facilitate the detection of clapping sounds or blowing on the microphone.

This is my breadboard circuit:

I actually used a loudspeaker for my microphone, as shown here:

This Arduino program switches an LED on/off on pin D2 when a clap is detected:

//
// Clap on/off - written by Ted Burke
// Last updated 3/4/2019
//

void setup()
{
  pinMode(2, OUTPUT); // LED output
}

float v, avg = 1.66;
int lamp = 0; // on-off state of LED

void loop()
{
  // Sample analog voltage on A7 and convert to volts
  v = analogRead(7) * 5.0 / 1023.0;

  // Update moving average (really an IIR low-pass filter)
  avg = 0.99 * avg + 0.01 * v;

  // Detect sudden changes above a certain magnitude
  if (abs(v - avg) > 0.2)
  {
    // Toggle lamp (LED on D2)
    lamp = 1 - lamp;
    digitalWrite(2, lamp);
    delay(100);
  }
}

This Arduino program prints a single character over the serial connection whenever a clap is detected:

//
// Clap click - written by Ted Burke - 3/4/2019
//
 
void setup()
{
  pinMode(2, OUTPUT);
  Serial.begin(9600);
}
 
float v, avg = 1.66;
 
void loop()
{
  // Sample analog voltage on A7 and convert to volts
  v = analogRead(7) * 5.0 / 1023.0;
 
  // Update moving average (really an IIR low-pass filter)
  avg = 0.99 * avg + 0.01 * v;
 
  // Detect sudden changes above a certain magnitude
  if (abs(v - avg) > 0.2)
  {
    // Send a character via serial link
    Serial.print("c");
 
    // Flash the LED
    digitalWrite(2, HIGH);
    delay(100);
    digitalWrite(2, LOW);
  }  
}

Finally, the following bash script generates a mouse click whenever a character is received from a device connected on /dev/ttyUSB0 (i.e. the Arduino). In combination with onboard (a free Linux onscreen keyboard) this allows text to be spelled out by blowing on the microphone in short bursts (or clapping. Onboard needs to be configured in scanning mode. This may perform better when /dev/ttyUSB0 is in configured in “raw” mode (e.g. by running sudo stty -F /dev/ttyUSB0 raw) because the read command may finish faster facilitating faster repetition.

#!/bin/bash

while true; do
read -n 1 </dev/ttyUSB0
xdotool click 1
done

Save the above bash script to a file called “clicky” and then run the following commands in the same directory:

chmod 755 clicky
sudo stty -F /dev/ttyUSB0 raw
sudo ./clicky
Posted in Uncategorized | Tagged , , , , , , , , , , , , , , , , , , , , , , | 1 Comment

AirMouse – control mouse pointer in Linux using one switch or by blowing on microphone

Article under construction!

To install xdotool:

sudo apt install xdotool

Bash script (save as “airmouse” and “chmod 755 airmouse” to make it executable):

#!/bin/bash

# Run these commands first in terminal (as root)
#
#    stty -F /dev/ttyUSB0 raw
#    cat /dev/ttyUSB0 | ./airmouse
#

# Process input characters from arduino (u,d,l,r,c)
while true;
do
    read -n 1 INPUT
    if [ "$INPUT" = "u" ]; then
        xdotool mousemove_relative -- 0 -10
    elif [ "$INPUT" = "d" ]; then
        xdotool mousemove_relative -- 0 10
    elif [ "$INPUT" = "l" ]; then
        xdotool mousemove_relative -- -10 0
    elif [ "$INPUT" = "r" ]; then
        xdotool mousemove_relative -- 10 0
    elif [ "$INPUT" = "c" ]; then
        xdotool click 1
    fi
done

Arduino code:

//
// AirMouse - Ted Burke - 2/4/2019
//

void setup()
{
  pinMode(2, OUTPUT);

  Serial.begin(9600);

  set_state(1);
}

int state = 1;
unsigned long t;
int direction = 0; // 0:up, 1:down, 2:left, 3:right

void set_state(int n)
{
  state = n;
  t = millis();
}

void loop()
{
  int v;
  unsigned long elapsed;

  delay(50);
  
  v = analogRead(7);
  elapsed = millis() - t; // time elapsed since entering current state

  if (state == 1) // STATE 1: WAIT FOR INPUT
  {
    digitalWrite(2, LOW);

    if (v > 512) set_state(2);
  }
  else if (state == 2) // STATE 2: IS THIS A CLICK OR A MOVE?
  {
    digitalWrite(2, HIGH);

    if (v <= 512) set_state(3);
    if (elapsed >= 200) set_state(4);
  }
  else if (state == 3) // STATE 3: SEND A CLICK
  {
    digitalWrite(2, LOW);
    Serial.print("c");
    delay(250);
    set_state(1);
  }
  else if (state == 4) // STATE 4: MOVE MOUSE
  {
    digitalWrite(2, HIGH);

    if (v <= 512) set_state(5);
    else if (direction == 0) Serial.print("u");
    else if (direction == 1) Serial.print("d");
    else if (direction == 2) Serial.print("l");
    else if (direction == 3) Serial.print("r");
  }
  else if (state == 5) // STATE 5: CHANGE MOUSE DIRECTION
  {
    digitalWrite(2, LOW);
    
    direction = (direction + 1)%4;

    set_state(1);
  }
}
Posted in Uncategorized | Tagged , , , , , , , , , , , , , , , , , , , , | Leave a comment

Some RGB fractal doodles

Click on the animation to view full size gif.

This is the code I used to generate the animation:

//
// fraktalismus modulo - written by Ted Burke 15-1-2019
// Compiled and tested on Xubuntu Linux 18.10
//
// To compile:
//     gcc -o fraktalismus fraktalismus.c -lm
//
// To run:
//     ./fraktalismus
//
// To create mp4 video from frames using ffmpeg:
//     ffmpeg -f image2 -framerate 15 -i "%04d.png" output.gif
//

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>

int main()
{
    // Declare variables
    unsigned char *p;             // buffer of rgb pixels for each frame 
    complex double *q;            // stores a frame of complex pixel values
    double qmagn, qarg;           // magnitude and angle of 
    unsigned char r, g, b;        // colour components of each pixel
    int w=4*1366, h=4*768;        // image width and height
    int n, x, y, t, T;            // t is the frame number
    int xoffset, yoffset;         // shifts centre point of image in complex plane
    complex double z, c, coffset; // z is the iterated complex value
    double zmax;                  // z values wrap around at this magnitude
    double px;                    // width/height of each pixel in complex plane
    FILE *fimage;                 // file pointer for writing pnm files
    char pnm_filename[256];       // stores PNM image filename for each frame
    char png_filename[256];       // stores PNG image filename for each frame
    char command[1024];           // buffer for imagemagick shell command
    
    // Allocate memory for pixels
    p = malloc(w*h*3);
    q = malloc(w*h*sizeof(complex double));
    
    // Generate frames of the animation one by one
    T = 31;
    for (t=0 ; t<T ; ++t)
    {
        // Generate pixel values
        c = -0.6 -0.725*I + 0.025*cexp(I*2.0*t*M_PI/T);
        xoffset = -600*4;
        yoffset = -500*4;
        px = 0.0007/4.0;
        zmax = 1000.0;
        for (y=0 ; y<h ; ++y) for (x=0 ; x<w ; ++x)
        {
            // Iterate complex function
            z = px * ((x-w/2.0+xoffset) + I*(y-h/2.0+yoffset));
            coffset = 0.025 * cexp(I*atan2(y,x));
            for (n=0 ; n<10 ; ++n)
            {
                z = z*z + c + coffset;
                if (cabs(z) > zmax)
                {
                    z = fmod(cabs(z), zmax) * cexp(I*carg(z));
                }
            }
            
            // Find end point of this point's N-step orbit
            q[y*w + x] = z;
        }
        
        for (y=0 ; y<h ; ++y) for (x=0 ; x<w ; ++x)
        {
            // Set RGB components of current pixel
            qmagn = log(cabs(q[y*w + x]));
            qarg = carg(q[y*w + x]);
            r = 255.0 * qmagn * 0.5 * (1.0 + cos(qarg + 0*2.0*M_PI/3.0));
            g = 255.0 * qmagn * 0.5 * (1.0 + cos(qarg + 1*2.0*M_PI/3.0));
            b = 255.0 * qmagn * 0.5 * (1.0 + cos(qarg + 2*2.0*M_PI/3.0));
            r = 255 - r; g = 255 - g; b = 255 - b;
            p[3*(y*w + x) + 0] = r;
            p[3*(y*w + x) + 1] = g;
            p[3*(y*w + x) + 2] = b;
        }
        
        // Write image to PNM file
        sprintf(pnm_filename, "%04d.pnm", t);
        sprintf(png_filename, "%04d.png", t);
        fprintf(stderr, "Writing %s\n", pnm_filename);
        fimage = fopen(pnm_filename, "w");
        fprintf(fimage, "P6\n%d %d\n255\n", w, h);
        fwrite(p, 3, w*h, fimage);
        fclose(fimage);
        
        // Convert image to PNG format and then delete PNM file
        sprintf(command, "convert %s -resize 25%% %s", pnm_filename, png_filename);
        fprintf(stderr, "Executing: %s\n", command);
        system(command);
        sprintf(command, "rm %s", pnm_filename);
        fprintf(stderr, "Executing: %s\n", command);
        system(command);
    }
    
    // Free dynamically allocated memory
    free(p);
    free(q);
}

The version below is obtained by modifying the iterating function on line 59 of the program, as follows:

z = (z*z + c + coffset)/(z*z - c + coffset);

Click on the animation to view full size version.

Posted in Uncategorized | Leave a comment