blob: 9d32ece440c5e5470a122e53c5ce433b3d8c9b1c [file] [log] [blame]
Austin Schuh8c794d52019-03-03 21:17:37 -08001/*
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"
46using namespace cimg_library;
47#ifndef cimg_imagepath
48#define cimg_imagepath "img/"
49#endif
50
51// Main procedure
52//----------------
53int 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).