Friday, August 01, 2008

Remote Capture Canon XTi

I have been trying to get my Canon Digital Rebel XTi (400D) to repeatedly capture images via computer generated triggers (remote capture). After much fiddling with the official (Windows only) Canon SDK, I gave up on the Windows messaging requirements, since I am looking for purely console-based applications that will work well with OpenGL.

This led me to the libgphoto2 API. After some tweaking and an excellent post on the devel messageboard. I got the camera to respond well to the API calls.
This left me dealing with JPEG images referenced as FILE pointers, so I decided to include some libjpeg calls to decompress the JPEG stream in memory instead of the added file I/O overhead.

The code provided below uses openCV to convert BGR-:RGB and display images. If anyone tries this code and has suggestions feel free to comment. I have not fully tested it, so it may contain bugs. It should compile with something like: "g++ cvtest.cpp -o cvtest -L/usr/lib/ -lcxcore -lcv -lhighgui -L/usr/local/lib -lgphoto2 -lgphoto2_port -lm"


// Capture and Display code for Canon Digital Rebel XTi (7/31/08)
//
// Much of the gphoto code came from a post on the gphoto-devel mailinglist
// http://sourceforge.net/mailarchive/message.php?msg_name=Pine.LNX.4.64.0807211838060.7583%40pl2.zayda.com
// The web archives do not list the poster's information, but thanks!
//
// The code for using a custom source manager in libjpeg was modified from http://wiki.allegro.cc/Libjpeg
//

#include <stdio.h>

#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#include <jpeglib.h>
#include <jerror.h>

#include "opencv/cv.h"
#include "opencv/cxcore.h"
#include "opencv/highgui.h"

extern
"C"{
#include <gphoto2/gphoto2-abilities-list.h>
#include <gphoto2/gphoto2-camera.h>
}


#define GPHOTO_ERROR(x,y) fprintf (stderr, "%s: '%s'.\n", x, gp_result_as_string (y))

typedef struct
my_src_mgr my_src_mgr;

struct
my_src_mgr{
struct
jpeg_source_mgr pub;

JOCTET eoi_buffer
[2];
};



static
void init_source(j_decompress_ptr cinfo){
}


static
int fill_input_buffer(j_decompress_ptr cinfo){
return
1;
}


static
void skip_input_data(j_decompress_ptr cinfo, long num_bytes){
my_src_mgr
*src = (my_src_mgr *)cinfo->src;

if
(num_bytes > 0) {
while
(num_bytes > (long)src->pub.bytes_in_buffer){

num_bytes
-= (long)src->pub.bytes_in_buffer;
fill_input_buffer
(cinfo);
}
}


src
->pub.next_input_byte += num_bytes;
src
->pub.bytes_in_buffer -= num_bytes;
}


static
void term_source(j_decompress_ptr cinfo){
}



void
jpeg_memory_src(j_decompress_ptr cinfo, unsigned char const *buffer, size_t bufsize){

my_src_mgr
*src;
if
(! cinfo->src){

cinfo
->src = (jpeg_source_mgr*)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(my_src_mgr));
}


src
= (my_src_mgr *)cinfo->src;
src
->pub.init_source = init_source;

src
->pub.fill_input_buffer = fill_input_buffer;
src
->pub.skip_input_data = skip_input_data;

src
->pub.resync_to_restart = jpeg_resync_to_restart;
src
->pub.term_source = term_source;

src
->pub.next_input_byte = buffer;
src
->pub.bytes_in_buffer = bufsize;
}



int
main(){

IplImage
* imgBGR, * imgRGB;

cvNamedWindow
("test", 0);


int
i =0;

int
retval;
int
key = -1;


CameraFilePath path
;
CameraFile
*file = NULL;
CameraFileInfo info
;

Camera
*camera = NULL;

unsigned long
size;

const
unsigned char *data;

// If true this will set up memory allocation for image data structure (IplImage)
bool firstCapture = true;

//libjpeg variabless

char * raw_image= NULL;
struct
jpeg_decompress_struct cinfo;

struct
jpeg_error_mgr jerr;

//libjpeg data structure for storing one row, that is, scanline of an image
JSAMPROW row_pointer[1];
unsigned long
location = 0;


//Variables for setting gphoto to "capture"

CameraWidget
*rootconfig;
CameraWidget
*actualrootconfig;
CameraWidget
*child;

const
char *widgetinfo;
const
char *widgetlabel;

int
widgetid;
CameraWidgetType widgettype
;
CameraWidget
*capture = NULL;

//Does 0 save to internal memory and 1 save to CF?
int captureValue = 1;


// Capture an image, download it and delete it
// Initializing camera

if((retval = gp_camera_new (&camera)) != GP_OK)
GPHOTO_ERROR
("New Camera", retval);

if
((retval = gp_camera_init(camera, NULL)) != GP_OK)

GPHOTO_ERROR
("Camera Init", retval);

//Get root config
if((retval = gp_camera_get_config(camera, &rootconfig, NULL)) != GP_OK)

GPHOTO_ERROR
("Get Config", retval);
actualrootconfig
= rootconfig;

//Get main config
if((retval = gp_widget_get_child_by_name(rootconfig, "main", &child)) != GP_OK)

GPHOTO_ERROR
("Get Child - Main", retval);


//Get settings config
rootconfig = child;

if
((retval = gp_widget_get_child_by_name(rootconfig, "settings", &child)) != GP_OK)

GPHOTO_ERROR
("Get Child - Settings", retval);


//Get capture config
rootconfig = child;

if
((retval = gp_widget_get_child_by_name(rootconfig, "capture", &child)) != GP_OK)

GPHOTO_ERROR
("Get Chile - Capture", retval);


capture
= child;

gp_widget_get_name
(capture, &widgetinfo);
gp_widget_get_label
(capture, &widgetlabel);
gp_widget_get_id
(capture, &widgetid);

gp_widget_get_type
(capture, &widgettype);

//Set value for save location internal memory or CF
if((retval = gp_widget_set_value(capture, &captureValue)) != GP_OK)

GPHOTO_ERROR
("Set capture location", retval);

//Enabling capture
if((retval = gp_camera_set_config(camera, actualrootconfig, NULL)) != GP_OK)

GPHOTO_ERROR
("Enable capture with config", retval);


//Begin continuous image capture / JPEG decompression / display image
while(key != 'q'){


// NOP: This gets overridden in the library to /capt0000.jpg
strcpy(path.folder, "/store_5");
strcpy
(path.name, "foo.jpg");


//Capturing image
if((retval=gp_camera_capture (camera, GP_CAPTURE_IMAGE, &path, NULL)) != GP_OK)

GPHOTO_ERROR
("Capture Image",retval);

if
((retval = gp_file_new (&file)) != GP_OK)

GPHOTO_ERROR
("Create new file", retval);

//Downloading image
if((retval= gp_camera_file_get (camera, path.folder, path.name, GP_FILE_TYPE_NORMAL, file, NULL))!= GP_OK)

GPHOTO_ERROR
("Retrieve file",retval);


//Getting Info
if((retval=gp_camera_file_get_info(camera, path.folder, path.name, &info, NULL)) != GP_OK)
GPHOTO_ERROR
("Retrieve file info",retval);

if
((retval=gp_file_get_data_and_size (file, (const char**)&data, &size)) != GP_OK)

GPHOTO_ERROR
("Retrieve file data and size", retval);


location
= 0;

i
= 0;

// here we set up the standard libjpeg error handler
cinfo.err = jpeg_std_error( &jerr );


// setup decompression process and source, then read JPEG header
jpeg_create_decompress( &cinfo );

// setup source for memory read
jpeg_memory_src(&cinfo, data, size);

//reading the image header which contains image information
jpeg_read_header( &cinfo, TRUE );

//Uncomment the following to output image information, if needed.
//printf( "JPEG File Information: \n" );
//printf( "Image width and height: %d pixels and %d pixels.\n", cinfo.image_width, cinfo.image_height );
//printf( "Color components per pixel: %d.\n", cinfo.num_components );
//printf( "Color space: %d.\n", cinfo.jpeg_color_space );

//Start decompression jpeg here
jpeg_start_decompress( &cinfo );


if
(firstCapture){
imgBGR
=cvCreateImageHeader(cvSize( cinfo.output_width,cinfo.output_height), IPL_DEPTH_8U, cinfo.num_components);

cvCreateData
(imgBGR);

imgRGB
=cvCreateImageHeader(cvSize( cinfo.output_width,cinfo.output_height), IPL_DEPTH_8U, cinfo.num_components);

cvCreateData
(imgRGB);


firstCapture
= false;
}



//allocate memory to hold the uncompressed image
raw_image = imgBGR->imageData;

//now actually read the jpeg into the raw buffer
row_pointer[0] = (unsigned char *)malloc( cinfo.output_width*cinfo.num_components );


//read one scan line at a time
while( cinfo.output_scanline < cinfo.image_height ){

jpeg_read_scanlines
( &cinfo, row_pointer, 1 );
for
( i=0; i<cinfo.image_width*cinfo.num_components;i++){

raw_image
[location++] = row_pointer[0][i];
}
}


//wrap up decompression, destroy objects, free pointers
jpeg_finish_decompress( &cinfo );

jpeg_destroy_decompress
( &cinfo );
free
( row_pointer[0] );

//Deleting image
gp_camera_file_delete (camera, path.folder, path.name, NULL);

//Show image with openCV window and wait for key (q quits loop)
cvCvtColor(imgBGR, imgRGB, CV_BGR2RGB);
cvShowImage
("test", imgRGB);

key
= cvWaitKey();
}


//Finish up with gphoto2
if((retval=gp_camera_exit(camera, NULL)) != GP_OK)

GPHOTO_ERROR
("Exit Camera", retval);


return
0;
}

Wednesday, June 18, 2008

Towards automatic photometric correction of casually illuminated documents

George V. Landon, Yun Lin, and W. Brent Seales. Towards automatic photometric correction of casually illuminated documents. 2007 IEEE Computer Society Conference on Computer Vision and Pattern Recognition (CVPR 2007), 18-23 June 2007, Minneapolis, Minnesota, USA.

Abstract:
Creating uniform lighting for archival-quality document acquisition remains a non-trivial problem. We propose a novel method for automatic photometric correction of non-planar documents by estimating a single, point light-source using a simple light probe. By adding a simple piece of folded white paper with a known 3D surface to a scene, we are able to extract the 3D position of a light source, automatically perform white balance correction, and determine areas of poor illumination. Furthermore, this method is designed with the purpose of adding it to an already implemented document digitization pipeline. To justify our claims, we provide an accuracy analysis of our correction technique using simulated ground-truth data which allows individual sources of error to be determined and compared. These techniques are then applied on real documents that have been acquired using a 3D scanner.
article available here