AMC-detect.cc

Alexis Bienvenüe, 12/06/2018 06:47 pm

Download (40.3 kB)

 
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
        // 'src' will only keep the red channel.
174
        src = cv::Mat(color.rows, color.cols,
175
          CV_MAKETYPE(color.depth(), 1 /* 1 channel for red */));
176
177
        // Take the red channel (2) from 'color' and put it in the
178
        // only channel of 'src' (0).
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
/* LINEAR TRANSFORMS */
239
240
/* the linear_transform  structure contains a linear transform
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
/* transforme(t,x,y,&xp,&yp) applies the linear transform t to the
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
/* POINTS AND LINES */
259
260
/* point structure */
261
262
typedef struct {
263
  double x,y;
264
} point;
265
266
/* line structure (through its equation ax+by+c=0) */
267
268
typedef struct {
269
  double a,b,c;
270
} ligne;
271
272
/* calcule_demi_plan(...) computes the equation of line (AB) from the
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
/* evalue_demi_plan(...) computes the sign of ax+by+c from line
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
/* VECTOR ARITHMETIC */
294
295
/* moyenne(x[],n) returns the mean of the n values from vector x[]
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
/* scalar_product(x[],y[],n) returns the scalar product of vectors x[]
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
/* sys_22(...) solves the 2x2 linear system
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
/* square of x */
348
349
double sqr(double x) { return(x*x); }
350
351
/* LINEAR TRANSFORM OPTIMIZATION */
352
353
/* revert_transform(...) computes the inverse transform of *direct,
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
/* optim(...) computes the linear transform T such that the sum S of
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
/* transform_quality(&t) returns a "square distance" from the
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
/* omit_optim(...) tries an optim() call omitting in turn one of the
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
/* calage(...) tries to detect the position of a page on a scan.
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
  /* computes target min and max size */
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
  /* 1) remove holes that are smaller than 1/8 times the target mark
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
    /* prepares *dst from a copy of the scan (after pre-processing). */
520
    dst = cv::Mat(cv::Size(src.rows, src.cols), CV_MAKETYPE(CV_8U, 3));
521
522
    // cvConvertImage(src, *dst) is not needed anymore as 'src' and 'dst' have
523
    // the same type.
524
    cv::bitwise_not(dst,dst);
525
  }
526
527
  printf("Target size: %.1f ; %.1f\n", target_min, target_max);
528
529
  /* 2) find connected components */
530
531
  // CvSeq* contour = 0;
532
  vector<vector<cv::Point> > contours;
533
  vector<cv::Vec4i> hierarchy; // unused; but could be used in drawContours
534
  cv::findContours(src, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
535
536
#ifdef OPENCV_21
537
  if(view == 1) {
538
    /* prepares *dst as a white image with same size as the scan. */
539
    dst = cv::Mat::zeros(cv::Size(src.rows, src.cols), CV_MAKETYPE(src.depth(), 3));
540
  }
541
#endif
542
543
  /* 3) returns the result, and draws reports */
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
    /* discard the connected components that are too large or too small */
553
    if(rect.width <= target_max && rect.width >= target_min &&
554
       rect.height <= target_max && rect.height >= target_min) {
555
      /* updates the extreme points coordinates from the coordinates
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
      /* outputs connected component center and size. */
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
       /* draws the connected component, and the enclosing rectangle,
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
       /* draws the connected component, and the enclosing rectangle,
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
      /* computes integer coordinates of the extreme coordinates, for
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
      /* outputs extreme points coordinates: the (supposed)
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
      /* draws a rectangle to see the corner marks positions on the scan. */
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
      /* draws a rectangle to see the corner marks positions on the scan. */
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
      /* draws a rectangle to see the corner marks positions on the scan. */
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
    /* There are less than 3 correct connected components: can't know
622
       where are the marks on the scan! */
623
    printf("! NMARKS=%d : Not enough marks detected.\n", n_cc);
624
  }
625
}
626
627
/* moves A and B to each other, proportion delta of the distance
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
/* deplace(...) moves coins[i] and coins[j] to each other */
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
/* deplace_xy(...) moves two real numbers *m1 and *m2 to each other,
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
/* restreint(...) ensures that the point (*x,*y) is inside the image,
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
/* if student>=0, check_zooms_dir(...) checks that the zoom directory
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
/* mesure_case(...) computes the darkness value (number of black
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
      /* draws the box on the illustrated image (for zoom) */
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
    /* bounding box for zoom */
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
    /* a little bit larger... */
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
  /* box reduction */
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
  /* output points used for mesuring */
844
  for(i = 0; i < 4; i++) {
845
    printf("COIN %.3f,%.3f\n",coins[i].x,coins[i].y);
846
  }
847
848
  /* bounding box */
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
    /* computes half planes equations */
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
        /* vertical oval */
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
        /* horizontal oval */
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
        /* With "mesure" command, checks if this point is in the box
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
        /* With "mesure0" command, computes the coordinates in the
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
          /* with option -k, colors (on the zooms) pixels that are
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
      /* draws the measuring box on the illustrated image (for zoom) */
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
    /* making zoom */
971
972
    if(zooms_dir != NULL && student >= 0) {
973
974
      /* check if directory is present, or ceate it */
975
      ok = check_zooms_dir(student, zooms_dir, 0);
976
977
      /* save zoom file */
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
/* MAIN
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
  // Options
1061
  // -x tx : gives the width of the original subject
1062
  // -y ty : gives the height of the opriginal subject
1063
  // -d d  : gives the diameter of the corner marks on the original subject
1064
  // -p dp : gives the tolerance above mark diameter (fraction of the diameter)
1065
  // -m dm : gives the tolerance below mark diameter
1066
  // -c n  : gives the minimum requested number of corner marks
1067
  // -t th : gives the threshold to convert to black&white
1068
  // -o file : gives output file name for detected layout report image
1069
  // -v / -P : asks for marks detection debugging image report
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
    //printf("LC_NUMERIC: %s\n",setlocale(LC_NUMERIC,NULL));
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
        /* TRYING TO OMIT EACH CORNER IN TURN */
1174
        /* "optim3" and 8 arguments: 4 marks positions (x y,
1175
           order: UL UR BR BL)
1176
           return: optimal linear transform and MSE */
1177
        /* "reoptim3": optim with the same arguments as for last "optim" call */
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
        /* "optim" and 8 arguments: 4 marks positions (x y,
1195
           order: UL UR BR BL)
1196
           return: optimal linear transform and MSE */
1197
        /* "reoptim": optim with the same arguments as for last "optim" call */
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
        /* validates upside down rotation */
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
        /* box id */
1248
      } else if(sscanf(commande, "mesure0 %lf %s %lf %lf %lf %lf",
1249
                       &prop, shape_name,
1250
                       &xmin, &xmax, &ymin, &ymax) == 6) {
1251
        /* "mesure0" and 6 arguments: proportion, shape, xmin, xmax, ymin, ymax
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
        /* output transformed points */
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
        /* "mesure" and 9 arguments: proportion, and 4 vertices
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