opencv/modules/contrib/src/chamfermatching.cpp
Andrey Kamaev 2a6fb2867e Remove all using directives for STL namespace and members
Made all STL usages explicit to be able automatically find all usages of
particular class or function.
2013-02-25 15:04:17 +04:00

1406 lines
42 KiB
C++

/*********************************************************************
* Software License Agreement (BSD License)
*
* Copyright (c) 2008-2010, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the Willow Garage nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*********************************************************************/
//
// The original code was written by
// Marius Muja
// and later modified and prepared
// for integration into OpenCV by
// Antonella Cascitelli,
// Marco Di Stefano and
// Stefano Fabri
// from Univ. of Rome
//
#include "precomp.hpp"
#include "opencv2/opencv_modules.hpp"
#ifdef HAVE_OPENCV_HIGHGUI
# include "opencv2/highgui/highgui.hpp"
#endif
#include <iostream>
#include <queue>
namespace cv
{
typedef std::pair<int,int> coordinate_t;
typedef float orientation_t;
typedef std::vector<coordinate_t> template_coords_t;
typedef std::vector<orientation_t> template_orientations_t;
typedef std::pair<Point, float> location_scale_t;
class ChamferMatcher
{
private:
class Matching;
int max_matches_;
float min_match_distance_;
///////////////////////// Image iterators ////////////////////////////
class ImageIterator
{
public:
virtual ~ImageIterator() {}
virtual bool hasNext() const = 0;
virtual location_scale_t next() = 0;
};
class ImageRange
{
public:
virtual ImageIterator* iterator() const = 0;
virtual ~ImageRange() {}
};
// Sliding window
class SlidingWindowImageRange : public ImageRange
{
int width_;
int height_;
int x_step_;
int y_step_;
int scales_;
float min_scale_;
float max_scale_;
public:
SlidingWindowImageRange(int width, int height, int x_step = 3, int y_step = 3, int _scales = 5, float min_scale = 0.6, float max_scale = 1.6) :
width_(width), height_(height), x_step_(x_step),y_step_(y_step), scales_(_scales), min_scale_(min_scale), max_scale_(max_scale)
{
}
ImageIterator* iterator() const;
};
class LocationImageRange : public ImageRange
{
const std::vector<Point>& locations_;
int scales_;
float min_scale_;
float max_scale_;
LocationImageRange(const LocationImageRange&);
LocationImageRange& operator=(const LocationImageRange&);
public:
LocationImageRange(const std::vector<Point>& locations, int _scales = 5, float min_scale = 0.6, float max_scale = 1.6) :
locations_(locations), scales_(_scales), min_scale_(min_scale), max_scale_(max_scale)
{
}
ImageIterator* iterator() const
{
return new LocationImageIterator(locations_, scales_, min_scale_, max_scale_);
}
};
class LocationScaleImageRange : public ImageRange
{
const std::vector<Point>& locations_;
const std::vector<float>& scales_;
LocationScaleImageRange(const LocationScaleImageRange&);
LocationScaleImageRange& operator=(const LocationScaleImageRange&);
public:
LocationScaleImageRange(const std::vector<Point>& locations, const std::vector<float>& _scales) :
locations_(locations), scales_(_scales)
{
assert(locations.size()==_scales.size());
}
ImageIterator* iterator() const
{
return new LocationScaleImageIterator(locations_, scales_);
}
};
public:
/**
* Class that represents a template for chamfer matching.
*/
class Template
{
friend class ChamferMatcher::Matching;
friend class ChamferMatcher;
public:
std::vector<Template*> scaled_templates;
std::vector<int> addr;
int addr_width;
float scale;
template_coords_t coords;
template_orientations_t orientations;
Size size;
Point center;
public:
Template() : addr_width(-1)
{
}
Template(Mat& edge_image, float scale_ = 1);
~Template()
{
for (size_t i=0;i<scaled_templates.size();++i) {
delete scaled_templates[i];
}
scaled_templates.clear();
coords.clear();
orientations.clear();
}
void show() const;
private:
/**
* Resizes a template
*
* @param scale Scale to be resized to
*/
Template* rescale(float scale);
std::vector<int>& getTemplateAddresses(int width);
};
/**
* Used to represent a matching result.
*/
class Match
{
public:
float cost;
Point offset;
const Template* tpl;
};
typedef std::vector<Match> Matches;
private:
/**
* Implements the chamfer matching algorithm on images taking into account both distance from
* the template pixels to the nearest pixels and orientation alignment between template and image
* contours.
*/
class Matching
{
float truncate_;
bool use_orientation_;
std::vector<Template*> templates;
public:
Matching(bool use_orientation = true, float _truncate = 10) : truncate_(_truncate), use_orientation_(use_orientation)
{
}
~Matching()
{
for (size_t i = 0; i<templates.size(); i++) {
delete templates[i];
}
}
/**
* Add a template to the detector from an edge image.
* @param templ An edge image
*/
void addTemplateFromImage(Mat& templ, float scale = 1.0);
/**
* Run matching using an edge image.
* @param edge_img Edge image
* @return a match object
*/
ChamferMatcher::Matches* matchEdgeImage(Mat& edge_img, const ImageRange& range, float orientation_weight = 0.5, int max_matches = 20, float min_match_distance = 10.0);
void addTemplate(Template& template_);
private:
float orientation_diff(float o1, float o2)
{
return fabs(o1-o2);
}
/**
* Computes the chamfer matching cost for one position in the target image.
* @param offset Offset where to compute cost
* @param dist_img Distance transform image.
* @param orientation_img Orientation image.
* @param tpl Template
* @param templ_orientations Orientations of the target points.
* @return matching result
*/
ChamferMatcher::Match* localChamferDistance(Point offset, Mat& dist_img, Mat& orientation_img, Template* tpl, float orientation_weight);
private:
/**
* Matches all templates.
* @param dist_img Distance transform image.
* @param orientation_img Orientation image.
*/
ChamferMatcher::Matches* matchTemplates(Mat& dist_img, Mat& orientation_img, const ImageRange& range, float orientation_weight);
void computeDistanceTransform(Mat& edges_img, Mat& dist_img, Mat& annotate_img, float truncate_dt, float a, float b);
void computeEdgeOrientations(Mat& edge_img, Mat& orientation_img);
void fillNonContourOrientations(Mat& annotated_img, Mat& orientation_img);
public:
/**
* Finds a contour in an edge image. The original image is altered by removing the found contour.
* @param templ_img Edge image
* @param coords Coordinates forming the contour.
* @return True while a contour is still found in the image.
*/
static bool findContour(Mat& templ_img, template_coords_t& coords);
/**
* Computes contour points orientations using the approach from:
*
* Matas, Shao and Kittler - Estimation of Curvature and Tangent Direction by
* Median Filtered Differencing
*
* @param coords Contour points
* @param orientations Contour points orientations
*/
static void findContourOrientations(const template_coords_t& coords, template_orientations_t& orientations);
/**
* Computes the angle of a line segment.
*
* @param a One end of the line segment
* @param b The other end.
* @param dx
* @param dy
* @return Angle in radians.
*/
static float getAngle(coordinate_t a, coordinate_t b, int& dx, int& dy);
/**
* Finds a point in the image from which to start contour following.
* @param templ_img
* @param p
* @return
*/
static bool findFirstContourPoint(Mat& templ_img, coordinate_t& p);
/**
* Method that extracts a single continuous contour from an image given a starting point.
* When it extracts the contour it tries to maintain the same direction (at a T-join for example).
*
* @param templ_
* @param coords
* @param direction
*/
static void followContour(Mat& templ_img, template_coords_t& coords, int direction);
};
class LocationImageIterator : public ImageIterator
{
const std::vector<Point>& locations_;
size_t iter_;
int scales_;
float min_scale_;
float max_scale_;
float scale_;
float scale_step_;
int scale_cnt_;
bool has_next_;
LocationImageIterator(const LocationImageIterator&);
LocationImageIterator& operator=(const LocationImageIterator&);
public:
LocationImageIterator(const std::vector<Point>& locations, int _scales, float min_scale, float max_scale);
bool hasNext() const {
return has_next_;
}
location_scale_t next();
};
class LocationScaleImageIterator : public ImageIterator
{
const std::vector<Point>& locations_;
const std::vector<float>& scales_;
size_t iter_;
bool has_next_;
LocationScaleImageIterator(const LocationScaleImageIterator&);
LocationScaleImageIterator& operator=(const LocationScaleImageIterator&);
public:
LocationScaleImageIterator(const std::vector<Point>& locations, const std::vector<float>& _scales) :
locations_(locations), scales_(_scales)
{
assert(locations.size()==_scales.size());
reset();
}
void reset()
{
iter_ = 0;
has_next_ = (locations_.size()==0 ? false : true);
}
bool hasNext() const {
return has_next_;
}
location_scale_t next();
};
class SlidingWindowImageIterator : public ImageIterator
{
int x_;
int y_;
float scale_;
float scale_step_;
int scale_cnt_;
bool has_next_;
int width_;
int height_;
int x_step_;
int y_step_;
int scales_;
float min_scale_;
float max_scale_;
public:
SlidingWindowImageIterator(int width, int height, int x_step, int y_step, int scales, float min_scale, float max_scale);
bool hasNext() const {
return has_next_;
}
location_scale_t next();
};
int count;
Matches matches;
int pad_x;
int pad_y;
int scales;
float minScale;
float maxScale;
float orientation_weight;
float truncate;
Matching * chamfer_;
public:
ChamferMatcher(int _max_matches = 20, float _min_match_distance = 1.0, int _pad_x = 3,
int _pad_y = 3, int _scales = 5, float _minScale = 0.6, float _maxScale = 1.6,
float _orientation_weight = 0.5, float _truncate = 20)
{
max_matches_ = _max_matches;
min_match_distance_ = _min_match_distance;
pad_x = _pad_x;
pad_y = _pad_y;
scales = _scales;
minScale = _minScale;
maxScale = _maxScale;
orientation_weight = _orientation_weight;
truncate = _truncate;
count = 0;
matches.resize(max_matches_);
chamfer_ = new Matching(true);
}
void showMatch(Mat& img, int index = 0);
void showMatch(Mat& img, Match match_);
const Matches& matching(Template&, Mat&);
private:
void addMatch(float cost, Point offset, const Template* tpl);
};
///////////////////// implementation ///////////////////////////
ChamferMatcher::SlidingWindowImageIterator::SlidingWindowImageIterator( int width,
int height,
int x_step = 3,
int y_step = 3,
int _scales = 5,
float min_scale = 0.6,
float max_scale = 1.6) :
width_(width),
height_(height),
x_step_(x_step),
y_step_(y_step),
scales_(_scales),
min_scale_(min_scale),
max_scale_(max_scale)
{
x_ = 0;
y_ = 0;
scale_cnt_ = 0;
scale_ = min_scale_;
has_next_ = true;
scale_step_ = (max_scale_-min_scale_)/scales_;
}
location_scale_t ChamferMatcher::SlidingWindowImageIterator::next()
{
location_scale_t next_val = std::make_pair(Point(x_,y_),scale_);
x_ += x_step_;
if (x_ >= width_) {
x_ = 0;
y_ += y_step_;
if (y_ >= height_) {
y_ = 0;
scale_ += scale_step_;
scale_cnt_++;
if (scale_cnt_ == scales_) {
has_next_ = false;
scale_cnt_ = 0;
scale_ = min_scale_;
}
}
}
return next_val;
}
ChamferMatcher::ImageIterator* ChamferMatcher::SlidingWindowImageRange::iterator() const
{
return new SlidingWindowImageIterator(width_, height_, x_step_, y_step_, scales_, min_scale_, max_scale_);
}
ChamferMatcher::LocationImageIterator::LocationImageIterator(const std::vector<Point>& locations,
int _scales = 5,
float min_scale = 0.6,
float max_scale = 1.6) :
locations_(locations),
scales_(_scales),
min_scale_(min_scale),
max_scale_(max_scale)
{
iter_ = 0;
scale_cnt_ = 0;
scale_ = min_scale_;
has_next_ = (locations_.size()==0 ? false : true);
scale_step_ = (max_scale_-min_scale_)/scales_;
}
location_scale_t ChamferMatcher::LocationImageIterator:: next()
{
location_scale_t next_val = std::make_pair(locations_[iter_],scale_);
iter_ ++;
if (iter_==locations_.size()) {
iter_ = 0;
scale_ += scale_step_;
scale_cnt_++;
if (scale_cnt_ == scales_) {
has_next_ = false;
scale_cnt_ = 0;
scale_ = min_scale_;
}
}
return next_val;
}
location_scale_t ChamferMatcher::LocationScaleImageIterator::next()
{
location_scale_t next_val = std::make_pair(locations_[iter_],scales_[iter_]);
iter_ ++;
if (iter_==locations_.size()) {
iter_ = 0;
has_next_ = false;
}
return next_val;
}
bool ChamferMatcher::Matching::findFirstContourPoint(Mat& templ_img, coordinate_t& p)
{
for (int y=0;y<templ_img.rows;++y) {
for (int x=0;x<templ_img.cols;++x) {
if (templ_img.at<uchar>(y,x)!=0) {
p.first = x;
p.second = y;
return true;
}
}
}
return false;
}
void ChamferMatcher::Matching::followContour(Mat& templ_img, template_coords_t& coords, int direction = -1)
{
const int dir[][2] = { {-1,-1}, {-1,0}, {-1,1}, {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1} };
coordinate_t next;
unsigned char ptr;
assert (direction==-1 || !coords.empty());
coordinate_t crt = coords.back();
// mark the current pixel as visited
templ_img.at<uchar>(crt.second,crt.first) = 0;
if (direction==-1) {
for (int j = 0; j<7; ++j) {
next.first = crt.first + dir[j][1];
next.second = crt.second + dir[j][0];
if (next.first >= 0 && next.first < templ_img.cols &&
next.second >= 0 && next.second < templ_img.rows){
ptr = templ_img.at<uchar>(next.second, next.first);
if (ptr!=0) {
coords.push_back(next);
followContour(templ_img, coords,j);
// try to continue contour in the other direction
reverse(coords.begin(), coords.end());
followContour(templ_img, coords, (j+4)%8);
break;
}
}
}
}
else {
int k = direction;
int k_cost = 3;
next.first = crt.first + dir[k][1];
next.second = crt.second + dir[k][0];
if (next.first >= 0 && next.first < templ_img.cols &&
next.second >= 0 && next.second < templ_img.rows){
ptr = templ_img.at<uchar>(next.second, next.first);
if (ptr!=0) {
k_cost = std::abs(dir[k][1]) + std::abs(dir[k][0]);
}
int p = k;
int n = k;
for (int j = 0 ;j<3; ++j) {
p = (p + 7) % 8;
n = (n + 1) % 8;
next.first = crt.first + dir[p][1];
next.second = crt.second + dir[p][0];
if (next.first >= 0 && next.first < templ_img.cols &&
next.second >= 0 && next.second < templ_img.rows){
ptr = templ_img.at<uchar>(next.second, next.first);
if (ptr!=0) {
int p_cost = std::abs(dir[p][1]) + std::abs(dir[p][0]);
if (p_cost<k_cost) {
k_cost = p_cost;
k = p;
}
}
next.first = crt.first + dir[n][1];
next.second = crt.second + dir[n][0];
if (next.first >= 0 && next.first < templ_img.cols &&
next.second >= 0 && next.second < templ_img.rows){
ptr = templ_img.at<uchar>(next.second, next.first);
if (ptr!=0) {
int n_cost = std::abs(dir[n][1]) + std::abs(dir[n][0]);
if (n_cost<k_cost) {
k_cost = n_cost;
k = n;
}
}
}
}
}
if (k_cost!=3) {
next.first = crt.first + dir[k][1];
next.second = crt.second + dir[k][0];
if (next.first >= 0 && next.first < templ_img.cols &&
next.second >= 0 && next.second < templ_img.rows) {
coords.push_back(next);
followContour(templ_img, coords, k);
}
}
}
}
}
bool ChamferMatcher::Matching::findContour(Mat& templ_img, template_coords_t& coords)
{
coordinate_t start_point;
bool found = findFirstContourPoint(templ_img,start_point);
if (found) {
coords.push_back(start_point);
followContour(templ_img, coords);
return true;
}
return false;
}
float ChamferMatcher::Matching::getAngle(coordinate_t a, coordinate_t b, int& dx, int& dy)
{
dx = b.first-a.first;
dy = -(b.second-a.second); // in image coordinated Y axis points downward
float angle = atan2((float)dy,(float)dx);
if (angle<0) {
angle+=(float)CV_PI;
}
return angle;
}
void ChamferMatcher::Matching::findContourOrientations(const template_coords_t& coords, template_orientations_t& orientations)
{
const int M = 5;
int coords_size = (int)coords.size();
std::vector<float> angles(2*M);
orientations.insert(orientations.begin(), coords_size, float(-3*CV_PI)); // mark as invalid in the beginning
if (coords_size<2*M+1) { // if contour not long enough to estimate orientations, abort
return;
}
for (int i=M;i<coords_size-M;++i) {
coordinate_t crt = coords[i];
coordinate_t other;
int k = 0;
int dx, dy;
// compute previous M angles
for (int j=M;j>0;--j) {
other = coords[i-j];
angles[k++] = getAngle(other,crt, dx, dy);
}
// compute next M angles
for (int j=1;j<=M;++j) {
other = coords[i+j];
angles[k++] = getAngle(crt, other, dx, dy);
}
// get the middle two angles
std::nth_element(angles.begin(), angles.begin()+M-1, angles.end());
std::nth_element(angles.begin()+M-1, angles.begin()+M, angles.end());
// sort(angles.begin(), angles.end());
// average them to compute tangent
orientations[i] = (angles[M-1]+angles[M])/2;
}
}
//////////////////////// Template /////////////////////////////////////
ChamferMatcher::Template::Template(Mat& edge_image, float scale_) : addr_width(-1), scale(scale_)
{
template_coords_t local_coords;
template_orientations_t local_orientations;
while (ChamferMatcher::Matching::findContour(edge_image, local_coords)) {
ChamferMatcher::Matching::findContourOrientations(local_coords, local_orientations);
coords.insert(coords.end(), local_coords.begin(), local_coords.end());
orientations.insert(orientations.end(), local_orientations.begin(), local_orientations.end());
local_coords.clear();
local_orientations.clear();
}
size = edge_image.size();
Point min, max;
min.x = size.width;
min.y = size.height;
max.x = 0;
max.y = 0;
center = Point(0,0);
for (size_t i=0;i<coords.size();++i) {
center.x += coords[i].first;
center.y += coords[i].second;
if (min.x>coords[i].first) min.x = coords[i].first;
if (min.y>coords[i].second) min.y = coords[i].second;
if (max.x<coords[i].first) max.x = coords[i].first;
if (max.y<coords[i].second) max.y = coords[i].second;
}
size.width = max.x - min.x;
size.height = max.y - min.y;
int coords_size = (int)coords.size();
center.x /= MAX(coords_size, 1);
center.y /= MAX(coords_size, 1);
for (int i=0;i<coords_size;++i) {
coords[i].first -= center.x;
coords[i].second -= center.y;
}
}
std::vector<int>& ChamferMatcher::Template::getTemplateAddresses(int width)
{
if (addr_width!=width) {
addr.resize(coords.size());
addr_width = width;
for (size_t i=0; i<coords.size();++i) {
addr[i] = coords[i].second*width+coords[i].first;
}
}
return addr;
}
/**
* Resizes a template
*
* @param scale Scale to be resized to
*/
ChamferMatcher::Template* ChamferMatcher::Template::rescale(float new_scale)
{
if (fabs(scale-new_scale)<1e-6) return this;
for (size_t i=0;i<scaled_templates.size();++i) {
if (fabs(scaled_templates[i]->scale-new_scale)<1e-6) {
return scaled_templates[i];
}
}
float scale_factor = new_scale/scale;
Template* tpl = new Template();
tpl->scale = new_scale;
tpl->center.x = int(center.x*scale_factor+0.5);
tpl->center.y = int(center.y*scale_factor+0.5);
tpl->size.width = int(size.width*scale_factor+0.5);
tpl->size.height = int(size.height*scale_factor+0.5);
tpl->coords.resize(coords.size());
tpl->orientations.resize(orientations.size());
for (size_t i=0;i<coords.size();++i) {
tpl->coords[i].first = int(coords[i].first*scale_factor+0.5);
tpl->coords[i].second = int(coords[i].second*scale_factor+0.5);
tpl->orientations[i] = orientations[i];
}
scaled_templates.push_back(tpl);
return tpl;
}
void ChamferMatcher::Template::show() const
{
int pad = 50;
//Attention size is not correct
Mat templ_color (Size(size.width+(pad*2), size.height+(pad*2)), CV_8UC3);
templ_color.setTo(0);
for (size_t i=0;i<coords.size();++i) {
int x = center.x+coords[i].first+pad;
int y = center.y+coords[i].second+pad;
templ_color.at<Vec3b>(y,x)[1]=255;
//CV_PIXEL(unsigned char, templ_color,x,y)[1] = 255;
if (i%3==0) {
if (orientations[i] < -CV_PI) {
continue;
}
Point p1;
p1.x = x;
p1.y = y;
Point p2;
p2.x = x + pad*(int)(sin(orientations[i])*100)/100;
p2.y = y + pad*(int)(cos(orientations[i])*100)/100;
line(templ_color, p1,p2, CV_RGB(255,0,0));
}
}
circle(templ_color,Point(center.x + pad, center.y + pad),1,CV_RGB(0,255,0));
#ifdef HAVE_OPENCV_HIGHGUI
namedWindow("templ",1);
imshow("templ",templ_color);
cvWaitKey(0);
#else
CV_Error(CV_StsNotImplemented, "OpenCV has been compiled without GUI support");
#endif
templ_color.release();
}
//////////////////////// Matching /////////////////////////////////////
void ChamferMatcher::Matching::addTemplateFromImage(Mat& templ, float scale)
{
Template* cmt = new Template(templ, scale);
templates.clear();
templates.push_back(cmt);
cmt->show();
}
void ChamferMatcher::Matching::addTemplate(Template& template_){
templates.clear();
templates.push_back(&template_);
}
/**
* Alternative version of computeDistanceTransform, will probably be used to compute distance
* transform annotated with edge orientation.
*/
void ChamferMatcher::Matching::computeDistanceTransform(Mat& edges_img, Mat& dist_img, Mat& annotate_img, float truncate_dt, float a = 1.0, float b = 1.5)
{
int d[][2] = { {-1,-1}, { 0,-1}, { 1,-1},
{-1,0}, { 1,0},
{-1,1}, { 0,1}, { 1,1} };
Size s = edges_img.size();
int w = s.width;
int h = s.height;
// set distance to the edge pixels to 0 and put them in the queue
std::queue<std::pair<int,int> > q;
for (int y=0;y<h;++y) {
for (int x=0;x<w;++x) {
// initialize
if (&annotate_img!=NULL) {
annotate_img.at<Vec2i>(y,x)[0]=x;
annotate_img.at<Vec2i>(y,x)[1]=y;
}
uchar edge_val = edges_img.at<uchar>(y,x);
if( (edge_val!=0) ) {
q.push(std::make_pair(x,y));
dist_img.at<float>(y,x)= 0;
}
else {
dist_img.at<float>(y,x)=-1;
}
}
}
// breadth first computation of distance transform
std::pair<int,int> crt;
while (!q.empty()) {
crt = q.front();
q.pop();
int x = crt.first;
int y = crt.second;
float dist_orig = dist_img.at<float>(y,x);
float dist;
for (size_t i=0;i<sizeof(d)/sizeof(d[0]);++i) {
int nx = x + d[i][0];
int ny = y + d[i][1];
if (nx<0 || ny<0 || nx>=w || ny>=h) continue;
if (std::abs(d[i][0]+d[i][1])==1) {
dist = (dist_orig)+a;
}
else {
dist = (dist_orig)+b;
}
float dt = dist_img.at<float>(ny,nx);
if (dt==-1 || dt>dist) {
dist_img.at<float>(ny,nx) = dist;
q.push(std::make_pair(nx,ny));
if (&annotate_img!=NULL) {
annotate_img.at<Vec2i>(ny,nx)[0]=annotate_img.at<Vec2i>(y,x)[0];
annotate_img.at<Vec2i>(ny,nx)[1]=annotate_img.at<Vec2i>(y,x)[1];
}
}
}
}
// truncate dt
if (truncate_dt>0) {
Mat dist_img_thr = dist_img.clone();
threshold(dist_img, dist_img_thr, truncate_dt,0.0 ,THRESH_TRUNC);
dist_img_thr.copyTo(dist_img);
}
}
void ChamferMatcher::Matching::computeEdgeOrientations(Mat& edge_img, Mat& orientation_img)
{
Mat contour_img(edge_img.size(), CV_8UC1);
orientation_img.setTo(3*(-CV_PI));
template_coords_t coords;
template_orientations_t orientations;
while (ChamferMatcher::Matching::findContour(edge_img, coords)) {
ChamferMatcher::Matching::findContourOrientations(coords, orientations);
// set orientation pixel in orientation image
for (size_t i = 0; i<coords.size();++i) {
int x = coords[i].first;
int y = coords[i].second;
// if (orientations[i]>-CV_PI)
// {
//CV_PIXEL(unsigned char, contour_img, x, y)[0] = 255;
contour_img.at<uchar>(y,x)=255;
// }
//CV_PIXEL(float, orientation_img, x, y)[0] = orientations[i];
orientation_img.at<float>(y,x)=orientations[i];
}
coords.clear();
orientations.clear();
}
//imwrite("contours.pgm", contour_img);
}
void ChamferMatcher::Matching::fillNonContourOrientations(Mat& annotated_img, Mat& orientation_img)
{
int cols = annotated_img.cols;
int rows = annotated_img.rows;
assert(orientation_img.cols==cols && orientation_img.rows==rows);
for (int y=0;y<rows;++y) {
for (int x=0;x<cols;++x) {
int xorig = annotated_img.at<Vec2i>(y,x)[0];
int yorig = annotated_img.at<Vec2i>(y,x)[1];
if (x!=xorig || y!=yorig) {
//orientation_img.at<float>(yorig,xorig)=orientation_img.at<float>(y,x);
orientation_img.at<float>(y,x)=orientation_img.at<float>(yorig,xorig);
}
}
}
}
ChamferMatcher::Match* ChamferMatcher::Matching::localChamferDistance(Point offset, Mat& dist_img, Mat& orientation_img,
ChamferMatcher::Template* tpl, float alpha)
{
int x = offset.x;
int y = offset.y;
float beta = 1-alpha;
std::vector<int>& addr = tpl->getTemplateAddresses(dist_img.cols);
float* ptr = dist_img.ptr<float>(y)+x;
float sum_distance = 0;
for (size_t i=0; i<addr.size();++i) {
if(addr[i] < (dist_img.cols*dist_img.rows) - (offset.y*dist_img.cols + offset.x)){
sum_distance += *(ptr+addr[i]);
}
}
float cost = (sum_distance/truncate_)/addr.size();
if (&orientation_img!=NULL) {
float* optr = orientation_img.ptr<float>(y)+x;
float sum_orientation = 0;
int cnt_orientation = 0;
for (size_t i=0;i<addr.size();++i) {
if(addr[i] < (orientation_img.cols*orientation_img.rows) - (offset.y*orientation_img.cols + offset.x)){
if (tpl->orientations[i]>=-CV_PI && (*(optr+addr[i]))>=-CV_PI) {
sum_orientation += orientation_diff(tpl->orientations[i], (*(optr+addr[i])));
cnt_orientation++;
}
}
}
if (cnt_orientation>0) {
cost = (float)(beta*cost+alpha*(sum_orientation/(2*CV_PI))/cnt_orientation);
}
}
if(cost > 0){
ChamferMatcher::Match* istance = new ChamferMatcher::Match();
istance->cost = cost;
istance->offset = offset;
istance->tpl = tpl;
return istance;
}
return NULL;
}
ChamferMatcher::Matches* ChamferMatcher::Matching::matchTemplates(Mat& dist_img, Mat& orientation_img, const ImageRange& range, float _orientation_weight)
{
ChamferMatcher::Matches* pmatches(new Matches());
// try each template
for(size_t i = 0; i < templates.size(); i++) {
ImageIterator* it = range.iterator();
while (it->hasNext()) {
location_scale_t crt = it->next();
Point loc = crt.first;
float scale = crt.second;
Template* tpl = templates[i]->rescale(scale);
if (loc.x-tpl->center.x<0 || loc.x+tpl->size.width/2>=dist_img.cols) continue;
if (loc.y-tpl->center.y<0 || loc.y+tpl->size.height/2>=dist_img.rows) continue;
ChamferMatcher::Match* is = localChamferDistance(loc, dist_img, orientation_img, tpl, _orientation_weight);
if(is)
{
pmatches->push_back(*is);
delete is;
}
}
delete it;
}
return pmatches;
}
/**
* Run matching using an edge image.
* @param edge_img Edge image
* @return a match object
*/
ChamferMatcher::Matches* ChamferMatcher::Matching::matchEdgeImage(Mat& edge_img, const ImageRange& range,
float _orientation_weight, int /*max_matches*/, float /*min_match_distance*/)
{
CV_Assert(edge_img.channels()==1);
Mat dist_img;
Mat annotated_img;
Mat orientation_img;
annotated_img.create(edge_img.size(), CV_32SC2);
dist_img.create(edge_img.size(),CV_32FC1);
dist_img.setTo(0);
// Computing distance transform
computeDistanceTransform(edge_img,dist_img, annotated_img, truncate_);
//orientation_img = NULL;
if (use_orientation_) {
orientation_img.create(edge_img.size(), CV_32FC1);
orientation_img.setTo(0);
Mat edge_clone = edge_img.clone();
computeEdgeOrientations(edge_clone, orientation_img );
edge_clone.release();
fillNonContourOrientations(annotated_img, orientation_img);
}
// Template matching
ChamferMatcher::Matches* pmatches = matchTemplates( dist_img,
orientation_img,
range,
_orientation_weight);
if (use_orientation_) {
orientation_img.release();
}
dist_img.release();
annotated_img.release();
return pmatches;
}
void ChamferMatcher::addMatch(float cost, Point offset, const Template* tpl)
{
bool new_match = true;
for (int i=0; i<count; ++i) {
if (std::abs(matches[i].offset.x-offset.x)+std::abs(matches[i].offset.y-offset.y)<min_match_distance_) {
// too close, not a new match
new_match = false;
// if better cost, replace existing match
if (cost<matches[i].cost) {
matches[i].cost = cost;
matches[i].offset = offset;
matches[i].tpl = tpl;
}
// re-bubble to keep ordered
int k = i;
while (k>0) {
if (matches[k-1].cost>matches[k].cost) {
std::swap(matches[k-1],matches[k]);
}
k--;
}
break;
}
}
if (new_match) {
// if we don't have enough matches yet, add it to the array
if (count<max_matches_) {
matches[count].cost = cost;
matches[count].offset = offset;
matches[count].tpl = tpl;
count++;
}
// otherwise find the right position to insert it
else {
// if higher cost than the worst current match, just ignore it
if (matches[count-1].cost<cost) {
return;
}
int j = 0;
// skip all matches better than current one
while (matches[j].cost<cost) j++;
// shift matches one position
int k = count-2;
while (k>=j) {
matches[k+1] = matches[k];
k--;
}
matches[j].cost = cost;
matches[j].offset = offset;
matches[j].tpl = tpl;
}
}
}
void ChamferMatcher::showMatch(Mat& img, int index)
{
if (index>=count) {
std::cout << "Index too big.\n" << std::endl;
}
assert(img.channels()==3);
Match match = matches[index];
const template_coords_t& templ_coords = match.tpl->coords;
int x, y;
for (size_t i=0;i<templ_coords.size();++i) {
x = match.offset.x + templ_coords[i].first;
y = match.offset.y + templ_coords[i].second;
if ( x > img.cols-1 || x < 0 || y > img.rows-1 || y < 0) continue;
img.at<Vec3b>(y,x)[0]=0;
img.at<Vec3b>(y,x)[2]=0;
img.at<Vec3b>(y,x)[1]=255;
}
}
void ChamferMatcher::showMatch(Mat& img, Match match)
{
assert(img.channels()==3);
const template_coords_t& templ_coords = match.tpl->coords;
for (size_t i=0;i<templ_coords.size();++i) {
int x = match.offset.x + templ_coords[i].first;
int y = match.offset.y + templ_coords[i].second;
if ( x > img.cols-1 || x < 0 || y > img.rows-1 || y < 0) continue;
img.at<Vec3b>(y,x)[0]=0;
img.at<Vec3b>(y,x)[2]=0;
img.at<Vec3b>(y,x)[1]=255;
}
match.tpl->show();
}
const ChamferMatcher::Matches& ChamferMatcher::matching(Template& tpl, Mat& image_){
chamfer_->addTemplate(tpl);
matches.clear();
matches.resize(max_matches_);
count = 0;
Matches* matches_ = chamfer_->matchEdgeImage( image_,
ChamferMatcher::
SlidingWindowImageRange(image_.cols,
image_.rows,
pad_x,
pad_y,
scales,
minScale,
maxScale),
orientation_weight,
max_matches_,
min_match_distance_);
for(int i = 0; i < (int)matches_->size(); i++){
addMatch(matches_->at(i).cost, matches_->at(i).offset, matches_->at(i).tpl);
}
matches_->clear();
delete matches_;
matches_ = NULL;
matches.resize(count);
return matches;
}
int chamerMatching( Mat& img, Mat& templ,
std::vector<std::vector<Point> >& results, std::vector<float>& costs,
double templScale, int maxMatches, double minMatchDistance, int padX,
int padY, int scales, double minScale, double maxScale,
double orientationWeight, double truncate )
{
CV_Assert(img.type() == CV_8UC1 && templ.type() == CV_8UC1);
ChamferMatcher matcher_(maxMatches, (float)minMatchDistance, padX, padY, scales,
(float)minScale, (float)maxScale,
(float)orientationWeight, (float)truncate);
ChamferMatcher::Template template_(templ, (float)templScale);
ChamferMatcher::Matches match_instances = matcher_.matching(template_, img);
size_t i, nmatches = match_instances.size();
results.resize(nmatches);
costs.resize(nmatches);
int bestIdx = -1;
double minCost = DBL_MAX;
for( i = 0; i < nmatches; i++ )
{
const ChamferMatcher::Match& match = match_instances[i];
double cval = match.cost;
if( cval < minCost)
{
minCost = cval;
bestIdx = (int)i;
}
costs[i] = (float)cval;
const template_coords_t& templ_coords = match.tpl->coords;
std::vector<Point>& templPoints = results[i];
size_t j, npoints = templ_coords.size();
templPoints.resize(npoints);
for (j = 0; j < npoints; j++ )
{
int x = match.offset.x + templ_coords[j].first;
int y = match.offset.y + templ_coords[j].second;
templPoints[j] = Point(x,y);
}
}
return bestIdx;
}
}