lines.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. #include<cmath>
  2. #include<algorithm>
  3. #include<unistd.h>
  4. #include<fstream>
  5. #include<iomanip>
  6. #ifdef HAS_OPENCV3
  7. #include <opencv2/core.hpp> //Any OPENCV3 code
  8. #else
  9. #include <opencv2/core/core.hpp> //Any Opencv2 code
  10. #endif
  11. #include"lines.h"
  12. #include "geometry.h"
  13. #include "cvutils.h"
  14. using namespace cv;
  15. #define likely(x) __builtin_expect(!!(x), 1)
  16. #define unlikely(x) __builtin_expect(!!(x), 0)
  17. // all those colors are still visible on b/w (the first component is not so low)
  18. auto WHITE = cv::Scalar(255,255,255);
  19. auto BLUE = cv::Scalar(200,50,50);
  20. auto MAGENTA = cv::Scalar(110,10,200);
  21. auto BROWN = cv::Scalar(90,100,60);
  22. auto BLACK = cv::Scalar(0,0,0);
  23. auto YELLOW = cv::Scalar(20,200,200);
  24. void usage(char* program, std::ostream &buf) {
  25. buf << "Usage: " << program << "[options] IMAGE-INPUT" << std::endl;
  26. buf << "Identify the image as a book in perspective, get deperspectivized content" << std::endl;
  27. buf << std::endl;
  28. buf << "Options:" << std::endl;
  29. buf << " -p PROFILE Save the transformation matrix in PROFILE" << std::endl;
  30. buf << " -l LEFTPAGE Save the left page in LEFTPAGE" << std::endl;
  31. buf << " -r RIGHTPAGE Save the right page in RIGHTPAGE" << std::endl;
  32. buf << " -h HELP Show this help and exit" << std::endl;
  33. }
  34. class Options
  35. {
  36. public:
  37. char *left;
  38. char *right;
  39. char *profile;
  40. char *input;
  41. Options();
  42. Options(int argc, char*argv[]);
  43. bool parse(int argc, char*argv[]);
  44. ~Options();
  45. };
  46. Options::Options()
  47. {
  48. left = right = profile = input = NULL;
  49. }
  50. Options::~Options()
  51. {
  52. if(left == NULL) {
  53. free(left);
  54. }
  55. if(right == NULL) {
  56. free(right);
  57. }
  58. if(profile == NULL) {
  59. free(profile);
  60. }
  61. //input doesn't need to be free-d: it points to argv[argc-1]
  62. }
  63. Options::Options(int argc, char *argv[])
  64. {
  65. left = right = profile = input = NULL;
  66. parse(argc, argv);
  67. }
  68. /* Options::parse parse arguments, return true if help is requested */
  69. bool Options::parse(int argc, char *argv[])
  70. {
  71. int c;
  72. while((c=getopt(argc, argv, "p:l:r:h")) != -1)
  73. {
  74. switch(c)
  75. {
  76. case 'l':
  77. left = strdup(optarg);
  78. break;
  79. case 'r':
  80. right = strdup(optarg);
  81. break;
  82. case 'p':
  83. profile = strdup(optarg);
  84. break;
  85. case 'h':
  86. return true;
  87. break;
  88. default:
  89. throw std::runtime_error("Parsing error: invalid argument");
  90. }
  91. }
  92. if(optind >= argc) {
  93. std::cerr << "Error: " << argv[0] << " needs an argument" << std::endl;
  94. throw std::runtime_error("Parsing error: argument needed");
  95. }
  96. input = argv[optind++];
  97. if(optind < argc) {
  98. std::cerr << "Error: too many arguments supplied" << std::endl;
  99. throw std::runtime_error("Parsing error: too many arguments");
  100. }
  101. return false;
  102. }
  103. class BookShape {
  104. private:
  105. cv::Mat trasf[2];
  106. void precompute();
  107. cv::Point2f trapezoids[2][4]; //transformation matrix
  108. public:
  109. BookShape(cv::Point tl, cv::Point tm, cv::Point tf,
  110. cv::Point bl, cv::Point bm, cv::Point br);
  111. double xsize();
  112. double ysize();
  113. cv::Mat* getTrasfs();
  114. };
  115. //topleft topmiddle topright
  116. //botleft botmiddle botright
  117. BookShape::BookShape(cv::Point tl, cv::Point tm, cv::Point tr, cv::Point bl, cv::Point bm, cv::Point br)
  118. {
  119. trapezoids[0][0] = tl;
  120. trapezoids[0][1] = tm;
  121. trapezoids[0][2] = bm;
  122. trapezoids[0][3] = bl;
  123. trapezoids[1][0] = tm;
  124. trapezoids[1][1] = tr;
  125. trapezoids[1][2] = br;
  126. trapezoids[1][3] = bm;
  127. precompute();
  128. }
  129. void BookShape::precompute(void) {
  130. cv::Point2f outsizes[4] = {cv::Point2f(0, 0), cv::Point(xsize(), 0),
  131. cv::Point(xsize(), ysize()), cv::Point(0, ysize())};
  132. for(int i=0; i < 2; i++) {
  133. trasf[i] = cv::getPerspectiveTransform(trapezoids[i], outsizes);
  134. assert(9==trasf[i].total()); // 3x3
  135. }
  136. }
  137. cv::Mat* BookShape::getTrasfs()
  138. {
  139. return trasf;
  140. }
  141. double BookShape::xsize()
  142. {
  143. return dist(trapezoids[0][0], trapezoids[0][1]);
  144. }
  145. double BookShape::ysize()
  146. {
  147. return dist(trapezoids[0][1], trapezoids[0][2]);
  148. }
  149. BookShape get_book_shape(cv::Mat img)
  150. {
  151. //dotwidth is just a simple size so that lines and dots are visible on big images
  152. //but not huge on small images
  153. #ifdef _DEBUG
  154. unsigned short dotwidth = img.cols >> 6; // divide by 64, so efficient
  155. #endif
  156. std::vector< std::vector<cv::Point> > contours;
  157. std::vector<cv::Vec4i> hierarchy; //this is not really useful
  158. cv::findContours(img,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);
  159. if( 0==contours.size() ) {
  160. std::cerr << "No contours found" << std::endl;
  161. throw "Error getting contours";
  162. }
  163. auto contour = contours[0];
  164. if( 1!=contours.size() )
  165. {
  166. // choosing the biggest contour in the image
  167. // you can test it with files/2contours.png
  168. for(auto cont: contours) {
  169. if(cont.size() > contour.size())
  170. contour = cont;
  171. }
  172. }
  173. //hull: points forming an external convex border
  174. std::vector<cv::Point> hull;
  175. cv::convexHull(cv::Mat(contour),hull,false);
  176. //because of the "butterfly" shape of a book, the most distant points in the hull
  177. //are the one between the corner.
  178. unsigned *maxdistances = max2_distance(hull);
  179. cv::Point corn_1, corn_2, corn_3, corn_4;
  180. corn_1 = hull[maxdistances[0]];
  181. corn_2 = hull[(maxdistances[0]+1)%hull.size()];
  182. corn_3 = hull[maxdistances[1]];
  183. corn_4 = hull[(maxdistances[1]+1)%hull.size()];
  184. std::vector<cv::Point> vertical_points[2];
  185. cv::Vec<double,3> verticals[2];
  186. // Between the two corners on the same side, the longest line is the vertical border of the book
  187. vertical_points[0] = find_longest_line(hull, (maxdistances[0]+1)%hull.size(), maxdistances[1]);
  188. std::cout << maxdistances[1] << std::endl;
  189. std::cout << maxdistances[1]+1 << std::endl;
  190. std::cout << (maxdistances[1]+1)%hull.size() << std::endl;
  191. vertical_points[1] = find_longest_line(hull, (maxdistances[1]+1)%hull.size(), maxdistances[0]);
  192. free(maxdistances);
  193. #ifdef _DEBUG
  194. img = cv::imread("files/masckera.png", CV_LOAD_IMAGE_COLOR);
  195. cv::circle(img, vertical_points[0][0], img.cols>>7, cv::Scalar(200, 0, 0), -1);
  196. cv::circle(img, vertical_points[0][vertical_points[0].size()-1], img.cols>>7, cv::Scalar(200, 0, 0), -1);
  197. cv::circle(img, vertical_points[1][0], img.cols>>7, cv::Scalar(200, 200, 0), -1);
  198. cv::circle(img, vertical_points[1][vertical_points[1].size()-1], img.cols>>7, cv::Scalar(200, 0, 0), -1);
  199. cv::circle(img, corn_1, img.cols>>7, cv::Scalar(200, 0, 200), -1);
  200. cv::namedWindow("aaa", CV_GUI_NORMAL);
  201. cv::imshow("aaa", img);
  202. cv::waitKey(0);
  203. #endif
  204. verticals[0] = get_line(vertical_points[0][0], vertical_points[0][vertical_points[0].size()-1]);
  205. std::cout << verticals[0] << std::endl;
  206. std::cout << line_value(verticals[0], corn_1) << std::endl;
  207. verticals[0][2] -= line_value(verticals[0], corn_1);
  208. std::cout << line_value(verticals[0], corn_1) << "=0?" << std::endl;
  209. verticals[1] = get_line(vertical_points[1][0], vertical_points[1][vertical_points[1].size()-1]);
  210. // theta is the angle of the line connecting point 1 and 2; it will be the
  211. // rotation of our new coordinate system
  212. auto cs = CoordinateSystem(corn_1, corn_2);
  213. assert(cs.map(corn_1).x == 0);
  214. assert(cs.map(corn_1).y == 0);
  215. assert(cs.map(corn_2).x > 0);
  216. assert(cs.map(corn_2).y == 0);
  217. cv::Vec<double,3> diag1, diag2;
  218. diag1 = get_line(cs.map(corn_1), cs.map(corn_3));
  219. diag2 = get_line(cs.map(corn_4), cs.map(corn_2));
  220. std::cout << "mapped diag1: " << diag1 << std::endl;
  221. std::cout << "mapped diag2: " << diag2 << std::endl;
  222. std::vector<cv::Point> points1, points2;
  223. for(cv::Point p: contour)
  224. { // the point is interesting where it is above both lines or below both lines
  225. cv::Point mapped = cs.map(p);
  226. if(is_above_line(diag1, mapped)) {
  227. if(is_above_line(diag2, mapped)) {
  228. points1.push_back(p);
  229. }
  230. } else {
  231. if(!is_above_line(diag2, mapped)) {
  232. points2.push_back(p);
  233. }
  234. }
  235. }
  236. cv::Point middle1 = further_point_from_line(get_line(corn_1, corn_2), points2);
  237. cv::Point middle2 = further_point_from_line(get_line(corn_3, corn_4), points1);
  238. BookShape bs = BookShape(
  239. vertical_points[1][vertical_points[1].size()-1], middle1, vertical_points[0][0],
  240. vertical_points[1][0], middle2, vertical_points[0][vertical_points[0].size()-1]
  241. );
  242. return bs;
  243. }
  244. int main(int argc, char *argv[])
  245. {
  246. Options args;
  247. try{
  248. if(args.parse(argc, argv)) {
  249. usage(argv[0], std::cout);
  250. return EXIT_SUCCESS;
  251. }
  252. } catch(std::runtime_error) {
  253. usage(argv[0], std::cerr);
  254. return EXIT_FAILURE;
  255. }
  256. cv::Mat img;
  257. img=cv::imread(args.input,CV_LOAD_IMAGE_GRAYSCALE);
  258. if( img.empty() ) {
  259. std::cerr << "Error opening image, aborting" << std::endl;
  260. return EXIT_FAILURE;
  261. }
  262. BookShape bs = get_book_shape(img);
  263. if(args.profile) {
  264. // format: each line is a tab-delimited 3x3 transformation matrix (as get by getPerspectiveTransform)
  265. // first three elements is the first line, etc
  266. std::ofstream profilebuf;
  267. profilebuf.open(args.profile);
  268. for(int t=0; t<2; t++) {
  269. for(int i=0; i<3; i++) {
  270. double* row= bs.getTrasfs()[t].ptr<double>(i);
  271. for(int j=0; j < 3; j++) {
  272. profilebuf << std::fixed << std::setprecision(16);
  273. profilebuf << row[j] << "\t"; //yes, there's a trailing tab
  274. }
  275. }
  276. profilebuf << std::endl;
  277. }
  278. profilebuf.close();
  279. }
  280. //TODO: distinguish left and right
  281. std::vector<int> params;
  282. if(args.left || args.right) {
  283. //we are rereading in full color to get colored output
  284. img = cv::imread(args.input,CV_LOAD_IMAGE_COLOR);
  285. cv::Mat rect[2]; //final pages, transformed in a nice rectangle
  286. params.push_back(CV_IMWRITE_PNG_COMPRESSION);
  287. params.push_back(9);
  288. if(args.left) {
  289. cv::warpPerspective(img, rect[0], bs.getTrasfs()[0], cv::Size(bs.xsize(), bs.ysize()));
  290. cv::imwrite(args.left, rect[0], params);
  291. }
  292. if(args.right) {
  293. cv::warpPerspective(img, rect[1], bs.getTrasfs()[1], cv::Size(bs.xsize(), bs.ysize()));
  294. cv::imwrite(args.right, rect[1], params);
  295. }
  296. }
  297. return EXIT_SUCCESS;
  298. }
  299. // vim: set noet ts=4 sw=4: