libcvd-members
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libcvd-members] libcvd/cvd esm.h


From: Gerhard Reitmayr
Subject: [libcvd-members] libcvd/cvd esm.h
Date: Tue, 02 Aug 2011 22:37:48 +0000

CVSROOT:        /cvsroot/libcvd
Module name:    libcvd
Changes by:     Gerhard Reitmayr <gerhard>      11/08/02 22:37:48

Added files:
        cvd            : esm.h 

Log message:
        added a generic implementation of ESM including different geometric 
transformations and appearance models

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/libcvd/cvd/esm.h?cvsroot=libcvd&rev=1.1

Patches:
Index: esm.h
===================================================================
RCS file: esm.h
diff -N esm.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ esm.h       2 Aug 2011 22:37:48 -0000       1.1
@@ -0,0 +1,773 @@
+#ifndef CVD_ESM_H_
+#define CVD_ESM_H_
+
+#include <TooN/wls.h>
+#include <TooN/so3.h>
+
+#include <cvd/image.h>
+#include <cvd/image_io.h>
+#include <cvd/vector_image_ref.h>
+#include <cvd/vision.h>
+
+#include <vector>
+#include <sstream>
+#include <iomanip>
+
+namespace CVD {
+
+static int DEBUG_ESM = 0;
+
+/// @defgroup gEsm Efficient Second Order Minimization (ESM)
+/// This module implements the ESM template tracking algorithm for 
homography-based
+/// image transformations as described by Benhimane & Mails, 
+/// "Real-time image-based tracking of planes using efficient second-order 
minimization", 2004.
+///
+/// Within this module the geometric transformation of the template and the 
radiometric (appearance) 
+/// transformation are separated into two different concepts. Concrete type 
instances are used to 
+/// parameterize the generic @ref ESMEstimator class. The following are the 
two important concepts.
+/// 
+/// The transform object @ref T has to implement the following concept:
+/// @code
+/// class Transform {
+///     static const int dimensions = X;
+///     Matrix<3> get_matrix(); 
+///     Vector<dimensions> get_jacobian( const Vector<2> & point, const 
Vector<2> & gradient );
+///     void update( const Vector<dimensions> & delta );
+/// };
+/// @endcode
+/// The implementations are @ref Homography, @ref HomographyPrefix and @ref 
CameraRotation.
+/// 
+/// A second object deals with the appearance model used and has to implement 
+/// the following concept:
+/// @code
+/// class Appearance {
+///     static const int dimensions = X;
+///     template <typename PIXEL> double difference( const PIXEL & warped, 
const PIXEL & templatePixel );
+///     template <typename PIXEL> tuple<double, Vector<dimensions> > 
difference_jacobian( const PIXEL & warped, const PIXEL & templatePixel );
+///     Vector<2> image_jacobian( const Vector<2> & gradWarped, const 
Vector<2> & gradTemplate );
+///     void update( const Vector<dimensions> & delta );
+/// };
+/// @endcode
+/// The implementations are @ref StaticAppearance, @ref OffsetAppearance and 
@ref BlurAppearance.
+///
+/// @ingroup gVision
+
+/// Result class storing some information about the optimization
+/// @ingroup gEsm
+struct ESMResult {
+    double error;   ///< final total squared error 
+    double delta;   ///< norm of the last update
+    int pixels;     ///< common pixels in last iteration
+    int iterations; ///< number of iterations performed
+    
+    /// returns RMSE error computed from @ref error and @pixels
+    double RMSE() const { return std::sqrt(error/std::max(1,pixels)); }
+};
+
+inline std::ostream & operator<<(std::ostream & out, const ESMResult & r ){
+    out << r.error << "\t" << r.pixels << "\t" << r.RMSE() << "\t" << "\t" << 
r.delta << "\t" << r.iterations;
+    return out;
+}
+
+namespace Internal { // forward declaration of some internal functions
+    // intersects the line AB with the rectangle rect, returns the interval 
for the parameter t
+    // for X = A + (B-A)*t
+    // A, B in homogeneous coordinates, yielding the correct perspective 
interpolation for t
+    inline TooN::Vector<2> getBounds(const TooN::Vector<2> & rect, const 
TooN::Vector<3> & A, const TooN::Vector<3> & B);
+    inline std::vector<TooN::Vector<2,int> > getBounds( const TooN::Vector<2> 
& rect, const ImageRef & in, const TooN::Matrix<3> & H );
+    inline std::vector<TooN::Vector<2,int> > erode( const 
std::vector<TooN::Vector<2,int> > & in );
+
+    // H takes pixels in out to pixels in in as a 2D homography (3x3 matrix)
+    template <typename T> inline std::vector<TooN::Vector<2,int> > 
transform_perspective( SubImage<T> & out, const SubImage<T> & in, const 
TooN::Matrix<3> & H);
+    // H takes pixels in out to pixels in in as a 2D affine transformation 
(3x3 matrix)
+    template <typename T> inline std::vector<TooN::Vector<2,int> > 
transform_affine( SubImage<T> & out, const SubImage<T> & in, const 
TooN::Matrix<3> & H);
+    // H takes pixels in out to pixels in in as a 2D translation only (stored 
in the 3x3 matrix)
+    // This is extra optimized to use the constant mixing factors in the 
bi-linear interpolation
+    template <typename T> inline std::vector<TooN::Vector<2,int> > 
transform_translation( SubImage<T> & out, const SubImage<T> & in, const 
TooN::Matrix<3> & H);
+    // automatically selects the right transform implementation based on the 
properties of H
+    template <typename T> inline std::vector<TooN::Vector<2,int> > transform( 
SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H);
+    
+    // calculates the gradient of a one component image within the given 
bounds only. The 
+    // function uses central differences, but does not divide by 2!
+    template<typename GRADIENT, typename IMAGE> inline Image<GRADIENT> 
gradient( const SubImage<IMAGE> & img, const std::vector<TooN::Vector<2,int> > 
& bounds );
+    // calculates the gradient of a one component image directly. The 
+    // function uses central differences, but does not divide by 2!
+    template<typename GRADIENT, typename IMAGE> inline Image<GRADIENT> 
gradient( const SubImage<IMAGE> & img );
+    
+    /// a full ESM optimization function. It takes a template image and its 
gradient, a target image and a 
+    /// general transform object, plus some parameters and iterates until 
convergence.
+    /// @param T the geometric transformation, used as output as well
+    /// @param A the appearance transformation, used as output as well
+    /// @param templateImage the reference template image
+    /// @param templateGradient and its gradient of the same size
+    /// @param target the target image, can be any size
+    /// @param max_iterations maximal number of iterations to perform
+    /// @param min_delta minimal change vector, if the vector is smaller than 
this, assume convergence and stop
+    /// @result struct carrying the convergence data of the optimization
+    ///
+    /// @ingroup gEsm
+    template <typename TRANSFORM, typename APPEARANCE, typename IMAGE, 
typename GRADIENT>
+    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 );                
+} // namespace Internal
+
+/// a generic implementation for 2D homography-based transformations 
parameterized
+/// by a 3x3 matrix with determinant == 1. The class implements the interface 
described by @ref ESMTransform.
+/// The template parameter defines the number of parameters used for the 2D 
transformation 
+/// and should lie within 1 to 8. The following lists the individual meaning 
for all 8 parameters:
+/// @li 0 x translation
+/// @li 1 y translation
+/// @li 2 in-plane rotation
+/// @li 3 uniform scale
+/// @li 4 non-uniform scale
+/// @li 5 symmetric component (the last for a full affine model)
+/// @li 6 and 7 missing perspective parameters
+/// @ingroup gEsm
+template<int PARAMS>
+class Homography {
+public:
+    static const int dimensions = PARAMS;
+    
+    Homography() : H(TooN::Identity) {}
+    
+    template <int R, int C, typename P, typename B>
+    Homography( const TooN::Matrix<R, C, P, B> & h) : H(h) {}
+    
+    const TooN::Matrix<3> & get_matrix() const { return H; }
+    TooN::Matrix<3> & get_matrix() { return H; }
+    const TooN::Vector<PARAMS> & get_jacobian( const TooN::Vector<2> & p, 
const TooN::Vector<2> & grad ) const {
+        switch(PARAMS){
+        case 8:
+            // 0 0 p[1]
+            J[7] = -p[1]*(grad*p); // TooN::makeVector(-p[1]*p[0], -p[1]*p[1]);
+        case 7:
+            // 0 0 p[0]
+            J[6] = -p[0]*(grad*p); // TooN::makeVector(-p[0]*p[0], -p[0]*p[1]);
+        case 6:
+            // p[1] p[0] 0 
+            J[5] = grad[0]*p[1] + grad[1]*p[0];// TooN::makeVector(p[1], p[0]);
+        case 5:
+            // p[0] -p[1] 0 
+            J[4] = grad[0]*p[0] - grad[1]*p[1]; // TooN::makeVector(p[0], 
-p[1]);
+        case 4:
+            // p[0] p[1] -2 
+            J[3] = 3 * (grad * p); //TooN::makeVector(3*p[0], 3*p[1]);
+        case 3:
+            // -p[1] p[0] 0 
+            J[2] = - grad[0]*p[1] + grad[1]*p[0] ; // TooN::makeVector(-p[1], 
p[0]);
+        case 2:
+            J[1] = grad[1]; // TooN::makeVector(0, 1);
+        case 1:
+            J[0] = grad[0]; // TooN::makeVector(1, 0);
+            break;
+        default: 
+            assert(false);
+        }
+        return J;
+    }
+    
+    void update( const TooN::Vector<PARAMS> & v ){
+        TooN::Matrix<3> G = TooN::Zeros;
+        switch(PARAMS){
+        case 8:
+            G(2,1) = v[7];
+        case 7:
+            G(2,0) = v[6];
+        case 6:
+            G(0,1) = v[5];
+            G(1,0) = v[5];
+        case 5:
+            G(0,0) = v[4];
+            G(1,1) = -v[4];
+        case 4:
+            G(0,0) += v[3];
+            G(1,1) += v[3];
+            G(2,2) += -2*v[3];
+        case 3:
+            G(0,1) -= v[2];
+            G(1,0) += v[2];
+        case 2:
+            G(1,2) = v[1];
+        case 1:
+            G(0,2) = v[0];
+            break;
+        default: assert(false);
+        }
+        H = H * TooN::exp(-G);
+    }
+
+protected:
+    TooN::Matrix<3> H;
+    mutable TooN::Vector<PARAMS> J;
+};
+
+/// This class provides a generic 2D homography transformation, but also 
applies
+/// a fixed transformation on the input pixel locations. This allows to change
+/// the reference point for the optimized mapping, for example to estimate it 
+/// around the center of the template image. The class implements the 
interface described by @ref ESMTransform.
+/// The parameterization is the same as for @ref Homography.
+/// @ingroup gEsm
+template<int PARAMS>
+class HomographyPrefix : public Homography<PARAMS> {
+public:
+    static const int dimensions = PARAMS;
+    
+    HomographyPrefix() : Pre(TooN::Identity) {}
+
+    template <int R, int C, typename P, typename B>
+    HomographyPrefix( const TooN::Matrix<R, C, P, B> & p ) : Pre(p) {
+        const TooN::Matrix<3> id = TooN::Identity;
+        H = TooN::gaussian_elimination(Pre, id);
+    }
+    
+    template <int R, int C, typename P, typename B, int R2, int C2, typename 
P2, typename B2>
+    HomographyPrefix( const TooN::Matrix<R, C, P, B> & h, const 
TooN::Matrix<R2, C2, P2, B2> & p) : Pre(p) {
+        const TooN::Matrix<3> id = TooN::Identity;
+        H = h * TooN::gaussian_elimination(Pre, id);
+    }
+    
+    const TooN::Matrix<3> get_matrix() const { return H * Pre; }
+    
+    const TooN::Vector<PARAMS> & get_jacobian( const TooN::Vector<2> & x, 
const TooN::Vector<2> & grad ) const {
+        if( PARAMS > 2){
+            const TooN::Vector<2> p = TooN::project(Pre * TooN::unproject(x));
+            return Homography<PARAMS>::get_jacobian(p, grad);
+        }
+        return Homography<PARAMS>::get_jacobian(x, grad);
+    }
+
+protected:
+    using Homography<PARAMS>::H;
+    TooN::Matrix<3> Pre;
+};
+
+/// a special implementation for 2D homography-based transformations described
+/// as a camera rotating around its centre. The class implements the interface 
described by @ref esm.
+/// It requires a linear camera model besides the rotation to be estimated.
+/// @ingroup gEsm
+class CameraRotation {
+public:
+    static const int dimensions = 3;
+
+    CameraRotation() {}
+    
+    template <typename P, typename B>
+    CameraRotation( const TooN::Vector<4, P, B> & camera_params, const 
TooN::SO3<> & init = TooN::SO3<>() ) 
+    : R(init) 
+    {
+        set_camera(camera_params);
+    }
+    
+    template <typename P, typename B>
+    void set_camera( const TooN::Vector<4, P, B> & camera_params ){
+        k = camera_params;
+        K = TooN::Identity;
+        K(0,0) = camera_params[0];
+        K(1,1) = camera_params[1];
+        K(0,2) = camera_params[2];
+        K(1,2) = camera_params[3];
+        
+        kinv = TooN::makeVector( 1/camera_params[0], 1/camera_params[1],  
-camera_params[2]/camera_params[0], -camera_params[3]/camera_params[1]);
+        Kinv = TooN::Identity;
+        Kinv(0,0) = kinv[0];
+        Kinv(1,1) = kinv[1];
+        Kinv(0,2) = kinv[2];
+        Kinv(1,2) = kinv[3];
+    }
+    
+    const TooN::Matrix<3> get_matrix() const { return K*R*Kinv; }
+    
+    const TooN::Vector<dimensions> & get_jacobian( const TooN::Vector<2> & p, 
const TooN::Vector<2> & grad ) const {
+        // this implements grad * proj * K * G_i * Kinv * p;
+        const TooN::Vector<3> pW = TooN::makeVector(kinv[0] * p[0] + kinv[2], 
kinv[1] * p[1] + kinv[3], 1);
+        const TooN::Vector<3> J_pK = TooN::makeVector(grad[0] * k[0], grad[1] 
* k[1], grad * (k.slice<2,2>() - p));
+        J[0] = J_pK * TooN::SO3<>::generator_field(0, pW);
+        J[1] = J_pK * TooN::SO3<>::generator_field(1, pW);
+        J[2] = J_pK * TooN::SO3<>::generator_field(2, pW);
+        return J;
+    }
+    
+    void update( const TooN::Vector<dimensions> & v ){
+        R = R * TooN::SO3<>::exp(-v);
+    }
+
+    const TooN::SO3<> & get_rotation() const { return R; }
+    TooN::SO3<> & get_rotation() { return R; }
+
+protected:
+    TooN::Matrix<3> K, Kinv; ///< linear camera matrix and its inverse
+    TooN::Vector<4> k, kinv; ///< same parameters in vector form for 
convenience
+    TooN::SO3<> R;           ///< the transformation, a rotation of the camera 
around its centre
+    mutable TooN::Vector<dimensions> J; ///< internal temporary to avoid 
multiple re-initialisations
+};
+
+/// Basic appearance model implementing no change in the image.
+/// @ingroup gEsm
+class StaticAppearance {
+public:
+    static const int dimensions = 0;
+    template <typename PIXEL> double difference( const PIXEL & warped, const 
PIXEL & templatePixel ) const { 
+        return warped - templatePixel;
+    }
+    template <typename PIXEL> std::pair<double, TooN::Vector<dimensions> > 
difference_jacobian( const PIXEL & warped, const PIXEL & templatePixel ) const {
+        return std::make_pair( difference(warped, templatePixel), 
TooN::Vector<dimensions>());
+    }
+    TooN::Vector<2> image_jacobian( const TooN::Vector<2> & gradWarped, const 
TooN::Vector<2> & gradTemplate ) const { 
+        return (gradWarped + gradTemplate) * 0.25;
+    }
+    void update( const TooN::Vector<dimensions> & d ) {}
+};
+
+/// Simple appearance model that assumes a constant offset in the intensities 
of the image vs. the template.
+/// The parameter @ref offset is estimated during the ESM optimization.
+/// @ingroup gEsm
+class OffsetAppearance {
+public:
+    static const int dimensions = 1;
+    template <typename PIXEL> double difference( const PIXEL & warped, const 
PIXEL & templatePixel ) const { 
+        return warped - templatePixel - offset; 
+    }
+    template <typename PIXEL> std::pair<double, TooN::Vector<dimensions> > 
difference_jacobian( const PIXEL & warped, const PIXEL & templatePixel ) const {
+        return std::make_pair( difference(warped, templatePixel), 
TooN::makeVector(1));
+    }
+    TooN::Vector<2> image_jacobian( const TooN::Vector<2> & gradWarped, const 
TooN::Vector<2> & gradTemplate ) const {
+        return (gradWarped + gradTemplate) * 0.25; 
+    }
+    void update( const TooN::Vector<dimensions> & d ) { 
+        offset += d[0]; 
+    }
+    
+    OffsetAppearance() : offset(0) {}
+    double offset;
+};
+
+/// Blur appearance model that assumes that the input image was subject to 
blur. 
+/// There is no parameter to estimate, but the image jacobians look different.
+/// @todo reference paper from Lepetit here
+/// @ingroup gEsm
+class BlurAppearance {
+public:
+    static const int dimensions = 0;
+    template <typename PIXEL> double difference( const PIXEL & warped, const 
PIXEL & templatePixel ) const { 
+        return warped - templatePixel; 
+    }
+    template <typename PIXEL> std::pair<double, TooN::Vector<dimensions> > 
difference_jacobian( const PIXEL & warped, const PIXEL & templatePixel ) const {
+        return std::make_pair( difference(warped, templatePixel), 
TooN::Vector<dimensions>());
+    }
+    TooN::Vector<2> image_jacobian( const TooN::Vector<2> & gradWarped, const 
TooN::Vector<2> & gradTemplate ) const { 
+        return (gradWarped + 0.5*gradTemplate) * 0.25; 
+    }
+    void update( const TooN::Vector<dimensions> & d ) { 
+    }
+};
+
+#if 0
+
+class LinearAppearance {
+    static const int dimensions = 2;
+
+    template <typename PIXEL> double difference( const PIXEL & templatePixel, 
const PIXEL & warped ) const { return warped - templatePixel*scale - offset; }
+    Vector<2> image_jacobian( const Vector<2> & gradTemplate, const Vector<2> 
& gradImage ) const { return gradTemplate + gradImage; }
+    Vector<dimensions> get_jacobian( const Vector<2> & point ) { return 
TooN::makeVector(1.0); }
+    void update( const Vector<dimensions> & d ) { offset += d[0]; }
+    
+    double offset;
+    double scale;
+};
+#endif
+
+template <typename P>
+inline TooN::Matrix<3,3,P> scaleHomography( const TooN::Matrix<3,3,P> & H, 
const P & f ){
+    const TooN::Vector<3,P> s = TooN::makeVector<P>(1/f, 1/f, 1);
+    const TooN::Vector<3,P> is = TooN::makeVector<P>(f, f, 1);
+    return is.as_diagonal() * H * s.as_diagonal();
+}
+
+#if 0
+    
+/// Helper function to compute the 2D homography between to images.
+/// @ingroup gEsm
+template<int PARAMS, typename APPEARANCE, typename IMAGE>
+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){    
+    TooN::Matrix<3> prefix = TooN::Identity;
+#if 1
+    prefix(0,2) = -from.size().x/2;
+    prefix(1,2) = -from.size().y/2;
+#else
+    prefix(0,0) = 2.0/from.size().x;
+    prefix(1,1) = 2.0/from.size().y;
+    prefix(0,2) = -1;
+    prefix(1,2) = -1;
+    cout << prefix << endl;
+#endif
+    
+    HomographyPrefix<PARAMS> H(init, prefix);
+    APPEARANCE appearance;
+    ESMTransform<HomographyPrefix<PARAMS>, APPEARANCE> T(H, appearance);
+        
+    ESMResult r = esm_opt(T, from, to, 40, 1e-2);
+    
+    if(res != NULL)
+        *res = r;
+    return T.transform.get_matrix();
+}
+
+#endif
+
+/// The main class for the ESM module. This class stores the template image, 
the transformations and other information required to run the optimization. It 
is 
+/// parameterized with two types that describe which geometric and radiometric 
transformations to use.
+/// @ingroup gEsm
+template <typename TRANSFORM, typename APPEARANCE, typename IMAGE = CVD::byte, 
typename GRADIENT = TooN::Vector<2, typename Pixel::traits<IMAGE>::wider_type> >
+class ESMEstimator {
+public:
+    TRANSFORM transform;
+    APPEARANCE appearance;
+        
+    Image<IMAGE> templ;
+    Image<GRADIENT> templGradient;
+    
+    int max_iterations;
+    double min_delta;
+    double max_RMSE;
+    
+    ESMResult result;
+    
+    ESMEstimator() : max_iterations(40), min_delta(1e-5), max_RMSE(1e-2) {}
+    ESMEstimator( const Image<IMAGE> & t, const Image<GRADIENT> & g ) : 
templ(t), templGradient(g), max_iterations(40), min_delta(1e-5), max_RMSE(1e-2) 
{}
+    ESMEstimator( const Image<IMAGE> & t) : templ(t), max_iterations(40), 
min_delta(1e-5), max_RMSE(1e-2) {
+        templGradient = Internal::gradient<IMAGE, GRADIENT>(templ);
+    }
+    ESMEstimator( const SubImage<IMAGE> & t) : max_iterations(40), 
min_delta(1e-5), max_RMSE(1e-2) {
+        templ.copy_from(t);
+        templGradient = Internal::gradient<IMAGE, GRADIENT>(templ);
+    }
+    
+    void set_image( const Image<IMAGE> & t, const Image<GRADIENT> & g ){
+        assert(t.size() == g.size());
+        templ = t;
+        templGradient = g;
+    }
+
+    void set_image( const Image<IMAGE> & t ){
+        templ = t;
+        templGradient = Internal::gradient<GRADIENT>(templ);
+    }
+
+    void set_image( const SubImage<IMAGE> & t ){
+        Image<IMAGE> temp;
+        temp.copy_from(t);
+        set_image(temp);
+    }
+    
+    void reset() { 
+        transform = TRANSFORM();
+        appearance = APPEARANCE();
+    }
+
+    const ESMResult & optimize( const SubImage<IMAGE> & to ){
+        return result = Internal::esm_opt( transform, appearance, templ, 
templGradient, to, max_iterations, min_delta, max_RMSE);
+    }
+
+    const ESMResult & optimize( const SubImage<IMAGE> & from, const 
SubImage<IMAGE> & to ){
+        Image<GRADIENT> fromGradient = Internal::gradient<IMAGE, 
GRADIENT>(from);
+        return optimize( from, fromGradient, to );
+    }
+
+    const ESMResult & optimize( const SubImage<IMAGE> & from, const 
SubImage<GRADIENT> & fromGradient, const SubImage<IMAGE> & to ){
+        return result = Internal::esm_opt( transform, appearance, from, 
fromGradient, to, max_iterations, min_delta, max_RMSE);
+    }
+
+    const ESMResult & get_result() const { return result; }
+};
+
+/// a specialization of @ref ESMEstimator for pure camera rotation only.
+/// @ingroup gEsm
+template <typename APPEARANCE, typename IMAGE = CVD::byte, typename GRADIENT = 
TooN::Vector<2, typename Pixel::traits<IMAGE>::wider_type> >
+class RotationEstimator : public ESMEstimator<CameraRotation, APPEARANCE, 
IMAGE, GRADIENT> {
+public:
+    using ESMEstimator<CameraRotation, APPEARANCE, IMAGE, GRADIENT>::transform;
+    using ESMEstimator<CameraRotation, APPEARANCE, IMAGE, 
GRADIENT>::appearance;
+
+    RotationEstimator( const TooN::Vector<4> & cam_params, const TooN::SO3<> & 
init = TooN::SO3<>() ) {
+        transform.set_camera( cam_params );
+        transform.get_rotation() = init;
+    }
+    
+    void reset() { 
+        transform.get_rotation() = TooN::SO3<>(); 
+        appearance = APPEARANCE();
+    }
+
+};
+
+    namespace Internal {
+        
+        // intersects the line AB with the rectangle rect, returns the 
interval for the parameter t
+        // for X = A + (B-A)*t
+        // A, B in homogeneous coordinates, yielding the correct perspective 
interpolation for t
+        inline TooN::Vector<2> getBounds(const TooN::Vector<2> & rect, const 
TooN::Vector<3> & A, const TooN::Vector<3> & B){
+            const TooN::Vector<3> dir = A - B;
+            TooN::Vector<2> interval = TooN::makeVector(0,1);
+            
+            TooN::Matrix<4,3> lines;
+            lines[0] = TooN::makeVector(1,0,0);
+            lines[1] = TooN::makeVector(-1,0,rect[0]);
+            lines[2] = TooN::makeVector(0,1,0);
+            lines[3] = TooN::makeVector(0,-1,rect[1]);
+            
+            const TooN::Vector<4> hitA = lines * A;
+            const TooN::Vector<4> hitB = lines * B;
+            
+            for(int i = 0; i < 4; ++i){
+                if(hitA[i] <=0 && hitB[i] <= 0)
+                    return TooN::makeVector(0,0);
+                if(hitA[i] * hitB[i] < 0){
+                    const double t = hitA[i] / (lines[i] * dir);
+                    if(hitA[i] < 0){
+                        interval[0] = std::max(t, interval[0]);
+                    } else {
+                        interval[1] = std::min(t, interval[1]);
+                    }
+                }
+            }
+            
+            if(interval[0] > interval[1])
+                return TooN::makeVector(0,0);
+            return interval;
+        }
+        
+        inline std::vector<TooN::Vector<2,int> > getBounds( const 
TooN::Vector<2> & rect, const ImageRef & in, const TooN::Matrix<3> & H ){
+            std::vector<TooN::Vector<2,int> > bounds(in.y);
+            for(int y = 0; y < in.y; ++y){
+                // map the current scan line
+                const TooN::Vector<3> A = H * TooN::makeVector(0,y,1);
+                const TooN::Vector<3> B = H * TooN::makeVector(in.x-1, y, 1);
+                // determine valid sample interval
+                const TooN::Vector<2> b = getBounds(rect, A, B) * (in.x-1);
+                bounds[y] = TooN::makeVector<int>(ceil(b[0]), floor(b[1])+1);
+            }
+            return bounds;
+        }
+        
+        inline std::vector<TooN::Vector<2,int> > erode( const 
std::vector<TooN::Vector<2,int> > & in ){
+            using std::min; using std::max;
+            std::vector<TooN::Vector<2,int> > out(in.size());
+            out.front() = out.back() = TooN::makeVector(0, 0);
+            for(unsigned i = 1; i < in.size()-1; ++i){
+                out[i][0] = max(in[i-1][0], max(in[i][0] + 1, in[i+1][0]));  
// the right most of all three 
+                out[i][1] = min(in[i-1][1], min(in[i][1] - 1, in[i+1][1]));  
// the left most of all three
+                if(out[i][0] > out[i][1])                               // if 
its empty, then mark as (0,0)
+                    out[i][0] = out[i][1] = 0;
+            }
+            return out;
+        }
+        
+        // H takes pixels in out to pixels in in as a 2D homography (3x3 
matrix)
+        template <typename T>
+        inline std::vector<TooN::Vector<2,int> > transform_perspective( 
SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H){
+            const ImageRef & size = out.size();
+            const TooN::Vector<2> insize = vec(in.size() - ImageRef(1,1));
+            const TooN::Vector<3> across = H.T()[0];
+            const TooN::Vector<3> down = H.T()[1];
+            TooN::Vector<3> base = H.T()[2];
+            
+            const std::vector<TooN::Vector<2, int> > bounds = 
getBounds(insize, size, H);
+            
+            for(int y = 0; y < size.y; ++y){
+                TooN::Vector<3> X = base + bounds[y][0] * across;
+                T * data = out[y] + bounds[y][0];
+                for(int x = bounds[y][0]; x < bounds[y][1]; ++x, X += across, 
++data){
+                    const TooN::DefaultPrecision inv = 1/X[2];
+                    sample(in, X[0] * inv, X[1] * inv, *data );
+                }
+                base += down;                                                  
         // next line
+            }
+            
+            return bounds;
+        }
+        
+        // H takes pixels in out to pixels in in as a 2D affine transformation 
(3x3 matrix)
+        template <typename T>
+        inline std::vector<TooN::Vector<2,int> > transform_affine( SubImage<T> 
& out, const SubImage<T> & in, const TooN::Matrix<3> & H){
+            const ImageRef & size = out.size();
+            const TooN::Vector<2> insize = vec(in.size() - ImageRef(1,1));
+            const TooN::Vector<2> across = H.T()[0].slice<0,2>();
+            const TooN::Vector<2> down = H.T()[1].slice<0,2>();
+            TooN::Vector<2> base = H.T()[2].slice<0,2>();
+            
+            const std::vector<TooN::Vector<2, int> > bounds = 
getBounds(insize, size, H);
+
+            for(int y = 0; y < size.y; ++y){
+                TooN::Vector<2> X = base + bounds[y][0] * across;
+                T * data = out[y] + bounds[y][0];
+                for(int x = bounds[y][0]; x < bounds[y][1]; ++x, X += across, 
++data){
+                    sample(in, X[0], X[1], *data );
+                }
+                base += down;                                                  
         // next line
+            }
+
+            return bounds;
+        }
+        
+        // H takes pixels in out to pixels in in as a 2D translation only 
(stored in the 3x3 matrix)
+        // This is extra optimized to use the constant mixing factors in the 
bi-linear interpolation
+        template <typename T>
+        inline std::vector<TooN::Vector<2,int> > transform_translation( 
SubImage<T> & out, const SubImage<T> & in, const TooN::Matrix<3> & H){
+            const ImageRef & size = out.size();
+            const TooN::Vector<2> insize = vec(in.size() - ImageRef(1,1));
+            const TooN::Vector<2> across = H.T()[0].slice<0,2>();
+            const TooN::Vector<2> down = H.T()[1].slice<0,2>();
+            TooN::Vector<2> base = H.T()[2].slice<0,2>();
+            
+            std::vector<TooN::Vector<2, int> > bounds = getBounds(insize, 
size, H);
+            
+            const double fx = H(0,2) - floor(H(0,2));
+            const double fy = H(1,2) - floor(H(1,2));
+            const double ul = fx * fy;
+            const double ur = (1-fx) * fy;
+            const double ll = fx * (1-fy);
+            const double lr = (1-fx)*(1-fy);
+            
+            for(int y = 0; y < size.y; ++y){
+                const TooN::Vector<2> & interval = bounds[y];                  
         // determine valid sample interval
+                int x = interval[0];
+                T * data = out[y] + x;                                         
         
+                TooN::Vector<2> X = base + x * across;                         
         // then sample
+                for(; x < interval[1]; ++x, ++data, X += across){
+                    sample(in, X[0], X[1], *data );
+                }
+                base += down;                                                  
         // next line
+            }
+            
+            return bounds;
+        }
+        
+        template <typename T>
+        inline std::vector<TooN::Vector<2,int> > transform( SubImage<T> & out, 
const SubImage<T> & in, const TooN::Matrix<3> & H){
+            const double perspective = H(2,0)*H(2,0) + H(2,1)*H(2,1);
+            if(perspective < 1e-10)
+                return transform_affine(out, in, H);
+            return transform_perspective(out, in, H);
+        }
+        
+        template<typename GRADIENT, typename IMAGE>
+        inline Image<GRADIENT> gradient( const SubImage<IMAGE> & img, const 
std::vector<TooN::Vector<2,int> > & bounds ){
+            assert(bounds.size() == unsigned(img.size().y));
+            Image<GRADIENT> grad(img.size());
+            for(int y = 1; y < img.size().y-1; ++y){
+                for(int x = bounds[y][0]; x < bounds[y][1]; ++x){
+                    grad[y][x][0] = img[y][x+1] - img[y][x-1];
+                    grad[y][x][1] = img[y+1][x] - img[y-1][x];
+                }
+            }
+            return grad;
+        }
+        
+        template<typename GRADIENT, typename IMAGE>
+        inline Image<GRADIENT> gradient( const SubImage<IMAGE> & img ){
+            Image<GRADIENT> grad(img.size());
+            for(int y = 1; y < img.size().y-1; ++y){
+                for(int x = 1; x < img.size().x-1; ++x){
+                    grad[y][x][0] = img[y][x+1] - img[y][x-1];
+                    grad[y][x][1] = img[y+1][x] - img[y-1][x];
+                }
+            }
+            return grad;
+        }
+        
+        template <typename TRANSFORM, typename APPEARANCE, typename IMAGE, 
typename GRADIENT>
+        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 ){
+            assert(templateImage.size() == templateGradient.size());
+            
+            const int dimensions = 
TRANSFORM::dimensions+APPEARANCE::dimensions;
+            
+            Image<IMAGE> warped(templateImage.size());
+            
+            TooN::WLS<dimensions> wls;
+        
+            ESMResult result = {1e100, 1e100, 0, 0};
+        
+            // first warp here to be able to compare error before and after
+            std::vector<TooN::Vector<2,int> > mask = 
Internal::transform(warped, target, T.get_matrix());
+            mask = Internal::erode(mask);
+        
+            do {
+                // get the gradient for the warped image
+                Image<GRADIENT> grad_warped = 
Internal::gradient<GRADIENT>(warped, mask);
+        
+                // create the least squares system
+                wls.clear();
+                TooN::Vector<dimensions> J;
+                for(unsigned y = 0; y < mask.size(); ++y){
+                    for(int x = mask[y][0]; x < mask[y][1]; ++x){
+                        std::pair<double, TooN::Vector<APPEARANCE::dimensions> 
> da = A.difference_jacobian( warped[y][x], templateImage[y][x]);
+                        J.template slice<0,TRANSFORM::dimensions>() = 
T.get_jacobian(TooN::makeVector(x,y), A.image_jacobian(grad_warped[y][x], 
templateGradient[y][x]));
+                        J.template slice<TRANSFORM::dimensions, 
APPEARANCE::dimensions>() = da.second;
+                        wls.add_mJ(da.first, J);
+                    }
+                }
+        
+                // solve and update
+                wls.compute();
+                T.update(wls.get_mu().template 
slice<0,TRANSFORM::dimensions>());
+                A.update(wls.get_mu().template slice<TRANSFORM::dimensions, 
APPEARANCE::dimensions>());
+                ++result.iterations;
+        
+                // compute new warp to calculate new error
+                mask = Internal::transform(warped, target, T.get_matrix());
+                mask = Internal::erode(mask);
+        
+                // compute error after update
+                result.error = 0;
+                // and number of pixels for RMS computation
+                result.pixels = 0;
+                for(unsigned y = 0; y < mask.size(); ++y){
+                    result.pixels += mask[y][1] - mask[y][0];
+                    for(int x = mask[y][0]; x < mask[y][1]; ++x){
+                        const double d = A.difference(warped[y][x], 
templateImage[y][x]);
+                        result.error += d*d;
+                    }
+                }
+        
+                // store results
+                result.delta = norm(wls.get_mu());
+
+#if 0                
+                if(DEBUG_ESM){
+                    ostringstream sout;
+                    sout << "debug_" << setw(4) << setfill('0') << DEBUG_ESM 
<< "_" << result.iterations << ".jpg";
+                    Image<byte> warped(templateImage.size());
+                    Internal::transform(warped, target, T.get_matrix());
+                    img_save(warped, sout.str());
+                    
+                }
+#endif
+
+            } while(result.iterations < max_iterations && result.delta > 
min_delta && result.RMSE() > max_RMSE);
+        
+            return result;
+        }
+                
+    } // namespace Internal
+
+template <int PARAMS>
+inline std::ostream & operator<<(std::ostream & out, const Homography<PARAMS> 
& t ){
+    out << t.H[0] << "\t" << t.H[1] << "\t" << t.H[2];
+    return out;
+}
+
+inline std::ostream & operator<<(std::ostream & out, const StaticAppearance & 
t ){
+    return out;
+}
+
+inline std::ostream & operator<<(std::ostream & out, const OffsetAppearance & 
t ){
+    out << t.offset;
+    return out;
+}
+
+inline std::ostream & operator<<(std::ostream & out, const BlurAppearance & t 
){
+    return out;
+}
+
+} // namespace CVD
+
+#endif // CVD_ESM_H



reply via email to

[Prev in Thread] Current Thread [Next in Thread]