Skip to content

Commit

Permalink
Add the existing source files
Browse files Browse the repository at this point in the history
  • Loading branch information
ytsutano committed Oct 22, 2012
1 parent 20bdbad commit c19cf0b
Show file tree
Hide file tree
Showing 14 changed files with 9,016 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
*.lai
*.la
*.a

# Ignore SCons related files
.sconsign.dblite

# Ignore OS X related files
.DS_Store
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
bookscan
========

Single camera solution for book scanner.
Single camera solution for book scanner.

## WARNING: EXPERIMENTAL PROJECT.

This program was written in ad-hoc manner without thinking about
publishing the source code. It was just a test of concept.

I've got many requests to publish the program after I posted YouTube videos:
* [Book Scanner: First Prototype](http://www.youtube.com/watch?v=rjzxlA9RWio)
* [Book Scanner: Marker Test](http://www.youtube.com/watch?v=YXANjnry6CU)
* [Book Scanner: Image Processing Test #1](http://www.youtube.com/watch?v=lHHPFBH2EkA)

So I published it. But it was a project that was done in a day years ago, and
I have no time to verify it still works today.

## Compilation

I have installed OpenCV using MacPorts:
sudo port install opencv

Compile using SCons:
scons

## Usage

The program takes three arguments:
./extpage test_input.jpg output_left.jpg output_right.jpg
where test_input.jpg is the input file name, and the following two are the
output file names.

## Contributor

[Yutaka Tsutano](http://yutaka.tsutano.com)
26 changes: 26 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os.path

sources = ['main.cpp', 'marker.cpp', 'page.cpp']
target = 'extpage'
objs = []

for s in sources:
objs.append(s.replace('.cpp', '.o'))

env = Environment(ENV = {'PATH': os.environ['PATH']})

env.AppendUnique(CCFLAGS = ['-O2'])
env.AppendUnique(CCFLAGS = ['-Wall'])

env.AppendUnique(CCFLAGS = ['-I/opt/local/include/'])
env.AppendUnique(CCFLAGS = ['-I/opt/local/include/opencv'])
env.AppendUnique(CCFLAGS = ['-I/opt/local/include/opencv2'])

env.AppendUnique(LINKFLAGS = ['-L/opt/local/lib'])
env.AppendUnique(LINKFLAGS = ['-lopencv_core'])
env.AppendUnique(LINKFLAGS = ['-lopencv_highgui'])
env.AppendUnique(LINKFLAGS = ['-lopencv_imgproc'])
env.AppendUnique(LINKFLAGS = ['-lopencv_calib3d'])

env.Object(source = sources)
env.Program(target = target, source = objs)
Binary file added extpage
Binary file not shown.
123 changes: 123 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/imgproc_c.h>

#include <iostream>
#include <vector>
#include <string>

#include "marker.h"
#include "page.h"

void process_image(IplImage *src_img,
std::map<int, CvPoint2D32f> &left_dst_markers,
LayoutInfo left_layout,
std::map<int, CvPoint2D32f> &right_dst_markers,
LayoutInfo right_layout)
{
BookImage book_image(src_img);

{
IplImage *dst_img
= book_image.create_page_image(left_dst_markers, left_layout);
if (dst_img != NULL) {
cvShowImage("Left", dst_img);
cvReleaseImage(&dst_img);
}
}

{
IplImage *dst_img
= book_image.create_page_image(right_dst_markers, right_layout);
if (dst_img != NULL) {
cvShowImage("Right", dst_img);
cvReleaseImage(&dst_img);
}
}
}

int main(int argc, char **argv)
{
// Configure left page.
std::map<int, CvPoint2D32f> left_dst_markers;
left_dst_markers[0] = cvPoint2D32f(0.00, 0.00);
left_dst_markers[1] = cvPoint2D32f(6.00, 0.00);
left_dst_markers[2] = cvPoint2D32f(6.00, 9.50);
left_dst_markers[3] = cvPoint2D32f(0.00, 9.50);
LayoutInfo left_layout;
left_layout.page_left = 0.50;
left_layout.page_top = 0.25;
left_layout.page_right = 6.30;
left_layout.page_bottom = 9.20;
left_layout.dpi = 600.0;

// Configure right page.
std::map<int, CvPoint2D32f> right_dst_markers;
right_dst_markers[4] = cvPoint2D32f(0.00, 0.00);
right_dst_markers[5] = cvPoint2D32f(6.00, 0.00);
right_dst_markers[6] = cvPoint2D32f(6.00, 9.50);
right_dst_markers[7] = cvPoint2D32f(0.00, 9.50);
LayoutInfo right_layout;
right_layout.page_left = -0.30;
right_layout.page_top = 0.25;
right_layout.page_right = 5.50;
right_layout.page_bottom = 9.20;
right_layout.dpi = 600.0;

// Process if an input image is supplied; otherwise, open a webcam for
// debugging.
if (argc > 3) {
IplImage *src_img = cvLoadImage(argv[1]);
if (src_img == NULL) {
std::cerr << "Failed to load the source image specified.\n";
return 1;
}

BookImage book_img(src_img);

IplImage *left_img
= book_img.create_page_image(left_dst_markers, left_layout);
if (left_img != NULL) {
cvSaveImage(argv[2], left_img);
cvReleaseImage(&left_img);
}

IplImage *right_img
= book_img.create_page_image(right_dst_markers, right_layout);
if (right_img != NULL) {
cvSaveImage(argv[3], right_img);
cvReleaseImage(&right_img);
}

cvReleaseImage(&src_img);
} else {
// Create windows.
cvNamedWindow("Source", 0);
cvResizeWindow("Source", 480, 640);

left_layout.dpi = 100;
right_layout.dpi = 100;

// Open webcam.
CvCapture* capture = cvCreateCameraCapture(0);
if (!capture) {
std::cerr << "Failed to load the camera device.\n";
return 1;
}
const double scale = 1.0;
cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, 1600 * scale);
cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, 1200 * scale);

while (cvWaitKey(10) < 0) {
IplImage *src_img = cvQueryFrame(capture);
cvShowImage("Source", src_img);
process_image(src_img,
left_dst_markers, left_layout,
right_dst_markers, right_layout);
}
}

return 0;
}
184 changes: 184 additions & 0 deletions marker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#include "marker.h"

int decode_marker(CvMat *mark_mat, marker_rotation_t &rotation)
{
// Make sure that the outermost cells are black.
for (int i = 0; i < 6; i++) {
if (cvmGet(mark_mat, i, 0) > 0.5
|| cvmGet(mark_mat, i, 5) > 0.5
|| cvmGet(mark_mat, 0, i) > 0.5
|| cvmGet(mark_mat, 5, i) > 0.5) {
return -1;
}
}

// Make sure that the next cells of the outermost cells are white.
if (cvmGet(mark_mat, 2, 1) < 0.5
|| cvmGet(mark_mat, 3, 1) < 0.5
|| cvmGet(mark_mat, 1, 2) < 0.5
|| cvmGet(mark_mat, 1, 3) < 0.5
|| cvmGet(mark_mat, 2, 4) < 0.5
|| cvmGet(mark_mat, 3, 4) < 0.5
|| cvmGet(mark_mat, 4, 2) < 0.5
|| cvmGet(mark_mat, 4, 3) < 0.5) {
return -1;
}

// Make sure that the number of corner marker is exactly one. Also
// detect the orientation.
int numberOfCornerMarkers = 0;
if (cvmGet(mark_mat, 1, 1) < 0.5) {
numberOfCornerMarkers++;
rotation = MARKER_ROT_0_DEG;
}
if (cvmGet(mark_mat, 1, 4) < 0.5) {
numberOfCornerMarkers++;
rotation = MARKER_ROT_90_DEG;
}
if (cvmGet(mark_mat, 4, 4) < 0.5) {
numberOfCornerMarkers++;
rotation = MARKER_ROT_180_DEG;
}
if (cvmGet(mark_mat, 4, 1) < 0.5) {
numberOfCornerMarkers++;
rotation = MARKER_ROT_270_DEG;
}
if (numberOfCornerMarkers != 1) {
return -1;
}

int id = 0;
switch (rotation) {
case MARKER_ROT_0_DEG:
id = ((cvmGet(mark_mat, 2, 2) < 0.5) << 3)
| ((cvmGet(mark_mat, 2, 3) < 0.5) << 2)
| ((cvmGet(mark_mat, 3, 2) < 0.5) << 1)
| ((cvmGet(mark_mat, 3, 3) < 0.5) << 0);
break;
case MARKER_ROT_90_DEG:
id = ((cvmGet(mark_mat, 2, 2) < 0.5) << 1)
| ((cvmGet(mark_mat, 2, 3) < 0.5) << 3)
| ((cvmGet(mark_mat, 3, 2) < 0.5) << 0)
| ((cvmGet(mark_mat, 3, 3) < 0.5) << 2);
break;
case MARKER_ROT_180_DEG:
id = ((cvmGet(mark_mat, 2, 2) < 0.5) << 0)
| ((cvmGet(mark_mat, 2, 3) < 0.5) << 1)
| ((cvmGet(mark_mat, 3, 2) < 0.5) << 2)
| ((cvmGet(mark_mat, 3, 3) < 0.5) << 3);
break;
case MARKER_ROT_270_DEG:
id = ((cvmGet(mark_mat, 2, 2) < 0.5) << 2)
| ((cvmGet(mark_mat, 2, 3) < 0.5) << 0)
| ((cvmGet(mark_mat, 3, 2) < 0.5) << 3)
| ((cvmGet(mark_mat, 3, 3) < 0.5) << 1);
break;
}

// Now determine the id using a table.
static const unsigned char id_table[] = {
8, 2, 4, 15, 6, 13, 11, 1,
0, 10, 12, 7, 14, 5, 3, 9
};
id = id_table[id];

return id;
}

int analyze_marker(const IplImage *src_img, CvSeq *poly, CvPoint2D32f *points)
{
// Make sure the shape is square and convex.
if (poly->total != 4
|| !cvCheckContourConvexity(poly)
|| cvContourArea(poly) < 360.0) {
return -1;
}

for (int i = 0; i < 4; i++) {
points[i] = cvPointTo32f(*CV_GET_SEQ_ELEM(CvPoint, poly, i));
}

// Refine to sub-pixel accuracy.
cvFindCornerSubPix(src_img, points, 4, cvSize(3, 3), cvSize(-1, -1),
cvTermCriteria (CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03));

double a[] = {
points[0].x, points[0].y,
points[1].x, points[1].y,
points[2].x, points[2].y,
points[3].x, points[3].y
};
CvMat src_points = cvMat(poly->total, 2, CV_64FC1, a);

static const int MARK_WIDTH = 6*3;
static const int MARK_HEIGHT = 6*3;
static const int MARK_TEMP_WIDTH = MARK_WIDTH * 10;
static const int MARK_TEMP_HEIGHT = MARK_HEIGHT * 10;
IplImage *mark_temp_img = cvCreateImage(
cvSize(MARK_TEMP_WIDTH, MARK_TEMP_HEIGHT), IPL_DEPTH_8U, 1);
double b[] = {
0, 0,
0, MARK_TEMP_HEIGHT,
MARK_TEMP_WIDTH, MARK_TEMP_HEIGHT,
MARK_TEMP_WIDTH, 0,
};
CvMat mark_points = cvMat(poly->total, 2, CV_64FC1, b);

// Compute homography matrix.
CvMat *h = cvCreateMat(3, 3, CV_64FC1);
cvFindHomography(&src_points, &mark_points, h);

// Transform perspective.
cvWarpPerspective(src_img, mark_temp_img, h);

IplImage *mark_img = cvCreateImage(
cvSize(MARK_WIDTH, MARK_HEIGHT), IPL_DEPTH_8U, 1);
cvResize(mark_temp_img, mark_img, CV_INTER_AREA);
int threshold = cvAvg(mark_img).val[0];
cvThreshold(mark_img, mark_img, threshold, 255, CV_THRESH_BINARY);
for (int i = 0; i < MARK_WIDTH; i++) {
for (int j = 0; j < MARK_HEIGHT; j++) {
if ((i % 3) != 1 || (j % 3) != 1) {
cvSetReal2D(mark_img, i, j, threshold);
}
}
}

// Create marker matrix.
CvMat *mark_mat = cvCreateMat(6, 6, CV_64FC1);
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 6; j++) {
cvmSet(mark_mat, i, j, (cvGetReal2D(mark_img, i*3+1, j*3+1) > 128));
}
}

// Decode the marker ID.
marker_rotation_t rotation;
int marker_id = decode_marker(mark_mat, rotation);

// Based on rotation, correct the points array so that it starts from
// the corner with the rotation dot.
CvPoint2D32f temp[4];
for (int i = 0; i < 4; i++) {
temp[i] = points[i];
}
for (int i = 0; i < 4; i++) {
points[i] = temp[(i + rotation) % 4];
}

// Show decoded marker image for debugging.
/*
if (marker_id != -1) {
cvResize(mark_img, mark_temp_img, CV_INTER_AREA);
cvShowImage("Window", mark_temp_img);
}
//*/

// Clean up.
cvReleaseMat(&h);
cvReleaseMat(&mark_mat);
cvReleaseImage(&mark_img);
cvReleaseImage(&mark_temp_img);

return marker_id;
}
Loading

0 comments on commit c19cf0b

Please sign in to comment.