Edges
detect edges in an image
|
00001 /* 00002 * This work is licensed under a Creative Commons 00003 * Attribution-Noncommercial-Share Alike 3.0 United States License. 00004 * 00005 * http://creativecommons.org/licenses/by-nc-sa/3.0/us/ 00006 * 00007 * You are free: 00008 * 00009 * to Share - to copy, distribute, display, and perform the work 00010 * to Remix - to make derivative works 00011 * 00012 * Under the following conditions: 00013 * 00014 * Attribution. You must attribute the work in the manner specified by the 00015 * author or licensor (but not in any way that suggests that they endorse you 00016 * or your use of the work). 00017 * 00018 * Noncommercial. You may not use this work for commercial purposes. 00019 * 00020 * Share Alike. If you alter, transform, or build upon this work, you may 00021 * distribute the resulting work only under the same or similar license to 00022 * this one. 00023 * 00024 * For any reuse or distribution, you must make clear to others the license 00025 * terms of this work. The best way to do this is by including this header. 00026 * 00027 * Any of the above conditions can be waived if you get permission from the 00028 * copyright holder. 00029 * 00030 * Apart from the remix rights granted under this license, nothing in this 00031 * license impairs or restricts the author's moral rights. 00032 */ 00033 00034 00084 #include <config.h> 00085 00086 #include <stdlib.h> 00087 #include <stdio.h> 00088 #include <assert.h> 00089 #include <math.h> 00090 00091 #ifdef EDGES_HAVE_DMALLOC 00092 #include <dmalloc.h> 00093 #endif 00094 00095 #include <jwsc/base/error.h> 00096 #include <jwsc/base/option.h> 00097 #include <jwsc/matrix/matrix.h> 00098 #include <jwsc/image/image.h> 00099 #include <jwsc/image/image_io.h> 00100 #include <jwsc/image/edge.h> 00101 00102 00103 #define DEFAULT_BEGIN_MAG_THRESH 0.05f 00104 #define DEFAULT_END_MAG_THRESH 0.025f 00105 #define DEFAULT_BREAK_DP_THRESH -1.0f 00106 #define DEFAULT_BREAK_DP_NUM_AVG 1 00107 #define DEFAULT_SIGMA 1.0f 00108 #define DEFAULT_SAMPLE_RATE 1.0f 00109 #define DEFAULT_MIN_LEN 1 00110 #define DEFAULT_SEED 289375 00111 #define DEFAULT_RANDOM_COLOR 0 00112 00113 #define NUM_OPTS_NO_ARG 3 00114 #define NUM_OPTS_WITH_ARG 12 00115 00116 00118 static Option_no_arg opts_no_arg[NUM_OPTS_NO_ARG]; 00119 00121 static Option_with_arg opts_with_arg[NUM_OPTS_WITH_ARG]; 00122 00123 00125 static const char* map_fname = NULL; 00126 00128 static const char* color_fname = NULL; 00129 00131 static const char* edges_in_fname = NULL; 00132 00134 static const char* edges_out_fname = NULL; 00135 00139 static float begin_mag_thresh = DEFAULT_BEGIN_MAG_THRESH; 00140 00144 static float end_mag_thresh = DEFAULT_END_MAG_THRESH; 00145 00147 static float break_dp_thresh = DEFAULT_BREAK_DP_THRESH; 00148 00153 static uint32_t break_dp_num_avg = DEFAULT_BREAK_DP_NUM_AVG; 00154 00156 static float sigma = DEFAULT_SIGMA; 00157 00159 static float sample_rate = DEFAULT_SAMPLE_RATE; 00160 00162 static uint32_t min_len = DEFAULT_MIN_LEN; 00163 00165 static uint32_t seed = DEFAULT_MIN_LEN; 00166 00168 static uint8_t random_color = DEFAULT_RANDOM_COLOR; 00169 00170 00172 static void print_usage(void) 00173 { 00174 fprintf(stderr, "usage: edges OPTIONS 'image'\n"); 00175 fprintf(stderr, "where OPTIONS is one or more of the following:\n"); 00176 print_options(stderr, 25, NUM_OPTS_NO_ARG, opts_no_arg, NUM_OPTS_WITH_ARG, 00177 opts_with_arg); 00178 } 00179 00181 static Error* process_help_opt(void) 00182 { 00183 print_usage(); 00184 exit(EXIT_SUCCESS); 00185 return NULL; 00186 } 00187 00189 static Error* process_version_opt(void) 00190 { 00191 fprintf(stderr, "%s\n", EDGES_PACKAGE_STRING); 00192 exit(EXIT_SUCCESS); 00193 return NULL; 00194 } 00195 00197 static Error* process_random_color_opt(void) 00198 { 00199 random_color = 1; 00200 return NULL; 00201 } 00202 00204 static Error* process_color_out_opt(Option_arg arg) 00205 { 00206 if (arg == NULL) 00207 { 00208 return JWSC_EARG("Option 'color-out' requires an argument"); 00209 } 00210 color_fname = arg; 00211 return NULL; 00212 } 00213 00215 static Error* process_map_out_opt(Option_arg arg) 00216 { 00217 if (arg == NULL) 00218 { 00219 return JWSC_EARG("Option 'map-out' requires an argument"); 00220 } 00221 map_fname = arg; 00222 return NULL; 00223 } 00224 00226 static Error* process_edges_in_opt(Option_arg arg) 00227 { 00228 if (arg == NULL) 00229 { 00230 return JWSC_EARG("Option 'edges-in' requires an argument"); 00231 } 00232 edges_in_fname = arg; 00233 return NULL; 00234 } 00235 00237 static Error* process_edges_out_opt(Option_arg arg) 00238 { 00239 if (arg == NULL) 00240 { 00241 return JWSC_EARG("Option 'edges-out' requires an argument"); 00242 } 00243 edges_out_fname = arg; 00244 return NULL; 00245 } 00246 00248 static Error* process_begin_mag_thresh_opt(Option_arg arg) 00249 { 00250 if (arg == NULL) 00251 { 00252 return JWSC_EARG("Option 'begin-mag-thresh' requires an argument"); 00253 } 00254 if (sscanf(arg, "%f", &begin_mag_thresh) != 1 || begin_mag_thresh < 0) 00255 { 00256 return JWSC_EARG("Option 'begin-mag-thresh' must be >= 0"); 00257 } 00258 return NULL; 00259 } 00260 00262 static Error* process_end_mag_thresh_opt(Option_arg arg) 00263 { 00264 if (arg == NULL) 00265 { 00266 return JWSC_EARG("Option 'end-mag-thresh' requires an argument"); 00267 } 00268 if (sscanf(arg, "%f", &end_mag_thresh) != 1 || end_mag_thresh < 0) 00269 { 00270 return JWSC_EARG("Option 'end-mag-thresh' must be >= 0"); 00271 } 00272 return NULL; 00273 } 00274 00276 static Error* process_break_dp_thresh_opt(Option_arg arg) 00277 { 00278 if (arg == NULL) 00279 { 00280 return JWSC_EARG("Option 'break-dp-thresh' requires an argument"); 00281 } 00282 if (sscanf(arg, "%f", &break_dp_thresh) != 1 || break_dp_thresh < -1 || 00283 break_dp_thresh > 1) 00284 { 00285 return JWSC_EARG("Option 'break-dp-thresh' must be in [-1,1]"); 00286 } 00287 return NULL; 00288 } 00289 00291 static Error* process_break_dp_num_avg_opt(Option_arg arg) 00292 { 00293 if (arg == NULL) 00294 { 00295 return JWSC_EARG("Option 'break-dp-num-avg' requires an argument"); 00296 } 00297 if (sscanf(arg, "%u", &break_dp_num_avg) != 1 || break_dp_num_avg < 1) 00298 { 00299 return JWSC_EARG("Option 'break-dp-num-avg' must be >= 1"); 00300 } 00301 return NULL; 00302 } 00303 00305 static Error* process_sigma_opt(Option_arg arg) 00306 { 00307 if (arg == NULL) 00308 { 00309 return JWSC_EARG("Option 'sigma' requires an argument"); 00310 } 00311 if (sscanf(arg, "%f", &sigma) != 1 || sigma <= 0) 00312 { 00313 return JWSC_EARG("Option 'sigma' must be > 0"); 00314 } 00315 return NULL; 00316 } 00317 00319 static Error* process_sample_rate_opt(Option_arg arg) 00320 { 00321 if (arg == NULL) 00322 { 00323 return JWSC_EARG("Option 'sample-rate' requires an argument"); 00324 } 00325 if (sscanf(arg, "%f", &sample_rate) != 1 || sample_rate <= 0 || 00326 sample_rate > 1.0) 00327 { 00328 return JWSC_EARG("Option 'sample-rate' must be in (0,1]"); 00329 } 00330 return NULL; 00331 } 00332 00334 static Error* process_min_len_opt(Option_arg arg) 00335 { 00336 if (arg == NULL) 00337 { 00338 return JWSC_EARG("Option 'min-len' requires an argument"); 00339 } 00340 if (sscanf(arg, "%u", &min_len) != 1) 00341 { 00342 return JWSC_EARG("Option 'min-len' requires an argument"); 00343 } 00344 return NULL; 00345 } 00346 00348 static Error* process_seed_opt(Option_arg arg) 00349 { 00350 if (arg == NULL) 00351 { 00352 return JWSC_EARG("Option 'seed' requires an argument"); 00353 } 00354 if (sscanf(arg, "%u", &seed) != 1) 00355 { 00356 return JWSC_EARG("Option 'seed' requires an argument"); 00357 } 00358 return NULL; 00359 } 00360 00361 00362 00363 00365 int main(int argc, const char** argv) 00366 { 00367 uint32_t num_rows, num_cols; 00368 uint32_t i, j; 00369 int argi; 00370 const char* input_fname; 00371 00372 Pixel_f pxl; 00373 00374 Image_f* img_in = NULL; 00375 Image_f* img_out = NULL; 00376 Matrix_f* m = NULL; 00377 Error* e = NULL; 00378 Edge_set_f* edges = NULL; 00379 Edge_point_f* pt = NULL; 00380 00381 /* Init the options. */ 00382 init_option_no_arg(&(opts_no_arg[0]), "help", 'h', 00383 "Prints program usage.", process_help_opt); 00384 init_option_no_arg(&(opts_no_arg[1]), "version", 'v', 00385 "Prints program version.", process_version_opt); 00386 init_option_no_arg(&(opts_no_arg[2]), "random-color", 0, 00387 "Color edges with random selected different colors.", 00388 process_random_color_opt); 00389 init_option_with_arg(&(opts_with_arg[0]), "color-out", 0, 00390 "Original image with edges colored.", process_color_out_opt); 00391 init_option_with_arg(&(opts_with_arg[1]), "map-out", 0, 00392 "Bi-level image edge map.", process_map_out_opt); 00393 init_option_with_arg(&(opts_with_arg[2]), "edges-in", 0, 00394 "File to read ASCII formatted edge set from.", 00395 process_edges_in_opt); 00396 init_option_with_arg(&(opts_with_arg[3]), "edges-out", 0, 00397 "File to write ASCII formatted edge set to.", 00398 process_edges_out_opt); 00399 init_option_with_arg(&(opts_with_arg[4]), "begin-mag-thresh", 0, 00400 "Hysterisis starting gradient magnitude threshold.", 00401 process_begin_mag_thresh_opt); 00402 init_option_with_arg(&(opts_with_arg[5]), "end-mag-thresh", 0, 00403 "Hysterisis stopping gradient magnitude threshold.", 00404 process_end_mag_thresh_opt); 00405 init_option_with_arg(&(opts_with_arg[6]), "break-dp-thresh", 0, 00406 "Gradient vector dot product threshold for breaking edges.", 00407 process_break_dp_thresh_opt); 00408 init_option_with_arg(&(opts_with_arg[7]), "break-dp-num-avg", 0, 00409 "Number of gradient vectors to average when computing the dot product threshold for breaking edges.", 00410 process_break_dp_num_avg_opt); 00411 init_option_with_arg(&(opts_with_arg[8]), "sigma", 0, 00412 "Gaussian smoothing sigma.", process_sigma_opt); 00413 init_option_with_arg(&(opts_with_arg[9]), "sample-rate", 0, 00414 "Edge point sampling rate.", process_sample_rate_opt); 00415 init_option_with_arg(&(opts_with_arg[10]), "min-len", 0, 00416 "Minimum length of an edge.", process_min_len_opt); 00417 init_option_with_arg(&(opts_with_arg[11]), "seed", 0, 00418 "Random seed.", process_seed_opt); 00419 00420 /* Check for the help option first. */ 00421 process_option_no_arg(argc, argv, &argi, &(opts_no_arg[0])); 00422 00423 if ((e = process_options(argc, argv, &argi, NUM_OPTS_NO_ARG, opts_no_arg, 00424 NUM_OPTS_WITH_ARG, opts_with_arg)) != NULL) 00425 { 00426 print_error_msg_exit("edges", e->msg); 00427 } 00428 if ((argc - argi) < 1) 00429 { 00430 print_error_msg_exit("edges", "No input image"); 00431 } 00432 input_fname = argv[ argi ]; 00433 00434 if ((e = read_image_f(&img_in, input_fname)) != NULL) 00435 { 00436 print_error_msg_exit("edges", e->msg); 00437 } 00438 00439 srand(seed); rand(); rand(); 00440 00441 num_rows = img_in->num_rows; 00442 num_cols = img_in->num_cols; 00443 00444 if (edges_in_fname) 00445 { 00446 if ((e = read_edge_set_f(&edges, edges_in_fname)) != NULL) 00447 { 00448 print_error_msg("edges", NULL); 00449 print_error_msg_exit(edges_in_fname, e->msg); 00450 } 00451 } 00452 else 00453 { 00454 /* Detect edges and sample them if requested. */ 00455 create_matrix_from_image_f(&m, img_in, 0.3f, 0.59f, 0.11f); 00456 if ((e = detect_matrix_edge_set_f(&edges, m, sigma, begin_mag_thresh, 00457 end_mag_thresh, 8)) != NULL) 00458 { 00459 print_error_msg_exit("edges", e->msg); 00460 } 00461 } 00462 00463 break_edges_at_corners_f(edges, break_dp_thresh, break_dp_num_avg); 00464 remove_short_edges_f(edges, min_len); 00465 00466 fprintf(stdout, "Number of detected edges: %d\n", edges->num_edges); 00467 fprintf(stdout, "Number of detected edge points: %d\n", 00468 edges->total_num_pts); 00469 00470 if (sample_rate < 1.0f) 00471 { 00472 sample_edge_set_f(&edges, edges, sample_rate); 00473 fprintf(stdout, "Number of sampled edge points: %d\n", 00474 edges->total_num_pts); 00475 } 00476 00477 /* Write an edge map image if requested. */ 00478 if (map_fname) 00479 { 00480 create_zero_matrix_f(&m, num_rows, num_cols); 00481 00482 for (i = 0; i < edges->num_edges; i++) 00483 { 00484 for (j = 0; j < edges->num_pts[ i ]; j++) 00485 { 00486 pt = &(edges->pts[ i ][ j ]); 00487 m->elts[ pt->row ][ pt->col ] = 1.0f; 00488 } 00489 } 00490 00491 create_image_from_matrix_f(&img_out, m); 00492 00493 if ((e = write_image_f(img_out, map_fname)) != NULL) 00494 { 00495 print_error_msg("edges", e->msg); 00496 free_error(e); 00497 } 00498 } 00499 00500 /* Write the image data with the edges colored on it if requested. */ 00501 if (color_fname) 00502 { 00503 if (random_color) 00504 { 00505 randomly_color_edge_set_f(&img_out, img_in, edges); 00506 } 00507 else 00508 { 00509 pxl.r = 1; 00510 pxl.g = 0; 00511 pxl.b = 0; 00512 00513 color_edge_set_f(&img_out, img_in, edges, &pxl); 00514 } 00515 00516 if ((e = write_image_f(img_out, color_fname)) != NULL) 00517 { 00518 print_error_msg("edges", e->msg); 00519 free_error(e); 00520 } 00521 } 00522 00523 /* Write the edge points if requested. */ 00524 if (edges_out_fname) 00525 { 00526 if ((e = write_edge_set_f(edges, edges_out_fname)) != NULL) 00527 { 00528 print_error_msg("edges", NULL); 00529 print_error_msg(edges_out_fname, e->msg); 00530 free_error(e); 00531 } 00532 } 00533 00534 free_edge_set_f(edges); 00535 free_image_f(img_in); 00536 free_image_f(img_out); 00537 free_matrix_f(m); 00538 00539 return EXIT_SUCCESS; 00540 }