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

Monday, March 05, 2007

Special issue on 3D acquisition technology for cultural heritage

Machine Vision and Applications has recently published a special issue on 3D acquisition for cultural heritage edited by Luc Van Gool and Robert Sablatnig.

Table of Contents:

  1. Three-dimensional acquisition of large and detailed cultural heritage objects
    • Gabriele Guidi, Bernard Frischer, Michele Russo, Alessandro Spinetti, Luca Carosso, and Laura Loredana Micoli

  2. Petroglyph digitization: enabling cultural heritage scholarship
    • George V. Landon and W. Brent Seales

  3. A System for 3D Modeling Frescoed Historical Buildings with Multispectral Texture
    • N. Brusco, S. Capeleto, M. Fedel, A. Paviotti, L. Poletto, G. M. Cortelazzo, and G. Tondello

  4. Recent Developments in 3D Multi-modal Laser Imaging Applied to Cultural Heritage
    • François Blais and J. Angelo Beraldin

  5. Web-based 3D Reconstruction Service
    • Maarten Vergauwen and Luc Van Gool

Thursday, November 02, 2006

Petroglyph digitization: enabling cultural heritage scholarship

George V. Landon and W. Brent Seales, “Petroglyph digitization: enabling cultural heritage scholarship,” Machine Vision and Applications, vol. 17, no. 6, pp. 361–371, December 2006.

Abstract:
The digitization of antiquities is facilitating a renaissance for scholars who have unprecedented access to rich representations of objects. Cultural Heritage digitization is a central challenge, and its subtleties are intertwined with object properties and the constraints of physical access and handling. In this paper, we present the design and analysis of a system built for the digitization of Puerto Rican petroglyphic iconography. The petroglyphs exhibit unique properties (shape, size, surface) that determine system design choices. The 3D models obtained with the system support new scholarly and educational activities, including interactive surface lighting, feature highlighting and annotation through mark-up, and immersive viewing using large-scale displays.

Available Online

Friday, September 22, 2006

Computer Science & Illusion

As noted on Robert Burke's blog, Trinity College Dublin (TCD) recently hosted a great lecture by Prof. Andrew Blake. He really runs the gambit on topics in computer vision covering various theories and applications.


Part 1:


Part 2:


If you prefer not to stream: Full Movie Download (118MB WMV).
Also, the PowerPoint slides are available (44MB ZIPPED PPT).

Monday, September 18, 2006

Is Computer Science losing its coolness?

The Seattle Times has just run an interesting story, titled Where'd The Whiz Kids Go?, on the lack of CS students in Washington Universities. It seems that this is a nation-wide problem that has gotten big enough to get the attention of politicians.

From the article:

"... despite the seemingly limitless potential of computers, educators are having a tougher time than ever convincing students to pursue the field. It can be hard work. Boring, even. And there's that enduring, if unfair, image problem. Picture the socially inept geek hunched over a screen at 3 a.m., Coke in hand, pecking away at pages of incomprehensible code.

'There was such a boom of interest in the '90s, and now you get the sense around the country that computer science is past its prime. But the most exciting stuff is still in front of us.'

Meanwhile, Microsoft continues to add workers locally at the rate of 4,000 a year. In this year's record class of 5,400 UW freshmen, 300 say they're hoping to graduate in computer science or engineering. Even if none dropped out or changed majors, the class of 2010 wouldn't amount to a month's supply of new workers needed just at Microsoft's Redmond campus."
There certainly does not seem to be an easy solution to this shortage. It is also interesting to note that the ACT 2006 National Report shows that 2.05% of test takers are interested in at least a 4-year Computer Science or Math degree.

Wednesday, September 13, 2006

Interactive Tabletop Museum Exhibits

In the most recent issue of IEEE Computer Graphics and Applications, there is an interesting article titled "Interactive Tabletop Exhibits in Museums and Galleries."

From the article:

"The museum experience is an unusually tactile, sensual one, and the standard keyboard-mouse-and-screen setup might seem out of place. This trend toward sensual involvement is particularly noticeable in tabletop displays, as they appeal to two aspects of familiar daily life: the horizontal surface as a workspace, and hand gestures (or common objects) as tools for manipulating information."
The full article can be viewed online (PDF). Readers should notice that the article contains a nice sampling of various interactive/tabletop exhibits. The Tilty Table, in particular, demonstrates that interactivity and intuitive access can (and should) be combined in the museum experience. A sample video is available (MOV).