mirror of
				https://github.com/RetroDECK/ES-DE.git
				synced 2025-04-10 19:15:13 +00:00 
			
		
		
		
	
		
			
	
	
		
			219 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			219 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|   | /*
 | ||
|  |  #
 | ||
|  |  #  File        : edge_explorer2d.cpp
 | ||
|  |  #                ( C++ source file )
 | ||
|  |  #
 | ||
|  |  #  Description : Real time edge detection while moving a ROI
 | ||
|  |  #                (rectangle of interest) over the original image.
 | ||
|  |  #                This file is a part of the CImg Library project.
 | ||
|  |  #                ( http://cimg.eu )
 | ||
|  |  #
 | ||
|  |  #  Copyright   : Orges Leka
 | ||
|  |  #                ( oleka(at)students.uni-mainz.de )
 | ||
|  |  #
 | ||
|  |  #  License     : CeCILL v2.0
 | ||
|  |  #                ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
 | ||
|  |  #
 | ||
|  |  #  This software is governed by the CeCILL  license under French law and
 | ||
|  |  #  abiding by the rules of distribution of free software.  You can  use,
 | ||
|  |  #  modify and/ or redistribute the software under the terms of the CeCILL
 | ||
|  |  #  license as circulated by CEA, CNRS and INRIA at the following URL
 | ||
|  |  #  "http://www.cecill.info".
 | ||
|  |  #
 | ||
|  |  #  As a counterpart to the access to the source code and  rights to copy,
 | ||
|  |  #  modify and redistribute granted by the license, users are provided only
 | ||
|  |  #  with a limited warranty  and the software's author,  the holder of the
 | ||
|  |  #  economic rights,  and the successive licensors  have only  limited
 | ||
|  |  #  liability.
 | ||
|  |  #
 | ||
|  |  #  In this respect, the user's attention is drawn to the risks associated
 | ||
|  |  #  with loading,  using,  modifying and/or developing or reproducing the
 | ||
|  |  #  software by the user in light of its specific status of free software,
 | ||
|  |  #  that may mean  that it is complicated to manipulate,  and  that  also
 | ||
|  |  #  therefore means  that it is reserved for developers  and  experienced
 | ||
|  |  #  professionals having in-depth computer knowledge. Users are therefore
 | ||
|  |  #  encouraged to load and test the software's suitability as regards their
 | ||
|  |  #  requirements in conditions enabling the security of their systems and/or
 | ||
|  |  #  data to be ensured and,  more generally, to use and operate it in the
 | ||
|  |  #  same conditions as regards security.
 | ||
|  |  #
 | ||
|  |  #  The fact that you are presently reading this means that you have had
 | ||
|  |  #  knowledge of the CeCILL license and that you accept its terms.
 | ||
|  |  #
 | ||
|  | */ | ||
|  | 
 | ||
|  | #include "CImg.h"
 | ||
|  | using namespace cimg_library; | ||
|  | #ifndef cimg_imagepath
 | ||
|  | #define cimg_imagepath "img/"
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | // Main procedure
 | ||
|  | //----------------
 | ||
|  | int main(int argc, char** argv) { | ||
|  | 
 | ||
|  |   // Usage of the program displayed at the command line
 | ||
|  |   cimg_usage("Real time edge detection with CImg. (c) Orges Leka"); | ||
|  | 
 | ||
|  |   // Read command line arguments
 | ||
|  |   // With cimg_option we can get a new name for the image which is to be loaded from the command line.
 | ||
|  |   const char* img_name = cimg_option("-i", cimg_imagepath "parrot.ppm","Input image."); | ||
|  |   double | ||
|  |     alpha = cimg_option("-a",1.0,"Blurring the gradient image."), | ||
|  |     thresL = cimg_option("-tl",13.5,"Lower thresholding used in Hysteresis."), | ||
|  |     thresH = cimg_option("-th",13.6,"Higher thresholding used in Hysteresis."); | ||
|  |   const unsigned int | ||
|  |     mode = cimg_option("-m",1,"Detection mode: 1 = Hysteresis, 2 = Gradient angle."), | ||
|  |     factor = cimg_option("-s",80,"Half-size of edge-explorer window."); | ||
|  | 
 | ||
|  |   cimg_help("\nAdditional notes : user can press following keys on main display window :\n" | ||
|  |             "     - Left arrow : Decrease alpha.\n" | ||
|  |             "     - Right arrow : Increase alpha.\n"); | ||
|  | 
 | ||
|  |   // Construct a new image called 'edge' of size (2*factor,2*factor)
 | ||
|  |   // and of type 'unsigned char'.
 | ||
|  |   CImg<unsigned char> edge(2*factor,2*factor); | ||
|  |   CImgDisplay disp_edge(512,512,"Edge Explorer"); | ||
|  | 
 | ||
|  |   // Load the image with the name 'img_name' into the CImg 'img'.
 | ||
|  |   // and create a display window 'disp' for the image 'img'.
 | ||
|  |   const CImg<unsigned char> img = CImg<float>::get_load(img_name).norm().normalize(0,255); | ||
|  |   CImgDisplay disp(img,"Original Image"); | ||
|  | 
 | ||
|  |   // Begin main interaction loop.
 | ||
|  |   int x = 0, y = 0; | ||
|  |   bool redraw = false; | ||
|  |   while (!disp.is_closed() && !disp.is_keyQ() && !disp.is_keyESC()) { | ||
|  |     disp.wait(100); | ||
|  |     if (disp.button()&1) { alpha+=0.05; redraw = true; } | ||
|  |     if (disp.button()&2) { alpha-=0.05; redraw = true; } | ||
|  |     if (disp.wheel()) { alpha+=0.05*disp.wheel(); disp.set_wheel(); redraw = true; } | ||
|  |     if (alpha<0) alpha = 0; | ||
|  |     if (disp_edge.is_resized()) { disp_edge.resize(); redraw = true; } | ||
|  |     if (disp_edge.is_closed()) disp_edge.show(); | ||
|  |     if (disp.is_resized()) disp.resize(disp); | ||
|  |     if (disp.mouse_x()>=0) { | ||
|  |       x = disp.mouse_x(); // Getting the current position of the mouse
 | ||
|  |       y = disp.mouse_y(); //
 | ||
|  |       redraw = true;    // The image should be redrawn
 | ||
|  |     } | ||
|  |     if (redraw) { | ||
|  |       disp_edge.set_title("Edge explorer (alpha=%g)",alpha); | ||
|  |       const int | ||
|  |         x0 = x - factor, y0 = y - factor,  // These are the coordinates for the red rectangle
 | ||
|  |         x1 = x + factor, y1 = y + factor;  // to be drawn on the original image
 | ||
|  |       const unsigned char | ||
|  |         red[3] = { 255,0,0 },          //
 | ||
|  |         black[3] = { 0,0,0 };          // Defining the colors we need for drawing
 | ||
|  | 
 | ||
|  |         (+img).draw_rectangle(x0,y0,x1,y1,red,1.0f,0x55555555U).display(disp); | ||
|  |         //^ We draw the red rectangle on the original window using 'draw_line'.
 | ||
|  |         // Then we display the result via '.display(disp)' .
 | ||
|  |         //  Observe, that the color 'red' has to be of type 'const unsigned char',
 | ||
|  |         //  since the image 'img' is of type 'const CImg<unsigned char>'.
 | ||
|  | 
 | ||
|  |         //'normalize' is used to get a greyscaled image.
 | ||
|  |         CImg<> visu_bw = CImg<>(img).get_crop(x0,y0,x1,y1).get_norm().normalize(0,255).resize(-100,-100,1,2,2); | ||
|  |         // get_crop(x0,y0,x1,y1) gets the rectangle we are interested in.
 | ||
|  | 
 | ||
|  |         edge.fill(255); // Background color in the edge-detection window is white
 | ||
|  | 
 | ||
|  |         // grad[0] is the gradient image of 'visu_bw' in x-direction.
 | ||
|  |         // grad[1] is the gradient image of 'visu_bw' in y-direction.
 | ||
|  |         CImgList<> grad(visu_bw.blur((float)alpha).normalize(0,255).get_gradient()); | ||
|  | 
 | ||
|  |         // To avoid unnecessary calculations in the image loops:
 | ||
|  |         const double | ||
|  |           pi = cimg::PI, | ||
|  |           p8 = pi/8.0, p38 = 3.0*p8, | ||
|  |           p58 = 5.0*p8, p78 = 7.0*p8; | ||
|  | 
 | ||
|  |         cimg_forXY(visu_bw,s,t) { | ||
|  |           // We take s,t instead of x,y, since x,y are already used.
 | ||
|  |           // s corresponds to the x-ordinate of the pixel while t corresponds to the y-ordinate.
 | ||
|  |           if ( 1 <= s && s <= visu_bw.width() - 1 && 1 <= t && t <=visu_bw.height() - 1) { // if - good points
 | ||
|  |             double | ||
|  |               Gs = grad[0](s,t),                    //
 | ||
|  |               Gt = grad[1](s,t),                    //  The actual pixel is (s,t)
 | ||
|  |               Gst = cimg::abs(Gs) + cimg::abs(Gt),  //
 | ||
|  |               // ^-- For efficient computation we observe that |Gs|+ |Gt| ~=~ sqrt( Gs^2 + Gt^2)
 | ||
|  |               Gr, Gur, Gu, Gul, Gl, Gdl, Gd, Gdr; | ||
|  |             // ^-- right, up right, up, up left, left, down left, down, down right.
 | ||
|  |             double theta = std::atan2(std::max(1e-8,Gt),Gs) + pi; // theta is from the interval [0,Pi]
 | ||
|  |             switch(mode) { | ||
|  |             case 1: // Hysterese is applied
 | ||
|  |               if (Gst>=thresH) { edge.draw_point(s,t,black); } | ||
|  |               else if (thresL <= Gst && Gst < thresH) { | ||
|  |                 // Neighbourhood of the actual pixel:
 | ||
|  |                 Gr = cimg::abs(grad[0](s + 1,t)) + cimg::abs(grad[1](s + 1,t)); // right
 | ||
|  |                 Gl = cimg::abs(grad[0](s - 1,t)) + cimg::abs(grad[1](s - 1,t)); // left
 | ||
|  |                 Gur = cimg::abs(grad[0](s + 1,t + 1)) + cimg::abs(grad[1](s + 1,t + 1)); // up right
 | ||
|  |                 Gdl = cimg::abs(grad[0](s - 1,t - 1)) + cimg::abs(grad[1](s - 1,t - 1)); // down left
 | ||
|  |                 Gu = cimg::abs(grad[0](s,t + 1)) + cimg::abs(grad[1](s,t + 1)); // up
 | ||
|  |                 Gd = cimg::abs(grad[0](s,t - 1)) + cimg::abs(grad[1](s,t - 1)); // down
 | ||
|  |                 Gul = cimg::abs(grad[0](s - 1,t + 1)) + cimg::abs(grad[1](s - 1,t + 1)); // up left
 | ||
|  |                 Gdr = cimg::abs(grad[0](s + 1,t - 1)) + cimg::abs(grad[1](s + 1,t - 1)); // down right
 | ||
|  |                 if (Gr>=thresH || Gur>=thresH || Gu>=thresH || Gul>=thresH | ||
|  |                     || Gl>=thresH || Gdl >=thresH || Gu >=thresH || Gdr >=thresH) { | ||
|  |                   edge.draw_point(s,t,black); | ||
|  |                 } | ||
|  |               }; | ||
|  |               break; | ||
|  |             case 2: // Angle 'theta' of the gradient (Gs,Gt) at the point (s,t)
 | ||
|  |               if(theta >= pi)theta-=pi; | ||
|  |               //rounding theta:
 | ||
|  |               if ((p8 < theta && theta <= p38 ) || (p78 < theta && theta <= pi)) { | ||
|  |                 // See (*) below for explanation of the vocabulary used.
 | ||
|  |                 // Direction-pixel is (s + 1,t) with corresponding gradient value Gr.
 | ||
|  |                 Gr = cimg::abs(grad[0](s + 1,t)) + cimg::abs(grad[1](s + 1,t)); // right
 | ||
|  |                 // Contra-direction-pixel is (s - 1,t) with corresponding gradient value Gl.
 | ||
|  |                 Gl = cimg::abs(grad[0](s - 1,t)) + cimg::abs(grad[1](s - 1,t)); // left
 | ||
|  |                 if (Gr < Gst && Gl < Gst) { | ||
|  |                   edge.draw_point(s,t,black); | ||
|  |                 } | ||
|  |               } | ||
|  |               else if ( p8 < theta && theta <= p38) { | ||
|  |                 // Direction-pixel is (s + 1,t + 1) with corresponding gradient value Gur.
 | ||
|  |                 Gur = cimg::abs(grad[0](s + 1,t + 1)) + cimg::abs(grad[1](s + 1,t + 1)); // up right
 | ||
|  |                 // Contra-direction-pixel is (s-1,t-1) with corresponding gradient value Gdl.
 | ||
|  |                 Gdl = cimg::abs(grad[0](s - 1,t - 1)) + cimg::abs(grad[1](s - 1,t - 1)); // down left
 | ||
|  |                 if (Gur < Gst && Gdl < Gst) { | ||
|  |                   edge.draw_point(s,t,black); | ||
|  |                       } | ||
|  |               } | ||
|  |               else if ( p38 < theta && theta <= p58) { | ||
|  |                 // Direction-pixel is (s,t + 1) with corresponding gradient value Gu.
 | ||
|  |                 Gu = cimg::abs(grad[0](s,t + 1)) + cimg::abs(grad[1](s,t + 1)); // up
 | ||
|  |                 // Contra-direction-pixel is (s,t - 1) with corresponding gradient value Gd.
 | ||
|  |                 Gd = cimg::abs(grad[0](s,t - 1)) + cimg::abs(grad[1](s,t - 1)); // down
 | ||
|  |                 if (Gu < Gst && Gd < Gst) { | ||
|  |                   edge.draw_point(s,t,black); | ||
|  |                 } | ||
|  |               } | ||
|  |               else if (p58 < theta && theta <= p78) { | ||
|  |                 // Direction-pixel is (s - 1,t + 1) with corresponding gradient value Gul.
 | ||
|  |                 Gul = cimg::abs(grad[0](s - 1,t + 1)) + cimg::abs(grad[1](s - 1,t + 1)); // up left
 | ||
|  |                 // Contra-direction-pixel is (s + 1,t - 1) with corresponding gradient value Gdr.
 | ||
|  |                 Gdr = cimg::abs(grad[0](s + 1,t - 1)) + cimg::abs(grad[1](s + 1,t - 1)); // down right
 | ||
|  |                 if (Gul < Gst && Gdr < Gst) { | ||
|  |                   edge.draw_point(s,t,black); | ||
|  |                 } | ||
|  |               }; | ||
|  |               break; | ||
|  |             } // switch
 | ||
|  |           } // if good-points
 | ||
|  |         }  // cimg_forXY */
 | ||
|  |         edge.display(disp_edge); | ||
|  |     }// if redraw
 | ||
|  |   } // while
 | ||
|  |   return 0; | ||
|  | } | ||
|  | 
 | ||
|  | // (*) Comments to the vocabulary used:
 | ||
|  | // If (s,t) is the current pixel, and G=(Gs,Gt) is the gradient at (s,t),
 | ||
|  | // then the _direction_pixel_ of (s,t) shall be the one of the eight neighbour pixels
 | ||
|  | // of (s,t) in whose direction the gradient G shows.
 | ||
|  | // The _contra_direction_pixel is the pixel in the opposite direction in which the gradient G shows.
 | ||
|  | // The _corresponding_gradient_value_ of the pixel (x,y) with gradient G = (Gx,Gy)
 | ||
|  | // shall be |Gx| + |Gy| ~=~ sqrt(Gx^2 + Gy^2).
 |