Commit ef8182e1 authored by Vadim Pisarevsky's avatar Vadim Pisarevsky

Merge pull request #4025 from vpisarev:features2d_fixes

parents 021473e9 af47b655
...@@ -660,6 +660,7 @@ CV_EXPORTS void write( FileStorage& fs, const String& name, const String& value ...@@ -660,6 +660,7 @@ CV_EXPORTS void write( FileStorage& fs, const String& name, const String& value
CV_EXPORTS void write( FileStorage& fs, const String& name, const Mat& value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const Mat& value );
CV_EXPORTS void write( FileStorage& fs, const String& name, const SparseMat& value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const SparseMat& value );
CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector<KeyPoint>& value); CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector<KeyPoint>& value);
CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector<DMatch>& value);
CV_EXPORTS void writeScalar( FileStorage& fs, int value ); CV_EXPORTS void writeScalar( FileStorage& fs, int value );
CV_EXPORTS void writeScalar( FileStorage& fs, float value ); CV_EXPORTS void writeScalar( FileStorage& fs, float value );
...@@ -678,6 +679,7 @@ CV_EXPORTS void read(const FileNode& node, String& value, const String& default_ ...@@ -678,6 +679,7 @@ CV_EXPORTS void read(const FileNode& node, String& value, const String& default_
CV_EXPORTS void read(const FileNode& node, Mat& mat, const Mat& default_mat = Mat() ); CV_EXPORTS void read(const FileNode& node, Mat& mat, const Mat& default_mat = Mat() );
CV_EXPORTS void read(const FileNode& node, SparseMat& mat, const SparseMat& default_mat = SparseMat() ); CV_EXPORTS void read(const FileNode& node, SparseMat& mat, const SparseMat& default_mat = SparseMat() );
CV_EXPORTS void read(const FileNode& node, std::vector<KeyPoint>& keypoints); CV_EXPORTS void read(const FileNode& node, std::vector<KeyPoint>& keypoints);
CV_EXPORTS void read(const FileNode& node, std::vector<DMatch>& matches);
template<typename _Tp> static inline void read(const FileNode& node, Point_<_Tp>& value, const Point_<_Tp>& default_value) template<typename _Tp> static inline void read(const FileNode& node, Point_<_Tp>& value, const Point_<_Tp>& default_value)
{ {
......
...@@ -5594,6 +5594,35 @@ void read(const FileNode& node, std::vector<KeyPoint>& keypoints) ...@@ -5594,6 +5594,35 @@ void read(const FileNode& node, std::vector<KeyPoint>& keypoints)
} }
} }
void write(FileStorage& fs, const String& objname, const std::vector<DMatch>& matches)
{
cv::internal::WriteStructContext ws(fs, objname, CV_NODE_SEQ + CV_NODE_FLOW);
int i, n = (int)matches.size();
for( i = 0; i < n; i++ )
{
const DMatch& m = matches[i];
cv::write(fs, m.queryIdx);
cv::write(fs, m.trainIdx);
cv::write(fs, m.imgIdx);
cv::write(fs, m.distance);
}
}
void read(const FileNode& node, std::vector<DMatch>& matches)
{
matches.resize(0);
FileNodeIterator it = node.begin(), it_end = node.end();
for( ; it != it_end; )
{
DMatch m;
it >> m.queryIdx >> m.trainIdx >> m.imgIdx >> m.distance;
matches.push_back(m);
}
}
int FileNode::type() const { return !node ? NONE : (node->tag & TYPE_MASK); } int FileNode::type() const { return !node ? NONE : (node->tag & TYPE_MASK); }
bool FileNode::isNamed() const { return !node ? false : (node->tag & NAMED) != 0; } bool FileNode::isNamed() const { return !node ? false : (node->tag & NAMED) != 0; }
......
...@@ -29,12 +29,12 @@ ...@@ -29,12 +29,12 @@
* *
* OpenCV functions for MSER extraction * OpenCV functions for MSER extraction
* *
* 1. there are two different implementation of MSER, one for grey image, one for color image * 1. there are two different implementation of MSER, one for gray image, one for color image
* 2. the grey image algorithm is taken from: Linear Time Maximally Stable Extremal Regions; * 2. the gray image algorithm is taken from: Linear Time Maximally Stable Extremal Regions;
* the paper claims to be faster than union-find method; * the paper claims to be faster than union-find method;
* it actually get 1.5~2m/s on my centrino L7200 1.2GHz laptop. * it actually get 1.5~2m/s on my centrino L7200 1.2GHz laptop.
* 3. the color image algorithm is taken from: Maximally Stable Colour Regions for Recognition and Match; * 3. the color image algorithm is taken from: Maximally Stable Colour Regions for Recognition and Match;
* it should be much slower than grey image method ( 3~4 times ); * it should be much slower than gray image method ( 3~4 times );
* the chi_table.h file is taken directly from paper's source code which is distributed under GPL. * the chi_table.h file is taken directly from paper's source code which is distributed under GPL.
* 4. though the name is *contours*, the result actually is a list of point set. * 4. though the name is *contours*, the result actually is a list of point set.
*/ */
...@@ -121,15 +121,129 @@ public: ...@@ -121,15 +121,129 @@ public:
}; };
typedef int PPixel; typedef int PPixel;
struct WParams
{
Params p;
vector<vector<Point> >* msers;
vector<Rect>* bboxvec;
Pixel* pix0;
int step;
};
// the history of region grown // the history of region grown
struct CompHistory struct CompHistory
{ {
CompHistory() { shortcut = child = 0; stable = val = size = 0; } CompHistory()
CompHistory* shortcut; {
CompHistory* child; parent_ = child_ = next_ = 0;
int stable; // when it ever stabled before, record the size val = size = 0;
var = -1.f;
head = 0;
checked = false;
}
void updateTree( WParams& wp, CompHistory** _h0, CompHistory** _h1, bool final )
{
if( var >= 0.f )
return;
int delta = wp.p.delta;
CompHistory* h0_ = 0, *h1_ = 0;
CompHistory* c = child_;
if( size >= wp.p.minArea )
{
for( ; c != 0; c = c->next_ )
{
if( c->var < 0.f )
c->updateTree(wp, c == child_ ? &h0_ : 0, c == child_ ? &h1_ : 0, final);
if( c->var < 0.f )
return;
}
}
// find h0 and h1 such that:
// h0->val >= h->val - delta and (h0->parent == 0 or h0->parent->val < h->val - delta)
// h1->val <= h->val + delta and (h1->child == 0 or h1->child->val < h->val + delta)
// then we will adjust h0 and h1 as h moves towards latest
CompHistory* h0 = this, *h1 = h1_ && h1_->size > size ? h1_ : this;
if( h0_ )
{
for( h0 = h0_; h0 != this && h0->val < val - delta; h0 = h0->parent_ )
;
}
else
{
for( ; h0->child_ && h0->child_->val >= val - delta; h0 = h0->child_ )
;
}
for( ; h1->parent_ && h1->parent_->val <= val + delta; h1 = h1->parent_ )
;
if( _h0 ) *_h0 = h0;
if( _h1 ) *_h1 = h1;
// when we do not well-defined ER(h->val + delta), we stop
// the process of computing variances unless we are at the final step
if( !final && !h1->parent_ && h1->val < val + delta )
return;
var = (float)(h1->size - h0->size)/size;
c = child_;
for( ; c != 0; c = c->next_ )
c->checkAndCapture(wp);
if( final && !parent_ )
checkAndCapture(wp);
}
void checkAndCapture( WParams& wp )
{
if( checked )
return;
checked = true;
if( size < wp.p.minArea || size > wp.p.maxArea || var < 0.f || var > wp.p.maxVariation )
return;
if( child_ )
{
CompHistory* c = child_;
for( ; c != 0; c = c->next_ )
{
if( c->var >= 0.f && var > c->var )
return;
}
}
if( parent_ && parent_->var >= 0.f && var >= parent_->var )
return;
int xmin = INT_MAX, ymin = INT_MAX, xmax = INT_MIN, ymax = INT_MIN, j = 0;
wp.msers->push_back(vector<Point>());
vector<Point>& region = wp.msers->back();
region.resize(size);
const Pixel* pix0 = wp.pix0;
int step = wp.step;
for( PPixel pix = head; j < size; j++, pix = pix0[pix].getNext() )
{
int y = pix/step;
int x = pix - y*step;
xmin = std::min(xmin, x);
xmax = std::max(xmax, x);
ymin = std::min(ymin, y);
ymax = std::max(ymax, y);
region[j] = Point(x, y);
}
wp.bboxvec->push_back(Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1));
}
CompHistory* child_;
CompHistory* parent_;
CompHistory* next_;
int val; int val;
int size; int size;
float var;
PPixel head;
bool checked;
}; };
struct ConnectedComp struct ConnectedComp
...@@ -144,141 +258,87 @@ public: ...@@ -144,141 +258,87 @@ public:
head = tail = 0; head = tail = 0;
history = 0; history = 0;
size = 0; size = 0;
grey_level = gray; gray_level = gray;
dvar = false;
var = 0;
} }
// add history chunk to a connected component // add history chunk to a connected component
void growHistory( CompHistory* h ) void growHistory( CompHistory*& hptr, WParams& wp, int new_gray_level, bool final, bool force=false )
{ {
h->child = h; bool update = final;
if( !history ) if( new_gray_level < 0 )
new_gray_level = gray_level;
if( !history || (history->size != size && size > 0 &&
(gray_level != history->val || force)))
{
CompHistory* h = hptr++;
h->parent_ = 0;
h->child_ = history;
h->next_ = 0;
if( history )
history->parent_ = h;
h->val = gray_level;
h->size = size;
h->head = head;
history = h;
h->var = FLT_MAX;
h->checked = true;
if( h->size >= wp.p.minArea )
{ {
h->shortcut = h; h->var = -1.f;
h->stable = 0; h->checked = false;
update = true;
} }
else
{
history->child = h;
h->shortcut = history->shortcut;
h->stable = history->stable;
} }
h->val = grey_level; gray_level = new_gray_level;
h->size = size; if( update && history )
history = h; history->updateTree(wp, 0, 0, final);
} }
// merging two connected components // merging two connected components
static void void merge( ConnectedComp* comp1, ConnectedComp* comp2,
merge( const ConnectedComp* comp1, CompHistory*& hptr, WParams& wp )
const ConnectedComp* comp2, {
ConnectedComp* comp, comp1->growHistory( hptr, wp, -1, false );
CompHistory* h, comp2->growHistory( hptr, wp, -1, false );
Pixel* pix0 )
{ if( comp1->size < comp2->size )
comp->grey_level = comp2->grey_level;
h->child = h;
// select the winner by size
if ( comp1->size < comp2->size )
std::swap(comp1, comp2); std::swap(comp1, comp2);
if( !comp1->history ) if( comp2->size == 0 )
{
h->shortcut = h;
h->stable = 0;
}
else
{ {
comp1->history->child = h; gray_level = comp1->gray_level;
h->shortcut = comp1->history->shortcut; head = comp1->head;
h->stable = comp1->history->stable; tail = comp1->tail;
} size = comp1->size;
if( comp2->history && comp2->history->stable > h->stable ) history = comp1->history;
h->stable = comp2->history->stable; return;
h->val = comp1->grey_level;
h->size = comp1->size;
// put comp1 to history
comp->var = comp1->var;
comp->dvar = comp1->dvar;
if( comp1->size > 0 && comp2->size > 0 )
pix0[comp1->tail].setNext(comp2->head);
PPixel head = comp1->size > 0 ? comp1->head : comp2->head;
PPixel tail = comp2->size > 0 ? comp2->tail : comp1->tail;
// always made the newly added in the last of the pixel list (comp1 ... comp2)
comp->head = head;
comp->tail = tail;
comp->history = h;
comp->size = comp1->size + comp2->size;
}
float calcVariation( int delta ) const
{
if( !history )
return 1.f;
int val = grey_level;
CompHistory* shortcut = history->shortcut;
while( shortcut != shortcut->shortcut && shortcut->val + delta > val )
shortcut = shortcut->shortcut;
CompHistory* child = shortcut->child;
while( child != child->child && child->val + delta <= val )
{
shortcut = child;
child = child->child;
}
// get the position of history where the shortcut->val <= delta+val and shortcut->child->val >= delta+val
history->shortcut = shortcut;
return (float)(size - shortcut->size)/(float)shortcut->size;
// here is a small modification of MSER where cal ||R_{i}-R_{i-delta}||/||R_{i-delta}||
// in standard MSER, cal ||R_{i+delta}-R_{i-delta}||/||R_{i}||
// my calculation is simpler and much easier to implement
}
bool isStable(const Params& p)
{
// tricky part: it actually check the stablity of one-step back
if( !history || history->size <= p.minArea || history->size >= p.maxArea )
return false;
float div = (float)(history->size - history->stable)/(float)history->size;
float _var = calcVariation( p.delta );
bool _dvar = (var < _var) || (history->val + 1 < grey_level);
bool stable = _dvar && !dvar && _var < p.maxVariation && div > p.minDiversity;
var = _var;
dvar = _dvar;
if( stable )
history->stable = history->size;
return stable;
} }
// convert the point set to CvSeq CompHistory* h1 = comp1->history;
Rect capture( const Pixel* pix0, int step, vector<Point>& region ) const CompHistory* h2 = comp2->history;
{
int xmin = INT_MAX, ymin = INT_MAX, xmax = INT_MIN, ymax = INT_MIN;
region.clear();
for( PPixel pix = head; pix != 0; pix = pix0[pix].getNext() ) gray_level = std::max(comp1->gray_level, comp2->gray_level);
{ history = comp1->history;
int y = pix/step; wp.pix0[comp1->tail].setNext(comp2->head);
int x = pix - y*step;
xmin = std::min(xmin, x);
xmax = std::max(xmax, x);
ymin = std::min(ymin, y);
ymax = std::max(ymax, y);
region.push_back(Point(x, y)); head = comp1->head;
tail = comp2->tail;
size = comp1->size + comp2->size;
bool keep_2nd = h2->size > wp.p.minArea;
growHistory( hptr, wp, -1, false, keep_2nd );
if( keep_2nd )
{
h1->next_ = h2;
h2->parent_ = history;
} }
return Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
} }
PPixel head; PPixel head;
PPixel tail; PPixel tail;
CompHistory* history; CompHistory* history;
int grey_level; int gray_level;
int size; int size;
float var; // the current variation (most time is the variation of one-step back)
bool dvar; // the derivative of last var
}; };
void detectRegions( InputArray image, void detectRegions( InputArray image,
...@@ -296,7 +356,7 @@ public: ...@@ -296,7 +356,7 @@ public:
heapbuf.resize(cols*rows + 256); heapbuf.resize(cols*rows + 256);
histbuf.resize(cols*rows); histbuf.resize(cols*rows);
Pixel borderpix; Pixel borderpix;
borderpix.setDir(4); borderpix.setDir(5);
for( j = 0; j < step; j++ ) for( j = 0; j < step; j++ )
{ {
...@@ -349,6 +409,12 @@ public: ...@@ -349,6 +409,12 @@ public:
Pixel** heap[256]; Pixel** heap[256];
ConnectedComp comp[257]; ConnectedComp comp[257];
ConnectedComp* comptr = &comp[0]; ConnectedComp* comptr = &comp[0];
WParams wp;
wp.p = params;
wp.msers = &msers;
wp.bboxvec = &bboxvec;
wp.pix0 = ptr0;
wp.step = step;
heap[0] = &heapbuf[0]; heap[0] = &heapbuf[0];
heap[0][0] = 0; heap[0][0] = 0;
...@@ -359,9 +425,9 @@ public: ...@@ -359,9 +425,9 @@ public:
heap[i][0] = 0; heap[i][0] = 0;
} }
comptr->grey_level = 256; comptr->gray_level = 256;
comptr++; comptr++;
comptr->grey_level = ptr->getGray(ptr0, imgptr0, mask); comptr->gray_level = ptr->getGray(ptr0, imgptr0, mask);
ptr->setDir(1); ptr->setDir(1);
int dir[] = { 0, 1, step, -1, -step }; int dir[] = { 0, 1, step, -1, -step };
for( ;; ) for( ;; )
...@@ -427,48 +493,32 @@ public: ...@@ -427,48 +493,32 @@ public:
ptr = *heap[curr_gray]; ptr = *heap[curr_gray];
heap[curr_gray]--; heap[curr_gray]--;
if( curr_gray < comptr[-1].grey_level )
{
// check the stablity and push a new history, increase the grey level
if( comptr->isStable(params) )
{
msers.push_back(vector<Point>());
vector<Point>& mser = msers.back();
Rect box = comptr->capture( ptr0, step, mser ); if( curr_gray < comptr[-1].gray_level )
bboxvec.push_back(box); comptr->growHistory(histptr, wp, curr_gray, false);
}
comptr->growHistory( histptr++ );
comptr[0].grey_level = curr_gray;
}
else else
{ {
// keep merging top two comp in stack until the grey level >= pixel_val // keep merging top two comp in stack until the gray level >= pixel_val
for(;;) for(;;)
{ {
comptr--; comptr--;
ConnectedComp::merge(comptr+1, comptr, comptr, histptr++, ptr0); comptr->merge(comptr, comptr+1, histptr, wp);
if( curr_gray <= comptr[0].grey_level ) if( curr_gray <= comptr[0].gray_level )
break; break;
if( curr_gray < comptr[-1].grey_level ) if( curr_gray < comptr[-1].gray_level )
{ {
// check the stablity here otherwise it wouldn't be an ER comptr->growHistory(histptr, wp, curr_gray, false);
if( comptr->isStable(params) )
{
msers.push_back(vector<Point>());
vector<Point>& mser = msers.back();
Rect box = comptr->capture( ptr0, step, mser );
bboxvec.push_back(box);
}
comptr->growHistory( histptr++ );
comptr[0].grey_level = curr_gray;
break; break;
} }
} }
} }
} }
} }
for( ; comptr->gray_level != 256; comptr-- )
{
comptr->growHistory(histptr, wp, 256, true, true);
}
} }
Mat tempsrc; Mat tempsrc;
......
...@@ -347,3 +347,97 @@ TEST( Features2d_DescriptorExtractor_AKAZE, regression ) ...@@ -347,3 +347,97 @@ TEST( Features2d_DescriptorExtractor_AKAZE, regression )
Hamming(), AKAZE::create()); Hamming(), AKAZE::create());
test.safe_run(); test.safe_run();
} }
TEST( Features2d_DescriptorExtractor, batch )
{
string path = string(cvtest::TS::ptr()->get_data_path() + "detectors_descriptors_evaluation/images_datasets/graf");
vector<Mat> imgs, descriptors;
vector<vector<KeyPoint> > keypoints;
int i, n = 6;
Ptr<ORB> orb = ORB::create();
for( i = 0; i < n; i++ )
{
string imgname = format("%s/img%d.png", path.c_str(), i+1);
Mat img = imread(imgname, 0);
imgs.push_back(img);
}
orb->detect(imgs, keypoints);
orb->compute(imgs, keypoints, descriptors);
ASSERT_EQ((int)keypoints.size(), n);
ASSERT_EQ((int)descriptors.size(), n);
for( i = 0; i < n; i++ )
{
EXPECT_GT((int)keypoints[i].size(), 100);
EXPECT_GT(descriptors[i].rows, 100);
}
}
TEST( Features2d_Feature2d, no_crash )
{
const String& pattern = string(cvtest::TS::ptr()->get_data_path() + "shared/*.png");
vector<String> fnames;
glob(pattern, fnames, false);
sort(fnames.begin(), fnames.end());
Ptr<AKAZE> akaze = AKAZE::create();
Ptr<ORB> orb = ORB::create();
Ptr<KAZE> kaze = KAZE::create();
Ptr<BRISK> brisk = BRISK::create();
size_t i, n = fnames.size();
vector<KeyPoint> keypoints;
Mat descriptors;
orb->setMaxFeatures(5000);
for( i = 0; i < n; i++ )
{
printf("%d. image: %s:\n", (int)i, fnames[i].c_str());
if( strstr(fnames[i].c_str(), "MP.png") != 0 )
continue;
bool checkCount = strstr(fnames[i].c_str(), "templ.png") == 0;
Mat img = imread(fnames[i], -1);
printf("\tAKAZE ... "); fflush(stdout);
akaze->detectAndCompute(img, noArray(), keypoints, descriptors);
printf("(%d keypoints) ", (int)keypoints.size()); fflush(stdout);
if( checkCount )
{
EXPECT_GT((int)keypoints.size(), 0);
}
ASSERT_EQ(descriptors.rows, (int)keypoints.size());
printf("ok\n");
printf("\tKAZE ... "); fflush(stdout);
kaze->detectAndCompute(img, noArray(), keypoints, descriptors);
printf("(%d keypoints) ", (int)keypoints.size()); fflush(stdout);
if( checkCount )
{
EXPECT_GT((int)keypoints.size(), 0);
}
ASSERT_EQ(descriptors.rows, (int)keypoints.size());
printf("ok\n");
printf("\tORB ... "); fflush(stdout);
orb->detectAndCompute(img, noArray(), keypoints, descriptors);
printf("(%d keypoints) ", (int)keypoints.size()); fflush(stdout);
if( checkCount )
{
EXPECT_GT((int)keypoints.size(), 0);
}
ASSERT_EQ(descriptors.rows, (int)keypoints.size());
printf("ok\n");
printf("\tBRISK ... "); fflush(stdout);
brisk->detectAndCompute(img, noArray(), keypoints, descriptors);
printf("(%d keypoints) ", (int)keypoints.size()); fflush(stdout);
if( checkCount )
{
EXPECT_GT((int)keypoints.size(), 0);
}
ASSERT_EQ(descriptors.rows, (int)keypoints.size());
printf("ok\n");
}
}
...@@ -543,3 +543,13 @@ TEST( Features2d_DescriptorMatcher_FlannBased, regression ) ...@@ -543,3 +543,13 @@ TEST( Features2d_DescriptorMatcher_FlannBased, regression )
DescriptorMatcher::create("FlannBased"), 0.04f ); DescriptorMatcher::create("FlannBased"), 0.04f );
test.safe_run(); test.safe_run();
} }
TEST( Features2d_DMatch, read_write )
{
FileStorage fs(".xml", FileStorage::WRITE + FileStorage::MEMORY);
vector<DMatch> matches;
matches.push_back(DMatch(1,2,3,4.5f));
fs << "Match" << matches;
String str = fs.releaseAndGetString();
ASSERT_NE( strstr(str.c_str(), "4.5"), (char*)0 );
}
...@@ -41,171 +41,121 @@ ...@@ -41,171 +41,121 @@
//M*/ //M*/
#include "test_precomp.hpp" #include "test_precomp.hpp"
#include "opencv2/imgproc/imgproc_c.h" #include "opencv2/highgui.hpp"
#if 0
#include <vector> #include <vector>
#include <string> #include <string>
using namespace std; using namespace std;
using namespace cv; using namespace cv;
class CV_MserTest : public cvtest::BaseTest #undef RENDER_MSERS
{ #define RENDER_MSERS 0
public:
CV_MserTest();
protected:
void run(int);
int LoadBoxes(const char* path, vector<CvBox2D>& boxes);
int SaveBoxes(const char* path, const vector<CvBox2D>& boxes);
int CompareBoxes(const vector<CvBox2D>& boxes1,const vector<CvBox2D>& boxes2, float max_rel_diff = 0.01f);
};
CV_MserTest::CV_MserTest()
{
}
int CV_MserTest::LoadBoxes(const char* path, vector<CvBox2D>& boxes) #if defined RENDER_MSERS && RENDER_MSERS
static void renderMSERs(const Mat& gray, Mat& img, const vector<vector<Point> >& msers)
{ {
boxes.clear(); cvtColor(gray, img, COLOR_GRAY2BGR);
FILE* f = fopen(path,"r"); RNG rng((uint64)1749583);
for( int i = 0; i < (int)msers.size(); i++ )
if (f==NULL)
{ {
return 0; uchar b = rng.uniform(0, 256);
} uchar g = rng.uniform(0, 256);
uchar r = rng.uniform(0, 256);
Vec3b color(b, g, r);
while (!feof(f)) const Point* pt = &msers[i][0];
{ size_t j, n = msers[i].size();
CvBox2D box; for( j = 0; j < n; j++ )
int values_read = fscanf(f,"%f,%f,%f,%f,%f\n",&box.angle,&box.center.x,&box.center.y,&box.size.width,&box.size.height); img.at<Vec3b>(pt[j]) = color;
CV_Assert(values_read == 5);
boxes.push_back(box);
} }
fclose(f);
return 1;
}
int CV_MserTest::SaveBoxes(const char* path, const vector<CvBox2D>& boxes)
{
FILE* f = fopen(path,"w");
if (f==NULL)
{
return 0;
}
for (int i=0;i<(int)boxes.size();i++)
{
fprintf(f,"%f,%f,%f,%f,%f\n",boxes[i].angle,boxes[i].center.x,boxes[i].center.y,boxes[i].size.width,boxes[i].size.height);
}
fclose(f);
return 1;
} }
#endif
int CV_MserTest::CompareBoxes(const vector<CvBox2D>& boxes1,const vector<CvBox2D>& boxes2, float max_rel_diff) TEST(Features2d_MSER, cases)
{ {
if (boxes1.size() != boxes2.size()) uchar buf[] =
return 0; {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
for (int i=0; i<(int)boxes1.size();i++) 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
float rel_diff; 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
if (!((boxes1[i].angle == 0.0f) && (abs(boxes2[i].angle) < max_rel_diff))) 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
{ 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255,
float angle_diff = (float)fmod(boxes1[i].angle - boxes2[i].angle, 180); 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255,
// for angular correctness, it makes no sense to use a "relative" error. 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255,
// a 1-degree error around 5 degrees is equally bas as around 250 degrees. 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255,
// in correct cases, angle_diff can now be a bit above 0 or a bit below 180 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255,
if (angle_diff > 90.0f) 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
angle_diff -= 180.0f; 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
} 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
rel_diff = (float)fabs(angle_diff); };
if (rel_diff > max_rel_diff) Mat big_image = imread(cvtest::TS::ptr()->get_data_path() + "mser/puzzle.png", 0);
return i; Mat small_image(14, 26, CV_8U, buf);
} static const int thresharr[] = { 0, 70, 120, 180, 255 };
const int kDelta = 5;
Ptr<MSER> mserExtractor = MSER::create( kDelta );
vector<vector<Point> > msers;
vector<Rect> boxes;
if (!((boxes1[i].center.x == 0.0f) && (abs(boxes2[i].center.x) < max_rel_diff))) RNG rng((uint64)123456);
{
rel_diff = abs(boxes1[i].center.x-boxes2[i].center.x)/abs(boxes1[i].center.x);
if (rel_diff > max_rel_diff)
return i;
}
if (!((boxes1[i].center.y == 0.0f) && (abs(boxes2[i].center.y) < max_rel_diff))) for( int i = 0; i < 100; i++ )
{ {
rel_diff = abs(boxes1[i].center.y-boxes2[i].center.y)/abs(boxes1[i].center.y); bool use_big_image = rng.uniform(0, 7) != 0;
if (rel_diff > max_rel_diff) bool invert = rng.uniform(0, 2) != 0;
return i; bool binarize = use_big_image ? rng.uniform(0, 5) != 0 : false;
} bool blur = rng.uniform(0, 2) != 0;
if (!((boxes1[i].size.width == 0.0f) && (abs(boxes2[i].size.width) < max_rel_diff))) int thresh = thresharr[rng.uniform(0, 5)];
{
rel_diff = abs(boxes1[i].size.width-boxes2[i].size.width)/abs(boxes1[i].size.width);
if (rel_diff > max_rel_diff)
return i;
}
if (!((boxes1[i].size.height == 0.0f) && (abs(boxes2[i].size.height) < max_rel_diff))) /*if( i == 0 )
{ {
rel_diff = abs(boxes1[i].size.height-boxes2[i].size.height)/abs(boxes1[i].size.height); use_big_image = true;
if (rel_diff > max_rel_diff) invert = binarize = blur = false;
return i; }*/
}
}
return -1; const Mat& src0 = use_big_image ? big_image : small_image;
} Mat src = src0.clone();
void CV_MserTest::run(int) int kMinArea = use_big_image ? 256 : 10;
{ int kMaxArea = (int)src.total()/4;
string image_path = string(ts->get_data_path()) + "mser/puzzle.png";
Mat img = imread( image_path ); mserExtractor->setMinArea(kMinArea);
if (img.empty()) mserExtractor->setMaxArea(kMaxArea);
{
ts->printf( cvtest::TS::LOG, "Unable to open image mser/puzzle.png\n");
ts->set_failed_test_info(cvtest::TS::FAIL_MISSING_TEST_DATA);
return;
}
Mat yuv; if( invert )
cvtColor(img, yuv, COLOR_BGR2YCrCb); bitwise_not(src, src);
vector<vector<Point> > msers; if( binarize )
MSER()(yuv, msers); threshold(src, src, thresh, 255, THRESH_BINARY);
if( blur )
GaussianBlur(src, src, Size(5, 5), 1.5, 1.5);
vector<CvBox2D> boxes; int minRegs = use_big_image ? 7 : 2;
vector<CvBox2D> boxes_orig; int maxRegs = use_big_image ? 1000 : 15;
for ( size_t i = 0; i < msers.size(); i++ ) if( binarize && (thresh == 0 || thresh == 255) )
{ minRegs = maxRegs = 0;
RotatedRect box = fitEllipse(msers[i]);
box.angle=(float)CV_PI/2-box.angle;
boxes.push_back(box);
}
string boxes_path = string(ts->get_data_path()) + "mser/boxes.txt"; mserExtractor->detectRegions( src, msers, boxes );
string calc_boxes_path = string(ts->get_data_path()) + "mser/boxes.calc.txt"; int nmsers = (int)msers.size();
ASSERT_EQ(nmsers, (int)boxes.size());
if (!LoadBoxes(boxes_path.c_str(),boxes_orig)) if( maxRegs < nmsers || minRegs > nmsers )
{ {
SaveBoxes(boxes_path.c_str(),boxes); printf("%d. minArea=%d, maxArea=%d, nmsers=%d, minRegs=%d, maxRegs=%d, "
ts->printf( cvtest::TS::LOG, "Unable to open data file mser/boxes.txt\n"); "image=%s, invert=%d, binarize=%d, thresh=%d, blur=%d\n",
ts->set_failed_test_info(cvtest::TS::FAIL_MISSING_TEST_DATA); i, kMinArea, kMaxArea, nmsers, minRegs, maxRegs, use_big_image ? "big" : "small",
return; (int)invert, (int)binarize, thresh, (int)blur);
#if defined RENDER_MSERS && RENDER_MSERS
Mat image;
imshow("source", src);
renderMSERs(src, image, msers);
imshow("result", image);
waitKey();
#endif
} }
const float dissimularity = 0.01f; ASSERT_LE(minRegs, nmsers);
int n_box = CompareBoxes(boxes_orig,boxes,dissimularity); ASSERT_GE(maxRegs, nmsers);
if (n_box < 0)
{
ts->set_failed_test_info(cvtest::TS::OK);
}
else
{
SaveBoxes(calc_boxes_path.c_str(), boxes);
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
ts->printf( cvtest::TS::LOG, "Incorrect correspondence in box %d\n",n_box);
} }
} }
TEST(Features2d_MSER, DISABLED_regression) { CV_MserTest test; test.safe_run(); }
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment