CVD 0.8
|
00001 #ifndef CVD_ESM_H_ 00002 #define CVD_ESM_H_ 00003 00004 #include <TooN/wls.h> 00005 #include <TooN/so3.h> 00006 00007 #include <cvd/image.h> 00008 #include <cvd/image_io.h> 00009 #include <cvd/vector_image_ref.h> 00010 #include <cvd/vision.h> 00011 00012 #include <vector> 00013 #include <sstream> 00014 #include <iomanip> 00015 00016 namespace CVD { 00017 00018 static int DEBUG_ESM = 0; 00019 00054 00057 struct ESMResult { 00058 double error; 00059 double delta; 00060 int pixels; 00061 int iterations; 00062 00064 double RMSE() const { return std::sqrt(error/std::max(1,pixels)); } 00065 }; 00066 00067 inline std::ostream & operator<<(std::ostream & out, const ESMResult & r ){ 00068 out << r.error << "\t" << r.pixels << "\t" << r.RMSE() << "\t" << "\t" << r.delta << "\t" << r.iterations; 00069 return out; 00070 } 00071 00072 namespace Internal { // forward declaration of some internal functions 00073 // intersects the line AB with the rectangle rect, returns the interval for the parameter t 00074 // for X = A + (B-A)*t 00075 // A, B in homogeneous coordinates, yielding the correct perspective interpolation for t 00076 inline TooN::Vector<2> getBounds(const TooN::Vector<2> & rect, const TooN::Vector<3> & A, const TooN::Vector<3> & B); 00077 inline std::vector<TooN::Vector<2,int> > getBounds( const TooN::Vector<2> & rect, const ImageRef & in, const TooN::Matrix<3> & H ); 00078 inline std::vector<TooN::Vector<2,int> > erode( const std::vector<TooN::Vector<2,int> > & in ); 00079 00080 // H takes pixels in out to pixels in in as a 2D homography (3x3 matrix) 00081 template <typename T> inline std::vector<TooN::Vector<2,int> > transform_perspective( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H); 00082 // H takes pixels in out to pixels in in as a 2D affine transformation (3x3 matrix) 00083 template <typename T> inline std::vector<TooN::Vector<2,int> > transform_affine( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H); 00084 // H takes pixels in out to pixels in in as a 2D translation only (stored in the 3x3 matrix) 00085 // This is extra optimized to use the constant mixing factors in the bi-linear interpolation 00086 template <typename T> inline std::vector<TooN::Vector<2,int> > transform_translation( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H); 00087 // automatically selects the right transform implementation based on the properties of H 00088 template <typename T> inline std::vector<TooN::Vector<2,int> > transform( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H); 00089 00090 // calculates the gradient of a one component image within the given bounds only. The 00091 // function uses central differences, but does not divide by 2! 00092 template<typename GRADIENT, typename IMAGE> inline Image<GRADIENT> gradient( const SubImage<IMAGE> & img, const std::vector<TooN::Vector<2,int> > & bounds ); 00093 // calculates the gradient of a one component image directly. The 00094 // function uses central differences, but does not divide by 2! 00095 template<typename GRADIENT, typename IMAGE> inline Image<GRADIENT> gradient( const SubImage<IMAGE> & img ); 00096 00109 template <typename TRANSFORM, typename APPEARANCE, typename IMAGE, typename GRADIENT> 00110 inline ESMResult esm_opt( TRANSFORM & T, APPEARANCE & A, const SubImage<IMAGE> & templateImage, const SubImage<GRADIENT> & templateGradient, const SubImage<IMAGE> & target, const int max_iterations = 40, const double min_delta = 1e-8, const double max_RMSE = 1.0 ); 00111 } // namespace Internal 00112 00125 template<int PARAMS> 00126 class Homography { 00127 public: 00128 static const int dimensions = PARAMS; 00129 00130 Homography() : H(TooN::Identity) {} 00131 00132 template <int R, int C, typename P, typename B> 00133 Homography( const TooN::Matrix<R, C, P, B> & h) : H(h) {} 00134 00135 const TooN::Matrix<3> & get_matrix() const { return H; } 00136 TooN::Matrix<3> & get_matrix() { return H; } 00137 const TooN::Vector<PARAMS> & get_jacobian( const TooN::Vector<2> & p, const TooN::Vector<2> & grad ) const { 00138 switch(PARAMS){ 00139 case 8: 00140 // 0 0 p[1] 00141 J[7] = -p[1]*(grad*p); // TooN::makeVector(-p[1]*p[0], -p[1]*p[1]); 00142 case 7: 00143 // 0 0 p[0] 00144 J[6] = -p[0]*(grad*p); // TooN::makeVector(-p[0]*p[0], -p[0]*p[1]); 00145 case 6: 00146 // p[1] p[0] 0 00147 J[5] = grad[0]*p[1] + grad[1]*p[0];// TooN::makeVector(p[1], p[0]); 00148 case 5: 00149 // p[0] -p[1] 0 00150 J[4] = grad[0]*p[0] - grad[1]*p[1]; // TooN::makeVector(p[0], -p[1]); 00151 case 4: 00152 // p[0] p[1] -2 00153 J[3] = 3 * (grad * p); //TooN::makeVector(3*p[0], 3*p[1]); 00154 case 3: 00155 // -p[1] p[0] 0 00156 J[2] = - grad[0]*p[1] + grad[1]*p[0] ; // TooN::makeVector(-p[1], p[0]); 00157 case 2: 00158 J[1] = grad[1]; // TooN::makeVector(0, 1); 00159 case 1: 00160 J[0] = grad[0]; // TooN::makeVector(1, 0); 00161 break; 00162 default: 00163 assert(false); 00164 } 00165 return J; 00166 } 00167 00168 void update( const TooN::Vector<PARAMS> & v ){ 00169 TooN::Matrix<3> G = TooN::Zeros; 00170 switch(PARAMS){ 00171 case 8: 00172 G(2,1) = v[7]; 00173 case 7: 00174 G(2,0) = v[6]; 00175 case 6: 00176 G(0,1) = v[5]; 00177 G(1,0) = v[5]; 00178 case 5: 00179 G(0,0) = v[4]; 00180 G(1,1) = -v[4]; 00181 case 4: 00182 G(0,0) += v[3]; 00183 G(1,1) += v[3]; 00184 G(2,2) += -2*v[3]; 00185 case 3: 00186 G(0,1) -= v[2]; 00187 G(1,0) += v[2]; 00188 case 2: 00189 G(1,2) = v[1]; 00190 case 1: 00191 G(0,2) = v[0]; 00192 break; 00193 default: assert(false); 00194 } 00195 H = H * TooN::exp(-G); 00196 } 00197 00198 protected: 00199 TooN::Matrix<3> H; 00200 mutable TooN::Vector<PARAMS> J; 00201 }; 00202 00209 template<int PARAMS> 00210 class HomographyPrefix : public Homography<PARAMS> { 00211 public: 00212 static const int dimensions = PARAMS; 00213 00214 HomographyPrefix() : Pre(TooN::Identity) {} 00215 00216 template <int R, int C, typename P, typename B> 00217 HomographyPrefix( const TooN::Matrix<R, C, P, B> & p ) : Pre(p) { 00218 const TooN::Matrix<3> id = TooN::Identity; 00219 H = TooN::gaussian_elimination(Pre, id); 00220 } 00221 00222 template <int R, int C, typename P, typename B, int R2, int C2, typename P2, typename B2> 00223 HomographyPrefix( const TooN::Matrix<R, C, P, B> & h, const TooN::Matrix<R2, C2, P2, B2> & p) : Pre(p) { 00224 const TooN::Matrix<3> id = TooN::Identity; 00225 H = h * TooN::gaussian_elimination(Pre, id); 00226 } 00227 00228 const TooN::Matrix<3> get_matrix() const { return H * Pre; } 00229 00230 const TooN::Vector<PARAMS> & get_jacobian( const TooN::Vector<2> & x, const TooN::Vector<2> & grad ) const { 00231 if( PARAMS > 2){ 00232 const TooN::Vector<2> p = TooN::project(Pre * TooN::unproject(x)); 00233 return Homography<PARAMS>::get_jacobian(p, grad); 00234 } 00235 return Homography<PARAMS>::get_jacobian(x, grad); 00236 } 00237 00238 protected: 00239 using Homography<PARAMS>::H; 00240 TooN::Matrix<3> Pre; 00241 }; 00242 00247 class CameraRotation { 00248 public: 00249 static const int dimensions = 3; 00250 00251 CameraRotation() {} 00252 00253 template <typename P, typename B> 00254 CameraRotation( const TooN::Vector<4, P, B> & camera_params, const TooN::SO3<> & init = TooN::SO3<>() ) 00255 : R(init) 00256 { 00257 set_camera(camera_params); 00258 } 00259 00260 template <typename P, typename B> 00261 void set_camera( const TooN::Vector<4, P, B> & camera_params ){ 00262 k = camera_params; 00263 K = TooN::Identity; 00264 K(0,0) = camera_params[0]; 00265 K(1,1) = camera_params[1]; 00266 K(0,2) = camera_params[2]; 00267 K(1,2) = camera_params[3]; 00268 00269 kinv = TooN::makeVector( 1/camera_params[0], 1/camera_params[1], -camera_params[2]/camera_params[0], -camera_params[3]/camera_params[1]); 00270 Kinv = TooN::Identity; 00271 Kinv(0,0) = kinv[0]; 00272 Kinv(1,1) = kinv[1]; 00273 Kinv(0,2) = kinv[2]; 00274 Kinv(1,2) = kinv[3]; 00275 } 00276 00277 const TooN::Matrix<3> get_matrix() const { return K*R*Kinv; } 00278 00279 const TooN::Vector<dimensions> & get_jacobian( const TooN::Vector<2> & p, const TooN::Vector<2> & grad ) const { 00280 // this implements grad * proj * K * G_i * Kinv * p; 00281 const TooN::Vector<3> pW = TooN::makeVector(kinv[0] * p[0] + kinv[2], kinv[1] * p[1] + kinv[3], 1); 00282 const TooN::Vector<3> J_pK = TooN::makeVector(grad[0] * k[0], grad[1] * k[1], grad * (k.slice<2,2>() - p)); 00283 J[0] = J_pK * TooN::SO3<>::generator_field(0, pW); 00284 J[1] = J_pK * TooN::SO3<>::generator_field(1, pW); 00285 J[2] = J_pK * TooN::SO3<>::generator_field(2, pW); 00286 return J; 00287 } 00288 00289 void update( const TooN::Vector<dimensions> & v ){ 00290 R = R * TooN::SO3<>::exp(-v); 00291 } 00292 00293 const TooN::SO3<> & get_rotation() const { return R; } 00294 TooN::SO3<> & get_rotation() { return R; } 00295 00296 protected: 00297 TooN::Matrix<3> K, Kinv; 00298 TooN::Vector<4> k, kinv; 00299 TooN::SO3<> R; 00300 mutable TooN::Vector<dimensions> J; 00301 }; 00302 00305 class StaticAppearance { 00306 public: 00307 static const int dimensions = 0; 00308 template <typename PIXEL> double difference( const PIXEL & warped, const PIXEL & templatePixel ) const { 00309 return warped - templatePixel; 00310 } 00311 template <typename PIXEL> std::pair<double, TooN::Vector<dimensions> > difference_jacobian( const PIXEL & warped, const PIXEL & templatePixel ) const { 00312 return std::make_pair( difference(warped, templatePixel), TooN::Vector<dimensions>()); 00313 } 00314 TooN::Vector<2> image_jacobian( const TooN::Vector<2> & gradWarped, const TooN::Vector<2> & gradTemplate ) const { 00315 return (gradWarped + gradTemplate) * 0.25; 00316 } 00317 void update( const TooN::Vector<dimensions> & d ) {} 00318 }; 00319 00323 class OffsetAppearance { 00324 public: 00325 static const int dimensions = 1; 00326 template <typename PIXEL> double difference( const PIXEL & warped, const PIXEL & templatePixel ) const { 00327 return warped - templatePixel - offset; 00328 } 00329 template <typename PIXEL> std::pair<double, TooN::Vector<dimensions> > difference_jacobian( const PIXEL & warped, const PIXEL & templatePixel ) const { 00330 return std::make_pair( difference(warped, templatePixel), TooN::makeVector(1)); 00331 } 00332 TooN::Vector<2> image_jacobian( const TooN::Vector<2> & gradWarped, const TooN::Vector<2> & gradTemplate ) const { 00333 return (gradWarped + gradTemplate) * 0.25; 00334 } 00335 void update( const TooN::Vector<dimensions> & d ) { 00336 offset += d[0]; 00337 } 00338 00339 OffsetAppearance() : offset(0) {} 00340 double offset; 00341 }; 00342 00347 class BlurAppearance { 00348 public: 00349 static const int dimensions = 0; 00350 template <typename PIXEL> double difference( const PIXEL & warped, const PIXEL & templatePixel ) const { 00351 return warped - templatePixel; 00352 } 00353 template <typename PIXEL> std::pair<double, TooN::Vector<dimensions> > difference_jacobian( const PIXEL & warped, const PIXEL & templatePixel ) const { 00354 return std::make_pair( difference(warped, templatePixel), TooN::Vector<dimensions>()); 00355 } 00356 TooN::Vector<2> image_jacobian( const TooN::Vector<2> & gradWarped, const TooN::Vector<2> & gradTemplate ) const { 00357 return (gradWarped + 0.5*gradTemplate) * 0.25; 00358 } 00359 void update( const TooN::Vector<dimensions> & d ) { 00360 } 00361 }; 00362 00363 #if 0 00364 00365 class LinearAppearance { 00366 static const int dimensions = 2; 00367 00368 template <typename PIXEL> double difference( const PIXEL & templatePixel, const PIXEL & warped ) const { return warped - templatePixel*scale - offset; } 00369 Vector<2> image_jacobian( const Vector<2> & gradTemplate, const Vector<2> & gradImage ) const { return gradTemplate + gradImage; } 00370 Vector<dimensions> get_jacobian( const Vector<2> & point ) { return TooN::makeVector(1.0); } 00371 void update( const Vector<dimensions> & d ) { offset += d[0]; } 00372 00373 double offset; 00374 double scale; 00375 }; 00376 #endif 00377 00378 template <typename P> 00379 inline TooN::Matrix<3,3,P> scaleHomography( const TooN::Matrix<3,3,P> & H, const P & f ){ 00380 const TooN::Vector<3,P> s = TooN::makeVector<P>(1/f, 1/f, 1); 00381 const TooN::Vector<3,P> is = TooN::makeVector<P>(f, f, 1); 00382 return is.as_diagonal() * H * s.as_diagonal(); 00383 } 00384 00385 #if 0 00386 00389 template<int PARAMS, typename APPEARANCE, typename IMAGE> 00390 inline TooN::Matrix<3> inter_frame_homography( const SubImage<IMAGE> & from, const SubImage<IMAGE> & to, const TooN::Matrix<3> & init = TooN::Identity, ESMResult * const res = NULL){ 00391 TooN::Matrix<3> prefix = TooN::Identity; 00392 #if 1 00393 prefix(0,2) = -from.size().x/2; 00394 prefix(1,2) = -from.size().y/2; 00395 #else 00396 prefix(0,0) = 2.0/from.size().x; 00397 prefix(1,1) = 2.0/from.size().y; 00398 prefix(0,2) = -1; 00399 prefix(1,2) = -1; 00400 cout << prefix << endl; 00401 #endif 00402 00403 HomographyPrefix<PARAMS> H(init, prefix); 00404 APPEARANCE appearance; 00405 ESMTransform<HomographyPrefix<PARAMS>, APPEARANCE> T(H, appearance); 00406 00407 ESMResult r = esm_opt(T, from, to, 40, 1e-2); 00408 00409 if(res != NULL) 00410 *res = r; 00411 return T.transform.get_matrix(); 00412 } 00413 00414 #endif 00415 00419 template <typename TRANSFORM, typename APPEARANCE, typename IMAGE = CVD::byte, typename GRADIENT = TooN::Vector<2, typename Pixel::traits<IMAGE>::wider_type> > 00420 class ESMEstimator { 00421 public: 00422 TRANSFORM transform; 00423 APPEARANCE appearance; 00424 00425 Image<IMAGE> templ; 00426 Image<GRADIENT> templGradient; 00427 00428 int max_iterations; 00429 double min_delta; 00430 double max_RMSE; 00431 00432 ESMResult result; 00433 00434 ESMEstimator() : max_iterations(40), min_delta(1e-5), max_RMSE(1e-2) {} 00435 ESMEstimator( const Image<IMAGE> & t, const Image<GRADIENT> & g ) : templ(t), templGradient(g), max_iterations(40), min_delta(1e-5), max_RMSE(1e-2) {} 00436 ESMEstimator( const Image<IMAGE> & t) : templ(t), max_iterations(40), min_delta(1e-5), max_RMSE(1e-2) { 00437 templGradient = Internal::gradient<IMAGE, GRADIENT>(templ); 00438 } 00439 ESMEstimator( const SubImage<IMAGE> & t) : max_iterations(40), min_delta(1e-5), max_RMSE(1e-2) { 00440 templ.copy_from(t); 00441 templGradient = Internal::gradient<IMAGE, GRADIENT>(templ); 00442 } 00443 00444 void set_image( const Image<IMAGE> & t, const Image<GRADIENT> & g ){ 00445 assert(t.size() == g.size()); 00446 templ = t; 00447 templGradient = g; 00448 } 00449 00450 void set_image( const Image<IMAGE> & t ){ 00451 templ = t; 00452 templGradient = Internal::gradient<GRADIENT>(templ); 00453 } 00454 00455 void set_image( const SubImage<IMAGE> & t ){ 00456 Image<IMAGE> temp; 00457 temp.copy_from(t); 00458 set_image(temp); 00459 } 00460 00461 void reset() { 00462 transform = TRANSFORM(); 00463 appearance = APPEARANCE(); 00464 } 00465 00466 const ESMResult & optimize( const SubImage<IMAGE> & to ){ 00467 return result = Internal::esm_opt( transform, appearance, templ, templGradient, to, max_iterations, min_delta, max_RMSE); 00468 } 00469 00470 const ESMResult & optimize( const SubImage<IMAGE> & from, const SubImage<IMAGE> & to ){ 00471 Image<GRADIENT> fromGradient = Internal::gradient<IMAGE, GRADIENT>(from); 00472 return optimize( from, fromGradient, to ); 00473 } 00474 00475 const ESMResult & optimize( const SubImage<IMAGE> & from, const SubImage<GRADIENT> & fromGradient, const SubImage<IMAGE> & to ){ 00476 return result = Internal::esm_opt( transform, appearance, from, fromGradient, to, max_iterations, min_delta, max_RMSE); 00477 } 00478 00479 const ESMResult & get_result() const { return result; } 00480 }; 00481 00484 template <typename APPEARANCE, typename IMAGE = CVD::byte, typename GRADIENT = TooN::Vector<2, typename Pixel::traits<IMAGE>::wider_type> > 00485 class RotationEstimator : public ESMEstimator<CameraRotation, APPEARANCE, IMAGE, GRADIENT> { 00486 public: 00487 using ESMEstimator<CameraRotation, APPEARANCE, IMAGE, GRADIENT>::transform; 00488 using ESMEstimator<CameraRotation, APPEARANCE, IMAGE, GRADIENT>::appearance; 00489 00490 RotationEstimator( const TooN::Vector<4> & cam_params, const TooN::SO3<> & init = TooN::SO3<>() ) { 00491 transform.set_camera( cam_params ); 00492 transform.get_rotation() = init; 00493 } 00494 00495 void reset() { 00496 transform.get_rotation() = TooN::SO3<>(); 00497 appearance = APPEARANCE(); 00498 } 00499 00500 }; 00501 00502 namespace Internal { 00503 00504 // intersects the line AB with the rectangle rect, returns the interval for the parameter t 00505 // for X = A + (B-A)*t 00506 // A, B in homogeneous coordinates, yielding the correct perspective interpolation for t 00507 inline TooN::Vector<2> getBounds(const TooN::Vector<2> & rect, const TooN::Vector<3> & A, const TooN::Vector<3> & B){ 00508 const TooN::Vector<3> dir = A - B; 00509 TooN::Vector<2> interval = TooN::makeVector(0,1); 00510 00511 TooN::Matrix<4,3> lines; 00512 lines[0] = TooN::makeVector(1,0,0); 00513 lines[1] = TooN::makeVector(-1,0,rect[0]); 00514 lines[2] = TooN::makeVector(0,1,0); 00515 lines[3] = TooN::makeVector(0,-1,rect[1]); 00516 00517 const TooN::Vector<4> hitA = lines * A; 00518 const TooN::Vector<4> hitB = lines * B; 00519 00520 for(int i = 0; i < 4; ++i){ 00521 if(hitA[i] <=0 && hitB[i] <= 0) 00522 return TooN::makeVector(0,0); 00523 if(hitA[i] * hitB[i] < 0){ 00524 const double t = hitA[i] / (lines[i] * dir); 00525 if(hitA[i] < 0){ 00526 interval[0] = std::max(t, interval[0]); 00527 } else { 00528 interval[1] = std::min(t, interval[1]); 00529 } 00530 } 00531 } 00532 00533 if(interval[0] > interval[1]) 00534 return TooN::makeVector(0,0); 00535 return interval; 00536 } 00537 00538 inline std::vector<TooN::Vector<2,int> > getBounds( const TooN::Vector<2> & rect, const ImageRef & in, const TooN::Matrix<3> & H ){ 00539 std::vector<TooN::Vector<2,int> > bounds(in.y); 00540 for(int y = 0; y < in.y; ++y){ 00541 // map the current scan line 00542 const TooN::Vector<3> A = H * TooN::makeVector(0,y,1); 00543 const TooN::Vector<3> B = H * TooN::makeVector(in.x-1, y, 1); 00544 // determine valid sample interval 00545 const TooN::Vector<2> b = getBounds(rect, A, B) * (in.x-1); 00546 bounds[y] = TooN::makeVector<int>(ceil(b[0]), floor(b[1])+1); 00547 } 00548 return bounds; 00549 } 00550 00551 inline std::vector<TooN::Vector<2,int> > erode( const std::vector<TooN::Vector<2,int> > & in ){ 00552 using std::min; using std::max; 00553 std::vector<TooN::Vector<2,int> > out(in.size()); 00554 out.front() = out.back() = TooN::makeVector(0, 0); 00555 for(unsigned i = 1; i < in.size()-1; ++i){ 00556 out[i][0] = max(in[i-1][0], max(in[i][0] + 1, in[i+1][0])); // the right most of all three 00557 out[i][1] = min(in[i-1][1], min(in[i][1] - 1, in[i+1][1])); // the left most of all three 00558 if(out[i][0] > out[i][1]) // if its empty, then mark as (0,0) 00559 out[i][0] = out[i][1] = 0; 00560 } 00561 return out; 00562 } 00563 00564 // H takes pixels in out to pixels in in as a 2D homography (3x3 matrix) 00565 template <typename T> 00566 inline std::vector<TooN::Vector<2,int> > transform_perspective( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H){ 00567 const ImageRef & size = out.size(); 00568 const TooN::Vector<2> insize = vec(in.size() - ImageRef(1,1)); 00569 const TooN::Vector<3> across = H.T()[0]; 00570 const TooN::Vector<3> down = H.T()[1]; 00571 TooN::Vector<3> base = H.T()[2]; 00572 00573 const std::vector<TooN::Vector<2, int> > bounds = getBounds(insize, size, H); 00574 00575 for(int y = 0; y < size.y; ++y){ 00576 TooN::Vector<3> X = base + bounds[y][0] * across; 00577 T * data = out[y] + bounds[y][0]; 00578 for(int x = bounds[y][0]; x < bounds[y][1]; ++x, X += across, ++data){ 00579 const TooN::DefaultPrecision inv = 1/X[2]; 00580 sample(in, X[0] * inv, X[1] * inv, *data ); 00581 } 00582 base += down; // next line 00583 } 00584 00585 return bounds; 00586 } 00587 00588 // H takes pixels in out to pixels in in as a 2D affine transformation (3x3 matrix) 00589 template <typename T> 00590 inline std::vector<TooN::Vector<2,int> > transform_affine( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H){ 00591 const ImageRef & size = out.size(); 00592 const TooN::Vector<2> insize = vec(in.size() - ImageRef(1,1)); 00593 const TooN::Vector<2> across = H.T()[0].slice<0,2>(); 00594 const TooN::Vector<2> down = H.T()[1].slice<0,2>(); 00595 TooN::Vector<2> base = H.T()[2].slice<0,2>(); 00596 00597 const std::vector<TooN::Vector<2, int> > bounds = getBounds(insize, size, H); 00598 00599 for(int y = 0; y < size.y; ++y){ 00600 TooN::Vector<2> X = base + bounds[y][0] * across; 00601 T * data = out[y] + bounds[y][0]; 00602 for(int x = bounds[y][0]; x < bounds[y][1]; ++x, X += across, ++data){ 00603 sample(in, X[0], X[1], *data ); 00604 } 00605 base += down; // next line 00606 } 00607 00608 return bounds; 00609 } 00610 00611 // H takes pixels in out to pixels in in as a 2D translation only (stored in the 3x3 matrix) 00612 // This is extra optimized to use the constant mixing factors in the bi-linear interpolation 00613 template <typename T> 00614 inline std::vector<TooN::Vector<2,int> > transform_translation( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H){ 00615 const ImageRef & size = out.size(); 00616 const TooN::Vector<2> insize = vec(in.size() - ImageRef(1,1)); 00617 const TooN::Vector<2> across = H.T()[0].slice<0,2>(); 00618 const TooN::Vector<2> down = H.T()[1].slice<0,2>(); 00619 TooN::Vector<2> base = H.T()[2].slice<0,2>(); 00620 00621 std::vector<TooN::Vector<2, int> > bounds = getBounds(insize, size, H); 00622 00623 const double fx = H(0,2) - floor(H(0,2)); 00624 const double fy = H(1,2) - floor(H(1,2)); 00625 const double ul = fx * fy; 00626 const double ur = (1-fx) * fy; 00627 const double ll = fx * (1-fy); 00628 const double lr = (1-fx)*(1-fy); 00629 00630 for(int y = 0; y < size.y; ++y){ 00631 const TooN::Vector<2> & interval = bounds[y]; // determine valid sample interval 00632 int x = interval[0]; 00633 T * data = out[y] + x; 00634 TooN::Vector<2> X = base + x * across; // then sample 00635 for(; x < interval[1]; ++x, ++data, X += across){ 00636 sample(in, X[0], X[1], *data ); 00637 } 00638 base += down; // next line 00639 } 00640 00641 return bounds; 00642 } 00643 00644 template <typename T> 00645 inline std::vector<TooN::Vector<2,int> > transform( SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H){ 00646 const double perspective = H(2,0)*H(2,0) + H(2,1)*H(2,1); 00647 if(perspective < 1e-10) 00648 return transform_affine(out, in, H); 00649 return transform_perspective(out, in, H); 00650 } 00651 00652 template<typename GRADIENT, typename IMAGE> 00653 inline Image<GRADIENT> gradient( const SubImage<IMAGE> & img, const std::vector<TooN::Vector<2,int> > & bounds ){ 00654 assert(bounds.size() == unsigned(img.size().y)); 00655 Image<GRADIENT> grad(img.size()); 00656 for(int y = 1; y < img.size().y-1; ++y){ 00657 for(int x = bounds[y][0]; x < bounds[y][1]; ++x){ 00658 grad[y][x][0] = img[y][x+1] - img[y][x-1]; 00659 grad[y][x][1] = img[y+1][x] - img[y-1][x]; 00660 } 00661 } 00662 return grad; 00663 } 00664 00665 template<typename GRADIENT, typename IMAGE> 00666 inline Image<GRADIENT> gradient( const SubImage<IMAGE> & img ){ 00667 Image<GRADIENT> grad(img.size()); 00668 for(int y = 1; y < img.size().y-1; ++y){ 00669 for(int x = 1; x < img.size().x-1; ++x){ 00670 grad[y][x][0] = img[y][x+1] - img[y][x-1]; 00671 grad[y][x][1] = img[y+1][x] - img[y-1][x]; 00672 } 00673 } 00674 return grad; 00675 } 00676 00677 template <typename TRANSFORM, typename APPEARANCE, typename IMAGE, typename GRADIENT> 00678 inline ESMResult esm_opt( TRANSFORM & T, APPEARANCE & A, const SubImage<IMAGE> & templateImage, const SubImage<GRADIENT> & templateGradient, const SubImage<IMAGE> & target, const int max_iterations = 40, const double min_delta = 1e-8, const double max_RMSE = 1.0 ){ 00679 assert(templateImage.size() == templateGradient.size()); 00680 00681 const int dimensions = TRANSFORM::dimensions+APPEARANCE::dimensions; 00682 00683 Image<IMAGE> warped(templateImage.size()); 00684 00685 TooN::WLS<dimensions> wls; 00686 00687 ESMResult result = {1e100, 1e100, 0, 0}; 00688 00689 // first warp here to be able to compare error before and after 00690 std::vector<TooN::Vector<2,int> > mask = Internal::transform(warped, target, T.get_matrix()); 00691 mask = Internal::erode(mask); 00692 00693 do { 00694 // get the gradient for the warped image 00695 Image<GRADIENT> grad_warped = Internal::gradient<GRADIENT>(warped, mask); 00696 00697 // create the least squares system 00698 wls.clear(); 00699 TooN::Vector<dimensions> J; 00700 for(unsigned y = 0; y < mask.size(); ++y){ 00701 for(int x = mask[y][0]; x < mask[y][1]; ++x){ 00702 std::pair<double, TooN::Vector<APPEARANCE::dimensions> > da = A.difference_jacobian( warped[y][x], templateImage[y][x]); 00703 J.template slice<0,TRANSFORM::dimensions>() = T.get_jacobian(TooN::makeVector(x,y), A.image_jacobian(grad_warped[y][x], templateGradient[y][x])); 00704 J.template slice<TRANSFORM::dimensions, APPEARANCE::dimensions>() = da.second; 00705 wls.add_mJ(da.first, J); 00706 } 00707 } 00708 00709 // solve and update 00710 wls.compute(); 00711 T.update(wls.get_mu().template slice<0,TRANSFORM::dimensions>()); 00712 A.update(wls.get_mu().template slice<TRANSFORM::dimensions, APPEARANCE::dimensions>()); 00713 ++result.iterations; 00714 00715 // compute new warp to calculate new error 00716 mask = Internal::transform(warped, target, T.get_matrix()); 00717 mask = Internal::erode(mask); 00718 00719 // compute error after update 00720 result.error = 0; 00721 // and number of pixels for RMS computation 00722 result.pixels = 0; 00723 for(unsigned y = 0; y < mask.size(); ++y){ 00724 result.pixels += mask[y][1] - mask[y][0]; 00725 for(int x = mask[y][0]; x < mask[y][1]; ++x){ 00726 const double d = A.difference(warped[y][x], templateImage[y][x]); 00727 result.error += d*d; 00728 } 00729 } 00730 00731 // store results 00732 result.delta = norm(wls.get_mu()); 00733 00734 #if 0 00735 if(DEBUG_ESM){ 00736 ostringstream sout; 00737 sout << "debug_" << setw(4) << setfill('0') << DEBUG_ESM << "_" << result.iterations << ".jpg"; 00738 Image<byte> warped(templateImage.size()); 00739 Internal::transform(warped, target, T.get_matrix()); 00740 img_save(warped, sout.str()); 00741 00742 } 00743 #endif 00744 00745 } while(result.iterations < max_iterations && result.delta > min_delta && result.RMSE() > max_RMSE); 00746 00747 return result; 00748 } 00749 00750 } // namespace Internal 00751 00752 template <int PARAMS> 00753 inline std::ostream & operator<<(std::ostream & out, const Homography<PARAMS> & t ){ 00754 out << t.H[0] << "\t" << t.H[1] << "\t" << t.H[2]; 00755 return out; 00756 } 00757 00758 inline std::ostream & operator<<(std::ostream & out, const StaticAppearance & t ){ 00759 return out; 00760 } 00761 00762 inline std::ostream & operator<<(std::ostream & out, const OffsetAppearance & t ){ 00763 out << t.offset; 00764 return out; 00765 } 00766 00767 inline std::ostream & operator<<(std::ostream & out, const BlurAppearance & t ){ 00768 return out; 00769 } 00770 00771 } // namespace CVD 00772 00773 #endif // CVD_ESM_H