1 | |
2 |
|
3 | Copyright (C) 2011-2017 Alexis Bienvenue <paamc@passoire.fr>
|
4 |
|
5 | This file is part of Auto-Multiple-Choice
|
6 |
|
7 | Auto-Multiple-Choice is free software: you can redistribute it
|
8 | and/or modify it under the terms of the GNU General Public License
|
9 | as published by the Free Software Foundation, either version 2 of
|
10 | the License, or (at your option) any later version.
|
11 |
|
12 | Auto-Multiple-Choice is distributed in the hope that it will be
|
13 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
14 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
15 | General Public License for more details.
|
16 |
|
17 | You should have received a copy of the GNU General Public License
|
18 | along with Auto-Multiple-Choice. If not, see
|
19 | <http://www.gnu.org/licenses/>.
|
20 |
|
21 | */
|
22 |
|
23 | #include <math.h>
|
24 | #include <cstddef>
|
25 |
|
26 | #include <stdio.h>
|
27 | #include <locale.h>
|
28 |
|
29 | #include <errno.h>
|
30 | #include <unistd.h>
|
31 | #include <sys/types.h>
|
32 | #include <sys/stat.h>
|
33 |
|
34 | #include <errno.h>
|
35 |
|
36 | #ifdef NEEDS_GETLINE
|
37 | #include <minimal-getline.c>
|
38 | #endif
|
39 |
|
40 | #include "opencv2/core/core.hpp"
|
41 |
|
42 | #if CV_MAJOR_VERSION > 2
|
43 | #define OPENCV_23 1
|
44 | #define OPENCV_21 1
|
45 | #define OPENCV_20 1
|
46 | #define OPENCV_30 1
|
47 | #else
|
48 | #if CV_MAJOR_VERSION == 2
|
49 | #define OPENCV_20 1
|
50 | #if CV_MINOR_VERSION >= 1
|
51 | #define OPENCV_21 1
|
52 | #endif
|
53 | #if CV_MINOR_VERSION >= 3
|
54 | #define OPENCV_23 1
|
55 | #endif
|
56 | #endif
|
57 | #endif
|
58 |
|
59 | #include "opencv2/imgproc/imgproc.hpp"
|
60 | #ifdef OPENCV_30
|
61 | #include "opencv2/imgcodecs/imgcodecs.hpp"
|
62 | #ifdef AMC_DETECT_HIGHGUI
|
63 | #include "opencv2/highgui/highgui.hpp"
|
64 | #endif
|
65 | #else
|
66 | #include "opencv2/highgui/highgui.hpp"
|
67 | #endif
|
68 |
|
69 | using namespace std;
|
70 |
|
71 | int processing_error = 0;
|
72 |
|
73 | |
74 | Note:
|
75 |
|
76 | IMAGE COORDINATES: (0,0) is upper-left corner
|
77 | */
|
78 |
|
79 | #define GET_PIXEL(src,x,y) *((uchar*)(src.data+src.step*(y)+src.channels()*(x)))
|
80 | #define PIXEL(src,x,y) GET_PIXEL(src,x,y)>100
|
81 |
|
82 | #define RGB_COLOR(r,g,b) cv::Scalar((b),(g),(r),0)
|
83 |
|
84 | #define BLEU RGB_COLOR(38,69,223)
|
85 | #define ROSE RGB_COLOR(223,38,203)
|
86 |
|
87 | #define SWAP(x,y,tmp) tmp=x;x=y;y=tmp
|
88 | #define SGN_ROT (1-2*upside_down)
|
89 | #define SUM_SQUARE(x,y) ((x)*(x)+(y)*(y))
|
90 | #define SHAPE_SQUARE 0
|
91 | #define SHAPE_OVAL 1
|
92 |
|
93 | #define DIR_X 1
|
94 | #define DIR_Y 2
|
95 |
|
96 | #define ILLUSTR_BOX 1
|
97 | #define ILLUSTR_PIXELS 2
|
98 |
|
99 | |
100 |
|
101 | the following functions select, from a points sequence, four
|
102 | extreme points:
|
103 |
|
104 | - the most NW one with coordinates (corner_x[0],corner_y[0]),
|
105 | - the most NE one with coordinates (corner_x[1],corner_y[1]),
|
106 | - the most SE one with coordinates (corner_x[2],corner_y[2]),
|
107 | - the most SW one with coordinates (corner_x[3],corner_y[3]),
|
108 |
|
109 | First call
|
110 |
|
111 | agrege_init(image_width,image_height,corners_x,corners_y)
|
112 |
|
113 | which will initialize the extreme points coordinates, and then
|
114 |
|
115 | agrege(x,y)
|
116 |
|
117 | for all points (x,y) from the sequence.
|
118 |
|
119 | */
|
120 |
|
121 | void agrege_init(double tx,double ty,double* coins_x,double* coins_y) {
|
122 | coins_x[0] = tx; coins_y[0] = ty;
|
123 | coins_x[1] = 0; coins_y[1] = ty;
|
124 | coins_x[2] = 0; coins_y[2] = 0;
|
125 | coins_x[3] = tx; coins_y[3] = 0;
|
126 | }
|
127 |
|
128 | #define AGREGE_POINT(op,comp,i) if((x op y) comp (coins_x[i] op coins_y[i])) { coins_x[i]=x;coins_y[i]=y; }
|
129 |
|
130 | void agrege(double x,double y,double* coins_x,double* coins_y) {
|
131 | AGREGE_POINT(+,<,0)
|
132 | AGREGE_POINT(+,>,2)
|
133 | AGREGE_POINT(-,>,1)
|
134 | AGREGE_POINT(-,<,3)
|
135 | }
|
136 |
|
137 | |
138 |
|
139 | load_image(...) loads the scan image, with some pre-processings:
|
140 |
|
141 | - if ignore_red is true, the red color is discarder from the scan:
|
142 | only the red channel is kept from the scan.
|
143 |
|
144 | - the image is (a little) smoothed with a Gaussian kernel, and a
|
145 | threshold is applied to convert the image from greyscale to
|
146 | black&white only. The threshold value is MAX*threshold, where MAX
|
147 | is the maximum value for all pixels (that is the grey value for
|
148 | the lighter pixel), and threshold is given to load_image as a
|
149 | parameter.
|
150 |
|
151 | - the image is flipped if necessary to get the upper-left pixel at
|
152 | coordinates (0,0)
|
153 |
|
154 | The result image is *src.
|
155 |
|
156 | */
|
157 |
|
158 | void load_image(cv::Mat &src,char *filename,
|
159 | int ignore_red,double threshold=0.6,int view=0) {
|
160 | cv::Mat color;
|
161 | double max;
|
162 |
|
163 | if(ignore_red) {
|
164 | printf(": loading red channel from %s ...\n", filename);
|
165 | if((color=cv::imread(filename,
|
166 | #ifdef OPENCV_23
|
167 | cv::IMREAD_ANYCOLOR
|
168 | #else
|
169 | cv::IMREAD_UNCHANGED
|
170 | #endif
|
171 | )).data != NULL) {
|
172 | if(color.channels() >= 3) {
|
173 |
|
174 | src = cv::Mat(color.rows, color.cols,
|
175 | CV_MAKETYPE(color.depth(), 1 ));
|
176 |
|
177 |
|
178 |
|
179 | int from_to[] = {2,0};
|
180 | cv::mixChannels(&color, 1, &src, 1, from_to, 1);
|
181 | color.release();
|
182 | } else if(color.channels() != 1) {
|
183 | printf("! LOAD : Scan file with 2 channels [%s]\n", filename);
|
184 | processing_error = 2;
|
185 | return;
|
186 | } else {
|
187 | src = color;
|
188 | }
|
189 | } else {
|
190 | printf("! LOAD : Error loading scan file in ANYCOLOR [%s]\n", filename);
|
191 | processing_error = 3;
|
192 | return;
|
193 | }
|
194 | } else {
|
195 | printf(": loading %s ...\n", filename);
|
196 | if((src=imread(filename, cv::IMREAD_GRAYSCALE)).data == NULL) {
|
197 | printf("! LOAD : Error loading scan file in GRAYSCALE [%s]\n", filename);
|
198 | processing_error = 3;
|
199 | return;
|
200 | }
|
201 | }
|
202 |
|
203 | cv::minMaxLoc(src, NULL, &max);
|
204 | printf(": Image max = %.3f\n", max);
|
205 | cv::GaussianBlur(src, src, cv::Size(3,3), 1);
|
206 | cv::threshold(src, src, max*threshold, 255, cv::THRESH_BINARY_INV);
|
207 | }
|
208 |
|
209 | |
210 |
|
211 | pre_traitement(...) tries to remove scan artefacts (dust and holes)
|
212 | from image *src, using morphological closure and opening.
|
213 |
|
214 | - lissage_trous is the radius of the holes to remove (in pixels)
|
215 | - lissage_poussieres is the radius of the dusts to remove (in pixels)
|
216 |
|
217 | */
|
218 |
|
219 | void pre_traitement(cv::Mat &src,int lissage_trous,int lissage_poussieres) {
|
220 | printf("Morph: +%d -%d\n", lissage_trous, lissage_poussieres);
|
221 | cv::Mat trous = cv::getStructuringElement(
|
222 | cv::MORPH_ELLIPSE,
|
223 | cv::Size(1 + 2 * lissage_trous, 1 + 2 * lissage_trous),
|
224 | cv::Point(lissage_trous, lissage_trous));
|
225 |
|
226 | cv::Mat poussieres = cv::getStructuringElement(
|
227 | cv::MORPH_ELLIPSE,
|
228 | cv::Size(1 + 2 * lissage_poussieres, 1 + 2 * lissage_poussieres),
|
229 | cv::Point(lissage_poussieres, lissage_poussieres));
|
230 |
|
231 | cv::morphologyEx(src, src, cv::MORPH_CLOSE, trous);
|
232 | cv::morphologyEx(src, src, cv::MORPH_OPEN, poussieres);
|
233 |
|
234 | trous.release();
|
235 | poussieres.release();
|
236 | }
|
237 |
|
238 |
|
239 |
|
240 | |
241 | x'=ax+by+e
|
242 | y'=cx+dy+f
|
243 | */
|
244 |
|
245 | typedef struct {
|
246 | double a,b,c,d,e,f;
|
247 | } linear_transform;
|
248 |
|
249 | |
250 | point (x,y) to give (xp,yp)
|
251 | */
|
252 |
|
253 | void transforme(linear_transform* t,double x,double y,double* xp,double* yp) {
|
254 | *xp = t->a * x + t->b * y + t->e;
|
255 | *yp = t->c * x + t->d * y + t->f;
|
256 | }
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | typedef struct {
|
263 | double x,y;
|
264 | } point;
|
265 |
|
266 |
|
267 |
|
268 | typedef struct {
|
269 | double a,b,c;
|
270 | } ligne;
|
271 |
|
272 | |
273 | coordinates *a of A and *b of B, and stores it to *l.
|
274 | */
|
275 |
|
276 | void calcule_demi_plan(point *a,point *b,ligne *l) {
|
277 | double vx,vy;
|
278 | vx = b->y - a->y;
|
279 | vy = -(b->x - a->x);
|
280 | l->a = vx;
|
281 | l->b = vy;
|
282 | l->c = - a->x*vx - a->y*vy;
|
283 | }
|
284 |
|
285 | |
286 | *equation l, giving which side of l the point at (x,y) is.
|
287 | */
|
288 |
|
289 | int evalue_demi_plan(ligne *l,double x,double y) {
|
290 | return l->a * x + l->b * y + l->c <= 0 ? 1 : 0;
|
291 | }
|
292 |
|
293 |
|
294 |
|
295 | |
296 | */
|
297 |
|
298 | double moyenne(double *x, int n, int omit=-1) {
|
299 | double s = 0;
|
300 | for(int i = 0; i < n; i++) {
|
301 | if(i != omit) {
|
302 | s += x[i];
|
303 | }
|
304 | }
|
305 | return s / (n - (omit >= 0 ? 1 : 0));
|
306 | }
|
307 |
|
308 | |
309 | and y[] (both of size n), that is the sum over i of x[i]*y[i].
|
310 | */
|
311 |
|
312 | double scalar_product(double *x,double *y,int n, int omit=-1) {
|
313 | double sx = 0, sy = 0, sxy = 0;
|
314 | for(int i = 0; i < n; i++) {
|
315 | if(i != omit) {
|
316 | sx += x[i];
|
317 | sy += y[i];
|
318 | sxy += x[i]*y[i];
|
319 | }
|
320 | }
|
321 | if(omit>=0)
|
322 | n--;
|
323 | return sxy/n - sx/n * sy/n;
|
324 | }
|
325 |
|
326 | |
327 |
|
328 | ax+by+e=0
|
329 | cx+dy+f=0
|
330 |
|
331 | and sets *x and *y with the solution. If the system is
|
332 | not-invertible, *x and *y are left unchanged and a warning is
|
333 | printed out.
|
334 | */
|
335 |
|
336 | void sys_22(double a,double b,double c,double d,double e,double f,
|
337 | double *x,double *y) {
|
338 | double delta = a*d - b*c;
|
339 | if(delta == 0) {
|
340 | printf("! NONINV : Non-invertible system.\n");
|
341 | return;
|
342 | }
|
343 | *x = (d*e - b*f) / delta;
|
344 | *y = (a*f - c*e) / delta;
|
345 | }
|
346 |
|
347 |
|
348 |
|
349 | double sqr(double x) { return(x*x); }
|
350 |
|
351 |
|
352 |
|
353 | |
354 | and stores it to *back.
|
355 | */
|
356 |
|
357 | void revert_transform(linear_transform *direct,
|
358 | linear_transform *back) {
|
359 | double delta = direct->a * direct->d - direct->b * direct->c;
|
360 | if(delta == 0) {
|
361 | printf("! NONINV : Non-invertible system.\n");
|
362 | return;
|
363 | }
|
364 | back->a = direct->d / delta;
|
365 | back->b = - direct->b / delta;
|
366 | back->e = (direct->b * direct->f - direct->e * direct->d) / delta;
|
367 |
|
368 | back->c = - direct->c / delta;
|
369 | back->d = direct->a / delta;
|
370 | back->f = (direct->e * direct->c - direct->a * direct->f) / delta;
|
371 |
|
372 | printf("Back:\na'=%f\nb'=%f\nc'=%f\nd'=%f\ne'=%f\nf'=%f\n",
|
373 | back->a, back->b,
|
374 | back->c, back->d,
|
375 | back->e, back->f);
|
376 | }
|
377 |
|
378 | |
379 | square distances from T(M[i]) to MP[i] is minimal, where M[] and
|
380 | MP[] are sequences of n points.
|
381 |
|
382 | points_x[] and points_y[] are the coordinates of the points M[],
|
383 | and points_xp[] and points_yp[] are the coordinates of the points
|
384 | M[].
|
385 |
|
386 | The return value is the mean square error (square root of S/n).
|
387 | */
|
388 |
|
389 | double optim(double* points_x,double* points_y,
|
390 | double* points_xp,double* points_yp,
|
391 | int n,
|
392 | linear_transform* t,
|
393 | int omit=-1) {
|
394 | double sxx = scalar_product(points_x, points_x, n, omit);
|
395 | double sxy = scalar_product(points_x, points_y, n, omit);
|
396 | double syy = scalar_product(points_y, points_y, n, omit);
|
397 |
|
398 | double sxxp = scalar_product(points_x, points_xp, n, omit);
|
399 | double syxp = scalar_product(points_y, points_xp, n, omit);
|
400 | double sxyp = scalar_product(points_x, points_yp, n, omit);
|
401 | double syyp = scalar_product(points_y, points_yp, n, omit);
|
402 |
|
403 | sys_22(sxx, sxy, sxy, syy, sxxp, syxp, &(t->a), &(t->b));
|
404 | sys_22(sxx, sxy, sxy, syy, sxyp, syyp, &(t->c), &(t->d));
|
405 | t->e = moyenne(points_xp,n,omit)
|
406 | - (t->a * moyenne(points_x,n,omit) + t->b*moyenne(points_y,n,omit));
|
407 | t->f = moyenne(points_yp,n,omit)
|
408 | - (t->c * moyenne(points_x,n,omit) + t->d*moyenne(points_y,n,omit));
|
409 |
|
410 | double mse = 0;
|
411 | for(int i = 0; i < n; i++) {
|
412 | if(i != omit) {
|
413 | mse += sqr(points_xp[i] - (t->a * points_x[i] + t->b * points_y[i] + t->e));
|
414 | mse += sqr(points_yp[i] - (t->c * points_x[i] + t->d * points_y[i] + t->f));
|
415 | }
|
416 | }
|
417 | mse = sqrt(mse / (n - (omit <= 0 ? 1 : 0)));
|
418 | return mse;
|
419 | }
|
420 |
|
421 | |
422 | transform t to an exact orthonormal transform.
|
423 | */
|
424 | double transform_quality_2(linear_transform* t) {
|
425 | return SUM_SQUARE(t->c+t->b,t->d-t->a) / SUM_SQUARE(t->a,t->b);
|
426 | }
|
427 |
|
428 | |
429 | points, and returns the best transform (the more "orthonormal"
|
430 | one).
|
431 | */
|
432 | double omit_optim(double* points_x, double* points_y,
|
433 | double* points_xp, double* points_yp,
|
434 | int n,
|
435 | linear_transform* t) {
|
436 | linear_transform t_best;
|
437 | double q, q_best;
|
438 | int i_best = -1;
|
439 | for(int i = 0; i < n; i++) {
|
440 | optim(points_x, points_y, points_xp, points_yp, n, t, i);
|
441 | q = transform_quality_2(t);
|
442 | printf("OMIT_CORNER=%d Q2=%lf\n", i, q);
|
443 | if(i_best < 0 || q < q_best) {
|
444 | i_best = i;
|
445 | q_best = q;
|
446 | memcpy((void*)&t_best, (void*)t, sizeof(linear_transform));
|
447 | }
|
448 | }
|
449 | memcpy((void*)t, (void*)&t_best, sizeof(linear_transform));
|
450 | return sqrt(q_best);
|
451 | }
|
452 |
|
453 | |
454 |
|
455 | - *src is the scan image (comming from load_image).
|
456 |
|
457 | - if illustr is not NULL, a rectangle is drawn on image *illustr to
|
458 | show where the corner marks (circles) has been detected.
|
459 |
|
460 | - taille_orig_x and taille_orig_y are the width and height of the
|
461 | model page.
|
462 |
|
463 | - dia_orig is the diameter of the corner marks (circles) on the model
|
464 | page.
|
465 |
|
466 | - tol_plus and tol_moins are tolerence ratios for corner marks:
|
467 | calage will look for marks with diameter between
|
468 | dia_orig*(1-tol_moins) and dia_orig*(1+tol_plus) (scaled to scan
|
469 | size).
|
470 |
|
471 | - coins_x[] and coins_y[] will be filled with the coordinates of the
|
472 | 4 corner marks detected on the scan.
|
473 |
|
474 | - if view==1, a report image *dst will be created to show all
|
475 | connected components from source image that has correct diameter.
|
476 |
|
477 | - if view==2, a report image *dst will be created from the source
|
478 | image with over-printed connected components with correct diameter.
|
479 |
|
480 | 1) pre_traitement is called to remove dusts and holes.
|
481 |
|
482 | 2) cvFindContours find the connected components from the image. All
|
483 | connected components with diameter too far from target diameter (see
|
484 | tol_plus and tol_moins parameters) are discarded.
|
485 |
|
486 | 3) the centers of the extreme connected components with correct
|
487 | diameter are returned.
|
488 |
|
489 | */
|
490 |
|
491 | void calage(cv::Mat src, cv::Mat illustr,
|
492 | double taille_orig_x, double taille_orig_y,
|
493 | double dia_orig,
|
494 | double tol_plus, double tol_moins,
|
495 | int n_min_cc,
|
496 | double* coins_x, double *coins_y,
|
497 | cv::Mat &dst,int view=0) {
|
498 | cv::Point coins_int[4];
|
499 | int n_cc;
|
500 |
|
501 |
|
502 |
|
503 | double rx = src.cols / taille_orig_x;
|
504 | double ry = src.rows / taille_orig_y;
|
505 | double target = dia_orig * (rx + ry) / 2;
|
506 | double target_max = target * (1 + tol_plus);
|
507 | double target_min = target * (1 - tol_moins);
|
508 |
|
509 | |
510 | diameter, and dusts that are smaller than 1/20 times the target
|
511 | mark diameter.
|
512 | */
|
513 |
|
514 | pre_traitement(src,
|
515 | 1 + (int)((target_min+target_max)/2 /20),
|
516 | 1 + (int)((target_min+target_max)/2 /8));
|
517 |
|
518 | if(view == 2) {
|
519 |
|
520 | dst = cv::Mat(cv::Size(src.rows, src.cols), CV_MAKETYPE(CV_8U, 3));
|
521 |
|
522 |
|
523 |
|
524 | cv::bitwise_not(dst,dst);
|
525 | }
|
526 |
|
527 | printf("Target size: %.1f ; %.1f\n", target_min, target_max);
|
528 |
|
529 |
|
530 |
|
531 |
|
532 | vector<vector<cv::Point> > contours;
|
533 | vector<cv::Vec4i> hierarchy;
|
534 | cv::findContours(src, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
|
535 |
|
536 | #ifdef OPENCV_21
|
537 | if(view == 1) {
|
538 |
|
539 | dst = cv::Mat::zeros(cv::Size(src.rows, src.cols), CV_MAKETYPE(src.depth(), 3));
|
540 | }
|
541 | #endif
|
542 |
|
543 |
|
544 |
|
545 | agrege_init(src.cols, src.rows, coins_x, coins_y);
|
546 | n_cc = 0;
|
547 |
|
548 | printf("Detected connected components:\n");
|
549 |
|
550 | for(int i = 0; i < contours.size(); i++) {
|
551 | cv::Rect rect = cv::boundingRect(cv::Mat(contours[i]));
|
552 |
|
553 | if(rect.width <= target_max && rect.width >= target_min &&
|
554 | rect.height <= target_max && rect.height >= target_min) {
|
555 | |
556 | of the center of the connected component. */
|
557 | agrege(rect.x + (rect.width - 1) / 2.0,
|
558 | rect.y + (rect.height - 1) / 2.0,
|
559 | coins_x,
|
560 | coins_y);
|
561 |
|
562 |
|
563 | printf("(%d;%d)+(%d;%d)\n",
|
564 | rect.x, rect.y, rect.width, rect.height);
|
565 | n_cc++;
|
566 |
|
567 | #ifdef OPENCV_21
|
568 | if(view == 1) {
|
569 | |
570 | with a random color. */
|
571 | cv::Scalar color = RGB_COLOR(rand() & 255, rand() & 255, rand() & 255);
|
572 | cv::rectangle(dst, cv::Point(rect.x,rect.y), cv::Point(rect.x+rect.width,rect.y+rect.height), color);
|
573 | cv::drawContours(dst, contours, i, color, cv::FILLED);
|
574 | }
|
575 | #endif
|
576 | if(view==2) {
|
577 | |
578 | in green. */
|
579 | cv::Scalar color = RGB_COLOR(60,198,127);
|
580 | cv::rectangle(dst, cv::Point(rect.x,rect.y), cv::Point(rect.x+rect.width,rect.y+rect.height), color);
|
581 | cv::drawContours(dst, contours, i, color, cv::FILLED);
|
582 | }
|
583 | }
|
584 | }
|
585 |
|
586 | if(n_cc >= n_min_cc) {
|
587 | for(int i = 0; i < 4; i++) {
|
588 | |
589 | later drawings */
|
590 | if(view > 0 || illustr.data != NULL) {
|
591 | coins_int[i].x = (int)coins_x[i];
|
592 | coins_int[i].y = (int)coins_y[i];
|
593 | }
|
594 | |
595 | coordinates of the marks on the scan. */
|
596 | printf("Frame[%d]: %.1f ; %.1f\n", i, coins_x[i], coins_y[i]);
|
597 | }
|
598 |
|
599 | if(view==1) {
|
600 | #ifdef OPENCV_21
|
601 |
|
602 | for(int i = 0; i < 4; i++) {
|
603 | cv::line(dst, coins_int[i], coins_int[(i+1)%4], RGB_COLOR(255,255,255), 1, cv::LINE_AA);
|
604 | }
|
605 | #endif
|
606 | }
|
607 | if(view==2) {
|
608 |
|
609 | for(int i = 0; i < 4; i++) {
|
610 | cv::line(dst, coins_int[i], coins_int[(i+1)%4], RGB_COLOR(193,29,27), 1, cv::LINE_AA);
|
611 | }
|
612 | }
|
613 |
|
614 | if(illustr.data!=NULL) {
|
615 |
|
616 | for(int i = 0; i < 4; i++) {
|
617 | cv::line(illustr, coins_int[i], coins_int[(i+1)%4], BLEU, 1, cv::LINE_AA);
|
618 | }
|
619 | }
|
620 | } else {
|
621 | |
622 | where are the marks on the scan! */
|
623 | printf("! NMARKS=%d : Not enough marks detected.\n", n_cc);
|
624 | }
|
625 | }
|
626 |
|
627 | |
628 | between A and B -- here only one coordinate is processed: cn is 'x'
|
629 | or 'y'.
|
630 |
|
631 | d is a temporary variable.
|
632 | */
|
633 |
|
634 | #define CLOSER(pointa,pointb,cn,dist,delta) dist=delta*(pointb.cn-pointa.cn);pointa.cn+=dist;pointb.cn-=dist;
|
635 |
|
636 |
|
637 |
|
638 | void deplace(int i,int j,double delta,point *coins) {
|
639 | double d;
|
640 | CLOSER(coins[i], coins[j],x, d, delta);
|
641 | CLOSER(coins[i], coins[j],y, d, delta);
|
642 | }
|
643 |
|
644 | |
645 | proportion delta of the distance between them.
|
646 | */
|
647 |
|
648 | void deplace_xy(double *m1,double *m2,double delta) {
|
649 | double d = (*m2-*m1) * delta;
|
650 | *m1 += d;
|
651 | *m2 -= d;
|
652 | }
|
653 |
|
654 | |
655 | moving it inside if necessary.
|
656 |
|
657 | tx and ty are the width and height of the image.
|
658 | */
|
659 |
|
660 | void restreint(int *x,int *y,int tx,int ty) {
|
661 | if(*x < 0) *x = 0;
|
662 | if(*y < 0) *y = 0;
|
663 | if(*x >= tx) *x = tx - 1;
|
664 | if(*y >= ty) *y = ty - 1;
|
665 | }
|
666 |
|
667 | |
668 | zooms_dir (for student number given as a parameter) exists, or
|
669 | tries to create it.
|
670 |
|
671 | In case of problem, error message is printer to STDOUT.
|
672 |
|
673 | if log is true, some more messages are printed.
|
674 | */
|
675 |
|
676 | int check_zooms_dir(int student, char *zooms_dir=NULL,int log=0) {
|
677 | int ok = 1;
|
678 | struct stat zd;
|
679 |
|
680 | if(student >= 0) {
|
681 | if(stat(zooms_dir,&zd) != 0) {
|
682 | if(errno == ENOENT) {
|
683 | if(mkdir(zooms_dir,0755) != 0) {
|
684 | ok = 0;
|
685 | printf("! ZOOMDC : Zoom dir creation error %d : %s\n", errno, zooms_dir);
|
686 | } else {
|
687 | printf(": Zoom dir created %s\n", zooms_dir);
|
688 | }
|
689 | } else {
|
690 | ok = 0;
|
691 | printf("! ZOOMDS : Zoom dir stat error %d : %s\n", errno, zooms_dir);
|
692 | }
|
693 | } else {
|
694 | if(!S_ISDIR(zd.st_mode)) {
|
695 | ok = 0;
|
696 | printf("! ZOOMDP : Zoom dir is not a directory : %s\n", zooms_dir);
|
697 | }
|
698 | }
|
699 | } else {
|
700 | ok = 0;
|
701 | if(log) {
|
702 | printf(": No zoom dir to create (student<0).\n");
|
703 | }
|
704 | }
|
705 | return ok;
|
706 | }
|
707 |
|
708 | |
709 | pixels, and total number of pixels) of a particular box on the
|
710 | scan. A "zoom" (small image with the box on the scan only) can be
|
711 | extracted in order to have a closer look at the scaned box later.
|
712 |
|
713 | - *src is the source black&white image.
|
714 |
|
715 | - *illustr is an image on which drawings will be made:
|
716 |
|
717 | with illustr_mode==ILLUSTR_BOX, a blue rectangle shows the box
|
718 | position, and a pink rectangle shows the measuring box (a box a
|
719 | little smaller than the box itself).
|
720 |
|
721 | with illustr_mode==ILLUSTR_PIXELS, all measured pixels will be
|
722 | coloured (black pixels in green, and white pixels in blue)
|
723 |
|
724 | - student is the student number. student<0 means that the student
|
725 | number is not yet known (we are measuring the ID binary boxes to
|
726 | detect the page and student numbers), so that zooms are extracted
|
727 | only when student>=0
|
728 |
|
729 | - page is the page number (unused)
|
730 |
|
731 | - question,answer are the question and answer numbers for the box
|
732 | beeing measured. These are used to build a zoom file name from
|
733 | the template zooms_dir/question-answer.png
|
734 |
|
735 | - prop is a ratio that is used to reduce the box before measuring
|
736 | how many pixels are black (the goal here is to try to avoid
|
737 | measuring the border of the box, that are always dark...). It
|
738 | should be small (0.1 seems to be reasonable), otherwise only a
|
739 | small part in the center of the box will be considered -- but not
|
740 | too small, otherwise the border of the box could be taken into
|
741 | account, so that the measures are less reliable to determine if a
|
742 | box is ticked or not.
|
743 |
|
744 | - shape_id is the shape id of the box: SHAPE_OVAL or SHAPE_SQUARE.
|
745 |
|
746 | - o_xmin,o_xmax,o_ymin,o_ymax are the box coordinates on the
|
747 | original subject. NOTE: if o_xmin<0, the box coordinates on the
|
748 | scan are not given through these variables values, but directly
|
749 | in the coins[] variables.
|
750 |
|
751 | - transfo_back is the optimal linear transform that gets
|
752 | coordinates on the scan to coordinates on the original
|
753 | subject. NOTE: only used if o_xmin>=0.
|
754 |
|
755 | - coins[] will be filled with the coordinates of the 4 corners of
|
756 | the measuring box on the scan. NOTE: if o_xmin<0, coins[]
|
757 | contains as an input the coordinates of 4 corners of the box on
|
758 | the scan.
|
759 |
|
760 | - some reports will be drawn on *dst:
|
761 |
|
762 | if view==1, the measuring boxes will be drawn.
|
763 |
|
764 | - zooms_dir is the directory path where to store zooms extracted
|
765 | from the *src image.
|
766 |
|
767 | */
|
768 |
|
769 | void mesure_case(cv::Mat src, cv::Mat illustr,int illustr_mode,
|
770 | int student,int page,int question, int answer,
|
771 | double prop,int shape_id,
|
772 | double o_xmin,double o_xmax,double o_ymin,double o_ymax,
|
773 | linear_transform *transfo_back,
|
774 | point *coins, cv::Mat &dst,
|
775 | char *zooms_dir=NULL,int view=0) {
|
776 | int npix, npixnoir, xmin, xmax, ymin, ymax, x, y;
|
777 | int z_xmin, z_xmax, z_ymin, z_ymax;
|
778 | ligne lignes[4];
|
779 | int i, ok;
|
780 | double delta;
|
781 | double o_x, o_y;
|
782 | cv::Scalar pixel;
|
783 |
|
784 | double ov_r, ov_r2, ov_dir, ov_center, ov_x0, ov_x1, ov_y0, ov_y1;
|
785 |
|
786 | int tx = src.cols;
|
787 | int ty = src.rows;
|
788 |
|
789 | cv::Point coins_int[4];
|
790 |
|
791 | static char* zoom_file = NULL;
|
792 |
|
793 | #if OPENCV_20
|
794 | vector<int> save_options;
|
795 | save_options.push_back(cv::IMWRITE_PNG_COMPRESSION);
|
796 | save_options.push_back(7);
|
797 | #endif
|
798 |
|
799 | npix = 0;
|
800 | npixnoir = 0;
|
801 |
|
802 | if(illustr.data != NULL) {
|
803 | for(int i = 0; i < 4; i++) {
|
804 | coins_int[i].x = (int)coins[i].x;
|
805 | coins_int[i].y = (int)coins[i].y;
|
806 | }
|
807 |
|
808 | if(illustr_mode == ILLUSTR_BOX) {
|
809 |
|
810 | for(int i = 0; i < 4; i++) {
|
811 | cv::line(illustr, coins_int[i], coins_int[(i+1)%4], BLEU, 1, cv::LINE_AA);
|
812 | }
|
813 | }
|
814 |
|
815 |
|
816 | z_xmin = tx - 1;
|
817 | z_xmax = 0;
|
818 | z_ymin = ty - 1;
|
819 | z_ymax = 0;
|
820 | for(int i = 0; i < 4; i++) {
|
821 | if(coins_int[i].x < z_xmin) z_xmin = coins_int[i].x;
|
822 | if(coins_int[i].x > z_xmax) z_xmax = coins_int[i].x;
|
823 | if(coins_int[i].y < z_ymin) z_ymin = coins_int[i].y;
|
824 | if(coins_int[i].y > z_ymax) z_ymax = coins_int[i].y;
|
825 | }
|
826 |
|
827 |
|
828 | int delta = (z_xmax - z_xmin + z_ymax - z_ymin) / 20;
|
829 | z_xmin -= delta;
|
830 | z_ymin -= delta;
|
831 | z_xmax += delta;
|
832 | z_ymax += delta;
|
833 | }
|
834 |
|
835 |
|
836 | delta = (1 - prop) / 2;
|
837 | deplace(0, 2, delta, coins);
|
838 | deplace(1, 3, delta, coins);
|
839 |
|
840 | deplace_xy(&o_xmin, &o_xmax, delta);
|
841 | deplace_xy(&o_ymin, &o_ymax, delta);
|
842 |
|
843 |
|
844 | for(i = 0; i < 4; i++) {
|
845 | printf("COIN %.3f,%.3f\n",coins[i].x,coins[i].y);
|
846 | }
|
847 |
|
848 |
|
849 | xmin = tx - 1;
|
850 | xmax = 0;
|
851 | ymin = ty - 1;
|
852 | ymax = 0;
|
853 | for(i = 0; i < 4; i++) {
|
854 | if(coins[i].x < xmin) xmin = (int)coins[i].x;
|
855 | if(coins[i].x > xmax) xmax = (int)coins[i].x;
|
856 | if(coins[i].y < ymin) ymin = (int)coins[i].y;
|
857 | if(coins[i].y > ymax) ymax = (int)coins[i].y;
|
858 | }
|
859 |
|
860 | restreint(&xmin, &ymin, tx, ty);
|
861 | restreint(&xmax, &ymax, tx, ty);
|
862 |
|
863 | if(o_xmin < 0) {
|
864 |
|
865 | calcule_demi_plan(&coins[0], &coins[1], &lignes[0]);
|
866 | calcule_demi_plan(&coins[1], &coins[2], &lignes[1]);
|
867 | calcule_demi_plan(&coins[2], &coins[3], &lignes[2]);
|
868 | calcule_demi_plan(&coins[3], &coins[0], &lignes[3]);
|
869 | } else {
|
870 | if(shape_id == SHAPE_OVAL) {
|
871 | if(o_xmax-o_xmin < o_ymax-o_ymin) {
|
872 |
|
873 | ov_dir = DIR_Y;
|
874 | ov_r = (o_xmax - o_xmin) / 2;
|
875 | ov_x0 = o_xmin;
|
876 | ov_x1 = o_xmax;
|
877 | ov_y0 = o_ymin + ov_r;
|
878 | ov_y1 = o_ymax - ov_r;
|
879 | ov_center = (o_xmin + o_xmax) / 2;
|
880 | } else {
|
881 |
|
882 | ov_dir = DIR_X;
|
883 | ov_r = (o_ymax - o_ymin) / 2;
|
884 | ov_x0 = o_xmin + ov_r;
|
885 | ov_x1 = o_xmax - ov_r;
|
886 | ov_y0 = o_ymin;
|
887 | ov_y1 = o_ymax;
|
888 | ov_center = (o_ymin + o_ymax) / 2;
|
889 | }
|
890 | ov_r2 = ov_r * ov_r;
|
891 | }
|
892 | }
|
893 |
|
894 | for(x = xmin; x <= xmax; x++) {
|
895 | for(y = ymin; y <= ymax; y++) {
|
896 | if(o_xmin < 0) {
|
897 | |
898 | or not from the scan coordinates (x,y) */
|
899 | ok = 1;
|
900 | for(i = 0; i < 4; i++) {
|
901 | if(evalue_demi_plan(&lignes[i], (double)x, (double)y) == 0)
|
902 | ok = 0;
|
903 | }
|
904 | } else {
|
905 | |
906 | original image with transfo_back, and then check if the
|
907 | point is in the box (this is easier since this box has
|
908 | edges parallel to coordinate axis) */
|
909 | transforme(transfo_back, (double)x, (double)y, &o_x, &o_y);
|
910 | if(shape_id == SHAPE_OVAL) {
|
911 | if(ov_dir == DIR_X) {
|
912 | if(o_x <= ov_x0) {
|
913 | ok = (SUM_SQUARE(o_x - ov_x0, o_y - ov_center) <= ov_r2);
|
914 | } else if(o_x>=ov_x1) {
|
915 | ok = (SUM_SQUARE(o_x - ov_x1, o_y - ov_center) <= ov_r2);
|
916 | } else {
|
917 | ok = (o_y>=ov_y0 && o_y<=ov_y1);
|
918 | }
|
919 | } else {
|
920 | if(o_y<=ov_y0) {
|
921 | ok = (SUM_SQUARE(o_y - ov_y0, o_x - ov_center) <= ov_r2);
|
922 | } else if(o_y>=ov_y1) {
|
923 | ok = (SUM_SQUARE(o_y - ov_y1, o_x - ov_center) <= ov_r2);
|
924 | } else {
|
925 | ok = (o_x >= ov_x0 && o_x <= ov_x1);
|
926 | }
|
927 | }
|
928 | } else {
|
929 | ok = !(o_x < o_xmin || o_x > o_xmax || o_y < o_ymin || o_y > o_ymax);
|
930 | }
|
931 | }
|
932 | if(ok == 1) {
|
933 | npix++;
|
934 | if(PIXEL(src,x,y))
|
935 | npixnoir++;
|
936 | if(illustr.data != NULL && illustr_mode == ILLUSTR_PIXELS) {
|
937 | |
938 | taken into account while computing the darkness ratio of
|
939 | the boxes */
|
940 | illustr.ptr<uchar>(y)[x*3] = (PIXEL(src,x,y) ? 0 : 255);
|
941 | illustr.ptr<uchar>(y)[x*3 + 1] = 128;
|
942 | illustr.ptr<uchar>(y)[x*3 + 2] = 0;
|
943 | }
|
944 | }
|
945 | }
|
946 | }
|
947 |
|
948 | if(view == 1 || illustr.data != NULL) {
|
949 | for(int i = 0; i < 4; i++) {
|
950 | coins_int[i].x = (int)coins[i].x;
|
951 | coins_int[i].y = (int)coins[i].y;
|
952 | }
|
953 | }
|
954 | #ifdef OPENCV_21
|
955 | if(view == 1) {
|
956 | for(int i = 0; i < 4; i++) {
|
957 | cv::line(dst, coins_int[i], coins_int[(i+1)%4], RGB_COLOR(255,255,255), 1, cv::LINE_AA);
|
958 | }
|
959 | }
|
960 | #endif
|
961 | if(illustr.data != NULL) {
|
962 |
|
963 | if(illustr_mode == ILLUSTR_BOX) {
|
964 |
|
965 | for(int i = 0; i < 4; i++) {
|
966 | cv::line(illustr, coins_int[i], coins_int[(i+1)%4], ROSE, 1, cv::LINE_AA);
|
967 | }
|
968 | }
|
969 |
|
970 |
|
971 |
|
972 | if(zooms_dir != NULL && student >= 0) {
|
973 |
|
974 |
|
975 | ok = check_zooms_dir(student, zooms_dir, 0);
|
976 |
|
977 |
|
978 | if(ok) {
|
979 | if(asprintf(&zoom_file, "%s/%d-%d.png", zooms_dir, question, answer)>0) {
|
980 | printf(": Saving zoom to %s\n", zoom_file);
|
981 | printf(": Z=(%d,%d)+(%d,%d)\n",
|
982 | z_xmin, z_ymin, z_xmax - z_xmin, z_ymax - z_ymin);
|
983 | cv::Mat roi = illustr(cv::Rect(z_xmin, z_ymin, z_xmax - z_xmin, z_ymax - z_ymin));
|
984 |
|
985 | bool result = false;
|
986 | try {
|
987 | result = cv::imwrite(zoom_file, roi
|
988 | #if OPENCV_20
|
989 | , save_options
|
990 | #endif
|
991 | );
|
992 | } catch(const cv::Exception& ex) {
|
993 | printf("! ZOOMS : Zoom save error: %s\n", ex.what());
|
994 | }
|
995 | if(result)
|
996 | printf("ZOOM %d-%d.png\n", question, answer);
|
997 |
|
998 | } else {
|
999 | printf("! ZOOMFN : Zoom file name error\n");
|
1000 | }
|
1001 | }
|
1002 | }
|
1003 | }
|
1004 |
|
1005 | printf("PIX %d %d\n", npixnoir, npix);
|
1006 | }
|
1007 |
|
1008 | |
1009 |
|
1010 | Processes command-line parameters, and then reads commands from
|
1011 | standard input, and answers them on standard output.
|
1012 |
|
1013 | */
|
1014 |
|
1015 | int main(int argc, char** argv)
|
1016 | {
|
1017 | if(! setlocale(LC_ALL, "POSIX")) {
|
1018 | printf("! LOCALE : setlocale failed\n");
|
1019 | }
|
1020 |
|
1021 | double threshold = 0.6;
|
1022 | double taille_orig_x = 0;
|
1023 | double taille_orig_y = 0;
|
1024 | double dia_orig = 0;
|
1025 | double tol_plus = 0;
|
1026 | double tol_moins = 0;
|
1027 | int n_min_cc = 3;
|
1028 |
|
1029 | double prop, xmin, xmax, ymin, ymax;
|
1030 | double coins_x[4], coins_y[4];
|
1031 | double coins_x0[4], coins_y0[4];
|
1032 | double tmp;
|
1033 | int upside_down;
|
1034 | int i;
|
1035 | int student, page, question, answer;
|
1036 | point box[4];
|
1037 | linear_transform transfo, transfo_back;
|
1038 | double mse;
|
1039 |
|
1040 | cv::Mat src;
|
1041 | cv::Mat dst;
|
1042 | cv::Mat illustr;
|
1043 | cv::Mat src_calage;
|
1044 |
|
1045 | int illustr_mode = ILLUSTR_BOX;
|
1046 |
|
1047 | char *scan_file = NULL;
|
1048 | char *out_image_file = NULL;
|
1049 | char *zooms_dir = NULL;
|
1050 | int view = 0;
|
1051 | int post_process_image = 0;
|
1052 | int ignore_red = 0;
|
1053 |
|
1054 | #if OPENCV_20
|
1055 | vector<int> save_options;
|
1056 | save_options.push_back(cv::IMWRITE_JPEG_QUALITY);
|
1057 | save_options.push_back(75);
|
1058 | #endif
|
1059 |
|
1060 |
|
1061 |
|
1062 |
|
1063 |
|
1064 |
|
1065 |
|
1066 |
|
1067 |
|
1068 |
|
1069 |
|
1070 |
|
1071 | char c;
|
1072 | while ((c = getopt(argc, argv, "x:y:d:i:p:m:t:c:o:vPrk")) != -1) {
|
1073 | switch (c) {
|
1074 | case 'x': taille_orig_x = atof(optarg); break;
|
1075 | case 'y': taille_orig_y = atof(optarg); break;
|
1076 | case 'd': dia_orig = atof(optarg); break;
|
1077 | case 'p': tol_plus = atof(optarg); break;
|
1078 | case 'm': tol_moins = atof(optarg); break;
|
1079 | case 't': threshold = atof(optarg); break;
|
1080 | case 'c': n_min_cc = atoi(optarg); break;
|
1081 | case 'o': out_image_file = strdup(optarg); break;
|
1082 | case 'v': view = 1; break;
|
1083 | case 'r': ignore_red = 1; break;
|
1084 | case 'P': post_process_image = 1; view = 2; break;
|
1085 | case 'k': illustr_mode=ILLUSTR_PIXELS; break;
|
1086 | }
|
1087 | }
|
1088 |
|
1089 | printf("TX=%.2f TY=%.2f DIAM=%.2f\n", taille_orig_x, taille_orig_y, dia_orig);
|
1090 |
|
1091 | size_t commande_t;
|
1092 | char* commande = NULL;
|
1093 | char* endline;
|
1094 | char text[128];
|
1095 | char shape_name[32];
|
1096 | int shape_id;
|
1097 |
|
1098 | cv::Point textpos;
|
1099 | double fh;
|
1100 |
|
1101 | while(getline(&commande, &commande_t, stdin) >= 6) {
|
1102 |
|
1103 |
|
1104 | if((endline=strchr(commande, '\r')))
|
1105 | *endline='\0';
|
1106 | if((endline=strchr(commande, '\n')))
|
1107 | *endline='\0';
|
1108 |
|
1109 | if(processing_error == 0) {
|
1110 |
|
1111 | if(strncmp(commande, "output ", 7) == 0) {
|
1112 | free(out_image_file);
|
1113 | out_image_file = strdup(commande + 7);
|
1114 | } else if(strncmp(commande,"zooms ", 6)==0) {
|
1115 | free(zooms_dir);
|
1116 | zooms_dir = strdup(commande + 6);
|
1117 | } else if(strncmp(commande,"load ", 5)==0) {
|
1118 | free(scan_file);
|
1119 | scan_file = strdup(commande + 5);
|
1120 |
|
1121 | if(out_image_file != NULL
|
1122 | && !post_process_image) {
|
1123 | illustr = cv::imread(scan_file, cv::IMREAD_COLOR);
|
1124 | if(illustr.data == NULL) {
|
1125 | printf("! LOAD : Error loading scan file with color mode [%s]\n", scan_file);
|
1126 | processing_error = 4;
|
1127 | } else {
|
1128 | printf(": Image background loaded\n");
|
1129 | }
|
1130 | }
|
1131 |
|
1132 | load_image(src,scan_file, ignore_red, threshold, view);
|
1133 | printf(": Image loaded\n");
|
1134 |
|
1135 | if(processing_error == 0) {
|
1136 | src_calage = src.clone();
|
1137 | if(src_calage.data == NULL) {
|
1138 | printf("! LOAD : Error cloning image\n");
|
1139 | processing_error = 5;
|
1140 | }
|
1141 | }
|
1142 | if(processing_error == 0) {
|
1143 | calage(src_calage,
|
1144 | illustr,
|
1145 | taille_orig_x,
|
1146 | taille_orig_y,
|
1147 | dia_orig,
|
1148 | tol_plus,
|
1149 | tol_moins,
|
1150 | n_min_cc,
|
1151 | coins_x,
|
1152 | coins_y,
|
1153 | dst,
|
1154 | view);
|
1155 |
|
1156 | upside_down = 0;
|
1157 | }
|
1158 |
|
1159 | if(out_image_file != NULL && illustr.data == NULL) {
|
1160 | printf(": Storing layout image\n");
|
1161 | illustr = dst;
|
1162 | dst = cv::Mat();
|
1163 | }
|
1164 |
|
1165 | src_calage.release();
|
1166 |
|
1167 | } else if((sscanf(commande,"optim3 %lf,%lf %lf,%lf %lf,%lf %lf,%lf",
|
1168 | &coins_x0[0], &coins_y0[0],
|
1169 | &coins_x0[1], &coins_y0[1],
|
1170 | &coins_x0[2], &coins_y0[2],
|
1171 | &coins_x0[3], &coins_y0[3]) == 8)
|
1172 | || (strncmp(commande,"reoptim3",8) == 0) ) {
|
1173 |
|
1174 | |
1175 | order: UL UR BR BL)
|
1176 | return: optimal linear transform and MSE */
|
1177 |
|
1178 | mse = omit_optim(coins_x0, coins_y0, coins_x, coins_y, 4, &transfo);
|
1179 | printf("Transfo:\na=%f\nb=%f\nc=%f\nd=%f\ne=%f\nf=%f\n",
|
1180 | transfo.a, transfo.b,
|
1181 | transfo.c, transfo.d,
|
1182 | transfo.e, transfo.f);
|
1183 | printf("MSE=0.0\n");
|
1184 | printf("QUALITY=%f\n", mse);
|
1185 |
|
1186 | revert_transform(&transfo, &transfo_back);
|
1187 |
|
1188 | } else if((sscanf(commande,"optim %lf,%lf %lf,%lf %lf,%lf %lf,%lf",
|
1189 | &coins_x0[0], &coins_y0[0],
|
1190 | &coins_x0[1], &coins_y0[1],
|
1191 | &coins_x0[2], &coins_y0[2],
|
1192 | &coins_x0[3], &coins_y0[3]) == 8)
|
1193 | || (strncmp(commande,"reoptim",7) == 0) ) {
|
1194 | |
1195 | order: UL UR BR BL)
|
1196 | return: optimal linear transform and MSE */
|
1197 |
|
1198 | mse = optim(coins_x0,coins_y0,coins_x,coins_y,4,&transfo);
|
1199 | printf("Transfo:\na=%f\nb=%f\nc=%f\nd=%f\ne=%f\nf=%f\n",
|
1200 | transfo.a, transfo.b,
|
1201 | transfo.c, transfo.d,
|
1202 | transfo.e, transfo.f);
|
1203 | printf("MSE=%f\n",mse);
|
1204 |
|
1205 | revert_transform(&transfo, &transfo_back);
|
1206 |
|
1207 | } else if(strncmp(commande,"rotateOK",8) == 0) {
|
1208 |
|
1209 | if(upside_down) {
|
1210 | transfo.a = - transfo.a;
|
1211 | transfo.b = - transfo.b;
|
1212 | transfo.c = - transfo.c;
|
1213 | transfo.d = - transfo.d;
|
1214 | transfo.e = (src.cols - 1) - transfo.e;
|
1215 | transfo.f = (src.rows - 1) - transfo.f;
|
1216 |
|
1217 | if(src.data != NULL)
|
1218 | cv::flip(src, src, -1);
|
1219 | if(illustr.data != NULL)
|
1220 | cv::flip(illustr, illustr, -1);
|
1221 | if(dst.data != NULL)
|
1222 | cv::flip(dst, dst, -1);
|
1223 |
|
1224 | for(i = 0; i < 4; i++) {
|
1225 | coins_x[i] = (src.cols - 1) - coins_x[i];
|
1226 | coins_y[i] = (src.rows - 1) - coins_y[i];
|
1227 | }
|
1228 |
|
1229 | upside_down = 0;
|
1230 |
|
1231 | printf("Transfo:\na=%f\nb=%f\nc=%f\nd=%f\ne=%f\nf=%f\n",
|
1232 | transfo.a, transfo.b,
|
1233 | transfo.c, transfo.d,
|
1234 | transfo.e, transfo.f);
|
1235 |
|
1236 | revert_transform(&transfo, &transfo_back);
|
1237 | }
|
1238 | } else if(strncmp(commande,"rotate180", 9) == 0) {
|
1239 | for(i = 0; i < 2; i++) {
|
1240 | SWAP(coins_x[i], coins_x[i+2], tmp);
|
1241 | SWAP(coins_y[i], coins_y[i+2], tmp);
|
1242 | }
|
1243 | upside_down = 1 - upside_down;
|
1244 | printf("UpsideDown=%d\n", upside_down);
|
1245 | } else if(sscanf(commande,"id %d %d %d %d",
|
1246 | &student, &page, &question, &answer) == 4) {
|
1247 |
|
1248 | } else if(sscanf(commande, "mesure0 %lf %s %lf %lf %lf %lf",
|
1249 | &prop, shape_name,
|
1250 | &xmin, &xmax, &ymin, &ymax) == 6) {
|
1251 | |
1252 | return: number of black pixels and total number of pixels */
|
1253 | transforme(&transfo, xmin, ymin, &box[0].x, &box[0].y);
|
1254 | transforme(&transfo, xmax, ymin, &box[1].x, &box[1].y);
|
1255 | transforme(&transfo, xmax, ymax, &box[2].x, &box[2].y);
|
1256 | transforme(&transfo, xmin, ymax, &box[3].x, &box[3].y);
|
1257 |
|
1258 | if(strcmp(shape_name,"oval") == 0) {
|
1259 | shape_id = SHAPE_OVAL;
|
1260 | } else {
|
1261 | shape_id = SHAPE_SQUARE;
|
1262 | }
|
1263 |
|
1264 |
|
1265 | for(i = 0; i < 4; i++) {
|
1266 | printf("TCORNER %.3f,%.3f\n", box[i].x, box[i].y);
|
1267 | }
|
1268 |
|
1269 | mesure_case(src, illustr, illustr_mode,
|
1270 | student, page, question, answer,
|
1271 | prop, shape_id,
|
1272 | xmin, xmax, ymin, ymax, &transfo_back,
|
1273 | box, dst, zooms_dir, view);
|
1274 | student = -1;
|
1275 | } else if(sscanf(commande,"mesure %lf %lf %lf %lf %lf %lf %lf %lf %lf",
|
1276 | &prop,
|
1277 | &box[0].x, &box[0].y,
|
1278 | &box[1].x, &box[1].y,
|
1279 | &box[2].x, &box[2].y,
|
1280 | &box[3].x, &box[3].y) == 9) {
|
1281 | |
1282 | (x y, order: UL UR BR BL)
|
1283 | returns: number of black pixels and total number of pixels */
|
1284 | mesure_case(src, illustr, illustr_mode,
|
1285 | student, page, question, answer,
|
1286 | prop, SHAPE_SQUARE,
|
1287 | -1, -1, -1, -1, NULL,
|
1288 | box, dst, zooms_dir, view);
|
1289 | student = -1;
|
1290 | } else if(strlen(commande) < 100 &&
|
1291 | sscanf(commande, "annote %s", text) == 1) {
|
1292 | fh = src.rows / 50.0;
|
1293 | textpos.x = 10;
|
1294 | textpos.y = (int)(1.6 * fh);
|
1295 | cv::putText(illustr, text, textpos, cv::FONT_HERSHEY_PLAIN, fh/14, BLEU, 1+(int)(fh/20), cv::LINE_AA);
|
1296 | } else {
|
1297 | printf(": %s\n", commande);
|
1298 | printf("! SYNERR : Syntax error\n");
|
1299 | }
|
1300 |
|
1301 | } else {
|
1302 | printf("! ERROR : not responding due to previous error.\n");
|
1303 | }
|
1304 |
|
1305 | printf("__END__\n");
|
1306 | fflush(stdout);
|
1307 | }
|
1308 |
|
1309 | #ifdef OPENCV_21
|
1310 | #ifdef AMC_DETECT_HIGHGUI
|
1311 | if(view == 1) {
|
1312 | cvNamedWindow("Source", cv::WINDOW_NORMAL);
|
1313 | cvShowImage("Source", src);
|
1314 | cvNamedWindow("Components", cv::WINDOW_NORMAL);
|
1315 | cvShowImage("Components", dst);
|
1316 | cvWaitKey(0);
|
1317 |
|
1318 | cvReleaseImage(&dst);
|
1319 | }
|
1320 | #endif
|
1321 | #endif
|
1322 |
|
1323 | if(illustr.data && strlen(out_image_file) > 1) {
|
1324 | printf(": Saving layout image to %s\n", out_image_file);
|
1325 | try {
|
1326 | cv::imwrite(out_image_file, illustr
|
1327 | #if OPENCV_20
|
1328 | , save_options
|
1329 | #endif
|
1330 | );
|
1331 | } catch(const cv::Exception& ex) {
|
1332 | printf("! LAYS : Layout image save error: %s\n", ex.what());
|
1333 | }
|
1334 | }
|
1335 |
|
1336 | illustr.release();
|
1337 | src.release();
|
1338 |
|
1339 | free(commande);
|
1340 | free(scan_file);
|
1341 |
|
1342 | return(0);
|
1343 | }
|
1344 |
|