2011-11-29

Camera tethered capturing using libgphoto2

Here is some code showing how to use libgphoto2 to do some tethered capturing using a DSLR camera connected to the computer's USB port, since I haven't seen much documentation on how to do this, and the samples from libgphoto2 don't work as-is if you want to do continuous capturing.

This code is mainly based on the lunkwill-canon-capture.c example included with libgphoto2, but it has been adapted for continuous shooting by looking at the gphoto2 code.

This code has been tested with a Nikon D700, but any camera with tethered shooting support for libgphoto2 should work. Note that if you are using a Canon camera, you may have to enable the Canon-specific capture mode using the canon_enable_capture function found in the config.c file in the examples included with libgphoto2.

I'll just be showing how to capture pictures, so in case you want to use some of the other functions, like read or modify camera settings, please refer to the examples included with libgphoto2.

Also, please notice that for a lot of purposes it's easier to interface your code with gphoto2 through command line parameters or running the gphoto2 shell (with gphoto2 --shell) and sending commands through standard input. The libgphoto2 approach gives you more control, but it's also easier to mess up and leave the camera in an inconsistent state that will require unplugging and plugging it again.

Let's start with includes and globals:
 #include <stdio.h>  
 #include <fcntl.h>  
   
 #include <gphoto2/gphoto2-camera.h>  
   
 Camera *camera;  
 GPContext *context;  
The pointers camera and context are used by libgphoto2 to interact with the camera.

Now we are going to define the callback functions that libgphoto2 is going to call when there's an error with the capturing or it has something to tell us. They are mandatory, and failing to register them will results in errors being thrown when we try to initialize the camera. We'll use them to just print their respective messages:
void error_func (GPContext *context, const char *format, va_list args, void *data) {
 fprintf  (stderr, "*** Contexterror ***\n");
 vfprintf (stderr, format, args);
 fprintf  (stderr, "\n");
}

void message_func (GPContext *context, const char *format, va_list args, void *data) {
 vprintf (format, args);
 printf ("\n");
}
The next thing to do will be to define our main function. At the beginning we will just initialize the camera and context:
int main (int argc, char *argv[]) {
 gp_camera_new (&camera);
 context = gp_context_new();
Next step will be to register the callbacks we defined previously:
 // set callbacks for camera messages
 gp_context_set_error_func(context, error_func, NULL);
 gp_context_set_message_func(context, message_func, NULL);
Next we will detect cameras and choose the first one. In case you want to be fancier and let the user choose a different camera, check the sample-multi-detect.c file in libgphoto2 examples.
 //This call will autodetect cameras, take the first one from the list and use it
 printf("Camera init. Can take more than 10 seconds depending on the "
  "memory card's contents (remove card from camera to speed up).\n");
 int ret = gp_camera_init(camera, context);
 if (ret < GP_OK) {
  printf("No camera auto detected.\n");
  gp_camera_free(camera);
  return 1;
 }
Now we are ready to do some capturing. As an example of continuous capturing I just wrote a loop that captures ten shots as fast as possible. For your application you may want to pause for a certain time, or react to some user input, network buffer, etc.
 // take 10 shots
 char filename[256];
 int const nShots = 10;
 int i;

 // do some capturing
 for (i = 0; i < nShots; i++) {
  snprintf(filename, 256, "shot-%04d.nef", i);
  printf("Capturing to file %s\n", filename);
  capture(filename);
 }
This will generate consecutive file names for all the shots and do the capturing. The capture function contains all the juicy stuff, but we'll go to it in a while. Before that we'll finish writing the main function by closing the camera:
 // close camera
 gp_camera_unref(camera);
 gp_context_unref(context);

 return 0;
}
The capture function contains all the code necessary to take a shot and copy the result image to the computer's file system, while avoiding saturating the camera by sending it requests before it's done doing its work. First we'll define the variables needed.
int capture (const char *filename) {
 int fd, retval;
 CameraFile *file;
 CameraFilePath camera_file_path;

 // this was done in the libphoto2 example code, but doesn't seem to be necessary
 // NOP: This gets overridden in the library to /capt0000.jpg
 //snprintf(camera_file_path.folder, 1024, "/");
 //snprintf(camera_file_path.name, 128, "foo.jpg");
fd will be the file descriptor we use to write to disk. retval will hold the return value of libgphoto2 calls so we can check for errors. file will hold a reference to the picture in the camera's file system, and camera_file_path will tell us the path of the captured picture in the camera's file system (necessary to retrieve the captured picture).

Next we'll capture a picture:
 // take a shot
 retval = gp_camera_capture(camera, GP_CAPTURE_IMAGE, &camera_file_path, context);
 
 if (retval) {
  // do some error handling, probably return from this function
 }

 printf("Pathname on the camera: %s/%s\n", camera_file_path.folder, camera_file_path.name);
After running gp_camera_capture, the camera_file_path variable has been written. Next we'll open the file in the computer's file system and create the CameraFile object:
 fd = open(filename, O_CREAT | O_WRONLY, 0644);
 // create new CameraFile object from a file descriptor
 retval = gp_file_new_from_fd(&file, fd);
 
 if (retval) {
  // error handling
 }
Next we'll retrieve the picture from the camera and write it to disk:
 // copy picture from camera
 retval = gp_camera_file_get(camera, camera_file_path.folder, camera_file_path.name,
  GP_FILE_TYPE_NORMAL, file, context);
 
 if (retval) {
  // error handling
 }
And finally remove the picture from the camera's file system and free the CameraFile object:
 printf("Deleting\n");
 // remove picture from camera memory
 retval = gp_camera_file_delete(camera, camera_file_path.folder, camera_file_path.name,
   context);
 
 if (retval) {
  // error handling
 }

 // free CameraFile object
 gp_file_free(file);
Now, before we can return from the function and risk it being called again while the camera is still busy, we'll wait for events from the camera until we receive GP_EVENT_CAPTURE_COMPLETE and there are no more events to process. This probably could be done more efficiently, since the program could be doing something else while waiting for events.
 printf("Wait for events from camera\n");
 while(1) {
  retval = gp_camera_wait_for_event(camera, waittime, &type, &data, context);
  if(type == GP_EVENT_TIMEOUT) {
   break;
  }
  else if (type == GP_EVENT_CAPTURE_COMPLETE) {
   printf("Capture completed\n");
   waittime = 100;
  }
  else if (type != GP_EVENT_UNKNOWN) {
   printf("Unexpected event received from camera: %d\n", (int)type);
  }
 }
 return 0;
}
This is it. This should be all you need to do to tethered capturing using libgphoto2. I hope this is useful to somebody. Below I'll paste the entire source code. To compile it using gcc you can run:


gcc -o tethered tethered.c -lgphoto2


You will need the development files of libgphoto. In Ubuntu you can install them by running:

sudo apt-get install libgphoto2-2-dev

Here is the complete source code:

#include <stdio.h>
#include <fcntl.h>

#include <gphoto2/gphoto2-camera.h>

Camera *camera;
GPContext *context;

void error_func (GPContext *context, const char *format, va_list args, void *data) {
 fprintf(stderr, "*** Contexterror ***\n");
 vfprintf(stderr, format, args);
 fprintf(stderr, "\n");
}

void message_func (GPContext *context, const char *format, va_list args, void *data) {
 vprintf(format, args);
 printf("\n");
}

int capture (const char *filename) {
 int fd, retval;
 CameraFile *file;
 CameraFilePath camera_file_path;

 // this was done in the libphoto2 example code, but doesn't seem to be necessary
 // NOP: This gets overridden in the library to /capt0000.jpg
 //snprintf(camera_file_path.folder, 1024, "/");
 //snprintf(camera_file_path.name, 128, "foo.jpg");

 // take a shot
 retval = gp_camera_capture(camera, GP_CAPTURE_IMAGE, &camera_file_path, context);
 
 if (retval) {
  // do some error handling, probably return from this function
 }

 printf("Pathname on the camera: %s/%s\n", camera_file_path.folder, camera_file_path.name);

 fd = open(filename, O_CREAT | O_WRONLY, 0644);
 // create new CameraFile object from a file descriptor
 retval = gp_file_new_from_fd(&file, fd);
 
 if (retval) {
  // error handling
 }

 // copy picture from camera
 retval = gp_camera_file_get(camera, camera_file_path.folder, camera_file_path.name,
   GP_FILE_TYPE_NORMAL, file, context);
 
 if (retval) {
  // error handling
 }

 printf("Deleting\n");
 // remove picture from camera memory
 retval = gp_camera_file_delete(camera, camera_file_path.folder, camera_file_path.name,
   context);
 
 if (retval) {
  // error handling
 }

 // free CameraFile object
 gp_file_free(file);

 // Code from here waits for camera to complete everything.
 // Trying to take two successive captures without waiting
 // will result in the camera getting randomly stuck.
 int waittime = 2000;
 CameraEventType type;
 void *data;

 printf("Wait for events from camera\n");
 while(1) {
  retval = gp_camera_wait_for_event(camera, waittime, &type, &data, context);
  if(type == GP_EVENT_TIMEOUT) {
   break;
  }
  else if (type == GP_EVENT_CAPTURE_COMPLETE) {
   printf("Capture completed\n");
   waittime = 100;
  }
  else if (type != GP_EVENT_UNKNOWN) {
   printf("Unexpected event received from camera: %d\n", (int)type);
  }
 }
 return 0;
}

int main (int argc, char *argv[]) {
 gp_camera_new (&camera);
 context = gp_context_new();

 // set callbacks for camera messages
 gp_context_set_error_func(context, error_func, NULL);
 gp_context_set_message_func(context, message_func, NULL);

 //This call will autodetect cameras, take the first one from the list and use it
 printf("Camera init. Can take more than 10 seconds depending on the "
  "memory card's contents (remove card from camera to speed up).\n");
 int ret = gp_camera_init(camera, context);
 if (ret < GP_OK) {
  printf("No camera auto detected.\n");
  gp_camera_free(camera);
  return 1;
 }

 // take 10 shots
 char filename[256];
 int const nShots = 10;
 int i;

 // do some capturing
 for (i = 0; i < nShots; i++) {
  snprintf(filename, 256, "shot-%04d.nef", i);
  printf("Capturing to file %s\n", filename);
  capture(filename);
 }

 // close camera
 gp_camera_unref(camera);
 gp_context_unref(context);

 return 0;
}

27 comments:

  1. getting some errors on compilation

    undefined reference to `gp_camera_capture'

    Assuming I need to link against some libraries...which ones?

    ReplyDelete
  2. For me it works using the provided command:

    gcc -o tethered tethered.c -lgphoto2

    Maybe you're messing up the order of the parameters to gcc. The source file has to be specified before the libraries, so for example running:

    gcc -lgphoto2 -o tethered tethered.c

    will generate the error you mention.

    ReplyDelete
  3. I think this is some outstanding code.

    I'm attempting to put a GUI wrapper around the code using Qt. I created a QT project and placed your code into a new class called CameraControl. Although I'm an accomplished C# programmer, This is my first project ever in C++ under Linux so I'm really struggling to understand a very cryptic error message that is shown in the line

    gp_context_set_error_func(context, error_func, NULL);

    argument of type 'void (CameraControl::)(GPContext*, const char*, __va_list_tag*, void*) {aka void (CameraControl::)(_GPContext*, const char*, __va_list_tag*, void*)}' does not match 'GPContextErrorFunc {aka void (*)(_GPContext*, const char*, __va_list_tag*, void*)}'

    If you or anyone else who watches your blog can help me out with this compiler error it would be greatly appreciated

    ReplyDelete
    Replies
    1. DarwinIcesurfer, can you post your definition of the function error_func?

      Delete
    2. Hi,

      I have had the same problem (i'm doing the same as you, using Qt !).

      Cast your pointer function with (GPContextErrorFunc) and (GPContextStatusFunc) respectively :

      gp_context_set_error_func(context, (GPContextErrorFunc) error_func, NULL);
      gp_context_set_message_func(context, (GPContextStatusFunc) message_func, NULL);

      I think that the default options to qt compiler are strict.

      Delete
  4. Hi! Thanks for the information.
    Where did you get a "libgphoto example code"?
    I'm interesting in CameraWidget structure.

    ReplyDelete
    Replies
    1. The example code is inside examples/ folder in the libgphoto source tar file you can download from http://sourceforge.net/projects/gphoto/files/latest/download?source=files

      Delete
  5. is there any way to take photos in burst mode (but with a manual trigger, so additional commands can be run between images)? Right now photos are taken once every ~2 instead of ~0.2 seconds, which is the camera's specification. (Canon 60D)

    ReplyDelete
    Replies
    1. I don't really know how you would do that. The current code is transferring every image to the computer just after capture. Maybe you could send repeated capture commands and select every time a different path in the camera's memory.
      You could keep capturing pictures until the memory is full. I don't know how you would go about detecting that, though, so you'll have to figure that out.

      Delete
  6. Hi there!
    Sorry I'm quite a newbie at this. Is there a way I can see what is on the live view of the camera and then pass that stream into opencv to do image detection and motion capture? I essentially want to create a pan and tilt bed for my DSLR and capture images based on specific triggers through image processing.

    ReplyDelete
    Replies
    1. Hi you!
      I haven't tried doing it myself, but apparently it is possible if your camera supports live view.
      Maybe you can take some hints from the code of the following program:
      http://eos-movrec.sourceforge.net/

      Delete
    2. Awesome!
      Thank you very much for your help!
      That's basically what I wanted to do =)

      Delete
  7. Thanks for taking the time to do this! Immensely helpful!

    ReplyDelete
  8. It works pretty fine for me!
    If you are working with cmake you must add gphoto2 on target_link_libraries in CMakeLists.txt of your project. It also works and compiles in c++

    ReplyDelete
  9. I did as stated by you but I din't delete the file on camera. The file which I saved on my computer takes 15Mb while the one on camera is only 5Mb. What could create so much difference?

    ReplyDelete
    Replies
    1. Raj, I really have no idea.
      I guess I would start to investigate by checking the format of both images and verifying that they open correctly.

      Delete
    2. Ahh...got the error. There was mistake in my fwrite statement. Don't know how, but file dint get corrupt. It was showing me jpeg image but with large size. After putting the fwrite parameters correctly, I'm getting the correct image size. Thanks.

      Delete
  10. Good post, but technically tethered capture is capturing with the shuttercable, not through the USB command. For example the command with the api interface is ' --capture-tethered[=COUNT] Wait for shutter release on the camera and download'

    ReplyDelete
  11. Hi,
    Great app!
    What would I have to add to the source code if I want it to have for instance 15 second intervals between shots?

    ReplyDelete
  12. When I run this program, the images get saved in the same folder as the executable. How do I change this to save the images in a folder passed as an argument?
    Please let me know.
    Thanks!

    ReplyDelete
  13. Thanks for publishing this code.

    On my Nikon D300s I get this:
    Camera init. Can take more than 10 seconds depending on the memory card's contents (remove card from camera to speed up).
    Capturing to file capt0000.jpg
    *** Contexterror ***
    Out of Focus
    Pathname on the camera: /
    Deleting
    *** Contexterror ***
    The path '' is not absolute.
    Wait for events from camera
    Capturing to file capt0001.jpg
    *** Contexterror ***
    PTP Store Not Available
    Pathname on the camera: /
    Deleting
    *** Contexterror ***
    The path '' is not absolute.
    Wait for events from camera

    and the downloaded files are empty.

    I appreciate any help offered.

    ReplyDelete
    Replies
    1. I have a problem with my Nikon D5100. If my lens cap is on while trying to take a photo, the app just crashes. Have you tried to focus the camera yourself before taking a photo?

      Delete
  14. This comment has been removed by a blog administrator.

    ReplyDelete
  15. Hey thanks this was super helpful
    For any OS X issues who have trouble claiming their camera's USB, see this
    http://gphoto-software.10949.n7.nabble.com/gphoto2-Error-53-Could-not-claim-the-USB-device-td2218.html

    ReplyDelete
  16. Awesome!
    How about taking a movie?

    ReplyDelete
  17. In order to perform a real-time object detection, I connected a Canon digital camera via usb to a Raspberry Pi 2 platform. I did successfully what is written above. Now, I got two separate files which are built and perfectly work via two separate builders. However, I cannot combine them for the above purpose; and I want you, to resolve this problem for me:
    1- First one is a C program based file (your tethered.c file) built on Terminal by:
    gcc –o filename filename.c –lgphoto2
    2- The second one is a c++ program based file that captures video from a webcam for video analysis by OpenCV. This file is built by CMakeLists.txt.
    (References:
    http://docs.opencv.org/2.4/doc/tutorials/introduction/linux_install/linux_install.html
    http://docs.opencv.org/2.4/doc/tutorials/introduction/linux_gcc_cmake/linux_gcc_cmake.html )
    I need to have a single file capable of both abovementioned jobs, except I need video (GP_CAPTURE_MOVIE) rather a sequence of images. The operating system is Debian Jessie.
    (References: http://www.gphoto.org/doc/api/gphoto2-camera_8h.html#a6a66768c14ce761d6810f64572c79ec1afa8c68bfa3fa365ad562ca6929e6cee7 ).
    Thank you in advance.
    Cheeeers.

    ReplyDelete