Controlling a photo camera with gPhoto2

gPhoto2 is an open source, vendor-neutral camera control tool for Unix-like systems, such as Linux and Mac. It supports photo cameras of multiple brands, including Canon, Nikon, Olympus, Pentax, Sony, and Fuji. The supported features vary by model. The following table lists gPhoto2's major features, alongside the official count of supported cameras for each feature:

Feature

Number of Supported Devices

Description

File transfer

2105

files to and from the device

Image capture

489

Make the device capture an image to its local storage

Configuration

428

Change the device's settings, such as shutter speed

Liveview

309

Continuously grab frames of live video from the device

These numbers are current as of version 2.5.8, and are conservative. For example, some configuration features are supported on the Nikon D80, even though the gPhoto2 documentation does not list this camera as configurable. For our purposes, image capture and configuration are required features, so gPhoto2 adequately supports at least 428 cameras, and perhaps many more. This number includes all manner of cameras, from point-and-shoot compacts to professional DSLRs.

Note

To check whether the latest version of gPhoto2 officially supports a feature on a specific camera, see the official list at http://www.gphoto.org/proj/libgphoto2/support.php.

Typically, gPhoto2 communicates with a camera via USB using a protocol called Picture Transfer Protocol (PTP). Before proceeding, check whether your camera has any instructions regarding PTP mode. You might need to change a setting on the camera to ensure that the host computer will see it as a PTP device and not a USB mass storage device. For example, on many Nikon cameras, it is necessary to select SETUP MENU | USB | PTP, as seen in the following image:

Controlling a photo camera with gPhoto2

Moreover, if a camera is mounted as a disk drive, gPhoto2 cannot communicate with it. This is slightly problematic because most operating systems automatically mount a camera as a disk drive, regardless of whether the camera is in the PTP mode. Thus, before we proceed to install and use gPhoto2, let's look at ways to programmatically unmount a camera drive.

Writing a shell script to unmount camera drives

On Mac, a process called PTPCamera is responsible for mounting and controlling cameras on behalf of applications such as iPhoto. After connecting a camera, we can kill PTPCamera by running the following command in Terminal:

$ killall PTPCamera

Then, the camera will be available to receive commands from gPhoto2. However, keep reading because we want to write code that will support Linux too!

On most desktop Linux systems, when the camera is connected, it will be mounted as a Gnome Virtual File System (GVFS) volume. We can list the mounted GVFS volumes by running the following command in Terminal:

$ gvfs-mount -l

For example, this command produces the following output in Ubuntu, on a MacBook Pro laptop with a Nikon D80 camera attached via USB:

Drive(0): APPLE SSD SM1024F
  Type: GProxyDrive (GProxyVolumeMonitorUDisks2)
  Volume(0): Recovery HD
    Type: GProxyVolume (GProxyVolumeMonitorUDisks2)
  Volume(1): Macintosh HD
    Type: GProxyVolume (GProxyVolumeMonitorUDisks2)
Drive(1): APPLE SD Card Reader
  Type: GProxyDrive (GProxyVolumeMonitorUDisks2)
Volume(0): NIKON DSC D80
  Type: GProxyVolume (GProxyVolumeMonitorGPhoto2)
  Mount(0): NIKON DSC D80 -> gphoto2://[usb:001,007]/
    Type: GProxyShadowMount (GProxyVolumeMonitorGPhoto2)
Mount(1): NIKON DSC D80 -> gphoto2://[usb:001,007]/
  Type: GDaemonMount

Note that the output includes the camera's mount point, in this case, gphoto2://[usb:001,007]/. For a camera drive, the GVFS mount point will always start with gphoto2://. We can unmount the camera drive by running a command such as the following:

$ gvfs-mount –u gphoto2://[usb:001,007]/

Now, if we run gvfs-mount -l again, we should see that the camera is no longer listed. Thus, it is unmounted and should be available to receive commands from gPhoto2.

Tip

Alternatively, a file browser such as Nautilus will show mounted camera drives, and will provide GUI controls to unmount them. However, as programmers, we prefer shell commands because they are easier to automate.

We will need to unmount the camera every time it is plugged in. To simplify this, let's write a Bash shell script that supports multiple operating systems (Mac or any Linux system with GVFS) and multiple cameras. Create a file named unmount_cameras.sh and fill it with the following Bash code:

#!/usr/bin/env bash

if [ "$(uname)" == "Darwin" ]; then
  killall PTPCamera
else
  mounted_cameras=`gvfs-mount -l | grep -Po 'gphoto2://.*/' | uniq`
  for mounted_camera in $mounted_cameras; do
    gvfs-mount -u $mounted_camera
  done
fi

Note that this script checks the operating system's family (where "Darwin" is the family of Mac). On Mac, it runs killall PTPCamera. On other systems, it uses a combination of the gvfs-mount, grep, and uniq commands to find each unique gphoto2:// mount point and then unmount all the cameras.

Let's give the script "executable" permissions by running the following command:

$ chmod +x unmount_cameras.sh

Anytime we want to ensure that the camera drives are unmounted, we can execute the script like this:

$ ./unmount_cameras.sh

Now, we have a standard way to make a camera available, so we are ready to install and use gPhoto2.

Setting up and testing gPhoto2

gPhoto2 and related libraries are widely available in open source software repositories for Unix-like systems. No wonder—connecting to a photo camera is a common task in desktop computing today!

For Mac, Apple does not provide a package manager but third parties do. The MacPorts package manager has the most extensive repository.

Note

To set up MacPorts and its dependencies, follow the official guide at https://www.macports.org/install.php.

To install gPhoto2 via MacPorts, run the following command in Terminal:

$ sudo port install gphoto2

On Debian and its derivatives, including Ubuntu, Linux Mint, and Raspbian, we can install gPhoto2 by running the following command:

$ sudo apt-get install gphoto2

On Fedora and its derivatives, including Red Hat Enterprise Linux (RHEL) and CentOS, we can use the following installation command:

$ sudo yum install gphoto2

OpenSUSE has a one-click installer for gPhoto2 at https://software.opensuse.org/package/gphoto.

After installing gPhoto2, let's connect a camera. Ensure that the camera is turned on and in PTP mode. Then, run the following commands to unmount the camera drive and take a photo:

$ ./unmount_cameras.sh
$ gphoto2 --capture-image

If the camera is in autofocus mode, you might see or hear the lens move. (Ensure that the camera has a subject in view so that autofocus will succeed. Otherwise, no photo will be captured.) Then, you might hear the shutter open and close. Disconnect the camera and use its review menu to browse the captured photos. If a new photo is there, gPhoto2 is working!

To upload all images from the camera to the current working directory, we could reconnect the camera and run the following commands:

$ ./unmount_cameras.sh
$ gphoto2 --get-all-files

To read about all the flags that gphoto2 supports, we can open its manual by running the following command:

$ man gphoto2

Next, let's try a more advanced task, involving configuration as well as image capture. We will take a series of photos with exposure bracketing.

Writing a shell script for exposure bracketing

gPhoto2 provides a flag, --set-config, which allows us to reconfigure many camera parameters, including exposure compensation. For example, suppose we want to overexpose an image by the equivalent of one full f-stop (doubling the aperture's area or increasing its radius by a factor of sqrt(2)). This bias is called an exposure compensation (or exposure adjustment) of +1.0 exposure value (EV). The following command configures the camera to use +1.0 EV and then takes a photo:

$ gphoto2 --set-config exposurecompensation=1000 --capture-image

Note that the value of exposurecompensation is denominated in thousandths of an EV, so 1000 is +1.0 EV. To underexpose, we would use a negative value. A series of these commands, each with a different EV, would achieve exposure bracketing.

We can use the --set-config flag to control many photographic properties, not just exposure compensation. For example, the following command captures a photo with an exposure time of one second, while firing the flash in slow sync mode:

$ gphoto2 --set-config shutterspeed=1s flashmode=2 --capture-image

The following command lists all the supported properties and values for the given camera:

$ gphoto2 --list-all-config

Note

For further discussion of f-stops, exposure, and other photographic properties, refer back to Chapter 1, Getting the Most out of Your Camera System, especially the Capturing the subject in the moment section.

Before taking a series of exposure bracketed photos, dial your camera to the aperture priority (A) mode. This means that the aperture will be held constant while the shutter speed will vary based on lighting and EV. A constant aperture will help ensure that the same region is in focus in all images.

Let's automate the exposure bracketing commands with another shell script, which we will call capture_exposure_bracket.sh. It will accept a flag, -s, to specify the exposure step size between frames (in thousandths of an EV), and another flag -f, to specify the number of frames. The defaults will be 3 frames spaced at an interval of 1.0 EV. Here is the script's implementation:

#!/usr/bin/env bash

ev_step=1000
frames=3
while getopts s:f: flag; do
  case $flag in
    s)
      ev_step="$OPTARG"
      ;;
    f)
      frames="$OPTARG"
      ;;
    ?)
      exit
      ;;
  esac
done

min_ev=$((-ev_step * (frames - 1) / 2))
for ((i=0; i<frames; i++)); do
  ev=$((min_ev + i * ev_step))
  gphoto2 --set-config exposurecompensation=$ev 
    --capture-image
done
gphoto2 --set-config exposurecompensation=0

All the commands in this script are cross-platform for Linux and Mac. Note that we are using the getopts command to parse arguments, and Bash arithmetic to compute the EV of each photo.

Remember to give the script "executable" permissions by running the following command:

$ chmod +x capture_exposure_bracket.sh

To unmount the camera and capture 5 photos at an interval of 1.5 EV, we could run the following commands:

$ ./unmount_cameras.sh
$ ./capture_exposure_bracket.sh –s 1500 –f 5

Now that we have a clear idea of how to control a camera from the command line, let's consider how to wrap this functionality in a general-purpose programming language that can also interface with OpenCV.

Writing a Python script to wrap gPhoto2

Python is a high-level, dynamic programming language with great third-party libraries for mathematics and science. OpenCV's Python bindings are efficient and quite mature, wrapping all the C++ library's major functionality except GPU optimizations. Python is also a convenient scripting language, as its standard libraries provide cross-platform interfaces to access much of the system's functionality. For example, it is easy to write Python code to spawn a subprocess (also called a child process), which may run any executable, even another interpreter such as a Bash shell.

Note

For more information on spawning and communicating with a child process from Python, see the subprocess module's documentation at https://docs.python.org/2/library/subprocess.html. For the special case where the child process is an additional Python interpreter, see the documentation of the multiprocessing module at https://docs.python.org/2/library/multiprocessing.html.

We will use Python's standard subprocessing functionality to wrap gPhoto2 and our own shell scripts. By sending camera commands from a child process, we will enable the caller (in Python) to treat these as "fire and forget" commands. That is to say, functions in the Python process return immediately so that the caller is not obliged to wait for the camera to handle the commands. This is a good thing because a camera might typically require several seconds to autofocus and capture a series of photos.

Let's create a new file, CameraCommander.py, and begin its implementation with the following import statements:

import os
import subprocess

We will write a class, CameraCommander. As member variables, it will have a current capture process (which may be None) and a log file. By default, the log file will be /dev/null, which means that the log output will be discarded. After setting member variables, the initialization method will call a helper method to unmount thecamera drive so that the camera is ready to receive commands. Here are the class's declaration and initializer:

class CameraCommander(object):

  def __init__(self, logPath=os.devnull):
    self._logFile = open(logPath, 'w')
    self._capProc = None
    self.unmount_cameras()

When an instance of CameraCommander is deleted, it should close the log file, as seen in the following code:

  def __del__(self):
    self._logFile.close()

Every time CameraCommander opens a subprocess, the command should be interpreted by the shell (Bash), and the command's print output and errors should be redirected to the log file. Let's standardize this configuration of a subprocess in the following helper method:

  def _open_proc(self, command):
    return subprocess.Popen(
      command, shell=True, stdout=self._logFile,
      stderr=self._logFile)

Now, as our first wrapper around a shell command, let's write a method to run unmount_cameras.sh in a subprocess. Unmounting the camera drives is a short process, and it must finish before other camera commands can run. Thus, we will implement our wrapper method so that it does not return until unmount_cameras.sh returns. That is to say, the subprocess will run synchronously in this case. Here is the wrapper's implementation:

  def unmount_cameras(self):
    proc = self._open_proc('./unmount_cameras.sh')
    proc.wait()

Next, let's consider how to capture a single image. We will start by calling a helper method to stop any previous, conflicting command. Then, we will invoke the gphoto2 command with the usual --capture-image flag. Here is the implementation of the wrapper method:

  def capture_image(self):
    self.stop_capture()
    self._capProc = self._open_proc(
      'gphoto2 --capture-image')

As another capture mode, we can invoke gphoto2 to record a time-lapse series. The -I or --interval flag, with an integer value, specifies the delay between frames, in seconds. The -F or --frames flag also takes an integer value, specifying the number of frames in the series. If the -I flag is used but -F is omitted, the process continues to capture frames indefinitely until forced to terminate. Let's provide the following wrapper for time-lapse functionality:

  def capture_time_lapse(self, interval, frames=0):
    self.stop_capture()
    if frames <= 0:
      # Capture an indefinite number of images.
      command = 'gphoto2 --capture-image -I %d' % interval
    else:
      command = 'gphoto2 --capture-image -I %d -F %d' %
        (interval, frames)
    self._capProc = self._open_proc(command)

Before taking a series of time-lapse photos, you might want to dial your camera to the manual exposure (M) mode. This means that the aperture and shutter speed will be held constant at manually specified values. Assuming that the scene's light level is approximately constant, a constant exposure will help prevent unpleasant flickering in the time-lapse video. On the other hand, if we do expect lighting conditions to vary a lot over the course of the time-lapse series, the M mode may be inappropriate because in these circumstances, it will cause some frames to be noticeably underexposed and others overexposed.

To allow for exposure bracketing, we can simply wrap our capture_exposure_bracket.sh script, as seen in the following code:

  def capture_exposure_bracket(self, ev_step=1.0, frames=3):
    self.stop_capture()
    self._capProc = self._open_proc(
      './capture_exposure_bracket.sh -s %d -f %d' %
        (int(ev_step * 1000), frames))

As we have seen in the previous three methods, it is sensible to terminate any ongoing capture process before trying to start another. (After all, a camera can only process one command at a time). Moreover, a caller might have other reasons to terminate a capture process. For example, the subject might have gone away. We will provide the following method to force the termination of any ongoing capture process:

  def stop_capture(self):
    if self._capProc is not None:
      if self._capProc.poll() is None:
        # The process is currently running but might finish
        # before the next function call.
        try:
          self._capProc.terminate()
        except:
          # The process already finished.
          pass
      self._capProc = None

Similarly, we will provide the following method to await the completion of any currently running capture process:

  def wait_capture(self):
    if self._capProc is not None:
      self._capProc.wait()
      self._capProc = None

Finally, we will provide the following property getter to enable a caller to check whether a capture process is currently running:

  @property
  def capturing(self):
    if self._capProc is None:
      return False
    elif self._capProc.poll() is None:
      return True
    else:
      self._capProc = None
      return False

This concludes the CameraCommander module. To test our work, let's write another script, test_camera_commands.py, with the following implementation:

#!/usr/bin/env python

import CameraCommander

def main():

  cc = CameraCommander.CameraCommander('test_camera_commands.log')

  cc.capture_image()
  print('Capturing image...')
  cc.wait_capture()
  print('Done')

  cc.capture_time_lapse(3, 2)
  print('Capturing 2 images at time interval of 3 seconds...')
  cc.wait_capture()
  print('Done')

  cc.capture_exposure_bracket(1.0, 3)
  print('Capturing 3 images at exposure interval of 1.0 EV...')
  cc.wait_capture()
  print('Done')

if __name__ == '__main__':
  main()

Ensure that your camera is on, is in the PTP mode, and is connected. Then, make the test script executable and run it, like this:

$ chmod +x test_camera_commands.py
$ ./test_camera_commands.py

Wait for all the commands to finish, and then disconnect the camera to review the images. Check the timestamp and EV number of each photo. Ideally, a total of six photos should have been captured. However, the actual number could vary depending on factors such as the success or failure of autofocus, and the time spent capturing and saving each image. In case of any doubts, review the log file, test_camera_commands.log, in a text editor.

Finding libgphoto2 and wrappers

As an alternative to using the gPhoto2 command line tool, we could use the underlying C library, libgphoto2 (https://github.com/gphoto/libgphoto2). The library has several third-party wrappers, including a set of up-to-date Python bindings called python-gphoto2 (https://github.com/gphoto/libgphoto2-python).

OpenCV 3's videoio module has optional support for libgphoto2. To enable this feature, we could configure and build OpenCV from source using the WITH_GPHOTO2 CMake definition. Of course, for this option to work, the system must already have an installation of libgphoto2 and its header files. For example, these can be installed by the following command on Debian, Ubuntu, Linux Mint, Raspbian, and similar systems:

$ sudo apt-get install libgphoto2-dev

For our purposes, controlling the photo camera via libgphoto2 or OpenCV's videoio module is overkill. We do not want to grab frames for real-time processing. We simply want our Python scripts to initiate additional processes to unmount and configure the camera, and make it capture photos to its local storage. The gPhoto2 command-line tool and our own shell scripts are perfectly convenient to use as subprocesses, so we will continue to rely on them throughout the rest of this chapter.

Note

One of OpenCV's official samples demonstrates the use of a gPhoto2-compatible camera via the videoio module. Specifically, the sample deals with focus control. See the source code in OpenCV's GitHub repository at https://github.com/Itseez/opencv/blob/master/samples/cpp/autofocus.cpp.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset