123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- #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;
- }
- 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;
- img=cv::imread(args.input,CV_LOAD_IMAGE_GRAYSCALE);
- if( img.empty() ) {
- std::cerr << "Error opening image, aborting" << std::endl;
- return EXIT_FAILURE;
- }
- //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;
- return EXIT_FAILURE;
- }
- 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()];
- #ifdef _DEBUG
- img = cv::imread(args.input,CV_LOAD_IMAGE_COLOR); // uncomment to this to display image with colors
- std::cout << "Maxdist1:" << maxdistances[0] << " " << corn_1 << corn_2 << std::endl;
- std::cout << "Maxdist2:" << maxdistances[1] << " " << corn_3 << corn_4 << std::endl;
- cv::namedWindow("win", CV_GUI_NORMAL);
- // cv::imshow("4 corners",img);
- // cv::waitKey(0);
- #endif
- std::vector<cv::Point> verticals[2];
- // Between the two corners on the same side, the longest line is the vertical border of the book
- verticals[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;
- verticals[1] = find_longest_line(hull, (maxdistances[1]+1)%hull.size(), maxdistances[0]);
- free(maxdistances);
- // 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);
- // we now have all the points of the two trapezoid (middle1 and middle2 are shared)
- cv::Mat trasf[2]; //transformation matrix
- double xsize = dist(corn_1, middle1), ysize=dist(middle1, middle2);
- cv::Point2f trapezoid[2][4] = {
- {corn_1, middle1, middle2, corn_4},
- {corn_2, corn_3, middle2, middle1}};
- cv::Point2f outsizes[4] = {cv::Point2f(0, 0), cv::Point(xsize, 0),
- cv::Point(xsize, ysize), cv::Point(0, ysize)};
- cv::Mat rect[2]; //final pages, transformed in a nice rectangle
- //we are rereading in full color to get colored output
- img = cv::imread(args.input,CV_LOAD_IMAGE_COLOR);
- for(int i=0; i < 2; i++) {
- trasf[i] = cv::getPerspectiveTransform(trapezoid[i], outsizes);
- assert(9==trasf[i].total()); // 3x3
- cv::warpPerspective(img, rect[i], trasf[i], cv::Size(xsize, ysize));
- }
- 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= trasf[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;
- }
- profilebuf.close();
- }
- #ifdef _DEBUG
- cv::line(img, corn_1, middle1, MAGENTA, dotwidth>>3);
- cv::line(img, corn_2, middle1, MAGENTA, dotwidth>>3);
- cv::line(img, middle1, middle2, BLACK, dotwidth>>4);
- cv::line(img, corn_3, middle2, MAGENTA, dotwidth>>3);
- cv::line(img, corn_4, middle2, MAGENTA, dotwidth>>3);
- cv::line(img, corn_2, corn_3, BLUE, dotwidth>>4);
- cv::line(img, corn_1, corn_4, BLUE, dotwidth>>4);
- std::cout << middle1 << middle2 << std::endl;
- cv::imshow("win",img);
- while(1)
- {
- if( (char)cv::waitKey(0) == 113 )
- break;
- }
- #endif
- //TODO: distinguish left and right
- std::vector<int> params;
- if(args.left || args.right) {
- params.push_back(CV_IMWRITE_PNG_COMPRESSION);
- params.push_back(9);
- if(args.left) {
- cv::imwrite(args.left, rect[0], params);
- }
- if(args.right) {
- cv::imwrite(args.right, rect[1], params);
- }
- }
- return EXIT_SUCCESS;
- }
- // vim: set noet ts=4 sw=4:
|