Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

11 changed files with 56 additions and 947 deletions

5
.gitignore vendored
View file

@ -19,10 +19,5 @@ compile_commands.json
CTestTestfile.cmake
/build/
# generated code (unit tests)
Test*.cpp
# executables
/lines
unittest_*
gmon.out

View file

@ -1,22 +1,13 @@
cmake_minimum_required( VERSION 2.8 )
project( bs_dsp )
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif(NOT CMAKE_BUILD_TYPE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall -Werror -pedantic")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Werror -pedantic -pg")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -pedantic -D_DEBUG")
# Eigen library
include_directories( "/usr/include/eigen3/" )
# OpenCV
find_package( OpenCV 3.1 REQUIRED )
# CUDA (optional)
# CUDA
find_package( CUDA )
if (CUDA_FOUND)
include( FindCUDA )
@ -26,31 +17,5 @@ else()
message(STATUS "CUDA not found; will skip bs_dsp")
endif (CUDA_FOUND)
# OpenCV
find_package( OpenCV REQUIRED )
if(OpenCV_VERSION VERSION_LESS "2.4.8")
message(FATAL_ERROR "ERROR: Can't find OpenCV 2.4.8+")
endif()
if(OpenCV_VERSION VERSION_LESS "3.0")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_OPENCV2")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_OPENCV3")
endif (OpenCV_VERSION VERSION_LESS "3.0")
#CxxTest
find_package(CxxTest)
if(CXXTEST_FOUND)
include_directories(${CXXTEST_INCLUDE_DIR})
enable_testing()
CXXTEST_ADD_TEST(unittest_changecoord TestChangeCoord.cpp TestChangeCoord.h geometry.cpp)
CXXTEST_ADD_TEST(unittest_line TestLine.cpp TestLine.h geometry.cpp)
target_link_libraries(unittest_changecoord ${OpenCV_LIBS})
target_link_libraries(unittest_line ${OpenCV_LIBS})
else()
message(STATUS "Warning: Can't find CxxTest; testing will be disabled")
endif()
add_executable( lines lines.cpp geometry.cpp cvutils.cpp)
add_executable( lines lines.cpp )
target_link_libraries( lines ${OpenCV_LIBS} )
# vim: set et ts=4 sw=4:

View file

@ -1,133 +0,0 @@
#include <math.h>
#ifdef HAS_OPENCV3
#include <opencv2/core.hpp> //Any OPENCV3 code
#else
#include <opencv2/core/core.hpp> //Any Opencv2 code
#endif
using namespace cv;
#include <cxxtest/TestSuite.h>
#include "geometry.h"
class TestChangeCoord : public CxxTest::TestSuite
{
public:
void testOrigin(void)
{
Point origin = Point(1,2);
//the angle here is actually irrelevant
Point p = map_point(M_PI/2, origin, origin);
TS_ASSERT_EQUALS(p.x, 0);
TS_ASSERT_EQUALS(p.y, 0);
}
void testOnlyTranslation(void)
{
Point origin = Point(1,2);
Point p = map_point(0, origin, Point(0,0));
TS_ASSERT_EQUALS(p.x, -1);
TS_ASSERT_EQUALS(p.y, -2);
}
void testOnlyRotationOrigin(void)
{
Point origin = Point(0,0);
Point p = map_point(M_PI, origin, origin);
TS_ASSERT_EQUALS(p.x, 0);
TS_ASSERT_EQUALS(p.y, 0);
}
void testOnlyRotation180(void)
{
Point origin = Point(0,0);
Point p = map_point(M_PI, origin, Point(1,2));
TS_ASSERT_EQUALS(p.x, -1);
TS_ASSERT_EQUALS(p.y, -2);
}
void testOnlyRotation90(void)
{
Point origin = Point(0,0);
Point p = map_point(M_PI/2, origin, Point(1,2));
TS_ASSERT_EQUALS(p.x, 2);
TS_ASSERT_EQUALS(p.y, -1);
}
void testOnlyRotation30(void)
{
Point origin = Point(0,0);
Point p = map_point(M_PI/6, origin, Point(200,0));
TS_ASSERT_EQUALS(p.y, -100);
TS_ASSERT_LESS_THAN(p.x, 180); //0.866*2 ~= 1.73
TS_ASSERT_LESS_THAN(170, p.x); //0.866*2 ~= 1.73
}
};
class TestChangeBack : public CxxTest::TestSuite
{
public:
void testOrigin(void)
{
Point origin = Point(1,2);
Point p = unmap_point(M_PI/2, origin, Point(0,0));
TS_ASSERT_EQUALS(p.x, 1);
TS_ASSERT_EQUALS(p.y, 2);
}
void testOnlyTranslation(void)
{
Point origin = Point(1,2);
Point p = unmap_point(0, origin, Point(-1,-2));
TS_ASSERT_EQUALS(p.x, 0);
TS_ASSERT_EQUALS(p.y, 0);
}
void testOnlyRotationOrigin(void)
{
Point origin = Point(0,0);
Point p = unmap_point(M_PI, origin, origin);
TS_ASSERT_EQUALS(p.x, 0);
TS_ASSERT_EQUALS(p.y, 0);
}
void testOnlyRotation180(void)
{
Point origin = Point(0,0);
Point p = unmap_point(M_PI, origin, Point(1,2));
TS_ASSERT_EQUALS(p.x, -1);
TS_ASSERT_EQUALS(p.y, -2);
}
void testOnlyRotation90(void)
{
Point origin = Point(0,0);
Point p = unmap_point(M_PI/2, origin, Point(-2,1));
TS_ASSERT_EQUALS(p.x, -1);
TS_ASSERT_EQUALS(p.y, -2);
}
void testOnlyRotation30(void)
{
Point origin = Point(0,0);
Point p = unmap_point(M_PI/6, origin, Point(200,0));
TS_ASSERT_LESS_THAN(p.x, 175); //0.866*2 ~= 1.73
TS_ASSERT_LESS_THAN(170, p.x); //0.866*2 ~= 1.73
TS_ASSERT_EQUALS(100, p.y);
}
};
class TestCoordSystem : public CxxTest::TestSuite
{
public:
void testCreate(void)
{
auto cs = CoordinateSystem(Point(100,200), Point(200,300));
TS_ASSERT_EQUALS(cs.map(Point(300,400)).y, 0);
TS_ASSERT_EQUALS(cs.map(Point(200,100)).x, 0); //moving orthogonally
}
void testInverse(void)
{
CoordinateSystem(Point(200,300), Point(100,200));
}
};

View file

@ -1,49 +0,0 @@
#include <math.h>
#ifdef HAS_OPENCV3
#include <opencv2/core.hpp> //Any OPENCV3 code
#else
#include <opencv2/core/core.hpp> //Any Opencv2 code
#endif
using namespace cv;
#include <cxxtest/TestSuite.h>
#include "geometry.h"
class TestLine : public CxxTest::TestSuite
{
public:
void testBisector(void)
{
// x-y = 0
auto l = get_line(Point(0,0), Point(1,1));
TS_ASSERT_EQUALS(l(0), -l(1));
TS_ASSERT_EQUALS(l(2), 0);
}
void testBisectorNegative(void)
{
// x+y = 0
auto l = get_line(Point(0,0), Point(1,-1));
TS_ASSERT_EQUALS(l(0), l(1));
TS_ASSERT_EQUALS(l(2), 0);
}
void testConstant(void)
{
// y = 42
// 0x+1y-42=0
auto l = get_line(Point(0,42), Point(10,42));
TS_ASSERT_EQUALS(l(0), 0);
TS_ASSERT_EQUALS(l(2), -42*l(1));
}
void testVertical(void)
{
// x = 42
// 1x+0y-42=0
auto l = get_line(Point(42,0), Point(42,10));
TS_ASSERT_EQUALS(l(1), 0);
TS_ASSERT_EQUALS(l(2), -42*l(0));
}
};

View file

@ -1,156 +0,0 @@
#include <iostream>
#ifdef HAS_OPENCV3
#include <opencv2/core.hpp> //Any OPENCV3 code
#else
#include <opencv2/core/core.hpp> //Any Opencv2 code
#endif
#include "geometry.h"
#include "cvutils.h"
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// takes the two points that are more distant from the next, ie:
// i st distance(hull[i], hull[i+1]) is maximum and 2nd maximum
unsigned* max2_distance(std::vector<cv::Point> hull)
{
#ifdef _DEBUG
std::cout << "Hull = ";
for(auto p: hull) {
std::cout << p << " ";
}
std::cout << std::endl;
#endif
unsigned *idx = (unsigned*) calloc(2, sizeof(int));
double distance[2] = {0};
for( unsigned i=0; hull.size()>i; i++ )
{
cv::Point thispoint=hull[i];
cv::Point nextpoint=hull[(i+1)%hull.size()];
double d=dist(thispoint,nextpoint);
if( d>distance[0] ) //the biggest till now
{
idx[1]=idx[0];
idx[0]=i;
distance[1]=distance[0];
distance[0]=d;
}
else if( d>distance[1] ) // it is the second biggest till now
{
idx[1]=i;
distance[1]=d;
}
}
return idx;
}
/* check if a,b,newpoint belong to the same line (with minor approximation) */
bool similar_fit(cv::Point a, cv::Point b, cv::Point newpoint) {
return similar_fit(a, b, newpoint, 5);
}
bool similar_fit(cv::Point a, cv::Point b, cv::Point newpoint, float tolerance_degrees) {
double angle = inner_angle(a, b, newpoint) * 180 / M_PI;
if( angle > (180-tolerance_degrees) && angle < (180+tolerance_degrees) ) {
return true;
}
return false;
}
bool similar_fit(std::vector<cv::Point> group, cv::Point newpoint) {
// TODO: it should perform a fit instead of just taking first and last
assert(group.size()>=2);
return similar_fit(group[0], group[group.size()-1], newpoint);
}
/* simplify_hull will group all the points of the hull in groups that fit
* each group is a "line"
*/
std::vector<std::vector<cv::Point>>
simplify_hull(std::vector<cv::Point> hull) {
return simplify_hull( hull, 0 );
}
std::vector<std::vector<cv::Point>>
simplify_hull(std::vector<cv::Point> hull, double mindistance) {
//each element is a group of point; a group of point is a vector of points that were
//contigous and that "fit together"
std::vector<std::vector<cv::Point>> pointgroups;
std::vector<cv::Point> *group = new std::vector<cv::Point>();
cv::Point last, current;
group->push_back(hull[0]);
group->push_back(hull[1]);
for( unsigned i=2; hull.size()>i; i++ )
{
current = hull[i];
last = (*group)[group->size()-1];
if( similar_fit((*group)[0], last, hull[i]) )
{
group->push_back( hull[i] );
}
else
{
if( dist((*group)[0],last)>=mindistance )
pointgroups.push_back(*group);
group = new std::vector<cv::Point>();
group->push_back(last);
group->push_back(hull[i]);
}
}
pointgroups.push_back(*group);
//TODO: first and last might just be the same
return pointgroups;
}
/* find_longest_line: walks along the hull starting at "begin" and ending at "end", and find the
* longest (well, not strictl: it's a very simple eurhistics) group of points
* that are aligned we don't care about the number of points, but about the
* euclidean distance between the first and the last
* arg hull: the whole convex hull
* arg begin: index of the array to start from
* arg end: index of the array to end. It can be lower than start, in which case the hull is used
* "circularly"
* returns: a vector of useful points. Just use the first and the last, do not expect that the points in
* between are added to the vector
*/
std::vector<cv::Point> //two points, actually
find_longest_line(std::vector<cv::Point> hull, unsigned begin, unsigned end) //the hull can be open
{
std::vector<cv::Point> bestline, thisline;
int bestdistance = 0;
thisline.push_back(hull[(begin)%hull.size()]);
thisline.push_back(hull[(begin+1)%hull.size()]);
for(unsigned i=(begin+2)%hull.size(); i!=end; i++)
{
assert(2<=thisline.size());
if(i==hull.size()) {
i=0;
}
assert(i < hull.size());
if(similar_fit(thisline, hull[i])) {
thisline.push_back(hull[i]);
} else { // considering if the just-finished line is the best
double thisdistance = dist(thisline[0],thisline[thisline.size()-1]);
if(thisdistance>bestdistance) {
bestline = thisline;
bestdistance = thisdistance;
}
thisline.clear();
assert(bestline.size()>=2);
thisline.push_back(hull[(i-1)%hull.size()]);
thisline.push_back(hull[i]);
};
}
double thisdistance = dist(thisline[0],thisline[thisline.size()-1]);
if(thisdistance>bestdistance) { // this is relevant if the best line ends at the last point
bestline = thisline;
bestdistance = thisdistance;
}
assert(bestline.size()>=2);
return bestline;
}

View file

@ -1,22 +0,0 @@
#ifdef HAS_OPENCV3
#include <opencv2/core.hpp> //Any OPENCV3 code
#else
#include <opencv2/core/core.hpp> //Any Opencv2 code
#endif
unsigned* max2_distance(std::vector<cv::Point> hull);
bool
similar_fit(cv::Point a, cv::Point b, cv::Point newpoint);
bool
similar_fit(cv::Point a, cv::Point b, cv::Point newpoint, float tolerance_degrees);
bool
similar_fit(std::vector<cv::Point> group, cv::Point newpoint);
std::vector<std::vector<cv::Point>>
simplify_hull(std::vector<cv::Point> hull);
std::vector<std::vector<cv::Point>>
simplify_hull(std::vector<cv::Point> hull, double mindistance);
std::vector<cv::Point>
find_longest_line(std::vector<cv::Point> hull, unsigned begin, unsigned end);

View file

@ -1,53 +0,0 @@
#!/usr/bin/env python2
import numpy as np
import sys
import cv2
cv_version = int(cv2.__version__.split('.')[0])
GRAY = cv2.CV_LOAD_IMAGE_GRAYSCALE if cv_version == 2 else cv2.IMREAD_GRAYSCALE
COLOR = cv2.CV_LOAD_IMAGE_COLOR if cv_version == 2 else cv2.IMREAD_COLOR
def show(img):
#cv2.namedWindow("immagine",cv2.WINDOW_AUTOSIZE)
cv2.imshow("immagine", img)
cv2.waitKey(0)
def contourIndex(lista):
# ritorna l'indice del contorno con perimetro maggiore
val=-1
index=0
for num, cont in enumerate(lista):
value=cv2.arcLength(cont,True)
if val<value:
val=value
index=num
return index
img=cv2.imread(sys.argv[1], GRAY)
#img=cv2.imread(sys.argv[1],cv2.CV_LOAD_IMAGE_COLOR)
#la riga successiva serve a rendere visualizzabile l immagine sul nostro pc
#img=cv2.resize(img,(350,262))
#assumendo che il background e nero nelle condizioni attuali (27/12/2016) del bookscanner il valore buono e 100 (su 255)
buono=100
mask=cv2.inRange(img,buono,255)
if cv_version == 2:
contorni, hier = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
else:
_, contorni, hier = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
bianco=np.zeros(mask.shape,dtype=np.uint8) # crea un coso nero
# indexImportante indica il contorno piu grande, che crediamo sia quello del libro
indexImportante=contourIndex(contorni)
cv2.drawContours(img,contorni,indexImportante,255,thickness=3)
cv2.fillPoly(bianco,[contorni[indexImportante]], 255)
img=cv2.imread(sys.argv[1], COLOR)
img = cv2.bitwise_and(img, img, mask=bianco)
if len(sys.argv) > 2:
cv2.imwrite(sys.argv[2], img)
else:
show(img)
# vim: set ts=4 sw=4 et:

View file

@ -1,136 +0,0 @@
#include <iostream>
#ifdef HAS_OPENCV3
#include <opencv2/core.hpp> //Any OPENCV3 code
#else
#include <opencv2/core/core.hpp> //Any Opencv2 code
#endif
using namespace cv;
#include "geometry.h"
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
double dist(Point a, Point b)
{
return cv::norm(a-b);
}
double inner_angle(Point a, Point middle, Point b)
{
Point v1 = a - middle;
Point v2 = b - middle;
double v1_length = dist(Point(0, 0), v1);
double v2_length = dist(Point(0, 0), v2);
return acos( (v1.x*v2.x + v1.y*v2.y) / (v1_length*v2_length) );
}
CoordinateSystem::CoordinateSystem(double angle, Point a)
{
theta = angle;
origin = a;
precompute();
assert(map(a) == Point(0,0));
assert(unmap(Point(0,0)) == a);
}
CoordinateSystem::CoordinateSystem(Point a, Point b)
{
theta = atan2(b.y-a.y, b.x-a.x);
origin = a;
precompute();
assert(map(a) == Point(0,0));
assert(unmap(Point(0,0)) == a);
assert(map(b).y == 0);
assert(map(b).x > 0);
}
void CoordinateSystem::precompute() {
double costheta = cos(theta);
double sintheta = sin(theta);
R(0,0) = costheta;
R(0,1) = -sintheta;
R(1,0) = sintheta;
R(1,1) = costheta;
Rt(0,0) = costheta;
Rt(0,1) = sintheta;
Rt(1,0) = -sintheta;
Rt(1,1) = costheta;
}
Point CoordinateSystem::map(Point tomap) {
//that's the algorithm: res = Rt*(tomap - origin)
cv::Vec<double,2> p_as_vector;
Point p;
p = tomap - origin;
p_as_vector[0] = p.x;
p_as_vector[1] = p.y;
cv::Vec<double,2> res_as_vector = Rt*p_as_vector;
return Point(res_as_vector);
}
Point CoordinateSystem::unmap(Point tounmap) {
cv::Vec<double,2> q_as_vector;
q_as_vector[0] = tounmap.x;
q_as_vector[1] = tounmap.y;
Point RQ = Point(R*q_as_vector);
return Point(RQ + origin);
}
Point
map_point(double theta, Point origin, Point tomap)
{
auto cs = CoordinateSystem(theta, origin);
return cs.map(tomap);
}
Point
unmap_point(double theta, Point origin, Point tounmap)
{
return CoordinateSystem(theta, origin).unmap(tounmap);
}
//that's the algorithm: res = Rt*(tomap - origin)
cv::Vec<double,3>
get_line(Point p, Point q)
{
cv::Vec<double,3> line; //the three positions are a,b,c as in: ax + by + c = 0
Point vector = q-p;
line(0) = - vector.y;
line(1) = vector.x;
line(2) = vector.y * p.x - vector.x * p.y;
assert(is_in_line(line, p));
assert(is_in_line(line, q));
return line;
}
bool
is_in_line(cv::Vec<double,3> line, Point p)
{
return line_value(line, p) == 0;
}
bool
is_above_line(cv::Vec<double,3> line, Point p)
{
return line_value(line, p) > 0;
}
double line_value(cv::Vec<double,3> line, Point p)
{
return line(0)*p.x + line(1)*p.y + line(2);
}
cv::Point
further_point_from_line(cv::Vec<double,3> line, std::vector<Point> points)
{
//distance = abs(a*p.x+b*p.y+c) / sqrt(a^2+b^2)
Point further = points[0];
double maxdist = -1;
for(Point p: points)
{
double numerator = abs(line(0)*p.x + line(1)*p.y + line(2));
if(numerator > maxdist) {
maxdist = numerator;
further = p;
}
}
return further;
}

View file

@ -1,30 +0,0 @@
#ifdef HAS_OPENCV3
#include <opencv2/core.hpp> //Any OPENCV3 code
#else
#include <opencv2/core/core.hpp> //Any Opencv2 code
#endif
double dist(cv::Point, cv::Point);
double inner_angle(cv::Point, cv::Point, cv::Point);
cv::Point map_point(double, cv::Point, cv::Point);
cv::Point unmap_point(double, cv::Point, cv::Point);
cv::Vec<double,3> get_line(cv::Point, cv::Point);
bool is_in_line(cv::Vec<double,3> line, cv::Point p);
bool is_above_line(cv::Vec<double,3> line, cv::Point p);
double line_value(cv::Vec<double,3>, cv::Point);
cv::Point further_point_from_line(cv::Vec<double,3>, std::vector<cv::Point>);
class CoordinateSystem
{
private:
double theta;
cv::Matx<double,2,2> R, Rt;
cv::Point origin;
void precompute();
public:
CoordinateSystem(cv::Point, cv::Point);
CoordinateSystem(double angle, cv::Point origin_);
cv::Point map(cv::Point p);
cv::Point unmap(cv::Point p);
};

367
lines.cpp
View file

@ -1,326 +1,63 @@
#include<cmath>
#include<algorithm>
#include<unistd.h>
#include<fstream>
#include<iomanip>
#ifdef HAS_OPENCV3
#include <opencv2/core.hpp> //Any OPENCV3 code
#else
#include <opencv2/core/core.hpp> //Any Opencv2 code
#endif
#include"lines.h"
#include "geometry.h"
#include "cvutils.h"
using namespace cv;
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// all those colors are still visible on b/w (the first component is not so low)
auto WHITE = cv::Scalar(255,255,255);
auto BLUE = cv::Scalar(200,50,50);
auto MAGENTA = cv::Scalar(110,10,200);
auto BROWN = cv::Scalar(90,100,60);
auto BLACK = cv::Scalar(0,0,0);
auto YELLOW = cv::Scalar(20,200,200);
void usage(char* program, std::ostream &buf) {
buf << "Usage: " << program << "[options] IMAGE-INPUT" << std::endl;
buf << "Identify the image as a book in perspective, get deperspectivized content" << std::endl;
buf << std::endl;
buf << "Options:" << std::endl;
buf << " -p PROFILE Save the transformation matrix in PROFILE" << std::endl;
buf << " -l LEFTPAGE Save the left page in LEFTPAGE" << std::endl;
buf << " -r RIGHTPAGE Save the right page in RIGHTPAGE" << std::endl;
buf << " -h HELP Show this help and exit" << std::endl;
}
class Options
{
public:
char *left;
char *right;
char *profile;
char *input;
Options();
Options(int argc, char*argv[]);
bool parse(int argc, char*argv[]);
~Options();
};
Options::Options()
{
left = right = profile = input = NULL;
}
Options::~Options()
{
if(left == NULL) {
free(left);
}
if(right == NULL) {
free(right);
}
if(profile == NULL) {
free(profile);
}
//input doesn't need to be free-d: it points to argv[argc-1]
}
Options::Options(int argc, char *argv[])
{
left = right = profile = input = NULL;
parse(argc, argv);
}
/* Options::parse parse arguments, return true if help is requested */
bool Options::parse(int argc, char *argv[])
{
int c;
while((c=getopt(argc, argv, "p:l:r:h")) != -1)
{
switch(c)
{
case 'l':
left = strdup(optarg);
break;
case 'r':
right = strdup(optarg);
break;
case 'p':
profile = strdup(optarg);
break;
case 'h':
return true;
break;
default:
throw std::runtime_error("Parsing error: invalid argument");
}
}
if(optind >= argc) {
std::cerr << "Error: " << argv[0] << " needs an argument" << std::endl;
throw std::runtime_error("Parsing error: argument needed");
}
input = argv[optind++];
if(optind < argc) {
std::cerr << "Error: too many arguments supplied" << std::endl;
throw std::runtime_error("Parsing error: too many arguments");
}
return false;
}
class BookShape {
private:
cv::Mat trasf[2];
void precompute();
cv::Point2f trapezoids[2][4]; //transformation matrix
public:
BookShape(cv::Point tl, cv::Point tm, cv::Point tf,
cv::Point bl, cv::Point bm, cv::Point br);
double xsize();
double ysize();
cv::Mat* getTrasfs();
};
//topleft topmiddle topright
//botleft botmiddle botright
BookShape::BookShape(cv::Point tl, cv::Point tm, cv::Point tr, cv::Point bl, cv::Point bm, cv::Point br)
{
trapezoids[0][0] = tl;
trapezoids[0][1] = tm;
trapezoids[0][2] = bm;
trapezoids[0][3] = bl;
trapezoids[1][0] = tm;
trapezoids[1][1] = tr;
trapezoids[1][2] = br;
trapezoids[1][3] = bm;
precompute();
}
void BookShape::precompute(void) {
cv::Point2f outsizes[4] = {cv::Point2f(0, 0), cv::Point(xsize(), 0),
cv::Point(xsize(), ysize()), cv::Point(0, ysize())};
for(int i=0; i < 2; i++) {
trasf[i] = cv::getPerspectiveTransform(trapezoids[i], outsizes);
assert(9==trasf[i].total()); // 3x3
}
}
cv::Mat* BookShape::getTrasfs()
{
return trasf;
}
double BookShape::xsize()
{
return dist(trapezoids[0][0], trapezoids[0][1]);
}
double BookShape::ysize()
{
return dist(trapezoids[0][1], trapezoids[0][2]);
}
BookShape get_book_shape(cv::Mat img)
{
//dotwidth is just a simple size so that lines and dots are visible on big images
//but not huge on small images
#ifdef _DEBUG
unsigned short dotwidth = img.cols >> 6; // divide by 64, so efficient
#endif
std::vector< std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy; //this is not really useful
cv::findContours(img,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);
if( 0==contours.size() ) {
std::cerr << "No contours found" << std::endl;
throw "Error getting contours";
}
auto contour = contours[0];
if( 1!=contours.size() )
{
// choosing the biggest contour in the image
// you can test it with files/2contours.png
for(auto cont: contours) {
if(cont.size() > contour.size())
contour = cont;
}
}
//hull: points forming an external convex border
std::vector<cv::Point> hull;
cv::convexHull(cv::Mat(contour),hull,false);
//because of the "butterfly" shape of a book, the most distant points in the hull
//are the one between the corner.
unsigned *maxdistances = max2_distance(hull);
cv::Point corn_1, corn_2, corn_3, corn_4;
corn_1 = hull[maxdistances[0]];
corn_2 = hull[(maxdistances[0]+1)%hull.size()];
corn_3 = hull[maxdistances[1]];
corn_4 = hull[(maxdistances[1]+1)%hull.size()];
std::vector<cv::Point> vertical_points[2];
cv::Vec<double,3> verticals[2];
// Between the two corners on the same side, the longest line is the vertical border of the book
vertical_points[0] = find_longest_line(hull, (maxdistances[0]+1)%hull.size(), maxdistances[1]);
std::cout << maxdistances[1] << std::endl;
std::cout << maxdistances[1]+1 << std::endl;
std::cout << (maxdistances[1]+1)%hull.size() << std::endl;
vertical_points[1] = find_longest_line(hull, (maxdistances[1]+1)%hull.size(), maxdistances[0]);
free(maxdistances);
#ifdef _DEBUG
img = cv::imread("files/masckera.png", CV_LOAD_IMAGE_COLOR);
cv::circle(img, vertical_points[0][0], img.cols>>7, cv::Scalar(200, 0, 0), -1);
cv::circle(img, vertical_points[0][vertical_points[0].size()-1], img.cols>>7, cv::Scalar(200, 0, 0), -1);
cv::circle(img, vertical_points[1][0], img.cols>>7, cv::Scalar(200, 200, 0), -1);
cv::circle(img, vertical_points[1][vertical_points[1].size()-1], img.cols>>7, cv::Scalar(200, 0, 0), -1);
cv::circle(img, corn_1, img.cols>>7, cv::Scalar(200, 0, 200), -1);
cv::namedWindow("aaa", CV_GUI_NORMAL);
cv::imshow("aaa", img);
cv::waitKey(0);
#endif
verticals[0] = get_line(vertical_points[0][0], vertical_points[0][vertical_points[0].size()-1]);
std::cout << verticals[0] << std::endl;
std::cout << line_value(verticals[0], corn_1) << std::endl;
verticals[0][2] -= line_value(verticals[0], corn_1);
std::cout << line_value(verticals[0], corn_1) << "=0?" << std::endl;
verticals[1] = get_line(vertical_points[1][0], vertical_points[1][vertical_points[1].size()-1]);
// theta is the angle of the line connecting point 1 and 2; it will be the
// rotation of our new coordinate system
auto cs = CoordinateSystem(corn_1, corn_2);
assert(cs.map(corn_1).x == 0);
assert(cs.map(corn_1).y == 0);
assert(cs.map(corn_2).x > 0);
assert(cs.map(corn_2).y == 0);
cv::Vec<double,3> diag1, diag2;
diag1 = get_line(cs.map(corn_1), cs.map(corn_3));
diag2 = get_line(cs.map(corn_4), cs.map(corn_2));
std::cout << "mapped diag1: " << diag1 << std::endl;
std::cout << "mapped diag2: " << diag2 << std::endl;
std::vector<cv::Point> points1, points2;
for(cv::Point p: contour)
{ // the point is interesting where it is above both lines or below both lines
cv::Point mapped = cs.map(p);
if(is_above_line(diag1, mapped)) {
if(is_above_line(diag2, mapped)) {
points1.push_back(p);
}
} else {
if(!is_above_line(diag2, mapped)) {
points2.push_back(p);
}
}
}
cv::Point middle1 = further_point_from_line(get_line(corn_1, corn_2), points2);
cv::Point middle2 = further_point_from_line(get_line(corn_3, corn_4), points1);
BookShape bs = BookShape(
vertical_points[1][vertical_points[1].size()-1], middle1, vertical_points[0][0],
vertical_points[1][0], middle2, vertical_points[0][vertical_points[0].size()-1]
);
return bs;
}
#include<opencv2/opencv.hpp>
int main(int argc, char *argv[])
{
Options args;
try{
if(args.parse(argc, argv)) {
usage(argv[0], std::cout);
return EXIT_SUCCESS;
}
} catch(std::runtime_error) {
usage(argv[0], std::cerr);
return EXIT_FAILURE;
cv::Mat img=cv::imread("files/masckera.png",CV_LOAD_IMAGE_GRAYSCALE);
std::vector< std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(img,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);
cv::drawContours(img,contours,-1,cv::Scalar(255,255,255),5);
if( 1!=contours.size() )
{
std::cout << "non ci piace" << std::endl;
return -1;
}
cv::Mat img;
img=cv::imread(args.input,CV_LOAD_IMAGE_GRAYSCALE);
if( img.empty() ) {
std::cerr << "Error opening image, aborting" << std::endl;
return EXIT_FAILURE;
}
BookShape bs = get_book_shape(img);
std::vector<cv::Point> hull;
cv::convexHull(cv::Mat(contours[0]),hull,false);
if(args.profile) {
// format: each line is a tab-delimited 3x3 transformation matrix (as get by getPerspectiveTransform)
// first three elements is the first line, etc
std::ofstream profilebuf;
profilebuf.open(args.profile);
for(int t=0; t<2; t++) {
for(int i=0; i<3; i++) {
double* row= bs.getTrasfs()[t].ptr<double>(i);
for(int j=0; j < 3; j++) {
profilebuf << std::fixed << std::setprecision(16);
profilebuf << row[j] << "\t"; //yes, there's a trailing tab
}
}
profilebuf << std::endl;
int i;
int idx[4]={0};
int distance[2]={0};
for( i=0; hull.size()>i; i++ )
{
cv::Point one=hull[i];
cv::Point two=hull[(i+1)%hull.size()];
int d=pow(one.x-two.x,2)+pow(one.y-two.y,2);
if( d>distance[0] )
{
idx[1]=idx[0];
idx[3]=(idx[1]+1)%hull.size();
idx[0]=i;
idx[2]=(idx[0]+1)%hull.size();
distance[1]=distance[0];
distance[0]=d;
}
profilebuf.close();
}
//TODO: distinguish left and right
std::vector<int> params;
if(args.left || args.right) {
//we are rereading in full color to get colored output
img = cv::imread(args.input,CV_LOAD_IMAGE_COLOR);
cv::Mat rect[2]; //final pages, transformed in a nice rectangle
params.push_back(CV_IMWRITE_PNG_COMPRESSION);
params.push_back(9);
if(args.left) {
cv::warpPerspective(img, rect[0], bs.getTrasfs()[0], cv::Size(bs.xsize(), bs.ysize()));
cv::imwrite(args.left, rect[0], params);
}
if(args.right) {
cv::warpPerspective(img, rect[1], bs.getTrasfs()[1], cv::Size(bs.xsize(), bs.ysize()));
cv::imwrite(args.right, rect[1], params);
else if( d>distance[1] )
{
idx[1]=i;
idx[3]=(i+1)%hull.size();
distance[1]=d;
}
}
return EXIT_SUCCESS;
cv::circle(img,hull[idx[0]],30,255,-1);
cv::circle(img,hull[(idx[0]+1)%hull.size()],30,255,-1);
cv::circle(img,hull[idx[1]],20,255,-1);
cv::circle(img,hull[(idx[1]+1)%hull.size()],20,255,-1);
std::cout << hull.size() << ", " << contours[0].size() << std::endl;
std::cout
<< "(" << idx[0] << "," << distance[0] << ") -- "
<< "(" << idx[1] << "," << distance[1] << ")" << std::endl;
cv::namedWindow("test",CV_WINDOW_NORMAL);
cv::imshow("test",img);
cv::waitKey(0);
return 0;
}
// vim: set noet ts=4 sw=4:

View file

@ -1,9 +0,0 @@
#include<algorithm>
#include<opencv2/opencv.hpp>
std::vector<std::vector<cv::Point>> simplify_hull(std::vector<cv::Point> hull);
std::vector<std::vector<cv::Point>> simplify_hull(std::vector<cv::Point> hull, double mindistance);
bool similar_fit(cv::Point a, cv::Point b, cv::Point newpoint);
bool similar_fit(cv::Point a, cv::Point b, cv::Point newpoint, float tolerance_degrees);