Wed, 05 Aug 2009 15:02:31 +0100
PTdecode: add support for uncompressed data (NOTE: *NOT* supported by the PT-2450DX)
philpem@5 | 1 | /* |
philpem@5 | 2 | # |
philpem@5 | 3 | # File : image_registration.cpp |
philpem@5 | 4 | # ( C++ source file ) |
philpem@5 | 5 | # |
philpem@5 | 6 | # Description : Compute a motion field between two images, |
philpem@5 | 7 | # with a multiscale and variational algorithm. |
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 : David Tschumperle |
philpem@5 | 12 | # ( http://www.greyc.ensicaen.fr/~dtschump/ ) |
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 | // animate_warp() : Create warping animation from two images and a motion field |
philpem@5 | 62 | //---------------- |
philpem@5 | 63 | void animate_warp(const CImg<unsigned char>& src, const CImg<unsigned char>& dest, const CImg<>& u, |
philpem@5 | 64 | const bool morph, const bool imode, const char *filename,int nb, CImgDisplay& disp) { |
philpem@5 | 65 | CImg<unsigned char> visu = CImgList<unsigned char>(src,dest,src).get_append('x'), warp(src); |
philpem@5 | 66 | float t=0; |
philpem@5 | 67 | for (unsigned int iter=0; !disp || (!disp.is_closed && !disp.is_keyQ); iter++) { |
philpem@5 | 68 | if (morph) cimg_forXYV(warp,x,y,k) { |
philpem@5 | 69 | const float dx = u(x,y,0), dy = u(x,y,1), |
philpem@5 | 70 | I1 = (float)src.linear_atXY(x-t*dx, y-t*dy, k), |
philpem@5 | 71 | I2 = (float)dest.linear_atXY(x+(1-t)*dx,y+(1-t)*dy,k); |
philpem@5 | 72 | warp(x,y,k) = (unsigned char)((1-t)*I1 + t*I2); |
philpem@5 | 73 | } else cimg_forXYV(warp,x,y,k) { |
philpem@5 | 74 | const float dx = u(x,y,0), dy = u(x,y,1), I1 = (float)src.linear_atXY(x-t*dx, y-t*dy, 0,k); |
philpem@5 | 75 | warp(x,y,k) = (unsigned char)I1; |
philpem@5 | 76 | } |
philpem@5 | 77 | if (disp) visu.draw_image(2*src.dimx(),warp).display(disp.resize().wait(30)); |
philpem@5 | 78 | if (filename && *filename && (imode || (int)iter<nb)) { |
philpem@5 | 79 | std::fprintf(stderr,"\r > frame %d ",iter); |
philpem@5 | 80 | warp.save(filename,iter); |
philpem@5 | 81 | } |
philpem@5 | 82 | t+=1.0f/nb; |
philpem@5 | 83 | if (t<0) { t=0; nb=-nb; } |
philpem@5 | 84 | if (t>1) { t=1; nb=-nb; if (filename && *filename) std::exit(0); } |
philpem@5 | 85 | } |
philpem@5 | 86 | } |
philpem@5 | 87 | |
philpem@5 | 88 | // get_warp() : Return the image src warped by the motion field u. |
philpem@5 | 89 | //------------ |
philpem@5 | 90 | template<typename T> CImg<T> getwarp(const CImg<T>& src, const CImg<>& u) { |
philpem@5 | 91 | CImg<T> warp(src); |
philpem@5 | 92 | cimg_forXY(warp,x,y) warp(x,y) = (T)src.linear_atXY(x - u(x,y,0), y - u(x,y,1)); |
philpem@5 | 93 | return warp; |
philpem@5 | 94 | } |
philpem@5 | 95 | |
philpem@5 | 96 | // optmonoflow() : Register images for one scale ( semi-implicite PDE scheme ) between I2->I1 |
philpem@5 | 97 | //--------------- |
philpem@5 | 98 | CImg<> optmonoflow(const CImg<>& I1, const CImg<>& I2, const CImg<>& u0, |
philpem@5 | 99 | const float smooth, const float precision, CImgDisplay& disp) { |
philpem@5 | 100 | |
philpem@5 | 101 | CImg<> u = u0.get_resize(I1.dimx(),I1.dimy(),1,2,3),dI(u); |
philpem@5 | 102 | CImg_3x3(I,float); |
philpem@5 | 103 | float dt=2,E=1e20f; |
philpem@5 | 104 | |
philpem@5 | 105 | // compute first derivatives of I2 |
philpem@5 | 106 | cimg_for3x3(I2,x,y,0,0,I) { |
philpem@5 | 107 | dI(x,y,0) = 0.5f*(Inc-Ipc); |
philpem@5 | 108 | dI(x,y,1) = 0.5f*(Icn-Icp); |
philpem@5 | 109 | } |
philpem@5 | 110 | |
philpem@5 | 111 | // Main PDE iteration |
philpem@5 | 112 | for (unsigned int iter=0; iter<100000; iter++) { |
philpem@5 | 113 | std::fprintf(stderr,"\r- Iteration %d - E = %g",iter,E); std::fflush(stderr); |
philpem@5 | 114 | const float Eold = E; |
philpem@5 | 115 | E = 0; |
philpem@5 | 116 | cimg_for3XY(u,x,y) { |
philpem@5 | 117 | const float |
philpem@5 | 118 | X = x + u(x,y,0), |
philpem@5 | 119 | Y = y + u(x,y,1), |
philpem@5 | 120 | deltaI = (float)(I2.linear_atXY(X,Y) - I1(x,y)); |
philpem@5 | 121 | float tmpf = 0; |
philpem@5 | 122 | cimg_forV(u,k) { |
philpem@5 | 123 | const float |
philpem@5 | 124 | ux = 0.5f*(u(_n1x,y,k)-u(_p1x,y,k)), |
philpem@5 | 125 | uy = 0.5f*(u(x,_n1y,k)-u(x,_p1y,k)); |
philpem@5 | 126 | u(x,y,k) = (float)( u(x,y,k) + |
philpem@5 | 127 | dt*( |
philpem@5 | 128 | -deltaI*dI.linear_atXY(X,Y,k) + |
philpem@5 | 129 | smooth* ( u(_n1x,y,k) + u(_p1x,y,k) + u(x,_n1y,k) + u(x,_p1y,k) ) |
philpem@5 | 130 | ) |
philpem@5 | 131 | )/(1+4*smooth*dt); |
philpem@5 | 132 | tmpf += ux*ux + uy*uy; |
philpem@5 | 133 | } |
philpem@5 | 134 | E += deltaI*deltaI + smooth * tmpf; |
philpem@5 | 135 | } |
philpem@5 | 136 | if (cimg::abs(Eold-E)<precision) break; |
philpem@5 | 137 | if (Eold<E) dt*=0.5; |
philpem@5 | 138 | if (disp) disp.resize(); |
philpem@5 | 139 | if (disp && disp.is_closed) std::exit(0); |
philpem@5 | 140 | if (disp && !(iter%300)) { |
philpem@5 | 141 | const unsigned char white = 255; |
philpem@5 | 142 | CImg<unsigned char> tmp = getwarp(I1,u).normalize(0,200); |
philpem@5 | 143 | tmp.resize(disp.dimx(),disp.dimy()).draw_quiver(u,&white,0.7f,15,-14,0).display(disp); |
philpem@5 | 144 | } |
philpem@5 | 145 | } |
philpem@5 | 146 | return u; |
philpem@5 | 147 | } |
philpem@5 | 148 | |
philpem@5 | 149 | // optflow() : multiscale version of the image registration algorithm |
philpem@5 | 150 | //----------- |
philpem@5 | 151 | CImg<> optflow(const CImg<>& xsrc, const CImg<>& xdest, |
philpem@5 | 152 | const float smooth, const float precision, const unsigned int pnb_scale, CImgDisplay& disp) { |
philpem@5 | 153 | const CImg<> |
philpem@5 | 154 | src = xsrc.get_pointwise_norm(1).resize(xdest.dimx(),xdest.dimy(),1,1,3).normalize(0,1), |
philpem@5 | 155 | dest = xdest.get_pointwise_norm(1).resize(xdest.dimx(),xdest.dimy(),1,1,3).normalize(0,1); |
philpem@5 | 156 | CImg<> u = CImg<>(src.dimx(),src.dimy(),1,2).fill(0); |
philpem@5 | 157 | |
philpem@5 | 158 | const unsigned int nb_scale = pnb_scale>0?pnb_scale:(unsigned int)(2*std::log((double)(cimg::max(src.dimx(),src.dimy())))); |
philpem@5 | 159 | for (int scale=nb_scale-1; scale>=0; scale--) { |
philpem@5 | 160 | const CImg<> I1 = src.get_resize((int)(src.dimx()/std::pow(1.5,scale)), (int)(src.dimy()/std::pow(1.5,scale)) ,1,1,3); |
philpem@5 | 161 | const CImg<> I2 = dest.get_resize((int)(src.dimx()/std::pow(1.5,scale)), (int)(src.dimy()/std::pow(1.5,scale)) ,1,1,3); |
philpem@5 | 162 | std::fprintf(stderr," * Scale %d\n",scale); |
philpem@5 | 163 | u*=1.5; |
philpem@5 | 164 | u = optmonoflow(I1,I2,u,smooth,(float)(precision/std::pow(2.25,1+scale)),disp); |
philpem@5 | 165 | std::fprintf(stderr,"\n"); |
philpem@5 | 166 | } |
philpem@5 | 167 | return u; |
philpem@5 | 168 | } |
philpem@5 | 169 | |
philpem@5 | 170 | /*------------------------ |
philpem@5 | 171 | |
philpem@5 | 172 | Main function |
philpem@5 | 173 | |
philpem@5 | 174 | ------------------------*/ |
philpem@5 | 175 | |
philpem@5 | 176 | int main(int argc,char **argv) { |
philpem@5 | 177 | |
philpem@5 | 178 | // Read command line parameters |
philpem@5 | 179 | cimg_usage("Compute an optical flow between two 2D images, and create a warped animation"); |
philpem@5 | 180 | const char |
philpem@5 | 181 | *name_i1 = cimg_option("-i",cimg_imagepath "sh0r.pgm","Input Image 1 (Destination)"), |
philpem@5 | 182 | *name_i2 = cimg_option("-i2",cimg_imagepath "sh1r.pgm","Input Image 2 (Source)"), |
philpem@5 | 183 | *name_o = cimg_option("-o",(const char*)NULL,"Output 2D flow (inrimage)"), |
philpem@5 | 184 | *name_seq = cimg_option("-o2",(const char*)NULL,"Output Warping Sequence"); |
philpem@5 | 185 | const float |
philpem@5 | 186 | smooth = cimg_option("-s",0.1f,"Flow Smoothness"), |
philpem@5 | 187 | precision = cimg_option("-p",0.9f,"Convergence precision"); |
philpem@5 | 188 | const unsigned int |
philpem@5 | 189 | nb = cimg_option("-n",40,"Number of warped frames"), |
philpem@5 | 190 | nbscale = cimg_option("-scale",0,"Number of scales (0=auto)"); |
philpem@5 | 191 | const bool |
philpem@5 | 192 | normalize = cimg_option("-equalize",true,"Histogram normalization of the images"), |
philpem@5 | 193 | morph = cimg_option("-m",true,"Morphing mode"), |
philpem@5 | 194 | imode = cimg_option("-c",true,"Complete interpolation (or last frame is missing)"), |
philpem@5 | 195 | dispflag = !cimg_option("-novisu",false,"Visualization"); |
philpem@5 | 196 | |
philpem@5 | 197 | // Init images and display |
philpem@5 | 198 | std::fprintf(stderr," - Init images.\n"); |
philpem@5 | 199 | const CImg<> |
philpem@5 | 200 | src(name_i1), |
philpem@5 | 201 | dest(CImg<>(name_i2).resize(src,3)), |
philpem@5 | 202 | src_blur = normalize?src.get_blur(0.5f).equalize(256):src.get_blur(0.5f), |
philpem@5 | 203 | dest_blur = normalize?dest.get_blur(0.5f).equalize(256):dest.get_blur(0.5f); |
philpem@5 | 204 | |
philpem@5 | 205 | CImgDisplay disp; |
philpem@5 | 206 | if (dispflag) { |
philpem@5 | 207 | unsigned int w = src.dimx(), h = src.dimy(); |
philpem@5 | 208 | const unsigned int dmin = cimg::min(w,h), minsiz = 512; |
philpem@5 | 209 | if (dmin<minsiz) { w=w*minsiz/dmin; h=h*minsiz/dmin; } |
philpem@5 | 210 | const unsigned int dmax = cimg::max(w,h), maxsiz = 1024; |
philpem@5 | 211 | if (dmax>maxsiz) { w=w*maxsiz/dmax; h=h*maxsiz/dmax; } |
philpem@5 | 212 | disp.assign(w,h,"Estimated Motion",0); |
philpem@5 | 213 | } |
philpem@5 | 214 | |
philpem@5 | 215 | // Run Motion estimation algorithm |
philpem@5 | 216 | std::fprintf(stderr," - Compute optical flow.\n"); |
philpem@5 | 217 | const CImg<> u = optflow(src_blur,dest_blur,smooth,precision,nbscale,disp); |
philpem@5 | 218 | if (name_o) u.save(name_o); |
philpem@5 | 219 | u.print("Computed flow"); |
philpem@5 | 220 | |
philpem@5 | 221 | // Do morphing animation |
philpem@5 | 222 | std::fprintf(stderr," - Create warped animation.\n"); |
philpem@5 | 223 | CImgDisplay disp2; |
philpem@5 | 224 | if (dispflag) { |
philpem@5 | 225 | unsigned int w = src.dimx(), h = src.dimy(); |
philpem@5 | 226 | const unsigned int dmin = cimg::min(w,h), minsiz = 100; |
philpem@5 | 227 | if (dmin<minsiz) { w=w*minsiz/dmin; h=h*minsiz/dmin; } |
philpem@5 | 228 | const unsigned int dmax = cimg::max(w,h), maxsiz = 1024/3; |
philpem@5 | 229 | if (dmax>maxsiz) { w=w*maxsiz/dmax; h=h*maxsiz/dmax; } |
philpem@5 | 230 | disp2.assign(3*w,h,"Source/Destination images and Motion animation",0); |
philpem@5 | 231 | } |
philpem@5 | 232 | |
philpem@5 | 233 | animate_warp(src.get_normalize(0,255),dest.get_normalize(0,255),u,morph,imode,name_seq,nb,disp2); |
philpem@5 | 234 | |
philpem@5 | 235 | std::exit(0); |
philpem@5 | 236 | return 0; |
philpem@5 | 237 | } |