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