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
5
.gitignore
vendored
|
@ -19,10 +19,5 @@ compile_commands.json
|
|||
CTestTestfile.cmake
|
||||
/build/
|
||||
|
||||
# generated code (unit tests)
|
||||
Test*.cpp
|
||||
|
||||
# executables
|
||||
/lines
|
||||
unittest_*
|
||||
gmon.out
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
49
TestLine.h
49
TestLine.h
|
@ -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));
|
||||
}
|
||||
};
|
156
cvutils.cpp
156
cvutils.cpp
|
@ -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;
|
||||
}
|
||||
|
22
cvutils.h
22
cvutils.h
|
@ -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);
|
|
@ -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:
|
136
geometry.cpp
136
geometry.cpp
|
@ -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;
|
||||
}
|
30
geometry.h
30
geometry.h
|
@ -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
367
lines.cpp
|
@ -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:
|
||||
|
|
9
lines.h
9
lines.h
|
@ -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);
|
Loading…
Reference in a new issue