PTdecode/CImg-1.3.0/examples/radon_transform.cpp

Wed, 05 Aug 2009 15:00:54 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Wed, 05 Aug 2009 15:00:54 +0100
changeset 12
96e1df9bd27c
parent 5
1204ebf9340d
permissions
-rwxr-xr-x

small changes to hexdump code to stop a gcc warning on platforms where sizeof(int) != sizeof(int*) e.g. x86_64

     1 /*
     2  #
     3  #  File        : radon_transform.cpp
     4  #                ( C++ source file )
     5  #
     6  #  Description : An implementation of the Radon Transform.
     7  #                This file is a part of the CImg Library project.
     8  #                ( http://cimg.sourceforge.net )
     9  #
    10  #  Copyright   : David G. Starkweather
    11  #                ( starkdg@sourceforge.net - starkweatherd@cox.net )
    12  #
    13  #  License     : CeCILL v2.0
    14  #                ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
    15  #
    16  #  This software is governed by the CeCILL  license under French law and
    17  #  abiding by the rules of distribution of free software.  You can  use,
    18  #  modify and/ or redistribute the software under the terms of the CeCILL
    19  #  license as circulated by CEA, CNRS and INRIA at the following URL
    20  #  "http://www.cecill.info".
    21  #
    22  #  As a counterpart to the access to the source code and  rights to copy,
    23  #  modify and redistribute granted by the license, users are provided only
    24  #  with a limited warranty  and the software's author,  the holder of the
    25  #  economic rights,  and the successive licensors  have only  limited
    26  #  liability.
    27  #
    28  #  In this respect, the user's attention is drawn to the risks associated
    29  #  with loading,  using,  modifying and/or developing or reproducing the
    30  #  software by the user in light of its specific status of free software,
    31  #  that may mean  that it is complicated to manipulate,  and  that  also
    32  #  therefore means  that it is reserved for developers  and  experienced
    33  #  professionals having in-depth computer knowledge. Users are therefore
    34  #  encouraged to load and test the software's suitability as regards their
    35  #  requirements in conditions enabling the security of their systems and/or
    36  #  data to be ensured and,  more generally, to use and operate it in the
    37  #  same conditions as regards security.
    38  #
    39  #  The fact that you are presently reading this means that you have had
    40  #  knowledge of the CeCILL license and that you accept its terms.
    41  #
    42 */
    44 #define ROUNDING_FACTOR(x) (((x) >= 0) ? 0.5 : -0.5)
    45 #include <cmath>
    46 #include "CImg.h"
    47 using namespace cimg_library;
    49 // The lines below are necessary when using a non-standard compiler as visualcpp6.
    50 #ifdef cimg_use_visualcpp6
    51 #define std
    52 #endif
    53 #ifdef min
    54 #undef min
    55 #undef max
    56 #endif
    58 #ifndef cimg_imagepath
    59 #define cimg_imagepath "img/"
    60 #endif
    62 CImg<double> GaussianKernel(double rho);
    63 CImg<float> ApplyGaussian(CImg<unsigned char> im,double rho);
    64 CImg<unsigned char> RGBtoGrayScale(CImg<unsigned char> &im);
    65 int GetAngle(int dy,int dx);
    66 CImg<unsigned char> CannyEdges(CImg<float> im, double T1, double T2,bool doHysteresis);
    67 CImg<> RadonTransform(CImg<unsigned char> im,int N);
    69 int main(int argc,char **argv) {
    70   cimg_usage("Illustration of the Radon Transform");
    72   const char *file = cimg_option("-f",cimg_imagepath "parrot_original.ppm","path and file name");
    73   const double sigma = cimg_option("-r",1.0,"blur coefficient for gaussian low pass filter (lpf)"),
    74     thresh1 = cimg_option("-t1",0.50,"lower threshold for canny edge detector"),
    75     thresh2 = cimg_option("-t2",1.25,"upper threshold for canny edge detector");;
    76   const int N = cimg_option("-n",64,"number of angles to consider in the Radon transform - should be a power of 2");
    78   //color to draw lines
    79   const unsigned char green[] = {0,255,0};
    80   CImg<unsigned char> src(file);
    82   int rhomax = (int)std::sqrt((double)(src.dimx()*src.dimx() + src.dimy()*src.dimy()))/2;
    84   if (cimg::dialog(cimg::basename(argv[0]),
    85                    "Instructions:\n"
    86                    "Click on space bar or Enter key to display Radon transform of given image\n"
    87                    "Click on anywhere in the transform window to display a \n"
    88                    "corresponding green line in the original image\n",
    89                    "Start", "Quit",0,0,0,0,
    90                    src.get_resize(100,100,1,3),true)) std::exit(0);
    92   //retrieve a grayscale from the image
    93   CImg<unsigned char> grayScaleIm;
    94   if ((src.dimv() == 3) && (src.dimx() > 0) && (src.dimy() > 0) && (src.dimz() == 1))
    95     grayScaleIm = (CImg<unsigned char>)src.get_pointwise_norm(0).quantize(255,false);
    96   else if ((src.dimv() == 1)&&(src.dimx() > 0) && (src.dimy() > 0) && (src.dimz() == 1))
    97     grayScaleIm = src;
    98   else { // image in wrong format
    99     if (cimg::dialog(cimg::basename("wrong file format"),
   100                      "Incorrect file format\n","OK",0,0,0,0,0,
   101                      src.get_resize(100,100,1,3),true)) std::exit(0);
   102   }
   104   //blur the image with a Gaussian lpf to remove spurious edges (e.g. noise)
   105   CImg<float> blurredIm = ApplyGaussian(grayScaleIm,sigma);
   107   //use canny edge detection algorithm to get edge map of the image
   108   //- the threshold values are used to perform hysteresis in the edge detection process
   109   CImg<unsigned char> cannyEdgeMap = CannyEdges(blurredIm,thresh1,thresh2,false);
   110   CImg<unsigned char> radonImage = *(new CImg<unsigned char>(500,400,1,1,0));
   112   //display the two windows
   113   CImgDisplay dispImage(src,"original image");
   114   dispImage.move(CImgDisplay::screen_dimx()/8,CImgDisplay::screen_dimy()/8);
   115   CImgDisplay dispRadon(radonImage,"Radon Transform");
   116   dispRadon.move(CImgDisplay::screen_dimx()/4,CImgDisplay::screen_dimy()/4);
   117   CImgDisplay dispCanny(cannyEdgeMap,"canny edges");
   118   //start main display loop
   119   while (!dispImage.is_closed && !dispRadon.is_closed &&
   120          !dispImage.is_keyQ && !dispRadon.is_keyQ &&
   121          !dispImage.is_keyESC && !dispRadon.is_keyESC){
   123     CImgDisplay::wait(dispImage,dispRadon);
   125     if (dispImage.is_keySPACE || dispRadon.is_keySPACE)
   126     {
   127       radonImage = (CImg<unsigned char>)RadonTransform(cannyEdgeMap,N).quantize(255,false).resize(500,400);
   128       radonImage.display(dispRadon);
   129     }
   131     //when clicking on dispRadon window, draw line in original image window
   132     if (dispRadon.button)
   133       {
   134         const double rho = dispRadon.mouse_y*rhomax/dispRadon.dimy(),
   135           theta = (dispRadon.mouse_x*N/dispRadon.dimx())*2*cimg::valuePI/N,
   136           x = src.dimx()/2 + rho*std::cos(theta),
   137           y = src.dimy()/2 + rho*std::sin(theta);
   138         const int x0 = (int)(x + 1000*std::cos(theta + cimg::valuePI/2)),
   139           y0 = (int)(y + 1000*std::sin(theta + cimg::valuePI/2)),
   140           x1 = (int)(x - 1000*std::cos(theta + cimg::valuePI/2)),
   141           y1 = (int)(y - 1000*std::sin(theta + cimg::valuePI/2));
   142         src.draw_line(x0,y0,x1,y1,green,1.0f,0xF0F0F0F0).display(dispImage);
   143       }
   144   }
   145   return 0;
   146 }
   147 /**
   148  * PURPOSE: create a 5x5 gaussian kernel matrix
   149  * PARAM rho - gaussiam equation parameter (default = 1.0)
   150  * RETURN CImg<double> the gaussian kernel
   151  **/
   153 CImg<double> GaussianKernel(double sigma = 1.0)
   154 {
   155   CImg<double> resultIm(5,5,1,1,0);
   156   int midX = 3, midY = 3;
   157   cimg_forXY(resultIm,X,Y)
   158     {
   159       resultIm(X,Y) = std::ceil(256.0*(std::exp(-(midX*midX + midY*midY)/(2*sigma*sigma)))/(2*cimg::valuePI*sigma*sigma));
   160     }
   161   return resultIm;
   162 }
   163 /*
   164  * PURPOSE: convolve a given image with the gaussian kernel
   165  * PARAM CImg<unsigned char> im - image to be convolved upon
   166  * PARAM double sigma - gaussian equation parameter
   167  * RETURN CImg<float> image resulting from the convolution
   168  * */
   169 CImg<float> ApplyGaussian(CImg<unsigned char> im,double sigma)
   170 {
   171   CImg<float> smoothIm(im.dimx(),im.dimy(),1,1,0);
   173   //make gaussian kernel
   174   CImg<float> gk = GaussianKernel(sigma);
   175   //apply gaussian
   177   CImg_5x5(I,int);
   178   cimg_for5x5(im,X,Y,0,0,I)
   179     {
   180       float sum = 0;
   181       sum += gk(0,0)*Ibb + gk(0,1)*Ibp + gk(0,2)*Ibc + gk(0,3)*Ibn + gk(0,4)*Iba;
   182       sum += gk(1,0)*Ipb + gk(1,1)*Ipp + gk(1,2)*Ipc + gk(1,3)*Ipn + gk(1,4)*Ipa;
   183       sum += gk(2,0)*Icb + gk(2,1)*Icp + gk(2,2)*Icc + gk(2,3)*Icn + gk(2,4)*Ica;
   184       sum += gk(3,0)*Inb + gk(3,1)*Inp + gk(3,2)*Inc + gk(3,3)*Inn + gk(3,4)*Ina;
   185       sum += gk(4,0)*Iab + gk(4,1)*Iap + gk(4,2)*Iac + gk(4,3)*Ian + gk(4,4)*Iaa;
   186       smoothIm(X,Y)= sum/256;
   187     }
   189   return smoothIm;
   190 }
   191 /**
   192  * PURPOSE: convert a given rgb image to a MxNX1 single vector grayscale image
   193  * PARAM: CImg<unsigned char> im - rgb image to convert
   194  * RETURN: CImg<unsigned char> grayscale image with MxNx1x1 dimensions
   195  **/
   197 CImg<unsigned char> RGBtoGrayScale(CImg<unsigned char> &im)
   198 {
   199   CImg<unsigned char> grayImage(im.dimx(),im.dimy(),im.dimz(),1,0);
   200   if (im.dimv() == 3)
   201     {   cimg_forXYZ(im,X,Y,Z)
   202       {
   203         grayImage(X,Y,Z,0) = (unsigned char)(0.299*im(X,Y,Z,0) + 0.587*im(X,Y,Z,1) + 0.114*im(X,Y,Z,2));
   204       }
   205     }
   206   grayImage.quantize(255,false);
   207   return grayImage;
   208 }
   209 /**
   210  * PURPOSE: aux. function used by CannyEdges to quantize an angle theta given by gradients, dx and dy
   211  *  into 0 - 7
   212  * PARAM: dx,dy - gradient magnitudes
   213  * RETURN int value between 0 and 7
   214  **/
   215 int GetAngle(int dy,int dx)
   216 {
   217   double angle = cimg::abs(std::atan2((double)dy,(double)dx));
   218   if ((angle >= -cimg::valuePI/8)&&(angle <= cimg::valuePI/8))//-pi/8 to pi/8 => 0
   219     return 0;
   220   else if ((angle >= cimg::valuePI/8)&&(angle <= 3*cimg::valuePI/8))//pi/8 to 3pi/8 => pi/4
   221     return 1;
   222   else if ((angle > 3*cimg::valuePI/8)&&(angle <= 5*cimg::valuePI/8))//3pi/8 to 5pi/8 => pi/2
   223     return 2;
   224   else if ((angle > 5*cimg::valuePI/8)&&(angle <= 7*cimg::valuePI/8))//5pi/8 to 7pi/8 => 3pi/4
   225     return 3;
   226   else if (((angle > 7*cimg::valuePI/8) && (angle <= cimg::valuePI))  || ((angle <= -7*cimg::valuePI/8)&&(angle >= -cimg::valuePI))) //-7pi/8 to -pi OR 7pi/8 to pi => pi
   227     return 4;
   228   else return 0;
   229 }
   230 /**
   231  * PURPOSE: create an edge map of the given image with hysteresis using thresholds T1 and T2
   232  * PARAMS: CImg<float> im the image to perform edge detection on
   233  *                 T1 lower threshold
   234  *         T2 upper threshold
   235  * RETURN CImg<unsigned char> edge map
   236  **/
   237 CImg<unsigned char> CannyEdges(CImg<float> im, double T1, double T2, bool doHysteresis=false)
   238 {
   239   CImg<unsigned char> edges(im);
   240   CImg<float> secDerivs(im);
   241   secDerivs.fill(0);
   242   edges.fill(0);
   243   CImgList<float> gradients = im.get_gradient("xy",1);
   244   int image_width = im.dimx();
   245   int image_height = im.dimy();
   247   { cimg_forXY(im,X,Y)
   248     {
   249       double Gr = std::sqrt(std::pow((double)gradients[0](X,Y),2.0) + std::pow((double)gradients[1](X,Y),2.0));
   250       double theta = GetAngle(Y,X);
   251       //if Gradient magnitude is positive and X,Y within the image
   252       //take the 2nd deriv in the appropriate direction
   253       if ((Gr > 0)&&(X < image_width-1)&&(Y < image_height - 1))
   254         {
   255           if (theta == 0)
   256             secDerivs(X,Y) = im(X+2,Y) - 2*im(X+1,Y) + im(X,Y);
   257           else if (theta == 1)
   258             secDerivs(X,Y) = im(X+2,Y+2) - 2*im(X+1,Y+1) + im(X,Y);
   259           else if (theta == 2)
   260             secDerivs(X,Y) = im(X,Y+2) - 2*im(X,Y+1) + im(X,Y);
   261           else if (theta == 3)
   262             secDerivs(X,Y) = im(X+2,Y+2) - 2*im(X+1,Y+1) + im(X,Y);
   263           else if (theta == 4)
   264             secDerivs(X,Y) = im(X+2,Y) - 2*im(X+1,Y) + im(X,Y);
   265         }
   266     }}
   267   //for each 2nd deriv that crosses a zero point and magnitude passes the upper threshold.
   268   //Perform hysteresis in the direction of the gradient, rechecking the gradient
   269   //angle for each pixel that meets the threshold requirement.  Stop checking when
   270   //the lower threshold is not reached.
   271   CImg_5x5(I,float);
   272   cimg_for5x5(secDerivs,X,Y,0,0,I)
   273     {
   274       if (   (Ipp*Ibb < 0)||
   275              (Ipc*Ibc < 0)||
   276              (Icp*Icb < 0)   )
   277         {
   278           double Gr = std::sqrt(std::pow((double)gradients[0](X,Y),2.0) + std::pow((double)gradients[1](X,Y),2.0));
   279           int dir = GetAngle(Y,X);
   280           int Xt=X, Yt=Y, delta_x = 0, delta_y=0;
   281           double GRt = Gr;
   282           if (Gr >= T2)
   283             edges(X,Y) = 255;
   284           //work along the gradient in one direction
   285           if (doHysteresis)
   286             {
   287               while ((Xt > 0) && (Xt < image_width-1) && (Yt > 0) && (Yt < image_height-1))
   288                 {
   289                   switch (dir){
   290                   case 0:delta_x=0;delta_y=1;break;
   291                   case 1:delta_x=1;delta_y=1;break;
   292                   case 2:delta_x=1;delta_y=0;break;
   293                   case 3:delta_x=1;delta_y=-1;break;
   294                   case 4:delta_x=0;delta_y=1;break;
   295                   }
   296                   Xt += delta_x;
   297                   Yt += delta_y;
   298                   GRt = std::sqrt(std::pow((double)gradients[0](Xt,Yt),2.0) + std::pow((double)gradients[1](Xt,Yt),2.0));
   299                   dir = GetAngle(Yt,Xt);
   300                   if (GRt >= T1)
   301                     edges(Xt,Yt) = 255;
   302                 }
   303               //work along gradient in other direction
   304               Xt = X; Yt = Y;
   305               while ((Xt > 0) && (Xt < image_width-1) && (Yt > 0) && (Yt < image_height-1))
   306                 {
   307                   switch (dir){
   308                   case 0:delta_x=0;delta_y=1;break;
   309                   case 1:delta_x=1;delta_y=1;break;
   310                   case 2:delta_x=1;delta_y=0;break;
   311                   case 3:delta_x=1;delta_y=-1;break;
   312                   case 4:delta_x=0;delta_y=1;break;
   313                   }
   314                   Xt -= delta_x;
   315                   Yt -= delta_y;
   316                   GRt = std::sqrt(std::pow((double)gradients[0](Xt,Yt),2.0) + std::pow((double)gradients[1](Xt,Yt),2.0));
   317                   dir = GetAngle(Yt,Xt);
   318                   if (GRt >= T1)
   319                     edges(Xt,Yt) = 255;
   320                 }
   321             }
   322         }
   323     }
   324   return edges;
   325 }
   326 /**
   327  * PURPOSE: perform radon transform of given image
   328  * PARAM: CImg<unsigned char> im - image to detect lines
   329  *                int N - number of angles to consider (should be a power of 2)
   330  *                (the values of N will be spread over 0 to 2PI)
   331  * RETURN CImg<unsigned char> - transform of given image of size, N x D
   332  *                              D = rhomax = sqrt(dimx*dimx + dimy*dimy)/2
   333  **/
   334 CImg<> RadonTransform(CImg<unsigned char> im,int N)
   335 {
   336   int image_width = im.dimx();
   337   int image_height = im.dimy();
   339   //calc offsets to center the image
   340   float xofftemp = image_width/2.0f - 1;
   341   float yofftemp = image_height/2.0f - 1;
   342   int xoffset = (int)std::floor(xofftemp + ROUNDING_FACTOR(xofftemp));
   343   int yoffset = (int)std::floor(yofftemp + ROUNDING_FACTOR(yofftemp));
   344   float dtemp = (float)std::sqrt((double)(xoffset*xoffset + yoffset*yoffset));
   345   int D = (int)std::floor(dtemp + ROUNDING_FACTOR(dtemp));
   347   CImg<> imRadon(N,D,1,1,0);
   349   //for each angle k to consider
   350   for (int k= 0 ; k < N; k++)
   351     {
   352       //only consider from PI/8 to 3PI/8 and 5PI/8 to 7PI/8
   353       //to avoid computational complexity of a steep angle
   354       if (k == 0){k = N/8;continue;}
   355       else if (k == (3*N/8 + 1)){       k = 5*N/8;continue;}
   356       else if (k == 7*N/8 + 1){k = N;   continue;}
   358       //for each rho length, determine linear equation and sum the line
   359       //sum is to sum the values along the line at angle k2pi/N
   360       //sum2 is to sum the values along the line at angle k2pi/N + N/4
   361       //The sum2 is performed merely by swapping the x,y axis as if the image were rotated 90 degrees.
   362       for (int d=0; d < D; d++){
   363         double theta = 2*k*cimg::valuePI/N;//calculate actual theta
   364         double alpha = std::tan(theta+cimg::valuePI/2);//calculate the slope
   365         double beta_temp = -alpha*d*std::cos(theta) + d*std::sin(theta);//y-axis intercept for the line
   366         int beta = (int)std::floor(beta_temp + ROUNDING_FACTOR(beta_temp));
   367         //for each value of m along x-axis, calculate y
   368         //if the x,y location is within the boundary for the respective image orientations, add to the sum
   369         unsigned int sum1 = 0,
   370           sum2 = 0;
   371         int M = (image_width >= image_height) ? image_width : image_height;
   372         for (int m=0;m < M; m++)
   373           {
   374             //interpolate in-between values using nearest-neighbor approximation
   375             //using m,n as x,y indices into image
   376             double n_temp = alpha*(m-xoffset) + beta;
   377             int n = (int)std::floor(n_temp + ROUNDING_FACTOR(n_temp));
   378             if ((m < image_width) && (n + yoffset >= 0) && (n + yoffset < image_height))
   379               {
   380                 sum1 += im(m, n + yoffset);
   381               }
   382             n_temp = alpha*(m-yoffset) + beta;
   383             n = (int)std::floor(n_temp + ROUNDING_FACTOR(n_temp));
   384             if ((m < image_height)&&(n + xoffset >= 0)&&(n + xoffset < image_width))
   385               {
   386                 sum2 += im(-(n + xoffset) + image_width - 1, m);
   387               }
   388           }
   389         //assign the sums into the result matrix
   390         imRadon(k,d) = (float)sum1;
   391         //assign sum2 to angle position for theta+PI/4
   392         imRadon(((k + N/4)%N),d) = (float)sum2;
   393       }
   394     }
   395   return imRadon;
   396 }
   397 /* references:
   398  * 1. See Peter Toft's thesis on the Radon transform: http://petertoft.dk/PhD/index.html
   399  * While I changed his basic algorithm, the main idea is still the same and provides an excellent explanation.
   400  *
   401  * */