Good news for Python lovers! You can control your DSLR camera with Python scripts via the USB cable, PTP protocol.
I've written a Python binding for gphoto2, called piggyphoto, available here:
http://github.com/alexdu/piggyphoto
The binding is based on ctypes, which means (only in theory) that you don't have to compile anything in order to use it. In practice, the stable version is almost useless, so you have to compile the latest SVN snapshot of gphoto2.
This is a test bed for various enhancements which can be implemented in Magic Lantern.
Related page: PTP
Installing the SVN version of gphoto2[]
On Ubuntu Karmic, I had to follow these steps:
svn co https://gphoto.svn.sourceforge.net/svnroot/gphoto/trunk gphoto
The README files from (lib)gphoto say that the default prefix for installation is /usr/local. Doing so resulted in conflicts with the stable version from Ubuntu package manager. So I've changed the prefix to /usr:
cd gphoto/trunk cd libgphoto2 autoreconf --install --symlink ./configure --with-prefix=/usr --with-camlibs=canon make sudo make install cd ../gphoto2 autoreconf -is ./configure --with-libgphoto2=/usr make sudo make install
gphoto2 -v ... gphoto2 2.4.99.2 gcc, popt(m), exif, no cdk, aa, jpeg, readline libgphoto2 2.4.99.7 INCOMPLETE CAMLIB SET (canon ptp2), gcc, ltdl, EXIF libgphoto2_port 0.10.0 gcc, ltdl, USB, serial without locking
On Osx Lion, Homebrew can install it:
brew install gphoto2
Quick Start[]
After you have installed the latest libgphoto2, get piggyphoto from github:
git clone git://github.com/alexdu/piggyphoto.git cd piggyphoto
Now run the test scripts from the piggyphoto main directory. Let's try a few of them:
Autodetect the camera[]
python autodetect.py libgphoto2 version: 2.4.99.7 INCOMPLETE CAMLIB SET (canon ptp2) gcc (C compiler used) ltdl (for portable loading of camlibs)v EXIF (for special handling of EXIF files) detected cameras: [('Canon EOS 550D', 'usb:002,039')]
Take a picture[]
python snap.py Model : Canon EOS 550D Status : 0 Port : 4 Operations : 25 File Operations : 10 Folder Operations : 14 USB (vendor/product) : 0x4a9/0x31ea USB class : 0x0/0x0/0x0 Library : /usr/lib/libgphoto2/2.4.99.7/ptp2 Id : PTP Saving preview.jpg Saving snap.jpg
Live Preview[]
For this one you need Pygame:
sudo easy_install pygame
python preview.py
Now, your DSLR is behaving like a webcam :)
Look at camera settings[]
Changing settings does not seem to work well, so for now we'll just reading the settings:
python list-config.py Model : Canon EOS 550D Status : 0 Port : 4 Operations : 25 File Operations : 10 Folder Operations : 14 USB (vendor/product) : 0x4a9/0x31ea USB class : 0x0/0x0/0x0 Library : /usr/lib/libgphoto2/2.4.99.7/ptp2 Id : PTP main.actions.focuslock = 2 main.actions.bulb = 2 main.actions.uilock = 2 main.actions.syncdatetime = None main.actions.autofocusdrive = None main.actions.manualfocusdrive = None main.actions.eoszoom = 0 main.actions.eoszoomposition = 0,0 main.settings.datetime = 1286126968 main.settings.reviewtime = Hold main.settings.output = Unknown value 0000 main.settings.evfmode = 1 main.settings.ownername = main.settings.artist = main.settings.copyright = main.settings.capturetarget = Internal RAM main.settings.capture = 2 main.status.model = 21######## main.status.ptpversion = 256 main.status.batterylevel = 100% main.status.lensname = EF50mm f/1.8 II main.status.serialnumber = 83####### main.status.shuttercounter = 12648 # after less than a month! wtf?! main.status.availableshots = 42450 main.imgsettings.imageformat = Large Fine JPEG main.imgsettings.imageformatsd = Large Fine JPEG main.imgsettings.iso = 1600 main.imgsettings.whitebalance = Cloudy main.imgsettings.whitebalanceadjusta = 0 main.imgsettings.whitebalanceadjustb = 0 main.imgsettings.whitebalancexa = 0 main.imgsettings.whitebalancexb = 0 main.capturesettings.exposurecompensation = 1.0 main.capturesettings.autoexposuremode = AV main.capturesettings.drivemode = Continuous main.capturesettings.picturestyle = User defined 3 main.capturesettings.aperture = 1.8 main.capturesettings.meteringmode = Evaluative main.capturesettings.bracketmode = 0 main.capturesettings.aeb = off main.other.d402 = Canon EOS 550D main.other.d407 = 1 main.other.d406 = Unknown Initiator main.other.d303 = 1 main.other.5001 = 100
Trap Focus[]
Trap focus functionality was requested by Magic Lantern users:
http://bitbucket.org/hudson/magic-lantern/issue/160/trap-focus-functionality
- "Trap Focus" is great functionality found in Nikons [and Pentax] is missing in entire Canon lineup. When enabled it delays firing shutter until central point becomes in focus. From the user perspective when MF (together with "Trap Focus") is enabled user presses (and holds) shutter button. Nothing happens until central point becomes in focus. When it happens - camera takes picture. It's great in any situation with manual lens, but especially suited for sports and macro photography.
Here is an experiment for implementing trap focus in tethered shooting mode, with piggyphoto. It's not really useful in the current state, but if you like it, it may be implemented into Magic Lantern. It's really useful for macro shots and old manual lenses.
How to try it[]
Connect the camera via USB and run the script focus-snap.py:
python focus-snap.py
It will display a live preview. If you want to change settings, close the window, press the Live View button on the camera (while it's still connected to USB) and you can change shooting settings. Put a short exposure time, like 1/250, 1/500, to avoid motion blur. Then run the script again.
Look at the title bar of the preview window. It should display "Looking for focus peak". Now point the camera at some object and rotate the focus ring. The object will be focused; turn the ring a bit farther, and the object will become blurred. The title bar will say "Focus peak found". Now turn the focus ring back sloooooooowly and camera will take a picture when the object will come back into focus.
How it works[]
My implementation estimates the amount of focus on the live preview image, and takes a picture when it detects a peak (well, almost).
Estimating focus for an image[]
The 550D outputs live preview frames which are 1056 x 704 pixels. The focus is computed from the central area of the image, having 100x100 pixels.
The function for evaluating the focus is implemented in focus.py:
def estimate(file, s=5): """Estimates the amount of focus of an image file. Returns a real number: higher values indicate better focus. Bug: a high-contrast, blurry image can be considered with better focus than a low-contrast, perfectly focused image. """ im = Image.open(file).convert("L") w,h = im.size box = (w/2 - 50, h/2 - 50, w/2 + 50, h/2 + 50) im = im.crop(box) imf = im.filter(ImageFilter.MedianFilter(s)) d = ImageChops.subtract(im, imf, 1, 100) return ImageStat.Stat(d).stddev[0]
The main idea is that if you blur a focused image, you will lose lots of details. But if you blur a blurred image, it won't change much. So, a measure for how much the image is in focus will be the difference between the original and slightly blurred image.
Problems:
- The focus measure is not absolute. Ideally, it should return a number between 0 and 1, with 0 = extremely blurry and 1 = perfectly focused. But not all lenses are able to give perfect focus, and the sampling rate is finite, so the algorithm may miss the sweet spot.
- The focus measure is dependent on the scene analyzed. A high-contrast, blurry image will be evaluated as being better than a low-contrast, perfect-focus image. This function just returns a number which, for the same scene, is greater when the image is focused.
Another solution, which seems better, is to use morphological opening or closing. I'll experiment with it.
Improvements to the focus estimation function (estimate in focus.py) are welcome.
Detecting focus peak[]
As a workaround for the focus measure not being absolute, I've implemented a slightly less easy-to-use procedure: you have to bring the object into focus, then defocus it, and then bring the object into focus again. This allows the algorithm to detect the peak value for the current scene, and snaps when the focus value is greater than 98% of the peak. If you turn the focus ring slowly enough, this seems to work (well... almost).
Limitations and known bugs[]
- If you turn the ring too fast, the image will be out of focus (because the picture is took too late). This is mainly due the communication delay between camera and PC, and slow implementation of focus evaluation function. It shouldn't be a problem if the method is implemented on the camera.
- If you change the scene, the camera may snap randomly, even if nothing is in focus.
- You have to bring the object into focus and continue turning the ring until the image blurs, and then turn the ring back.
TODO[]
- document the API
- wrap missing functions from gphoto2 (only a few are implemented right now)
- add support for Magic Lantern's PTP functions (by wrapping libptp with mweerden's patches?)
- allow uploading files to camera's card
- send ARM code to be executed, poke/peek memory locations, read/set properties...
- review memory leak when running Live Preview [?! what memory leak? ] Canon 7D, live preview for a few minutes and my system (ubuntu 12.04 & python 2.7) ran out of memory :S please help!!
- This is due to the camera file object (cfile) never being removed from memory. The object has a function to fix this error, it just needs to be called after the file is saved.
- <put your wishlist here>
libptp/win32 support[]
I've been able to compile libptp/ptpcam (the one with CHDK support, from [1]) as a DLL under windows, and call some functions from Python via ctypes. Experimental code is here:
https://github.com/alexdu/piggyPTP
However, I don't know how to compile ptpcam as a shared library (.so) under Linux. Does anyone know?