├── .gitignore
├── README.md
├── crosshatched.cpp
├── examples
├── emily.gif
├── goldengate.png
├── matrix.gif
├── obama.png
└── thailand.jpg
├── makefile
└── sources
├── goldengate.jpg
├── obama.jpg
├── pipe.jpg
├── sagrada.JPG
└── thailand.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.mp4
3 | *.m4a
4 | *.so
5 | *.mov
6 | /*.png
7 | a.out
8 | out\.*
9 | crosshatched
10 | historical/*
11 | www/*
12 | examples/ignore/*
13 | sources/ignore/*
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #### Use OpenCV to create crosshatch-style drawings and videos.
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | #### Try out a canvas-based implementation at https://www.sketchify.me
10 |
11 | #### To use:
12 | ```
13 | $ make
14 | $ ./crosshatched sources/goldengate.jpg
15 | $ ./crosshatched --laplacian sources/thailand.jpg
16 | $ ./crosshatched path/to/a/video.mp4
17 | ```
18 |
19 | This script is optimized for images and videos >= 1080 pixels wide. You can also
20 | get decent results out of smaller images by tweaking the constants although
21 | bigger seems to be better in most cases.
22 |
23 | There are a bunch more flags too worth playing around with
24 |
25 | #### The algorithm:
26 | - Calculate the gradient of the image
27 | - Draw short, connected bezier lines parallel and perpendicular to that gradient
28 | - Generate the "edge gradient" from either the Laplacian or Canny (default) algorithms
29 | - Using slightly tweaked rules, draw more bezier lines parallel to the edge gradient
30 |
31 | #### Dependencies:
32 | - g++ (Part of the GNU Compiler Collection)
33 | - OpenCV
34 | - Tons on other stuff (install everything one error at a time)
35 |
36 | #### TODO:
37 | I'd love to pipe this through a neural-network style-transfer first to get something really artistic
38 |
39 | #### A note:
40 | I'm generally a Python programmer so please forgive my shitty C++ code. I'm also not at work so I'm not keeping my code clean
41 |
42 |
--------------------------------------------------------------------------------
/crosshatched.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | This will take an input image and stylize it to make it look like a crosshatch
3 | drawing.
4 |
5 | Opencv is the big requirement here and you will need to install that.
6 | */
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include "opencv2/highgui/highgui.hpp"
18 | #include "opencv2/imgproc/imgproc.hpp"
19 | #include "opencv2/core/core.hpp"
20 | #include "opencv2/video/video.hpp"
21 |
22 | using namespace std;
23 | using namespace cv;
24 | using namespace std::chrono;
25 |
26 |
27 | #define BLACK 0
28 | #define WHITE 255
29 | #define PI 3.1415926
30 | #define OUT_VIDEO_FILENAME "out.mp4"
31 | #define WINDOW_NAME "window"
32 | #define DEBUG_WINDOW_WIDTH 700
33 |
34 | int SOBEL_KERNEL = 3;
35 | int BLUR_SIZE = 20;
36 | int BLOCK_SIZE_THRESHOLD = 35;
37 | int LINE_LEN = 5;
38 | int CANNY_LOWER = 80;
39 | int CANNY_UPPER = 250;
40 | int LAPLACE = 7;
41 | int CANNY_GAUSS_BLUR = 1;
42 | int PERPENDICULAR_TOLERANCE = 15;
43 | int GRADIENT_TOLERANCE = 30;
44 | int CANNY_TOLERANCE = 50;
45 |
46 | int GRAD_LIMIT = 4;
47 | int EDGE_LIMIT = 8;
48 | int CANNY_DIV = 2;
49 | int WHITE_THRESHOLD = 180;
50 | int NORMALIZE_BRIGHTNESS_TO = 140;
51 | int PERP_LESS_THRESHOLD = 50;
52 |
53 | int num_strokes = 0;
54 |
55 | mutex g_pages_mutex;
56 |
57 | struct Flags {
58 | bool laplacian = false;
59 | bool noequalize = false;
60 | bool debug = false;
61 | bool invert = false;
62 | bool noedges = false;
63 | bool nofill = false;
64 | bool strongcorners = false;
65 | bool crayolaize = false;
66 | bool saturate = false;
67 | bool threshold = false;
68 | bool bw = false;
69 | bool strokes = false;
70 | int resize_width = 2000;
71 | int line_density = 5;
72 | int brighten = 0;
73 | string out_filename = "out.png";
74 | string edges_filename = "";
75 | string edges_mask_filename = "";
76 | string fill_filename = "";
77 | string color_filename = "";
78 | };
79 |
80 | int get_int(Mat img, Point p) {
81 | return img.at(p.y, p.x);
82 | }
83 |
84 | float get_fl(Mat img, Point p) {
85 | return img.at(p.y, p.x);
86 | }
87 |
88 | Vec3b get_color(Mat img, Point p) {
89 | return img.at(p.y, p.x);
90 | }
91 |
92 | void set_color(Mat img, Point p, Vec3b color) {
93 | img.at(p.y, p.x) = color;
94 | }
95 |
96 | void set_int(Mat img, Point p, int val) {
97 | img.at(p.y, p.x) = val;
98 | }
99 |
100 | float vect_len(Point pnt) {
101 | return sqrt((float)(pnt.x*pnt.x + pnt.y*pnt.y));
102 | }
103 |
104 | float dot(Point a, Point b) {
105 | // dot product
106 | return a.x*b.x + a.y*b.y;
107 | }
108 |
109 | float rads_btwn_vectors(Point a, Point b) {
110 | // shortest distance angle between two vectors (so max == PI)
111 | return acos(dot(a, b)/(vect_len(a)*vect_len(b)));
112 | }
113 |
114 | Mat zeroes_int(Mat img) {
115 | return img.zeros(img.rows, img.cols, CV_8UC1);
116 | }
117 |
118 | Mat white_int(Mat img) {
119 | Mat ret = zeroes_int(img);
120 | ret.setTo(255);
121 | return ret;
122 | }
123 |
124 | Mat zeroes_fl(Mat img) {
125 | return img.zeros(img.rows, img.cols, CV_32FC1);
126 | }
127 |
128 | Mat white_fl(Mat img) {
129 | Mat ret = zeroes_fl(img);
130 | ret.setTo(Scalar(255,255,255));
131 | return ret;
132 | }
133 |
134 | float rand_float() {
135 | // returns a float between 0 and 1
136 | return (float)rand()/RAND_MAX;
137 | }
138 |
139 | int tim() {
140 | milliseconds ms = duration_cast< milliseconds >(system_clock::now().time_since_epoch());
141 | return ms.count();
142 | }
143 |
144 | void wait() {
145 | while (waitKey(30) < 0);
146 | }
147 |
148 | void show_and_wait(Mat img) {
149 | // show image and wait for keyboard input
150 | imshow(WINDOW_NAME, img);
151 | wait();
152 | }
153 |
154 |
155 | vector crayola(24);
156 |
157 | void setup_colors() {
158 | crayola.push_back(Vec3b(237, 10, 63)); // "#ED0A3F" //Red
159 | crayola.push_back(Vec3b(255, 134, 31)); // #FF3F34 // Red Orange (255, 63, 52)
160 | crayola.push_back(Vec3b(255, 134, 31)); // #FF861F // Orange
161 | crayola.push_back(Vec3b(251, 232, 112)); // #FBE870 // Yellow
162 | crayola.push_back(Vec3b(197, 225, 122)); // #C5E17A // Yellow Green
163 | crayola.push_back(Vec3b(1, 163, 104)); // #01A368 // Green
164 | crayola.push_back(Vec3b(118, 215, 234)); // #76D7EA // Sky Blue
165 | crayola.push_back(Vec3b(0, 102, 255)); //0066FF Blue
166 | crayola.push_back(Vec3b(131, 89, 163)); // #8359A3 // Violet (Purple)
167 | crayola.push_back(Vec3b(175, 89, 62)); // #AF593E // Brown
168 | crayola.push_back(Vec3b(0, 0, 0)); // #000000 //Black
169 | crayola.push_back(Vec3b(255, 255, 255)); // #FFFFFF // White
170 | crayola.push_back(Vec3b(3, 187, 133)); // Aqua Green #03BB85
171 | crayola.push_back(Vec3b(255, 223, 0)); // Golden Yellow #FFDF00
172 | crayola.push_back(Vec3b(139, 134, 128)); // Gray #8B8680
173 | crayola.push_back(Vec3b(10, 107, 13)); // Jade Green #0A6B0D
174 | crayola.push_back(Vec3b(143, 216, 216)); // Light Blue #8FD8D8
175 | crayola.push_back(Vec3b(246, 83, 166)); // Magenta #F653A6
176 | crayola.push_back(Vec3b(202, 52, 53)); // Mahogany #CA3435
177 | crayola.push_back(Vec3b(255, 203, 164)); // Peach #FFCBA4
178 | crayola.push_back(Vec3b(205, 145, 158)); // Pink #CD919E
179 | crayola.push_back(Vec3b(250, 157, 90)); // Tan #FA9D5A
180 | crayola.push_back(Vec3b(163, 111, 64)); // Light Brown #A36F40
181 | crayola.push_back(Vec3b(255, 174, 66)); // Yellow Orange #FFAE42
182 |
183 | for (int i=0; i 128)
196 | val = 256-val;
197 | if (val < best_val) {
198 | best_idx = i;
199 | best_val = val;
200 | }
201 |
202 | }
203 |
204 | return crayola[best_idx];
205 | }
206 |
207 | void crayolaize(Mat color_img, Flags f) {
208 | cvtColor(color_img, color_img, CV_RGB2HSV);
209 | vector channels(3);
210 | split(color_img, channels);
211 |
212 | Vec3b red = Vec3b(251, 232, 112);
213 |
214 | for (int j=0; j 255)
219 | color[0] = 255;
220 | color[2] = (color[2]/50)*50; // 5 shades
221 | if (f.saturate) {
222 | if (color[1] > 128 && color[2] < 128) {
223 | int diff = 255 - color[1];
224 | color[1] = 255;
225 | color[2] += diff;
226 |
227 | }
228 | }
229 | set_color(color_img, Point(i,j), color);
230 | }
231 | }
232 | cvtColor(color_img, color_img, CV_HSV2RGB);
233 | }
234 |
235 | Mat apply_threshold(Mat bw) {
236 | Mat mask = zeroes_int(bw);
237 | Mat ret = white_int(bw);
238 | adaptiveThreshold(bw, mask, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, bw.cols/10+1, 0);
239 | bw.copyTo(ret, mask);
240 | return ret;
241 | }
242 |
243 | Mat compute_gradient_mask(Mat img, bool is_edge) {
244 | // Returns an image of gradient angles.
245 | // Edges have smaller convolution kernels because they need to be more detailed
246 | // Non-edges use integer rather than float matricies to reduce distracting swirling
247 | // in the image and, as a side effect, speed up the program.
248 |
249 | int sobel_kernel_1;
250 | Size blur_size_1;
251 |
252 | Mat mask = zeroes_fl(img);
253 | Mat sobelx, sobely;
254 | int datatype;
255 |
256 | medianBlur(img, img, 3);
257 |
258 | if (is_edge) {
259 | sobel_kernel_1 = 7;
260 | blur_size_1 = Size(3, 3);
261 | sobelx = zeroes_fl(img);
262 | sobely = zeroes_fl(img);
263 | datatype = CV_32FC1;
264 | }
265 | else {
266 | sobel_kernel_1 = SOBEL_KERNEL*2+1;
267 | blur_size_1 = Size(BLUR_SIZE*2+1, BLUR_SIZE*2+1);
268 | sobelx = zeroes_int(img);
269 | sobely = zeroes_int(img);
270 | datatype = CV_8UC1;
271 | }
272 |
273 | Sobel(img, sobelx, datatype, 1, 0, sobel_kernel_1);
274 | Sobel(img, sobely, datatype, 0, 1, sobel_kernel_1);
275 |
276 | boxFilter(sobelx, sobelx, datatype, blur_size_1);
277 | boxFilter(sobely, sobely, datatype, blur_size_1);
278 |
279 | for (int j=0; j(j,i) = atan2(sobely.at(j,i), sobelx.at(j,i));
283 | else
284 | mask.at(j,i) = atan2(sobely.at(j,i), sobelx.at(j,i));
285 |
286 | if (mask.at(j,i) == 0)
287 | mask.at(j,i) = PI/2 + (rand_float()-1)/2;
288 | }
289 | }
290 | return mask;
291 | }
292 |
293 | Point gradient_vector(float grad_val) {
294 | int dx = (int)(round(cos(grad_val)*LINE_LEN));
295 | int dy = (int)(round(sin(grad_val)*LINE_LEN));
296 | return Point(dx, dy);
297 | }
298 |
299 | Point perpendicular_vector(float grad_val) {
300 | int dx = (int)(round(sin(grad_val)*-1*LINE_LEN));
301 | int dy = (int)(round(cos(grad_val)*LINE_LEN));
302 | return Point(dx, dy);
303 |
304 | }
305 |
306 | Point next_point(Mat grad, Point prev, bool is_edge, bool is_perpendicular) {
307 | // depending on whether we want a point perpendicular or parallel to this
308 | // one, find it, check the range, and return it
309 | Point nxt;
310 | float grad_val = get_fl(grad, prev);
311 | if (is_edge || is_perpendicular)
312 | nxt = perpendicular_vector(grad_val);
313 | else
314 | nxt = gradient_vector(grad_val);
315 | nxt += prev;
316 | if (nxt.x<0 || nxt.x>=grad.cols || nxt.y<0 || nxt.y>=grad.rows)
317 | throw exception();
318 | return nxt;
319 | }
320 |
321 | void draw_stroke_on_img(vector stroke, Mat color, Mat ret, Flags f) {
322 | Point cur, nxt;
323 | cur = stroke[0];
324 | Scalar the_color = Scalar(0,0,0);
325 | for (int j=1; j> all_strokes;
346 | bool is_edge;
347 | int line_density;
348 | Flags f;
349 | // vector< vector > &strokes;
350 |
351 | public:
352 | Parallel_process(Mat _img, Mat _grad, Mat _color, Mat &_ret, bool _is_edge, int _line_density, vector> _all_strokes, Flags _f)
353 | : img(_img), grad(_grad), color(_color), ret(_ret), is_edge(_is_edge), line_density(_line_density), all_strokes(_all_strokes), f(_f){}
354 |
355 | virtual void operator()(const Range& range) const {
356 | for(int _y = range.start; _y < range.end; _y++) {
357 | for(int x = 0; x < img.cols; x+=line_density) {
358 | int y = _y*line_density;
359 | // printf("%d,%d\n", x, y);
360 | Point p0, p1, p2, dxdy, o_dxdy;
361 | bool is_perpendicular = rand()%2 == 0;
362 |
363 | int original_val = get_int(img, Point(x, y));
364 | if (original_val > WHITE_THRESHOLD+(is_perpendicular-1)*PERP_LESS_THRESHOLD ||
365 | rand_float() < original_val/255.0)
366 | continue;
367 |
368 | if (f.strongcorners) {
369 | int middle_x = img.cols/2;
370 | int middle_y = img.rows/2;
371 | float norm = (float)(middle_x*middle_y);
372 | if (pow((middle_y-y)*(middle_x-x)/norm,2) > pow(rand_float(), 2))
373 | continue;
374 | }
375 |
376 | int tolerance;
377 | if (is_edge)
378 | tolerance = CANNY_TOLERANCE;
379 | else if (is_perpendicular)
380 | tolerance = PERPENDICULAR_TOLERANCE;
381 | else
382 | tolerance = GRADIENT_TOLERANCE;
383 |
384 | // float grad_val = get_fl(grad,Point(x,y);
385 | // if (is_edge || is_perpendicular)
386 | // o_dxdy = perpendicular_vector(grad_val);
387 | // else
388 | // o_dxdy = gradient_vector(grad_val);
389 |
390 | p0 = Point(x, y);
391 | // int limit = is_edge ? 10-line : GRAD_LIMIT;
392 | int limit = 10-line_density;
393 | for (int i=0; i<5; i++) {
394 |
395 | try {
396 | p1 = next_point(grad, p0, is_edge, is_perpendicular);
397 | p2 = next_point(grad, p1, is_edge, is_perpendicular);
398 | }
399 | catch (exception& e) {
400 | break;
401 | }
402 |
403 | // if (i>0 && rads_btwn_vectors(o_dxdy, dxdy) > PI/8.0)
404 | // break;
405 |
406 | vector stroke;
407 | vector> all_strokes_2;
408 |
409 | stroke.push_back(p0);
410 | for (float t=.2; t<=1.0; t+=.2) {
411 | float bez_x = p0.x*(1.-t)*(1.-t) + 2*p1.x*(1.-t)*t + p2.x*t*t;
412 | float bez_y = p0.y*(1.-t)*(1.-t) + 2*p1.y*(1.-t)*t + p2.y*t*t;
413 | stroke.push_back(Point((int)bez_x, (int)bez_y));
414 | }
415 |
416 | // if (f.strokes) {
417 | // #pragma omp critical
418 | // all_strokes.insert(all_strokes.end(), all_strokes_2.begin(), all_strokes_2.end())
419 | // // all_strokes.push_back(stroke);
420 | // }
421 | // else {
422 | num_strokes += 1;
423 | draw_stroke_on_img(stroke, color, ret, f);
424 | // }
425 |
426 | int val = get_int(img, p2);
427 | if (val == WHITE || original_val + tolerance < val)
428 | break;
429 | p0 = p2;
430 | }
431 | }
432 | }
433 | }
434 | };
435 |
436 |
437 | // Apply our gradient algorithm to draw multiple lines on the image
438 | // This is the meat of this program
439 | // Given an image:
440 | // A: Calculate the gradient
441 | // B. For every "line_density" pixels
442 | // a. Randomly decide whether this will be a perpendicular or gradient line
443 | // b. If the image is white at that point or less than our random function, break
444 | // c. Extend two connected lines from that point (either along or perpendicular
445 | // to the gradient)
446 | // d. Create a bezier line from these two lines
447 | // e. If we have reached a spot that is too white (by a random function), break
448 | // f. Otherwise, continue this line
449 |
450 | Mat get_l_free(Mat img) {
451 | Mat ret = img.zeros(img.rows, img.cols, CV_8UC1);
452 | Mat lab = img.clone();
453 | cvtColor(lab, lab, CV_BGR2HSV);
454 | Mat planes[3];
455 | split(lab, planes);
456 | for(int y = 0; y < lab.rows; y++) {
457 | for(int x = 0; x < lab.cols; x++) {
458 | int l = get_int(planes[2], Point(x, y));
459 | int a = get_int(planes[1], Point(x, y));
460 | int b = get_int(planes[0], Point(x, y));
461 | set_int(ret, Point(x, y), (a+b+l/5)/3);
462 | }
463 | }
464 | equalizeHist(ret, ret);
465 | GaussianBlur(ret, ret, Size(CANNY_GAUSS_BLUR*2+1,CANNY_GAUSS_BLUR*2+1), 5);
466 | medianBlur(ret, ret, 5);
467 | return ret;
468 | }
469 |
470 | Mat get_canny(Mat img) {
471 | // The canny edges are crisper and look more like a kid's pencil drawing
472 | // which is the goal.
473 |
474 | Mat canny = img.clone();
475 | int height = img.rows;
476 | int width = img.cols;
477 |
478 | resize(canny, canny, Size(width/CANNY_DIV, height/CANNY_DIV));
479 | canny = get_l_free(canny);
480 | // imwrite("lfree.png", canny);
481 | Canny(canny, canny, CANNY_LOWER, CANNY_UPPER);
482 | GaussianBlur(canny, canny, Size(CANNY_GAUSS_BLUR*2+1,CANNY_GAUSS_BLUR*2+1), 0);
483 | resize(canny, canny, Size(width, height));
484 | // imwrite("canny.png", WHITE - canny);
485 | return WHITE - canny;
486 | }
487 |
488 | Mat get_laplacian(Mat img) {
489 | // Laplacian edges look much more realistic but a little less like a drawing
490 |
491 | Mat lap = zeroes_fl(img);
492 | Laplacian(img, lap, CV_8UC1, LAPLACE*2+1);
493 | GaussianBlur(lap, lap, Size(5,5), 0);
494 | return WHITE - lap;
495 | }
496 |
497 | // Mat draw_strokes_on_img(vector< vector > strokes, Mat color, Mat ret) {
498 | // // Strokes -> lines on image.
499 | // // Using the color of the original image is kind of cheating but the effect
500 | // // is still there
501 |
502 | // for (int i=0; i ac) ac = 0;
525 | if (ac > 255) ac = 255;
526 | if (0 > bc) bc = 0;
527 | if (bc > 255) bc = 255;
528 | if (0 > cc) cc = 0;
529 | if (cc > 255) cc = 255;
530 | set_color(img, Point(x, y), Vec3b(ac, bc, cc));
531 | }
532 | }
533 | }
534 |
535 |
536 | void apply_contrast_equalization(Mat color) {
537 | cvtColor(color, color, CV_RGB2HSV);
538 | vector planes(3);
539 | split(color, planes);
540 | Ptr clahe = createCLAHE();
541 | clahe->setClipLimit(4);
542 | clahe->apply(planes[2], planes[2]);
543 |
544 | int mean_brightness = (int)mean(planes[2])[0];
545 | cout << mean_brightness << endl;
546 | int add = NORMALIZE_BRIGHTNESS_TO - mean_brightness;
547 |
548 | // for(int y = 0; y < color.rows; y++) {
549 | // for(int x = 0; x < color.cols; x++) {
550 | // int ac = get_int(planes[2], Point(x, y)) + add;
551 | // if (0 > ac) ac = 0;
552 | // if (ac > 255) ac = 255;
553 | // set_int(planes[2], Point(x, y), ac);
554 | // }
555 | // }
556 | planes[2].convertTo(planes[2], -1, 1.0, add);
557 |
558 | // TODO this is might bea a hue-shift
559 | // for(int y = 0; y < dst.rows; y++) {
560 | // for(int x = 0; x < dst.cols; x++) {
561 | // set_int(planes[2], Point(x, y), abs(get_int(planes[2], Point(x, y))-30));
562 | // }
563 | // }
564 |
565 | merge(planes, color);
566 | cvtColor(color, color, CV_HSV2RGB);
567 | }
568 |
569 | Mat resize_to_width(Mat img, int width) {
570 | float rows_per_col = ((float)img.rows) / ((float)img.cols);
571 | resize(img, img, Size(width, (int)(width*rows_per_col)));
572 | return img;
573 | }
574 |
575 |
576 | void parallel_it(Mat bw, Mat grad, Mat color, Mat ret, bool is_edge, int line_density, vector> all_strokes, Flags f) {
577 | if (f.bw) {
578 | line_density*=3/4;
579 | }
580 | parallel_for_(Range(0, bw.rows/line_density), Parallel_process(bw, grad, color, ret, is_edge, line_density, all_strokes, f));
581 | }
582 |
583 |
584 | Mat apply_fast_gradient(Mat color, Flags f) {
585 | // We apply the algorithm in separate ways for the edges and for the rest of
586 | // the image. This gives us thicker edges that look more like a crosshatch
587 | // drawing
588 |
589 | if (!f.noequalize)
590 | apply_contrast_equalization(color);
591 |
592 | if (f.crayolaize) {
593 | crayolaize(color, f);
594 | // imwrite("crayola.png", color);
595 | }
596 |
597 | if (f.brighten) {
598 | brighten(color, f.brighten);
599 | }
600 |
601 | color = resize_to_width(color, f.resize_width);
602 |
603 | if (f.color_filename.length()) {
604 | imwrite(f.color_filename, color);
605 | }
606 |
607 | Mat bw = color.zeros(color.rows, color.cols, CV_8UC1);
608 | cvtColor(color, bw, CV_BGR2GRAY);
609 |
610 | if (f.threshold)
611 | bw = apply_threshold(bw);
612 |
613 | int a, b;
614 | Mat grad, grad2;
615 |
616 | Mat ret = bw.zeros(color.rows, color.cols, CV_8UC3);
617 | ret.setTo(WHITE);
618 |
619 | if (f.invert)
620 | bitwise_not(bw, bw);
621 |
622 | vector> all_strokes;
623 |
624 | if (!f.noedges) {
625 | a = tim();
626 | Mat edges = f.laplacian ? get_laplacian(bw) : get_canny(color);
627 | if (f.edges_mask_filename.length())
628 | imwrite(f.edges_mask_filename, edges);
629 | grad = compute_gradient_mask(edges, true);
630 | if (f.edges_filename.length()){
631 | grad.convertTo(grad2, CV_8UC1, (int)(255/6.28));
632 | imwrite(f.edges_filename, grad2);
633 | }
634 | else{
635 | int line_density = f.line_density/2;
636 | parallel_it(edges, grad, color, ret, true, line_density, all_strokes, f);
637 | }
638 | b = tim();
639 | }
640 |
641 | if (!f.nofill) {
642 | a = tim();
643 | grad = compute_gradient_mask(bw, false);
644 | if (f.fill_filename.length()){
645 | grad.convertTo(grad2, CV_8UC1, (int)(255/6.28));
646 | imwrite(f.fill_filename, grad2);
647 | }
648 | else{
649 | int line_density = f.line_density+1;
650 | parallel_it(bw, grad, color, ret, false, line_density, all_strokes, f);
651 | }
652 | b = tim();
653 | }
654 |
655 | cout << num_strokes << endl;
656 |
657 | if (f.strokes) {
658 | cout << all_strokes.size() << endl;
659 | exit(0);
660 | }
661 |
662 | return ret;
663 | }
664 |
665 | // void white_edges_corner() {
666 | // GaussianBlur(edges, edges, Size(CANNY_GAUSS_BLUR*5+1,CANNY_GAUSS_BLUR*5+1), 0);
667 | // threshold(edges, edges, 240, 255,THRESH_BINARY);
668 | // Mat colors2 = img.zeros(color.rows, color.cols, CV_8UC3);
669 | // colors2.setTo(WHITE);
670 | // imwrite("edges.png", edges);
671 | // // bitwise_and(color,color,color,edges);
672 | // color.copyTo(colors2, edges);
673 | // imwrite("mask.png", colors2);
674 |
675 | // }
676 |
677 | bool endswith(string const &fullString, string const &ending) {
678 | // string endswith
679 | if (fullString.length() >= ending.length())
680 | return (0 == fullString.compare(fullString.length() - ending.length(),
681 | ending.length(), ending));
682 | else
683 | return false;
684 | }
685 |
686 | void make_debug_window() {
687 | // if we want to change some constants
688 | namedWindow(WINDOW_NAME,1);
689 | createTrackbar( "BLUR_SIZE", WINDOW_NAME, &BLUR_SIZE, 30);
690 | createTrackbar( "SOBEL_KERNEL", WINDOW_NAME, &SOBEL_KERNEL, 15);
691 | createTrackbar( "BLOCK_SIZE_THRESHOLD", WINDOW_NAME, &BLOCK_SIZE_THRESHOLD, 50);
692 | createTrackbar( "LINE_LEN", WINDOW_NAME, &LINE_LEN, 15);
693 | createTrackbar( "CANNY_UPPER", WINDOW_NAME, &CANNY_UPPER, 256);
694 | createTrackbar( "CANNY_LOWER", WINDOW_NAME, &CANNY_LOWER, 256);
695 | createTrackbar( "LAPLACE", WINDOW_NAME, &LAPLACE, 30);
696 | createTrackbar( "CANNY_TOLERANCE", WINDOW_NAME, &CANNY_TOLERANCE, 256);
697 | createTrackbar( "CANNY_GAUSS_BLUR", WINDOW_NAME, &CANNY_GAUSS_BLUR, 20);
698 | createTrackbar( "CANNY_DIV", WINDOW_NAME, &CANNY_DIV, 10);
699 | }
700 |
701 |
702 | bool handle_video(VideoCapture cap, Flags f, bool is_live) {
703 |
704 | if(!cap.isOpened())
705 | return -1;
706 |
707 | int num_frames = cap.get(CV_CAP_PROP_FRAME_COUNT);
708 | int frame_width = cap.get(CV_CAP_PROP_FRAME_WIDTH);
709 | int frame_height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
710 | VideoWriter outputVideo(OUT_VIDEO_FILENAME, CV_FOURCC('8', 'B', 'P', 'S'),
711 | 10, Size(frame_width,frame_height), true);
712 |
713 | if (!outputVideo.isOpened()) {
714 | cout << "Could not open the output video for write" << endl;
715 | return -1;
716 | }
717 |
718 | if (f.debug) {
719 | make_debug_window();
720 | is_live = true;
721 | }
722 |
723 | Mat color, grad, color_out, bw;
724 |
725 | cap >> color;
726 | imshow(WINDOW_NAME, color);
727 | cvSetWindowProperty(WINDOW_NAME, CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
728 |
729 | queue time_queue;
730 | if (!is_live) time_queue.push(tim());
731 |
732 |
733 | while (true) {
734 | // for (int i=0; i> frame;
744 | // cout << "frame" << endl;
745 | // }
746 |
747 | // }
748 | int a = tim();
749 | cap >> color;
750 | if (color.empty()) {
751 | continue;
752 | }
753 | if (is_live) {
754 | // Flip along y axis for mirror effect
755 | flip(color, color, 1);
756 | }
757 |
758 | int b = tim();
759 |
760 |
761 | grad = apply_fast_gradient(color, f);
762 |
763 | if (is_live) {
764 | a = tim();
765 | imshow(WINDOW_NAME, grad);
766 | waitKey(1);
767 | b = tim();
768 | }
769 | else {
770 | cvtColor(grad, color_out, CV_GRAY2BGR); // mp4 requires a color frame
771 | outputVideo.write(color_out);
772 |
773 | // int minutes_remaining = (int)round((num_frames-i)*avg_time/60.0);
774 | // cout << "frame " << i << "/" << num_frames << endl;
775 | // cout << minutes_remaining << " minutes remaining" << endl;
776 | }
777 |
778 | time_queue.push(tim());
779 | if (time_queue.size() > 10) time_queue.pop();
780 | long avg_time = ((time_queue.back() - time_queue.front()))/(time_queue.size());
781 |
782 | cout << avg_time << endl;
783 | }
784 | if (!is_live)
785 | cout << "Wrote as " << OUT_VIDEO_FILENAME << endl;
786 | return 0;
787 | }
788 |
789 |
790 | void loop_debug(Mat color, Flags f) {
791 | make_debug_window();
792 | for(;;) {
793 | Mat grad = apply_fast_gradient(color, f);
794 | imshow(WINDOW_NAME, grad);
795 | waitKey(1000);
796 | }
797 | }
798 |
799 |
800 | int handle_image(Mat color, Flags f) {
801 | if (f.debug)
802 | loop_debug(color, f);
803 |
804 | color = apply_fast_gradient(color, f);
805 | if (!f.color_filename.length())
806 | imwrite(f.out_filename, color);
807 | cout << "Wrote as " << f.out_filename << endl;
808 | return 0;
809 | }
810 |
811 | int main(int argc, char* argv[]) {
812 | srand (time(NULL));
813 |
814 | string in_filename = "";
815 | Flags flags;
816 |
817 | // Loop over arguments
818 | for (int i=1; i