Austin Schuh | 8c794d5 | 2019-03-03 21:17:37 -0800 | [diff] [blame] | 1 | /* |
| 2 | # |
| 3 | # File : image_registration2d.cpp |
| 4 | # ( C++ source file ) |
| 5 | # |
| 6 | # Description : Compute a motion field between two images, |
| 7 | # with a multiscale and variational algorithm. |
| 8 | # This file is a part of the CImg Library project. |
| 9 | # ( http://cimg.eu ) |
| 10 | # |
| 11 | # Copyright : David Tschumperle |
| 12 | # ( http://tschumperle.users.greyc.fr/ ) |
| 13 | # |
| 14 | # License : CeCILL v2.0 |
| 15 | # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ) |
| 16 | # |
| 17 | # This software is governed by the CeCILL license under French law and |
| 18 | # abiding by the rules of distribution of free software. You can use, |
| 19 | # modify and/ or redistribute the software under the terms of the CeCILL |
| 20 | # license as circulated by CEA, CNRS and INRIA at the following URL |
| 21 | # "http://www.cecill.info". |
| 22 | # |
| 23 | # As a counterpart to the access to the source code and rights to copy, |
| 24 | # modify and redistribute granted by the license, users are provided only |
| 25 | # with a limited warranty and the software's author, the holder of the |
| 26 | # economic rights, and the successive licensors have only limited |
| 27 | # liability. |
| 28 | # |
| 29 | # In this respect, the user's attention is drawn to the risks associated |
| 30 | # with loading, using, modifying and/or developing or reproducing the |
| 31 | # software by the user in light of its specific status of free software, |
| 32 | # that may mean that it is complicated to manipulate, and that also |
| 33 | # therefore means that it is reserved for developers and experienced |
| 34 | # professionals having in-depth computer knowledge. Users are therefore |
| 35 | # encouraged to load and test the software's suitability as regards their |
| 36 | # requirements in conditions enabling the security of their systems and/or |
| 37 | # data to be ensured and, more generally, to use and operate it in the |
| 38 | # same conditions as regards security. |
| 39 | # |
| 40 | # The fact that you are presently reading this means that you have had |
| 41 | # knowledge of the CeCILL license and that you accept its terms. |
| 42 | # |
| 43 | */ |
| 44 | |
| 45 | #include "CImg.h" |
| 46 | using namespace cimg_library; |
| 47 | #ifndef cimg_imagepath |
| 48 | #define cimg_imagepath "img/" |
| 49 | #endif |
| 50 | #undef min |
| 51 | #undef max |
| 52 | |
| 53 | // animate_warp() : Create warping animation from two images and a motion field |
| 54 | //---------------- |
| 55 | void animate_warp(const CImg<unsigned char>& src, const CImg<unsigned char>& dest, const CImg<>& U, |
| 56 | const bool morph, const bool imode, const char *filename,int nb, CImgDisplay& disp) { |
| 57 | CImg<unsigned char> visu = (src,dest,src)>'x', warp(src); |
| 58 | float t = 0; |
| 59 | for (unsigned int iteration = 0; !disp || (!disp.is_closed() && !disp.is_keyQ()); ++iteration) { |
| 60 | if (morph) cimg_forXYC(warp,x,y,k) { |
| 61 | const float dx = U(x,y,0), dy = U(x,y,1), |
| 62 | I1 = (float)src.linear_atXY(x - t*dx, y - t*dy, k), |
| 63 | I2 = (float)dest.linear_atXY(x + (1 - t)*dx,y + (1 - t)*dy,k); |
| 64 | warp(x,y,k) = (unsigned char)((1 - t)*I1 + t*I2); |
| 65 | } else cimg_forXYC(warp,x,y,k) { |
| 66 | 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); |
| 67 | warp(x,y,k) = (unsigned char)I1; |
| 68 | } |
| 69 | if (disp) visu.draw_image(2*src.width(),warp).display(disp.resize().wait(30)); |
| 70 | if (filename && *filename && (imode || (int)iteration<nb)) { |
| 71 | std::fprintf(stderr,"\r > frame %d ",iteration); |
| 72 | warp.save(filename,iteration); |
| 73 | } |
| 74 | t+=1.0f/nb; |
| 75 | if (t<0) { t = 0; nb = -nb; } |
| 76 | if (t>1) { t = 1; nb = -nb; if (filename && *filename) std::exit(0); } |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // optflow() : multiscale version of the image registration algorithm |
| 81 | //----------- |
| 82 | CImg<> optflow(const CImg<>& source, const CImg<>& target, |
| 83 | const float smoothness, const float precision, const unsigned int nb_scales, CImgDisplay& disp) { |
| 84 | const unsigned int iteration_max = 100000; |
| 85 | const float _precision = (float)std::pow(10.0,-(double)precision); |
| 86 | const CImg<> |
| 87 | src = source.get_resize(target,3).normalize(0,1), |
| 88 | dest = target.get_normalize(0,1); |
| 89 | CImg<> U; |
| 90 | |
| 91 | const unsigned int _nb_scales = nb_scales>0?nb_scales: |
| 92 | (unsigned int)(2*std::log((double)(std::max(src.width(),src.height())))); |
| 93 | for (int scale = _nb_scales - 1; scale>=0; --scale) { |
| 94 | const float factor = (float)std::pow(1.5,(double)scale); |
| 95 | const unsigned int |
| 96 | _sw = (unsigned int)(src.width()/factor), sw = _sw?_sw:1, |
| 97 | _sh = (unsigned int)(src.height()/factor), sh = _sh?_sh:1; |
| 98 | const CImg<> |
| 99 | I1 = src.get_resize(sw,sh,1,-100,2), |
| 100 | I2 = dest.get_resize(I1,2); |
| 101 | std::fprintf(stderr," * Scale %d\n",scale); |
| 102 | if (U) (U*=1.5f).resize(I2.width(),I2.height(),1,-100,3); |
| 103 | else U.assign(I2.width(),I2.height(),1,2,0); |
| 104 | |
| 105 | float dt = 2, energy = cimg::type<float>::max(); |
| 106 | const CImgList<> dI = I2.get_gradient(); |
| 107 | |
| 108 | for (unsigned int iteration = 0; iteration<iteration_max; ++iteration) { |
| 109 | std::fprintf(stderr,"\r- Iteration %d - E = %g",iteration,energy); std::fflush(stderr); |
| 110 | float _energy = 0; |
| 111 | cimg_for3XY(U,x,y) { |
| 112 | const float |
| 113 | X = x + U(x,y,0), |
| 114 | Y = y + U(x,y,1); |
| 115 | |
| 116 | float deltaI = 0; |
| 117 | cimg_forC(I2,c) deltaI+=(float)(I1(x,y,c) - I2.linear_atXY(X,Y,c)); |
| 118 | |
| 119 | float _energy_regul = 0; |
| 120 | cimg_forC(U,c) { |
| 121 | const float |
| 122 | Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)), |
| 123 | Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)), |
| 124 | Uxx = U(_n1x,y,c) + U(_p1x,y,c), |
| 125 | Uyy = U(x,_n1y,c) + U(x,_p1y,c); |
| 126 | U(x,y,c) = (float)( U(x,y,c) + dt*(deltaI*dI[c].linear_atXY(X,Y) + |
| 127 | smoothness* ( Uxx + Uyy )))/(1 + 4*smoothness*dt); |
| 128 | _energy_regul+=Ux*Ux + Uy*Uy; |
| 129 | } |
| 130 | _energy+=deltaI*deltaI + smoothness*_energy_regul; |
| 131 | } |
| 132 | const float d_energy = (_energy - energy)/(sw*sh); |
| 133 | if (d_energy<=0 && -d_energy<_precision) break; |
| 134 | if (d_energy>0) dt*=0.5f; |
| 135 | energy = _energy; |
| 136 | if (disp) disp.resize(); |
| 137 | if (disp && disp.is_closed()) std::exit(0); |
| 138 | if (disp && !(iteration%300)) { |
| 139 | const unsigned char white[] = { 255,255,255 }; |
| 140 | CImg<unsigned char> tmp = I1.get_warp(U,true,true,1).normalize(0,200); |
| 141 | tmp.resize(disp.width(),disp.height()).draw_quiver(U,white,0.7f,15,-14,true).display(disp); |
| 142 | } |
| 143 | } |
| 144 | std::fprintf(stderr,"\n"); |
| 145 | } |
| 146 | return U; |
| 147 | } |
| 148 | |
| 149 | /*------------------------ |
| 150 | |
| 151 | Main function |
| 152 | |
| 153 | ------------------------*/ |
| 154 | |
| 155 | int main(int argc,char **argv) { |
| 156 | |
| 157 | // Read command line parameters |
| 158 | cimg_usage("Compute an optical flow between two 2D images, and create a warped animation"); |
| 159 | const char |
| 160 | *name_i1 = cimg_option("-i",cimg_imagepath "sh0r.pgm","Input Image 1 (Destination)"), |
| 161 | *name_i2 = cimg_option("-i2",cimg_imagepath "sh1r.pgm","Input Image 2 (Source)"), |
| 162 | *name_o = cimg_option("-o",(const char*)NULL,"Output 2D flow (inrimage)"), |
| 163 | *name_seq = cimg_option("-o2",(const char*)NULL,"Output Warping Sequence"); |
| 164 | const float |
| 165 | smoothness = cimg_option("-s",0.1f,"Flow Smoothness"), |
| 166 | precision = cimg_option("-p",6.0f,"Convergence precision"); |
| 167 | const unsigned int |
| 168 | nb = cimg_option("-n",40,"Number of warped frames"), |
| 169 | nb_scales = cimg_option("-scale",0,"Number of scales (0=auto)"); |
| 170 | const bool |
| 171 | normalize = cimg_option("-equalize",true,"Histogram normalization of the images"), |
| 172 | morph = cimg_option("-m",true,"Morphing mode"), |
| 173 | imode = cimg_option("-c",true,"Complete interpolation (or last frame is missing)"), |
| 174 | dispflag = !cimg_option("-novisu",false,"Visualization"); |
| 175 | |
| 176 | // Init images and display |
| 177 | std::fprintf(stderr," - Init images.\n"); |
| 178 | const CImg<> |
| 179 | src(name_i1), |
| 180 | dest(CImg<>(name_i2).resize(src,3)), |
| 181 | src_blur = normalize?src.get_blur(0.5f).equalize(256):src.get_blur(0.5f), |
| 182 | dest_blur = normalize?dest.get_blur(0.5f).equalize(256):dest.get_blur(0.5f); |
| 183 | |
| 184 | CImgDisplay disp; |
| 185 | if (dispflag) { |
| 186 | unsigned int w = src.width(), h = src.height(); |
| 187 | const unsigned int dmin = std::min(w,h), minsiz = 512; |
| 188 | if (dmin<minsiz) { w=w*minsiz/dmin; h=h*minsiz/dmin; } |
| 189 | const unsigned int dmax = std::max(w,h), maxsiz = 1024; |
| 190 | if (dmax>maxsiz) { w=w*maxsiz/dmax; h=h*maxsiz/dmax; } |
| 191 | disp.assign(w,h,"Estimated Motion",0); |
| 192 | } |
| 193 | |
| 194 | // Run Motion estimation algorithm |
| 195 | std::fprintf(stderr," - Compute optical flow.\n"); |
| 196 | const CImg<> U = optflow(src_blur,dest_blur,smoothness,precision,nb_scales,disp); |
| 197 | if (name_o) U.save(name_o); |
| 198 | U.print("Computed flow"); |
| 199 | |
| 200 | // Do morphing animation |
| 201 | std::fprintf(stderr," - Create warped animation.\n"); |
| 202 | CImgDisplay disp2; |
| 203 | if (dispflag) { |
| 204 | unsigned int w = src.width(), h = src.height(); |
| 205 | const unsigned int dmin = std::min(w,h), minsiz = 100; |
| 206 | if (dmin<minsiz) { w = w*minsiz/dmin; h=h*minsiz/dmin; } |
| 207 | const unsigned int dmax = std::max(w,h), maxsiz = 1024/3; |
| 208 | if (dmax>maxsiz) { w = w*maxsiz/dmax; h=h*maxsiz/dmax; } |
| 209 | disp2.assign(3*w,h,"Source/Destination images and Motion animation",0); |
| 210 | } |
| 211 | |
| 212 | animate_warp(src.get_normalize(0,255),dest.get_normalize(0,255),U,morph,imode,name_seq,nb,disp2); |
| 213 | |
| 214 | std::exit(0); |
| 215 | return 0; |
| 216 | } |