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