当前位置: 首页 > 工具软件 > Fast-cast > 使用案例 >

subokita/FAsT-Match

陶宏浚
2023-12-01

FAsTMatch.cpp

//
//  FAsTMatch.cpp
//  FAsT-Match
//
//  Created by Saburo Okita on 23/05/14.
//  Copyright (c) 2014 Saburo Okita. All rights reserved.
//

#include "FAsTMatch.h"
#include <iomanip>
#include <random>
#include <tbb/tbb.h>


#define WITHIN( val, top_left, bottom_right ) (\
            val.x > top_left.x && val.y > top_left.y && \
            val.x < bottom_right.x && val.y < bottom_right.y )

namespace fast_match {
    FAsTMatch::FAsTMatch() {
        init();
    }

    void FAsTMatch::init( float epsilon, float delta, bool photometric_invariance, float min_scale, float max_scale ) {
        this->epsilon               = epsilon;
        this->delta                 = delta;
        this->photometricInvariance = photometric_invariance;
        this->minScale              = min_scale;
        this->maxScale              = max_scale;
    }

    /**
     * Apply Fast Template Matching algorithm
     */
    vector<Point2f> FAsTMatch::apply(Mat& original_image, Mat& original_template ) {
        /* Preprocess the image and template first */
        image = preprocessImage( original_image );
        templ = preprocessImage( original_template  );
        
        int r1x = 0.5 * (templ.cols - 1),
            r1y = 0.5 * (templ.rows - 1),
            r2x = 0.5 * (image.cols - 1),
            r2y = 0.5 * (image.rows - 1);
        
        float   min_trans_x  = -(r2x - r1x * minScale),
                max_trans_x  = -min_trans_x,
                min_trans_y  = -(r2y - r1y * minScale),
                max_trans_y  = -min_trans_y,
                min_rotation = -M_PI,
                max_rotation =  M_PI;
        
        /* Create the matching grid / net */
        MatchNet net( templ.cols, templ.rows, delta, min_trans_x, max_trans_x, min_trans_y, max_trans_y,
                       min_rotation, max_rotation, minScale, maxScale );
        
        /* Smooth our images */
        GaussianBlur( templ, templ, Size(0, 0), 2.0, 2.0 );
        GaussianBlur( image, image, Size(0, 0), 2.0, 2.0 );
        
        int no_of_points = round( 10 / (epsilon * epsilon) );
        
        /* Randomly sample points */
        Mat xs( 1, no_of_points, CV_32SC1 ),
            ys( 1, no_of_points, CV_32SC1 );
        
        rng.fill( xs, RNG::UNIFORM, 1, templ.cols );
        rng.fill( ys, RNG::UNIFORM, 1, templ.rows );
        
        int level = 0;
        
        float delta_fact = 1.511f;
        float new_delta  = delta;
        
        
        MatchConfig best_config;
        Mat best_trans;
        vector<double> best_distances(20, 0.0);
        double best_distance;
        vector<double> distances;
        vector<bool> insiders;
        
        while( true ) {
            level++;
            
            /* First create configurations based on our net */
            vector<MatchConfig> configs = createListOfConfigs( net, templ.size(), image.size() );
            
            int configs_count = static_cast<int>(configs.size());
            
            /* Convert the configurations into affine matrices */
            vector<Mat> affines = configsToAffine( configs, insiders );
            
            /* Filter out configurations that fall outside of the boundaries */
            /* the internal logic of configsToAffine has more information */
            vector<MatchConfig> temp_configs;
            for( int i = 0; i < insiders.size(); i++ )
                if( insiders[i] == true )
                    temp_configs.push_back( configs[i] );
            configs = temp_configs;
            
            /* For the configs, calculate the scores / distances */
            distances = evaluateConfigs( image, templ, affines, xs, ys, photometricInvariance );
            
            /* Find the minimum distance */
            auto min_itr          = min_element( distances.begin(), distances.end() );
            int min_index         = static_cast<int>(min_itr - distances.begin());
            best_distance         = distances[min_index];
            best_distances[level] = best_distance;
            
            best_config         = configs[min_index];
            best_trans          = best_config.getAffineMatrix();

            
            /* Conditions to exit the loop */
            if( (best_distance < 0.005) || ((level > 2) && (best_distance < 0.015)) || level >= 20 )
                break;
            
            if( level > 3 ) {
                float mean_value = std::accumulate( best_distances.begin() + level - 3, best_distances.begin() + level - 1, 0 ) * 1.0 / distances.size();
                
                if( best_distance > mean_value * 0.97 )
                    break;
            }
            
            
            float thresh;
            bool too_high_percentage;
            
            /* Get the good configurations that falls between certain thresholds */
            vector<MatchConfig> good_configs = getGoodConfigsByDistance( configs, best_distance, new_delta, distances, thresh, too_high_percentage );
            
            if ((too_high_percentage && (best_distance > 0.05) && ((level==1) && (configs_count < 7.5e6)) ) ||
                ((best_distance > 0.1) && ((level==1) && (configs_count < 5e6)) ) ) {
                
                static float factor = 0.9;
                new_delta    = new_delta * factor;
                level        = 0;
                net          = net * factor;
                configs      = createListOfConfigs( net, templ.size(), image.size() );
            }
            else {
                new_delta = new_delta / delta_fact;
                
                vector<MatchConfig> expanded_configs = randomExpandConfigs( good_configs, net, level, 80, delta_fact );
                
                configs.clear();
                configs.insert( configs.end(), good_configs.begin(), good_configs.end() );
                configs.insert( configs.end(), expanded_configs.begin(), expanded_configs.end() );
            }
            
            /* Randomly sample points again */
            rng.fill( xs, RNG::UNIFORM, 1, templ.cols );
            rng.fill( ys, RNG::UNIFORM, 1, templ.rows );
        }

        /* Return the rectangle corners based on the best affine transformation */
        return calcCorners( image.size(), templ.size(), best_trans );
    }

    /**
     * Given our grid / net, create a list of matching configurations
     */
    vector<MatchConfig> FAsTMatch::createListOfConfigs( MatchNet& net, Size templ_size, Size image_size ) {
        /* Creating the steps for all the parameters (i.e. translation, rotation, and scaling) */
        vector<float>   tx_steps = net.getXTranslationSteps(),
                        ty_steps = net.getYTranslationSteps(),
                        r_steps  = net.getRotationSteps(),
                        s_steps  = net.getScaleSteps();
        
        
        /* Getting the proper number of steps for each configuration parameters */
        int ntx_steps = static_cast<int>( tx_steps.size() ),
            nty_steps = static_cast<int>( ty_steps.size() ),
            ns_steps  = static_cast<int>( s_steps.size()  ),
            nr_steps  = static_cast<int>( r_steps.size()  ),
            nr2_steps = nr_steps;
        
        /* Refine the number of steps for the 2nd rotation parameter */
        if( fabs((net.boundsRotate.second - net.boundsRotate.first) - (2 * M_PI)) < 0.1 ) {
            nr2_steps = (int) count_if( r_steps.begin(), r_steps.end(), [&]( float r ){
                return r < (-M_PI / 2  + net.stepsRotate / 2);
            });
        }
        
        int grid_size = ntx_steps * nty_steps * ns_steps * ns_steps * nr_steps * nr2_steps;
        
        vector<MatchConfig> configs( grid_size );
        
        /* Iterate thru each possible affine configuration steps */
        tbb::parallel_for( 0, ntx_steps, 1, [&](int tx_index) {
            float tx = tx_steps[tx_index];
            
            for(int ty_index = 0; ty_index < nty_steps; ty_index++ ) {
                float ty = ty_steps[ty_index];
                
                for( int r1_index = 0; r1_index < nr_steps; r1_index++ ) {
                    float r1 = r_steps[r1_index];
                    
                    for( int r2_index = 0; r2_index < nr2_steps; r2_index++ ) {
                        float r2 = r_steps[r2_index];
                        
                        for( int sx_index = 0; sx_index < ns_steps; sx_index++ ) {
                            float sx = s_steps[sx_index];
                            
                            for( int sy_index = 0; sy_index < ns_steps; sy_index++ ) {
                                float sy = s_steps[sy_index];
                                
                                /* Maybe there's a better way for indexing when multithreading ... */
                                int grid_index  = (tx_index * nty_steps * nr_steps * nr2_steps * ns_steps * ns_steps)
                                                + (ty_index * nr_steps  * nr2_steps * ns_steps  * ns_steps)
                                                + (r1_index * nr2_steps * ns_steps * ns_steps)
                                                + (r2_index * ns_steps  * ns_steps)
                                                + (sx_index * ns_steps)
                                                + sy_index;

                                configs[grid_index].init( tx, ty, r2, sx, sy, r1 );
                            }
                        }
                    }
                }
            }
        });
        
        
        return configs;
    }

    /**
     * Randomly expands the configuration
     */
    vector<MatchConfig> FAsTMatch::randomExpandConfigs( vector<MatchConfig>& configs, MatchNet& net, int level,
                                                int no_of_points, float delta_factor ) {
        
        float factor = pow(delta_factor, level);
        
        float   half_step_tx = net.stepsTransX / factor,
                half_step_ty = net.stepsTransY / factor,
                half_step_r  = net.stepsRotate / factor,
                half_step_s  = net.stepsScale / factor;
        
        int no_of_configs = static_cast<int>( configs.size() );
        
        /* Create random vectors that contain values which are either -1, 0, or 1 */
        Mat random_vec( no_of_points * no_of_configs, 6, CV_32SC1 );
        rng.fill( random_vec, RNG::NORMAL, 0, 0.5 );
        random_vec.convertTo( random_vec, CV_32FC1 );

        /* Convert our vector of configurations into a large matrix */
        vector<Mat> configs_mat(no_of_configs);
        for(int i = 0; i < no_of_configs; i++ )
            configs_mat[i] = configs[i].asMatrix();
        
        Mat expanded;
        vconcat( configs_mat, expanded );
        expanded = repeat( expanded, no_of_points, 1 );

        vector<float> ranges_vec = {
            half_step_tx, half_step_ty, half_step_r, half_step_s, half_step_s, half_step_r
        };
        
        Mat ranges = repeat(Mat(ranges_vec).t() , no_of_points * no_of_configs, 1);
        
        /* The expanded configs is the original configs plus some random changes */
        Mat expanded_configs = expanded + random_vec.mul( ranges );

        return MatchConfig::fromMatrix( expanded_configs );
    }


    /**
     * From given list of configurations, convert them into affine matrices.
     * But filter out all the rectangles that are out of the given boundaries.
     **/
    vector<Mat> FAsTMatch::configsToAffine( vector<MatchConfig>& configs, vector<bool>& insiders ) {
        int no_of_configs = static_cast<int>(configs.size());
        vector<Mat> affines( no_of_configs );
        
        /* The boundary, between -10 to image size + 10 */
        Point2d top_left( -10., -10. );
        Point2d bottom_right( image.cols + 10, image.rows + 10 );
        
        
        /* These are for the calculations of affine transformed corners */
        int r1x  = 0.5 * ( templ.cols  - 1),
            r1y  = 0.5 * ( templ.rows - 1),
            r2x  = 0.5 * ( image.cols  - 1),
            r2y  = 0.5 * ( image.rows - 1);
        
        Mat corners = (Mat_<float>(3, 4) <<
                       1-(r1x+1), templ.cols-(r1x+1), templ.cols-(r1x+1),  1-(r1x+1),
                       1-(r1y+1), 1-(r1y+1)         , templ.rows-(r1y+1), templ.rows-(r1y+1),
                       1.0      , 1.0               , 1.0               , 1.0 );
        
        Mat transl = (Mat_<float>(4, 2) <<
                      r2x + 1, r2y + 1,
                      r2x + 1, r2y + 1,
                      r2x + 1, r2y + 1,
                      r2x + 1, r2y + 1 );
        
        insiders.assign( no_of_configs, false );
        
        /* Convert each configuration to corresponding affine transformation matrix */
        tbb::parallel_for( 0, no_of_configs, 1, [&](int i) {
            Mat affine = configs[i].getAffineMatrix();
            
            /* Check if our affine transformed rectangle still fits within our boundary */
            Mat affine_corners = (affine * corners).t();
            affine_corners =  affine_corners + transl;
            
            if( WITHIN( affine_corners.at<Point2f>(0), top_left, bottom_right) &&
                WITHIN( affine_corners.at<Point2f>(1), top_left, bottom_right) &&
                WITHIN( affine_corners.at<Point2f>(2), top_left, bottom_right) &&
                WITHIN( affine_corners.at<Point2f>(3), top_left, bottom_right) ) {
                
                affines[i]  = affine;
                insiders[i] = true;
            }
        });
        
        /* Filter out empty affine matrices (which initially don't fit within the preset boundary) */
        /* It's done this way, so that I could parallelize the loop */
        vector<Mat> result;
        for( int i = 0; i < no_of_configs; i++ ) {
            if( insiders[i] )
                result.push_back( affines[i] );
        }
        
        return result;
    }


    /**
     * Evaluate the score of the given configurations
     */
    vector<double> FAsTMatch::evaluateConfigs( Mat& image, Mat& templ, vector<Mat>& affine_matrices,
                                               Mat& xs, Mat& ys, bool photometric_invariance ) {
        
        int r1x = 0.5 * (templ.cols - 1),
            r1y = 0.5 * (templ.rows - 1),
            r2x = 0.5 * (image.cols - 1),
            r2y = 0.5 * (image.rows - 1);
        
        int no_of_configs = static_cast<int>(affine_matrices.size());
        int no_of_points  = xs.cols;
        
        /* Use a padded image, to avoid boundary checking */
        Mat padded( image.rows * 3, image.cols, image.type(), Scalar(0.0) );
        image.copyTo( Mat(padded, Rect(0, image.rows, image.cols, image.rows)) );
        
        /* Create a lookup array for our template values based on the given random x and y points */
        int * xs_ptr = xs.ptr<int>(0),
            * ys_ptr = ys.ptr<int>(0);
        
        vector<float> vals_i1( no_of_points );
        for( int i = 0; i < no_of_points; i++ )
            vals_i1[i] = templ.at<float>(ys_ptr[i] - 1, xs_ptr[i] - 1);
        
        
        /* Recenter our indices */
        Mat xs_centered = xs.clone() - (r1x + 1),
            ys_centered = ys.clone() - (r1y + 1);
        
        int * xs_ptr_cent = xs_centered.ptr<int>(0),
            * ys_ptr_cent = ys_centered.ptr<int>(0);
        
        vector<double> distances(no_of_configs, 0.0 );
        
        /* Calculate the score for each configurations on each of our randomly sampled points */
        tbb::parallel_for( 0, no_of_configs, 1, [&](int i) {

            float a11 = affine_matrices[i].at<float>(0, 0),
                  a12 = affine_matrices[i].at<float>(0, 1),
                  a13 = affine_matrices[i].at<float>(0, 2),
                  a21 = affine_matrices[i].at<float>(1, 0),
                  a22 = affine_matrices[i].at<float>(1, 1),
                  a23 = affine_matrices[i].at<float>(1, 2);
            
            double tmp_1 = (r2x + 1) + a13 + 0.5;
            double tmp_2 = (r2y + 1) + a23 + 0.5 + 1 * image.rows;
            double score = 0.0;
            
            if(!photometric_invariance) {
                for( int j = 0; j < no_of_points; j++ ) {
                    int target_x = int( a11 * xs_ptr_cent[j] + a12 * ys_ptr_cent[j] + tmp_1 ),
                        target_y = int( a21 * xs_ptr_cent[j] + a22 * ys_ptr_cent[j] + tmp_2 );
                    
                    score += abs(vals_i1[j] - padded.at<float>(target_y - 1, target_x - 1) );
                }
            }
            else {
                vector<double> xs_target(no_of_points),
                               ys_target(no_of_points);
                
                double  sum_x         = 0.0,
                        sum_y         = 0.0,
                        sum_x_squared = 0.0,
                        sum_y_squared = 0.0;
                
                for( int j = 0; j < no_of_points; j++ ) {
                    int target_x = int( a11 * xs_ptr_cent[j] + a12 * ys_ptr_cent[j] + tmp_1 ),
                        target_y = int( a21 * xs_ptr_cent[j] + a22 * ys_ptr_cent[j] + tmp_2 );
                    
                    float xi = vals_i1[j],
                          yi = padded.at<float>(target_y - 1, target_x - 1);
                    
                    xs_target[j] = xi;
                    ys_target[j] = yi;
                    
                    sum_x += xi;
                    sum_y += yi;
                    
                    sum_x_squared += (xi * xi);
                    sum_y_squared += (yi * yi);
                }
                
                double  epsilon = 1e-7;
                double  mean_x = sum_x / no_of_points,
                        mean_y = sum_y / no_of_points,
                        sigma_x = sqrt((sum_x_squared - ( sum_x * sum_x ) / no_of_points ) / no_of_points) + epsilon,
                        sigma_y = sqrt((sum_y_squared - ( sum_y * sum_y ) / no_of_points ) / no_of_points) + epsilon;

                double sigma_div = sigma_x / sigma_y;
                double temp = -mean_x + sigma_div * mean_y;
                
                
                for( int j = 0; j < no_of_points; j++ )
                    score += fabs( xs_target[j] - sigma_div * ys_target[j] + temp );
            }
            
            distances[i] = score / no_of_points;
        });
        
        return distances;
    }

    /**
     * Get threshold based on the given delta
     * the hardcoded values are claimed to be experimentally drawn
     */
    float FAsTMatch::getThresholdPerDelta( float delta ) {
        static const float p[2] = {0.1341, 0.0278};
        static const float safety = 0.02;
        
        return p[0] * delta + p[1] - safety;
    }

    /**
     * Given the previously calcuated distances for each configurations,
     * filter out all distances that fall within a certain threshold
     */
    vector<MatchConfig> FAsTMatch::getGoodConfigsByDistance( vector<MatchConfig>& configs, float best_dist, float new_delta,
                                                             vector<double>& distances, float& thresh, bool& too_high_percentage )
    {
        thresh = best_dist + getThresholdPerDelta( new_delta );
        
        /* Only those configs that have distances below the given threshold are */
        /* categorized as good configurations */
        vector<MatchConfig> good_configs;
        for(int i = 0; i < distances.size(); i++ ) {
            if( distances[i] <= thresh )
                good_configs.push_back( configs[i] );
        }
        
        int no_of_configs = static_cast<int>(good_configs.size());
        
        /* Well if there's still too many configurations */
        /* keep shrinking the threshold */
        while( no_of_configs > 27000 ) {
            thresh *= 0.99;
            good_configs.clear();
            
            for(int i = 0; i < distances.size(); i++ ) {
                if( distances[i] <= thresh )
                    good_configs.push_back( configs[i] );
            }
            
            no_of_configs = static_cast<int>(good_configs.size());
        }
        
        assert( no_of_configs > 0 );
        
        float percentage = 1.0 * no_of_configs / configs.size();
        
        /* If it's above 97.8% it's too high percentage */
        too_high_percentage = percentage > 0.022;
        
        return good_configs;
    }


    /**
     * Preprocess image, by first converting it to grayscale
     * then normalizing the value within 0.0 - 1.0 range
     * and finally make sure that the dimensions are in odd values
     **/
    Mat FAsTMatch::preprocessImage( Mat& image ) {
        Mat temp = image.clone();
        if( temp.channels() != 1 )
            cvtColor( temp, temp, CV_BGR2GRAY );
        
        if( temp.type() != CV_32FC1 )
            temp.convertTo( temp, CV_32FC1, 1.0 / 255.0 );
        
        return makeOdd( temp );
    }


    /**
     * If the image dimension is of odd value, leave it as is
     * if it's even, then minus 1 from the dimension
     */
    Mat FAsTMatch::makeOdd(Mat& image) {
        int rows = (image.rows % 2 == 0) ? image.rows - 1 : image.rows;
        int cols = (image.cols % 2 == 0) ? image.cols - 1 : image.cols;
        return Mat( image, Rect(0, 0, cols, rows)).clone();
    }

    /**
     * From the given affine matrix, calculate the four corners of the affine transformed
     * rectangle
     */
    vector<Point2f> FAsTMatch::calcCorners( Size image_size, Size templ_size, Mat& affine ) {
        float   r1x = 0.5 * (templ_size.width - 1),
                r1y = 0.5 * (templ_size.height - 1),
                r2x = 0.5 * (image_size.width - 1),
                r2y = 0.5 * (image_size.height - 1);
        
        float   a11 = affine.at<float>(0, 0),
                a12 = affine.at<float>(0, 1),
                a13 = affine.at<float>(0, 2),
                a21 = affine.at<float>(1, 0),
                a22 = affine.at<float>(1, 1),
                a23 = affine.at<float>(1, 2);
        
        float   templ_w = templ_size.width,
                templ_h = templ_size.height;
        
        /* The four corners of affine transformed template */
        double c1x = a11 * (1-(r1x+1))       + a12*(1-(r1y+1))        + (r2x+1) + a13;
        double c1y = a21 * (1-(r1x+1))       + a22*(1-(r1y+1))        + (r2y+1) + a23;
        
        double c2x = a11 * (templ_w-(r1x+1)) + a12*(1-(r1y+1))        + (r2x+1) + a13;
        double c2y = a21 * (templ_w-(r1x+1)) + a22*(1-(r1y+1))        + (r2y+1) + a23;
        
        double c3x = a11 * (templ_w-(r1x+1)) + a12*(templ_h-(r1y+1))  + (r2x+1) + a13;
        double c3y = a21 * (templ_w-(r1x+1)) + a22*(templ_h-(r1y+1))  + (r2y+1) + a23;
        
        double c4x = a11 * (1-(r1x+1))       + a12*(templ_h-(r1y+1))  + (r2x+1) + a13;
        double c4y = a21 * (1-(r1x+1))       + a22*(templ_h-(r1y+1))  + (r2y+1) + a23;
        
        return vector<Point2f> {
            Point2f(c1x, c1y),
            Point2f(c2x, c2y),
            Point2f(c3x, c3y),
            Point2f(c4x, c4y),
        };
    }
}


FAsTMatch.h

//
//  FAsTMatch.h
//  FAsT-Match
//
//  Created by Saburo Okita on 23/05/14.
//  Copyright (c) 2014 Saburo Okita. All rights reserved.
//

#ifndef __FAsT_Match__FAsTMatch__
#define __FAsT_Match__FAsTMatch__

#include <iostream>
#include <opencv2/opencv.hpp>

#include "MatchNet.h"
#include "MatchConfig.h"

using namespace std;
using namespace cv;

namespace fast_match {
    class FAsTMatch{
    public:
        FAsTMatch();
        
        void init( float epsilon = 0.15f, float delta = 0.25f, bool photometric_invariance = false,
                   float min_scale = 0.5f, float max_scale = 2.0f );
        
        vector<Point2f> apply(Mat& image, Mat& templ);
        
    protected:
        Mat image, templ;
        
        RNG rng;
        float epsilon;
        float delta;
        bool photometricInvariance;
        float minScale;
        float maxScale;
        Size halfTempl;
        Size halfImage;

        
        
        vector<MatchConfig> createListOfConfigs( MatchNet& net, Size templ_size, Size image_size );
        vector<Mat> configsToAffine( vector<MatchConfig>& configs, vector<bool>& insiders );
        
        vector<MatchConfig> getGoodConfigsByDistance( vector<MatchConfig>& configs, float best_dist, float new_delta,
                                                      vector<double>& distances, float& thresh, bool& too_high_percentage );
        
        vector<MatchConfig> randomExpandConfigs( vector<MatchConfig>& configs, MatchNet& net,
                                                 int level, int no_of_points, float delta_factor );
        
        float getThresholdPerDelta( float delta );
        
        vector<double> evaluateConfigs( Mat& image, Mat& templ, vector<Mat>& affine_matrices,
                                        Mat& xs, Mat& ys, bool photometric_invariance );
        
        
        Mat preprocessImage(Mat& image);
        Mat makeOdd(Mat& image);
        vector<Point2f> calcCorners( Size image_size, Size templ_size, Mat& affine );
        
    };
}

template<typename type>
ostream &operator <<( ostream& os, const std::pair<type, type> & vec ) {
    os << "[";
    os << vec.first << " " << vec.second;
    os << "]";
    return os;
}

template<typename type>
ostream &operator <<( ostream& os, const vector<type> & vec ) {
    os << "[";
    std::copy( vec.begin(), vec.end(), ostream_iterator<type>(os, ", ") );
    os << "]";
    return os;
}

#endif /* defined(__FAsT_Match__FAsTMatch__) */


MatchConfig.cpp

//
//  MatchConfig.cpp
//  FAsT-Match
//
//  Created by Saburo Okita on 02/06/14.
//  Copyright (c) 2014 Saburo Okita. All rights reserved.
//

#include "MatchConfig.h"
#include <iomanip>
#include <tbb/tbb.h>

using namespace std;
using namespace cv;

namespace fast_match {
    /**
     * Default constructor
     */
    MatchConfig::MatchConfig(){
    }
    
    /**
     * Constructor
     */
    MatchConfig::MatchConfig( float trans_x, float trans_y, float rotate_2, float scale_x, float scale_y, float rotate_1 ) {
        init( trans_x, trans_y, rotate_2, scale_x, scale_y, rotate_1 );
    }
    
    /**
     * Copy constructor
     */
    MatchConfig::MatchConfig( const MatchConfig& object ) {
        init( object.translateX, object.translateY, object.rotate2, object.scaleX, object.scaleY, object.rotate1 );
    }
    
    /**
     * Initialize our configuration parameters
     */
    void MatchConfig::init( float trans_x, float trans_y, float rotate_2, float scale_x, float scale_y, float rotate_1 ) {
        this->translateX    = trans_x;
        this->translateY    = trans_y;
        this->rotate2       = rotate_2;
        this->scaleX        = scale_x;
        this->scaleY        = scale_y;
        this->rotate1       = rotate_1;
        this->affine        = asAffineMatrix();
    }

    vector<MatchConfig> MatchConfig::fromMatrix(Mat& configs) {
        vector<MatchConfig> result( configs.rows );
        tbb::parallel_for(0, configs.rows, 1, [&]( int i ){
            float * ptr = configs.ptr<float>(i);
            result[i].init( ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5] );
        });
        return result;
    }
    
    /**
     * Create an affine transformation matrix from the configurations
     */
    Mat MatchConfig::asMatrix() {
        return (Mat_<float>(1, 6) << translateX, translateY, rotate2, scaleX, scaleY, rotate1 );
    }
    
    /**
     * Returns the affine matrix representation of the configuration
     */
    Mat MatchConfig::getAffineMatrix() {
        return this->affine;
    }
    
    /**
     * Create an affine transformation matrix from the configurations
     */
    Mat MatchConfig::asAffineMatrix() {
        /* [TODO] Should use lookup table or something in the future */
        float   cos_r1 = cosf( rotate1 ),
                sin_r1 = sinf( rotate1 ),
                cos_r2 = cosf( rotate2 ),
                sin_r2 = sinf( rotate2 );
        
        /* Create affine matrix based on the configuration */
        float   a11 =  scaleX * cos_r1 * cos_r2 - scaleY * sin_r1 * sin_r2,
                a12 = -scaleX * cos_r1 * sin_r2 - scaleY * cos_r2 * sin_r1,
                a21 =  scaleX * cos_r2 * sin_r1 + scaleY * cos_r1 * sin_r2,
                a22 =  scaleY * cos_r1 * cos_r2 - scaleX * sin_r1 * sin_r2;
        
        return (Mat_<float>(2, 3) <<
                a11, a12, translateX,
                a21, a22, translateY );
    }


    /**
     * Stream operator overloading
     */
    ostream& operator<<( ostream& os, const MatchConfig& config ) {
        os << "[" << config.translateX  << ", " << config.translateY << ", "
                  << config.rotate2     << ", " << config.scaleX     << ", "
                  << config.scaleY      << ", " << config.rotate1    << "]";
        return os;
    }
}


MatchConfig.h

//
//  MatchConfig.h
//  FAsT-Match
//
//  Created by Saburo Okita on 02/06/14.
//  Copyright (c) 2014 Saburo Okita. All rights reserved.
//

#ifndef __FAsT_Match__MatchConfig__
#define __FAsT_Match__MatchConfig__

#include <iostream>
#include <opencv2/opencv.hpp>

namespace fast_match {
    /**
     * Config class that describes the parameters used in creating affine transformations
     */
    class MatchConfig {
    public:
        MatchConfig();
        MatchConfig( const MatchConfig& object );
        MatchConfig( float trans_x, float trans_y, float rotate_2, float scale_x, float scale_y, float rotate_1 );
        void init( float trans_x, float trans_y, float rotate_2, float scale_x, float scale_y, float rotate_1 );
        
        static std::vector<MatchConfig> fromMatrix( cv::Mat& configs);
        cv::Mat asMatrix();
        cv::Mat asAffineMatrix();
        cv::Mat getAffineMatrix();
        
        friend std::ostream &operator <<( std::ostream& os, const MatchConfig & conf );
        
        
    protected:
        float translateX;
        float translateY;
        float rotate2;
        float rotate1;
        float scaleX;
        float scaleY;
        
        cv::Mat affine;
    };
}



#endif /* defined(__FAsT_Match__MatchConfig__) */


MatchNet.cpp

//
//  MatchNet.cpp
//  FAsT-Match
//
//  Created by Saburo Okita on 02/06/14.
//  Copyright (c) 2014 Saburo Okita. All rights reserved.
//

#include "MatchNet.h"
#include <cmath>
using namespace std;

namespace fast_match {
    
    MatchNet::MatchNet( const MatchNet& other ) {
        this->boundsTransX = other.boundsTransX;
        this->boundsTransY = other.boundsTransY;
        this->boundsRotate = other.boundsRotate;
        this->boundsScale  = other.boundsScale;
        
        this->stepsTransX = other.stepsTransX;
        this->stepsTransY = other.stepsTransY;
        this->stepsRotate = other.stepsRotate;
        this->stepsScale  = other.stepsScale;
    }
    
    MatchNet::MatchNet( int width, int height, float delta,
                        float min_tx, float max_tx, float min_ty, float max_ty,
                        float min_r, float max_r, float min_s, float max_s )
    {
        this->boundsTransX = { min_tx, max_tx };
        this->boundsTransY = { min_ty, max_ty };
        this->boundsRotate = { min_r, max_r };
        this->boundsScale  = { min_s, max_s };

        this->stepsTransX = delta * width  / sqrt(2.0f);
        this->stepsTransY = delta * height / sqrt(2.0f);
        this->stepsRotate = delta * sqrt( 2.0f );
        this->stepsScale  = delta / sqrt( 2.0f );
    }
    
    
    MatchNet MatchNet::operator*(const float& factor) const {
        MatchNet result(*this);
        result.stepsTransX *= factor;
        result.stepsTransY *= factor;
        result.stepsRotate *= factor;
        result.stepsScale  *= factor;
        return result;
    }
    
    MatchNet MatchNet::operator/(const float& factor) const {
        MatchNet result(*this);
        result.stepsTransX /= factor;
        result.stepsTransY /= factor;
        result.stepsRotate /= factor;
        result.stepsScale  /= factor;
        return result;
    }
    
    
    vector<float> MatchNet::getXTranslationSteps() {
        vector<float> tx_steps;
        for( float x = boundsTransX.first; x <= boundsTransX.second; x += stepsTransX )
            tx_steps.push_back( x );
        
        if( boundsTransX.second - tx_steps[tx_steps.size() - 1] > 0.5 * stepsTransX )
            tx_steps.push_back( tx_steps[tx_steps.size() - 1] + stepsTransX );
        
        return tx_steps;
    }
    
    vector<float> MatchNet::getYTranslationSteps() {
        vector<float> ty_steps;
        for( float y = boundsTransY.first; y <= boundsTransY.second; y += stepsTransY )
            ty_steps.push_back( y );
        
        if( boundsTransY.second - ty_steps[ty_steps.size() - 1] > 0.5 * stepsTransY )
            ty_steps.push_back( ty_steps[ty_steps.size() - 1] + stepsTransY );
        
        return ty_steps;
    }
    
    vector<float> MatchNet::getRotationSteps() {
        vector<float> r_steps;
        for( float r = boundsRotate.first; r <= boundsRotate.second; r += stepsRotate )
            r_steps.push_back( r );
        
        if( boundsRotate.second - r_steps[r_steps.size() - 1] > stepsRotate )
            r_steps.push_back( r_steps[r_steps.size() - 1] + stepsRotate );
        
        return r_steps;
    }
    
    vector<float> MatchNet::getScaleSteps() {
        vector<float> s_steps;
        for( float s = boundsScale.first; s <= boundsScale.second; s += stepsScale )
            s_steps.push_back( s );
        
        if( stepsScale == 0.0 )
            s_steps = { boundsScale.first };
        
        if( boundsScale.second - s_steps[s_steps.size() - 1] > 0.5 * stepsScale )
            s_steps.push_back( s_steps[s_steps.size() - 1] + stepsScale );
        
        return s_steps;
    }
}

MatchNet.h

//
//  MatchNet.h
//  FAsT-Match
//
//  Created by Saburo Okita on 02/06/14.
//  Copyright (c) 2014 Saburo Okita. All rights reserved.
//

#ifndef __FAsT_Match__MatchNet__
#define __FAsT_Match__MatchNet__

#include <iostream>
#include <vector>

namespace fast_match {
    
    /**
     * A representation of the 
     */
    class MatchNet {
    public:
        MatchNet( int width, int height, float delta,
                  float min_tx, float max_tx, float min_ty, float max_ty,
                  float min_r, float max_r, float min_s, float max_s );
        
        MatchNet( const MatchNet& other );
        
        
        MatchNet operator*(const float& factor)const;
        MatchNet operator/(const float& factor) const;
        
        std::vector<float> getXTranslationSteps();
        std::vector<float> getYTranslationSteps();
        std::vector<float> getRotationSteps();
        std::vector<float> getScaleSteps();
        
        std::pair<float, float> boundsTransX;
        std::pair<float, float> boundsTransY;
        std::pair<float, float> boundsRotate;
        std::pair<float, float> boundsScale;
        
        float stepsTransX;
        float stepsTransY;
        float stepsRotate;
        float stepsScale;
        
        
        
    };
}
#endif /* defined(__FAsT_Match__MatchNet__) */


main.cpp

//
//  main.cpp
//  FAsT-Match
//
//  Created by Saburo Okita on 22/05/14.
//  Copyright (c) 2014 Saburo Okita. All rights reserved.
//

#include <iostream>
#include <opencv2/opencv.hpp>

#include "FAsTMatch.h"
#include "MatchConfig.h"

using namespace std;
using namespace cv;

int main(int argc, const char * argv[])
{

    Mat image = imread( "/Users/saburookita/Personal Projects/FAsT-Match/image.png" );
    Mat templ = imread( "/Users/saburookita/Personal Projects/FAsT-Match/template.png" );
    
    fast_match::FAsTMatch fast_match;
    fast_match.init( 0.15f, 0.85f, false, 0.5f, 2.0f );
    vector<Point2f> corners = fast_match.apply( image, templ );

    
    namedWindow("");
    moveWindow("", 0, 0);
    
    line( image, corners[0], corners[1], Scalar(0, 0, 255), 2);
    line( image, corners[1], corners[2], Scalar(0, 0, 255), 2);
    line( image, corners[2], corners[3], Scalar(0, 0, 255), 2);
    line( image, corners[3], corners[0], Scalar(0, 0, 255), 2);
    
    Mat appended( image.rows, 2 * image.cols, CV_8UC3, Scalar(0, 0, 0) );
    
    CvFont font = cvFontQt("Helvetica", 14.0, CV_RGB(0, 255, 0) );
    addText( appended, "Template: ", Point( 50, 50 ), font );
    templ.copyTo( Mat(appended, Rect((image.cols - templ.cols) / 2, (image.rows - templ.cols) / 2, templ.cols, templ.rows)) );
    image.copyTo( Mat(appended, Rect( image.cols, 0, image.cols, image.rows)) );
    
    imshow("", appended );
    waitKey(0);
    
    return 0;
}

转载自:点击打开链接

 类似资料: