Edges
detect edges in an image
edges.c
Go to the documentation of this file.
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 }