fast-edge.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /*
  2. FAST-EDGE
  3. Copyright (c) 2009 Benjamin C. Haynor
  4. Permission is hereby granted, free of charge, to any person
  5. obtaining a copy of this software and associated documentation
  6. files (the "Software"), to deal in the Software without
  7. restriction, including without limitation the rights to use,
  8. copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the
  10. Software is furnished to do so, subject to the following
  11. conditions:
  12. The above copyright notice and this permission notice shall be
  13. included in all copies or substantial portions of the Software.
  14. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  16. OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  19. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  21. OTHER DEALINGS IN THE SOFTWARE.
  22. */
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <math.h>
  27. #include <time.h>
  28. #include "fast-edge.h"
  29. #define LOW_THRESHOLD_PERCENTAGE 0.8 // percentage of the high threshold value that the low threshold shall be set at
  30. #define PI 3.14159265
  31. #define HIGH_THRESHOLD_PERCENTAGE 0.10 // percentage of pixels that meet the high threshold - for example 0.15 will ensure that at least 15% of edge pixels are considered to meet the high threshold
  32. #define min(X,Y) ((X) < (Y) ? (X) : (Y))
  33. #define max(X,Y) ((X) < (Y) ? (Y) : (X))
  34. namespace ocr{
  35. /*
  36. CANNY EDGE DETECT
  37. DOES NOT PERFORM NOISE REDUCTION - PERFORM NOISE REDUCTION PRIOR TO USE
  38. Noise reduction omitted, as some applications benefit from morphological operations such as opening or closing as opposed to Gaussian noise reduction
  39. If your application always takes the same size input image, uncomment the definitions of WIDTH and HEIGHT in the header file and define them to the size of your input image,
  40. otherwise the required intermediate arrays will be dynamically allocated.
  41. If WIDTH and HEIGHT are defined, the arrays will be allocated in the compiler directive that follows:
  42. */
  43. #ifdef WIDTH
  44. int g[WIDTH * HEIGHT], dir[WIDTH * HEIGHT] = {0};
  45. unsigned char img_scratch_data[WIDTH * HEIGHT] = {0};
  46. #endif
  47. void canny_edge_detect(struct image * img_in, struct image * img_out) {
  48. struct image img_scratch;
  49. int high, low;
  50. #ifndef WIDTH
  51. int * g = (int*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(int));
  52. int * dir = (int*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(int));
  53. unsigned char * img_scratch_data = (unsigned char*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(char));
  54. #endif
  55. img_scratch.width = img_in->width;
  56. img_scratch.height = img_in->height;
  57. img_scratch.pixel_data = img_scratch_data;
  58. calc_gradient_sobel(img_in, g, dir);
  59. //printf("*** performing non-maximum suppression ***\n");
  60. non_max_suppression(&img_scratch, g, dir);
  61. estimate_threshold(&img_scratch, &high, &low);
  62. hysteresis(high, low, &img_scratch, img_out);
  63. #ifndef WIDTH
  64. free(g);
  65. free(dir);
  66. free(img_scratch_data);
  67. #endif
  68. }
  69. /*
  70. GAUSSIAN_NOISE_ REDUCE
  71. apply 5x5 Gaussian convolution filter, shrinks the image by 4 pixels in each direction, using Gaussian filter found here:
  72. http://en.wikipedia.org/wiki/Canny_edge_detector
  73. */
  74. void gaussian_noise_reduce(struct image * img_in, struct image * img_out)
  75. {
  76. #ifdef CLOCK
  77. clock_t start = clock();
  78. #endif
  79. int w, h, x, y, max_x, max_y;
  80. w = img_in->width;
  81. h = img_in->height;
  82. img_out->width = w;
  83. img_out->height = h;
  84. max_x = w - 2;
  85. max_y = w * (h - 2);
  86. for (y = w * 2; y < max_y; y += w) {
  87. for (x = 2; x < max_x; x++) {
  88. img_out->pixel_data[x + y] = (2 * img_in->pixel_data[x + y - 2 - w - w] +
  89. 4 * img_in->pixel_data[x + y - 1 - w - w] +
  90. 5 * img_in->pixel_data[x + y - w - w] +
  91. 4 * img_in->pixel_data[x + y + 1 - w - w] +
  92. 2 * img_in->pixel_data[x + y + 2 - w - w] +
  93. 4 * img_in->pixel_data[x + y - 2 - w] +
  94. 9 * img_in->pixel_data[x + y - 1 - w] +
  95. 12 * img_in->pixel_data[x + y - w] +
  96. 9 * img_in->pixel_data[x + y + 1 - w] +
  97. 4 * img_in->pixel_data[x + y + 2 - w] +
  98. 5 * img_in->pixel_data[x + y - 2] +
  99. 12 * img_in->pixel_data[x + y - 1] +
  100. 15 * img_in->pixel_data[x + y] +
  101. 12 * img_in->pixel_data[x + y + 1] +
  102. 5 * img_in->pixel_data[x + y + 2] +
  103. 4 * img_in->pixel_data[x + y - 2 + w] +
  104. 9 * img_in->pixel_data[x + y - 1 + w] +
  105. 12 * img_in->pixel_data[x + y + w] +
  106. 9 * img_in->pixel_data[x + y + 1 + w] +
  107. 4 * img_in->pixel_data[x + y + 2 + w] +
  108. 2 * img_in->pixel_data[x + y - 2 + w + w] +
  109. 4 * img_in->pixel_data[x + y - 1 + w + w] +
  110. 5 * img_in->pixel_data[x + y + w + w] +
  111. 4 * img_in->pixel_data[x + y + 1 + w + w] +
  112. 2 * img_in->pixel_data[x + y + 2 + w + w]) / 159;
  113. }
  114. }
  115. #ifdef CLOCK
  116. printf("Gaussian noise reduction - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  117. #endif
  118. }
  119. /*
  120. CALC_GRADIENT_SOBEL
  121. calculates the result of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle
  122. */
  123. /*void calc_gradient_sobel(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) {*/
  124. void calc_gradient_sobel(struct image * img_in, int g[], int dir[]) {
  125. #ifdef CLOCK
  126. clock_t start = clock();
  127. #endif
  128. int w, h, x, y, max_x, max_y, g_x, g_y;
  129. float g_div;
  130. w = img_in->width;
  131. h = img_in->height;
  132. max_x = w - 3;
  133. max_y = w * (h - 3);
  134. for (y = w * 3; y < max_y; y += w) {
  135. for (x = 3; x < max_x; x++) {
  136. g_x = (2 * img_in->pixel_data[x + y + 1]
  137. + img_in->pixel_data[x + y - w + 1]
  138. + img_in->pixel_data[x + y + w + 1]
  139. - 2 * img_in->pixel_data[x + y - 1]
  140. - img_in->pixel_data[x + y - w - 1]
  141. - img_in->pixel_data[x + y + w - 1]);
  142. g_y = 2 * img_in->pixel_data[x + y - w]
  143. + img_in->pixel_data[x + y - w + 1]
  144. + img_in->pixel_data[x + y - w - 1]
  145. - 2 * img_in->pixel_data[x + y + w]
  146. - img_in->pixel_data[x + y + w + 1]
  147. - img_in->pixel_data[x + y + w - 1];
  148. #ifndef ABS_APPROX
  149. g[x + y] = sqrt(g_x * g_x + g_y * g_y);
  150. #endif
  151. #ifdef ABS_APPROX
  152. g[x + y] = abs(g_x[x + y]) + abs(g_y[x + y]);
  153. #endif
  154. if (g_x == 0) {
  155. dir[x + y] = 2;
  156. } else {
  157. g_div = g_y / (float) g_x;
  158. /* the following commented-out code is slightly faster than the code that follows, but is a slightly worse approximation for determining the edge direction angle
  159. if (g_div < 0) {
  160. if (g_div < -1) {
  161. dir[n] = 0;
  162. } else {
  163. dir[n] = 1;
  164. }
  165. } else {
  166. if (g_div > 1) {
  167. dir[n] = 0;
  168. } else {
  169. dir[n] = 3;
  170. }
  171. }
  172. */
  173. if (g_div < 0) {
  174. if (g_div < -2.41421356237) {
  175. dir[x + y] = 0;
  176. } else {
  177. if (g_div < -0.414213562373) {
  178. dir[x + y] = 1;
  179. } else {
  180. dir[x + y] = 2;
  181. }
  182. }
  183. } else {
  184. if (g_div > 2.41421356237) {
  185. dir[x + y] = 0;
  186. } else {
  187. if (g_div > 0.414213562373) {
  188. dir[x + y] = 3;
  189. } else {
  190. dir[x + y] = 2;
  191. }
  192. }
  193. }
  194. }
  195. }
  196. }
  197. #ifdef CLOCK
  198. printf("Calculate gradient Sobel - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  199. #endif
  200. }
  201. /*
  202. CALC_GRADIENT_SCHARR
  203. calculates the result of the Scharr version of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle
  204. may have better rotational symmetry
  205. */
  206. void calc_gradient_scharr(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) {
  207. #ifdef CLOCK
  208. clock_t start = clock();
  209. #endif
  210. int w, h, x, y, max_x, max_y, n;
  211. float g_div;
  212. w = img_in->width;
  213. h = img_in->height;
  214. max_x = w - 1;
  215. max_y = w * (h - 1);
  216. n = 0;
  217. for (y = w; y < max_y; y += w) {
  218. for (x = 1; x < max_x; x++) {
  219. g_x[n] = (10 * img_in->pixel_data[x + y + 1]
  220. + 3 * img_in->pixel_data[x + y - w + 1]
  221. + 3 * img_in->pixel_data[x + y + w + 1]
  222. - 10 * img_in->pixel_data[x + y - 1]
  223. - 3 * img_in->pixel_data[x + y - w - 1]
  224. - 3 * img_in->pixel_data[x + y + w - 1]);
  225. g_y[n] = 10 * img_in->pixel_data[x + y - w]
  226. + 3 * img_in->pixel_data[x + y - w + 1]
  227. + 3 * img_in->pixel_data[x + y - w - 1]
  228. - 10 * img_in->pixel_data[x + y + w]
  229. - 3 * img_in->pixel_data[x + y + w + 1]
  230. - 3 * img_in->pixel_data[x + y + w - 1];
  231. #ifndef ABS_APPROX
  232. g[n] = sqrt(g_x[n] * g_x[n] + g_y[n] * g_y[n]);
  233. #endif
  234. #ifdef ABS_APPROX
  235. g[n] = abs(g_x[n]) + abs(g_y[n]);
  236. #endif
  237. if (g_x[n] == 0) {
  238. dir[n] = 2;
  239. } else {
  240. g_div = g_y[n] / (float) g_x[n];
  241. if (g_div < 0) {
  242. if (g_div < -2.41421356237) {
  243. dir[n] = 0;
  244. } else {
  245. if (g_div < -0.414213562373) {
  246. dir[n] = 1;
  247. } else {
  248. dir[n] = 2;
  249. }
  250. }
  251. } else {
  252. if (g_div > 2.41421356237) {
  253. dir[n] = 0;
  254. } else {
  255. if (g_div > 0.414213562373) {
  256. dir[n] = 3;
  257. } else {
  258. dir[n] = 2;
  259. }
  260. }
  261. }
  262. }
  263. n++;
  264. }
  265. }
  266. #ifdef CLOCK
  267. printf("Calculate gradient Scharr - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  268. #endif
  269. }
  270. /*
  271. NON_MAX_SUPPRESSION
  272. using the estimates of the Gx and Gy image gradients and the edge direction angle determines whether the magnitude of the gradient assumes a local maximum in the gradient direction
  273. if the rounded edge direction angle is 0 degrees, checks the north and south directions
  274. if the rounded edge direction angle is 45 degrees, checks the northwest and southeast directions
  275. if the rounded edge direction angle is 90 degrees, checks the east and west directions
  276. if the rounded edge direction angle is 135 degrees, checks the northeast and southwest directions
  277. */
  278. void non_max_suppression(struct image * img, int g[], int dir[]) {//float theta[]) {
  279. #ifdef CLOCK
  280. clock_t start = clock();
  281. #endif
  282. int w, h, x, y, max_x, max_y;
  283. w = img->width;
  284. h = img->height;
  285. max_x = w;
  286. max_y = w * h;
  287. for (y = 0; y < max_y; y += w) {
  288. for (x = 0; x < max_x; x++) {
  289. switch (dir[x + y]) {
  290. case 0:
  291. if(x+y-w-1<0){
  292. continue;
  293. }
  294. if (g[x + y] > g[x + y - w] && g[x + y] > g[x + y + w]) {
  295. if (g[x + y] > 255) {
  296. img->pixel_data[x + y] = 0xFF;
  297. } else {
  298. img->pixel_data[x + y] = g[x + y];
  299. }
  300. } else {
  301. img->pixel_data[x + y] = 0x00;
  302. }
  303. break;
  304. case 1:
  305. if(x+y-w-1<0){
  306. continue;
  307. }
  308. if (g[x + y] > g[x + y - w - 1] && g[x + y] > g[x + y + w + 1]) {
  309. if (g[x + y] > 255) {
  310. img->pixel_data[x + y] = 0xFF;
  311. } else {
  312. img->pixel_data[x + y] = g[x + y];
  313. }
  314. } else {
  315. img->pixel_data[x + y] = 0x00;
  316. }
  317. break;
  318. case 2:
  319. if (g[x + y] > g[x + y - 1] && g[x + y] > g[x + y + 1]) {
  320. if (g[x + y] > 255) {
  321. img->pixel_data[x + y] = 0xFF;
  322. } else {
  323. img->pixel_data[x + y] = g[x + y];
  324. }
  325. } else {
  326. img->pixel_data[x + y] = 0x00;
  327. }
  328. break;
  329. case 3:
  330. if(x+y-w-1<0){
  331. continue;
  332. }
  333. if (g[x + y] > g[x + y - w + 1] && g[x + y] > g[x + y + w - 1]) {
  334. if (g[x + y] > 255) {
  335. img->pixel_data[x + y] = 0xFF;
  336. } else {
  337. img->pixel_data[x + y] = g[x + y];
  338. }
  339. } else {
  340. img->pixel_data[x + y] = 0x00;
  341. }
  342. break;
  343. default:
  344. printf("ERROR - direction outside range 0 to 3");
  345. break;
  346. }
  347. }
  348. }
  349. #ifdef CLOCK
  350. printf("Non-maximum suppression - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  351. #endif
  352. }
  353. /*
  354. ESTIMATE_THRESHOLD
  355. estimates hysteresis threshold, assuming that the top X% (as defined by the HIGH_THRESHOLD_PERCENTAGE) of edge pixels with the greatest intesity are true edges
  356. and that the low threshold is equal to the quantity of the high threshold plus the total number of 0s at the low end of the histogram divided by 2
  357. */
  358. void estimate_threshold(struct image * img, int * high, int * low) {
  359. #ifdef CLOCK
  360. clock_t start = clock();
  361. #endif
  362. int i, max, pixels, high_cutoff;
  363. int histogram[256];
  364. max = img->width * img->height;
  365. for (i = 0; i < 256; i++) {
  366. histogram[i] = 0;
  367. }
  368. for (i = 0; i < max; i++) {
  369. histogram[img->pixel_data[i]]++;
  370. }
  371. pixels = (max - histogram[0]) * HIGH_THRESHOLD_PERCENTAGE;
  372. high_cutoff = 0;
  373. i = 255;
  374. while (high_cutoff < pixels) {
  375. high_cutoff += histogram[i];
  376. i--;
  377. }
  378. *high = i;
  379. i = 1;
  380. while (histogram[i] == 0) {
  381. i++;
  382. }
  383. *low = (*high + i) * LOW_THRESHOLD_PERCENTAGE;
  384. #ifdef PRINT_HISTOGRAM
  385. for (i = 0; i < 256; i++) {
  386. printf("i %d count %d\n", i, histogram[i]);
  387. }
  388. #endif
  389. #ifdef CLOCK
  390. printf("Estimate threshold - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  391. #endif
  392. }
  393. void hysteresis (int high, int low, struct image * img_in, struct image * img_out)
  394. {
  395. #ifdef CLOCK
  396. clock_t start = clock();
  397. #endif
  398. int x, y, n, max;
  399. max = img_in->width * img_in->height;
  400. for (n = 0; n < max; n++) {
  401. img_out->pixel_data[n] = 0x00;
  402. }
  403. for (y=0; y < img_out->height; y++) {
  404. for (x=0; x < img_out->width; x++) {
  405. if (img_in->pixel_data[y * img_out->width + x] >= high) {
  406. trace (x, y, low, img_in, img_out);
  407. }
  408. }
  409. }
  410. #ifdef CLOCK
  411. printf("Hysteresis - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  412. #endif
  413. }
  414. int trace(int x, int y, int low, struct image * img_in, struct image * img_out)
  415. {
  416. int y_off, x_off;//, flag;
  417. if (img_out->pixel_data[y * img_out->width + x] == 0)
  418. {
  419. img_out->pixel_data[y * img_out->width + x] = 0xFF;
  420. for (y_off = -1; y_off <=1; y_off++)
  421. {
  422. for(x_off = -1; x_off <= 1; x_off++)
  423. {
  424. if (!(y == 0 && x_off == 0) && range(img_in, x + x_off, y + y_off) && img_in->pixel_data[(y + y_off) * img_out->width + x + x_off] >= low) {
  425. if (trace(x + x_off, y + y_off, low, img_in, img_out))
  426. {
  427. return(1);
  428. }
  429. }
  430. }
  431. }
  432. return(1);
  433. }
  434. return(0);
  435. }
  436. int range(struct image * img, int x, int y)
  437. {
  438. if ((x < 0) || (x >= img->width)) {
  439. return(0);
  440. }
  441. if ((y < 0) || (y >= img->height)) {
  442. return(0);
  443. }
  444. return(1);
  445. }
  446. void dilate_1d_h(struct image * img, struct image * img_out) {
  447. int x, y, offset, y_max;
  448. y_max = img->height * (img->width - 2);
  449. for (y = 2 * img->width; y < y_max; y += img->width) {
  450. for (x = 2; x < img->width - 2; x++) {
  451. offset = x + y;
  452. img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]);
  453. }
  454. }
  455. }
  456. void dilate_1d_v(struct image * img, struct image * img_out) {
  457. int x, y, offset, y_max;
  458. y_max = img->height * (img->width - 2);
  459. for (y = 2 * img->width; y < y_max; y += img->width) {
  460. for (x = 2; x < img->width - 2; x++) {
  461. offset = x + y;
  462. img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]);
  463. }
  464. }
  465. }
  466. void erode_1d_h(struct image * img, struct image * img_out) {
  467. int x, y, offset, y_max;
  468. y_max = img->height * (img->width - 2);
  469. for (y = 2 * img->width; y < y_max; y += img->width) {
  470. for (x = 2; x < img->width - 2; x++) {
  471. offset = x + y;
  472. img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]);
  473. }
  474. }
  475. }
  476. void erode_1d_v(struct image * img, struct image * img_out) {
  477. int x, y, offset, y_max;
  478. y_max = img->height * (img->width - 2);
  479. for (y = 2 * img->width; y < y_max; y += img->width) {
  480. for (x = 2; x < img->width - 2; x++) {
  481. offset = x + y;
  482. img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]);
  483. }
  484. }
  485. }
  486. void erode(struct image * img_in, struct image * img_scratch, struct image * img_out) {
  487. #ifdef CLOCK
  488. clock_t start = clock();
  489. #endif
  490. erode_1d_h(img_in, img_scratch);
  491. erode_1d_v(img_scratch, img_out);
  492. #ifdef CLOCK
  493. printf("Erosion - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  494. #endif
  495. }
  496. void dilate(struct image * img_in, struct image * img_scratch, struct image * img_out) {
  497. #ifdef CLOCK
  498. clock_t start = clock();
  499. #endif
  500. dilate_1d_h(img_in, img_scratch);
  501. dilate_1d_v(img_scratch, img_out);
  502. #ifdef CLOCK
  503. printf("Dilation - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  504. #endif
  505. }
  506. void morph_open(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) {
  507. #ifdef CLOCK
  508. clock_t start = clock();
  509. #endif
  510. erode(img_in, img_scratch, img_scratch2);
  511. dilate(img_scratch2, img_scratch, img_out);
  512. #ifdef CLOCK
  513. printf("Morphological opening - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  514. #endif
  515. }
  516. void morph_close(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) {
  517. #ifdef CLOCK
  518. clock_t start = clock();
  519. #endif
  520. dilate(img_in, img_scratch, img_scratch2);
  521. erode(img_scratch2, img_scratch, img_out);
  522. #ifdef CLOCK
  523. printf("Morphological closing - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
  524. #endif
  525. }
  526. }