Austin Schuh | 8c794d5 | 2019-03-03 21:17:37 -0800 | [diff] [blame^] | 1 | /* |
| 2 | # |
| 3 | # File : edge_explorer2d.cpp |
| 4 | # ( C++ source file ) |
| 5 | # |
| 6 | # Description : Real time edge detection while moving a ROI |
| 7 | # (rectangle of interest) over the original image. |
| 8 | # This file is a part of the CImg Library project. |
| 9 | # ( http://cimg.eu ) |
| 10 | # |
| 11 | # Copyright : Orges Leka |
| 12 | # ( oleka(at)students.uni-mainz.de ) |
| 13 | # |
| 14 | # License : CeCILL v2.0 |
| 15 | # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ) |
| 16 | # |
| 17 | # This software is governed by the CeCILL license under French law and |
| 18 | # abiding by the rules of distribution of free software. You can use, |
| 19 | # modify and/ or redistribute the software under the terms of the CeCILL |
| 20 | # license as circulated by CEA, CNRS and INRIA at the following URL |
| 21 | # "http://www.cecill.info". |
| 22 | # |
| 23 | # As a counterpart to the access to the source code and rights to copy, |
| 24 | # modify and redistribute granted by the license, users are provided only |
| 25 | # with a limited warranty and the software's author, the holder of the |
| 26 | # economic rights, and the successive licensors have only limited |
| 27 | # liability. |
| 28 | # |
| 29 | # In this respect, the user's attention is drawn to the risks associated |
| 30 | # with loading, using, modifying and/or developing or reproducing the |
| 31 | # software by the user in light of its specific status of free software, |
| 32 | # that may mean that it is complicated to manipulate, and that also |
| 33 | # therefore means that it is reserved for developers and experienced |
| 34 | # professionals having in-depth computer knowledge. Users are therefore |
| 35 | # encouraged to load and test the software's suitability as regards their |
| 36 | # requirements in conditions enabling the security of their systems and/or |
| 37 | # data to be ensured and, more generally, to use and operate it in the |
| 38 | # same conditions as regards security. |
| 39 | # |
| 40 | # The fact that you are presently reading this means that you have had |
| 41 | # knowledge of the CeCILL license and that you accept its terms. |
| 42 | # |
| 43 | */ |
| 44 | |
| 45 | #include "CImg.h" |
| 46 | using namespace cimg_library; |
| 47 | #ifndef cimg_imagepath |
| 48 | #define cimg_imagepath "img/" |
| 49 | #endif |
| 50 | |
| 51 | // Main procedure |
| 52 | //---------------- |
| 53 | int main(int argc, char** argv) { |
| 54 | |
| 55 | // Usage of the program displayed at the command line |
| 56 | cimg_usage("Real time edge detection with CImg. (c) Orges Leka"); |
| 57 | |
| 58 | // Read command line arguments |
| 59 | // With cimg_option we can get a new name for the image which is to be loaded from the command line. |
| 60 | const char* img_name = cimg_option("-i", cimg_imagepath "parrot.ppm","Input image."); |
| 61 | double |
| 62 | alpha = cimg_option("-a",1.0,"Blurring the gradient image."), |
| 63 | thresL = cimg_option("-tl",13.5,"Lower thresholding used in Hysteresis."), |
| 64 | thresH = cimg_option("-th",13.6,"Higher thresholding used in Hysteresis."); |
| 65 | const unsigned int |
| 66 | mode = cimg_option("-m",1,"Detection mode: 1 = Hysteresis, 2 = Gradient angle."), |
| 67 | factor = cimg_option("-s",80,"Half-size of edge-explorer window."); |
| 68 | |
| 69 | cimg_help("\nAdditional notes : user can press following keys on main display window :\n" |
| 70 | " - Left arrow : Decrease alpha.\n" |
| 71 | " - Right arrow : Increase alpha.\n"); |
| 72 | |
| 73 | // Construct a new image called 'edge' of size (2*factor,2*factor) |
| 74 | // and of type 'unsigned char'. |
| 75 | CImg<unsigned char> edge(2*factor,2*factor); |
| 76 | CImgDisplay disp_edge(512,512,"Edge Explorer"); |
| 77 | |
| 78 | // Load the image with the name 'img_name' into the CImg 'img'. |
| 79 | // and create a display window 'disp' for the image 'img'. |
| 80 | const CImg<unsigned char> img = CImg<float>::get_load(img_name).norm().normalize(0,255); |
| 81 | CImgDisplay disp(img,"Original Image"); |
| 82 | |
| 83 | // Begin main interaction loop. |
| 84 | int x = 0, y = 0; |
| 85 | bool redraw = false; |
| 86 | while (!disp.is_closed() && !disp.is_keyQ() && !disp.is_keyESC()) { |
| 87 | disp.wait(100); |
| 88 | if (disp.button()&1) { alpha+=0.05; redraw = true; } |
| 89 | if (disp.button()&2) { alpha-=0.05; redraw = true; } |
| 90 | if (disp.wheel()) { alpha+=0.05*disp.wheel(); disp.set_wheel(); redraw = true; } |
| 91 | if (alpha<0) alpha = 0; |
| 92 | if (disp_edge.is_resized()) { disp_edge.resize(); redraw = true; } |
| 93 | if (disp_edge.is_closed()) disp_edge.show(); |
| 94 | if (disp.is_resized()) disp.resize(disp); |
| 95 | if (disp.mouse_x()>=0) { |
| 96 | x = disp.mouse_x(); // Getting the current position of the mouse |
| 97 | y = disp.mouse_y(); // |
| 98 | redraw = true; // The image should be redrawn |
| 99 | } |
| 100 | if (redraw) { |
| 101 | disp_edge.set_title("Edge explorer (alpha=%g)",alpha); |
| 102 | const int |
| 103 | x0 = x - factor, y0 = y - factor, // These are the coordinates for the red rectangle |
| 104 | x1 = x + factor, y1 = y + factor; // to be drawn on the original image |
| 105 | const unsigned char |
| 106 | red[3] = { 255,0,0 }, // |
| 107 | black[3] = { 0,0,0 }; // Defining the colors we need for drawing |
| 108 | |
| 109 | (+img).draw_rectangle(x0,y0,x1,y1,red,1.0f,0x55555555U).display(disp); |
| 110 | //^ We draw the red rectangle on the original window using 'draw_line'. |
| 111 | // Then we display the result via '.display(disp)' . |
| 112 | // Observe, that the color 'red' has to be of type 'const unsigned char', |
| 113 | // since the image 'img' is of type 'const CImg<unsigned char>'. |
| 114 | |
| 115 | //'normalize' is used to get a greyscaled image. |
| 116 | CImg<> visu_bw = CImg<>(img).get_crop(x0,y0,x1,y1).get_norm().normalize(0,255).resize(-100,-100,1,2,2); |
| 117 | // get_crop(x0,y0,x1,y1) gets the rectangle we are interested in. |
| 118 | |
| 119 | edge.fill(255); // Background color in the edge-detection window is white |
| 120 | |
| 121 | // grad[0] is the gradient image of 'visu_bw' in x-direction. |
| 122 | // grad[1] is the gradient image of 'visu_bw' in y-direction. |
| 123 | CImgList<> grad(visu_bw.blur((float)alpha).normalize(0,255).get_gradient()); |
| 124 | |
| 125 | // To avoid unnecessary calculations in the image loops: |
| 126 | const double |
| 127 | pi = cimg::PI, |
| 128 | p8 = pi/8.0, p38 = 3.0*p8, |
| 129 | p58 = 5.0*p8, p78 = 7.0*p8; |
| 130 | |
| 131 | cimg_forXY(visu_bw,s,t) { |
| 132 | // We take s,t instead of x,y, since x,y are already used. |
| 133 | // s corresponds to the x-ordinate of the pixel while t corresponds to the y-ordinate. |
| 134 | if ( 1 <= s && s <= visu_bw.width() - 1 && 1 <= t && t <=visu_bw.height() - 1) { // if - good points |
| 135 | double |
| 136 | Gs = grad[0](s,t), // |
| 137 | Gt = grad[1](s,t), // The actual pixel is (s,t) |
| 138 | Gst = cimg::abs(Gs) + cimg::abs(Gt), // |
| 139 | // ^-- For efficient computation we observe that |Gs|+ |Gt| ~=~ sqrt( Gs^2 + Gt^2) |
| 140 | Gr, Gur, Gu, Gul, Gl, Gdl, Gd, Gdr; |
| 141 | // ^-- right, up right, up, up left, left, down left, down, down right. |
| 142 | double theta = std::atan2(std::max(1e-8,Gt),Gs) + pi; // theta is from the interval [0,Pi] |
| 143 | switch(mode) { |
| 144 | case 1: // Hysterese is applied |
| 145 | if (Gst>=thresH) { edge.draw_point(s,t,black); } |
| 146 | else if (thresL <= Gst && Gst < thresH) { |
| 147 | // Neighbourhood of the actual pixel: |
| 148 | Gr = cimg::abs(grad[0](s + 1,t)) + cimg::abs(grad[1](s + 1,t)); // right |
| 149 | Gl = cimg::abs(grad[0](s - 1,t)) + cimg::abs(grad[1](s - 1,t)); // left |
| 150 | Gur = cimg::abs(grad[0](s + 1,t + 1)) + cimg::abs(grad[1](s + 1,t + 1)); // up right |
| 151 | Gdl = cimg::abs(grad[0](s - 1,t - 1)) + cimg::abs(grad[1](s - 1,t - 1)); // down left |
| 152 | Gu = cimg::abs(grad[0](s,t + 1)) + cimg::abs(grad[1](s,t + 1)); // up |
| 153 | Gd = cimg::abs(grad[0](s,t - 1)) + cimg::abs(grad[1](s,t - 1)); // down |
| 154 | Gul = cimg::abs(grad[0](s - 1,t + 1)) + cimg::abs(grad[1](s - 1,t + 1)); // up left |
| 155 | Gdr = cimg::abs(grad[0](s + 1,t - 1)) + cimg::abs(grad[1](s + 1,t - 1)); // down right |
| 156 | if (Gr>=thresH || Gur>=thresH || Gu>=thresH || Gul>=thresH |
| 157 | || Gl>=thresH || Gdl >=thresH || Gu >=thresH || Gdr >=thresH) { |
| 158 | edge.draw_point(s,t,black); |
| 159 | } |
| 160 | }; |
| 161 | break; |
| 162 | case 2: // Angle 'theta' of the gradient (Gs,Gt) at the point (s,t) |
| 163 | if(theta >= pi)theta-=pi; |
| 164 | //rounding theta: |
| 165 | if ((p8 < theta && theta <= p38 ) || (p78 < theta && theta <= pi)) { |
| 166 | // See (*) below for explanation of the vocabulary used. |
| 167 | // Direction-pixel is (s + 1,t) with corresponding gradient value Gr. |
| 168 | Gr = cimg::abs(grad[0](s + 1,t)) + cimg::abs(grad[1](s + 1,t)); // right |
| 169 | // Contra-direction-pixel is (s - 1,t) with corresponding gradient value Gl. |
| 170 | Gl = cimg::abs(grad[0](s - 1,t)) + cimg::abs(grad[1](s - 1,t)); // left |
| 171 | if (Gr < Gst && Gl < Gst) { |
| 172 | edge.draw_point(s,t,black); |
| 173 | } |
| 174 | } |
| 175 | else if ( p8 < theta && theta <= p38) { |
| 176 | // Direction-pixel is (s + 1,t + 1) with corresponding gradient value Gur. |
| 177 | Gur = cimg::abs(grad[0](s + 1,t + 1)) + cimg::abs(grad[1](s + 1,t + 1)); // up right |
| 178 | // Contra-direction-pixel is (s-1,t-1) with corresponding gradient value Gdl. |
| 179 | Gdl = cimg::abs(grad[0](s - 1,t - 1)) + cimg::abs(grad[1](s - 1,t - 1)); // down left |
| 180 | if (Gur < Gst && Gdl < Gst) { |
| 181 | edge.draw_point(s,t,black); |
| 182 | } |
| 183 | } |
| 184 | else if ( p38 < theta && theta <= p58) { |
| 185 | // Direction-pixel is (s,t + 1) with corresponding gradient value Gu. |
| 186 | Gu = cimg::abs(grad[0](s,t + 1)) + cimg::abs(grad[1](s,t + 1)); // up |
| 187 | // Contra-direction-pixel is (s,t - 1) with corresponding gradient value Gd. |
| 188 | Gd = cimg::abs(grad[0](s,t - 1)) + cimg::abs(grad[1](s,t - 1)); // down |
| 189 | if (Gu < Gst && Gd < Gst) { |
| 190 | edge.draw_point(s,t,black); |
| 191 | } |
| 192 | } |
| 193 | else if (p58 < theta && theta <= p78) { |
| 194 | // Direction-pixel is (s - 1,t + 1) with corresponding gradient value Gul. |
| 195 | Gul = cimg::abs(grad[0](s - 1,t + 1)) + cimg::abs(grad[1](s - 1,t + 1)); // up left |
| 196 | // Contra-direction-pixel is (s + 1,t - 1) with corresponding gradient value Gdr. |
| 197 | Gdr = cimg::abs(grad[0](s + 1,t - 1)) + cimg::abs(grad[1](s + 1,t - 1)); // down right |
| 198 | if (Gul < Gst && Gdr < Gst) { |
| 199 | edge.draw_point(s,t,black); |
| 200 | } |
| 201 | }; |
| 202 | break; |
| 203 | } // switch |
| 204 | } // if good-points |
| 205 | } // cimg_forXY */ |
| 206 | edge.display(disp_edge); |
| 207 | }// if redraw |
| 208 | } // while |
| 209 | return 0; |
| 210 | } |
| 211 | |
| 212 | // (*) Comments to the vocabulary used: |
| 213 | // If (s,t) is the current pixel, and G=(Gs,Gt) is the gradient at (s,t), |
| 214 | // then the _direction_pixel_ of (s,t) shall be the one of the eight neighbour pixels |
| 215 | // of (s,t) in whose direction the gradient G shows. |
| 216 | // The _contra_direction_pixel is the pixel in the opposite direction in which the gradient G shows. |
| 217 | // The _corresponding_gradient_value_ of the pixel (x,y) with gradient G = (Gx,Gy) |
| 218 | // shall be |Gx| + |Gy| ~=~ sqrt(Gx^2 + Gy^2). |