Merge pull request #4025 from vpisarev:features2d_fixes
This commit is contained in:
commit
ef8182e12a
@ -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 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<DMatch>& value);
|
||||
|
||||
CV_EXPORTS void writeScalar( FileStorage& fs, int 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_
|
||||
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, 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)
|
||||
{
|
||||
|
@ -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); }
|
||||
bool FileNode::isNamed() const { return !node ? false : (node->tag & NAMED) != 0; }
|
||||
|
||||
|
@ -29,12 +29,12 @@
|
||||
*
|
||||
* OpenCV functions for MSER extraction
|
||||
*
|
||||
* 1. there are two different implementation of MSER, one for grey image, one for color image
|
||||
* 2. the grey image algorithm is taken from: Linear Time Maximally Stable Extremal Regions;
|
||||
* 1. there are two different implementation of MSER, one for gray image, one for color image
|
||||
* 2. the gray image algorithm is taken from: Linear Time Maximally Stable Extremal Regions;
|
||||
* the paper claims to be faster than union-find method;
|
||||
* 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;
|
||||
* 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.
|
||||
* 4. though the name is *contours*, the result actually is a list of point set.
|
||||
*/
|
||||
@ -121,15 +121,129 @@ public:
|
||||
};
|
||||
typedef int PPixel;
|
||||
|
||||
struct WParams
|
||||
{
|
||||
Params p;
|
||||
vector<vector<Point> >* msers;
|
||||
vector<Rect>* bboxvec;
|
||||
Pixel* pix0;
|
||||
int step;
|
||||
};
|
||||
|
||||
// the history of region grown
|
||||
struct CompHistory
|
||||
{
|
||||
CompHistory() { shortcut = child = 0; stable = val = size = 0; }
|
||||
CompHistory* shortcut;
|
||||
CompHistory* child;
|
||||
int stable; // when it ever stabled before, record the size
|
||||
CompHistory()
|
||||
{
|
||||
parent_ = child_ = next_ = 0;
|
||||
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 size;
|
||||
float var;
|
||||
PPixel head;
|
||||
bool checked;
|
||||
};
|
||||
|
||||
struct ConnectedComp
|
||||
@ -144,141 +258,87 @@ public:
|
||||
head = tail = 0;
|
||||
history = 0;
|
||||
size = 0;
|
||||
grey_level = gray;
|
||||
dvar = false;
|
||||
var = 0;
|
||||
gray_level = gray;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if( !history )
|
||||
bool update = final;
|
||||
if( new_gray_level < 0 )
|
||||
new_gray_level = gray_level;
|
||||
if( !history || (history->size != size && size > 0 &&
|
||||
(gray_level != history->val || force)))
|
||||
{
|
||||
h->shortcut = h;
|
||||
h->stable = 0;
|
||||
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->var = -1.f;
|
||||
h->checked = false;
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
history->child = h;
|
||||
h->shortcut = history->shortcut;
|
||||
h->stable = history->stable;
|
||||
}
|
||||
h->val = grey_level;
|
||||
h->size = size;
|
||||
history = h;
|
||||
gray_level = new_gray_level;
|
||||
if( update && history )
|
||||
history->updateTree(wp, 0, 0, final);
|
||||
}
|
||||
|
||||
// merging two connected components
|
||||
static void
|
||||
merge( const ConnectedComp* comp1,
|
||||
const ConnectedComp* comp2,
|
||||
ConnectedComp* comp,
|
||||
CompHistory* h,
|
||||
Pixel* pix0 )
|
||||
void merge( ConnectedComp* comp1, ConnectedComp* comp2,
|
||||
CompHistory*& hptr, WParams& wp )
|
||||
{
|
||||
comp->grey_level = comp2->grey_level;
|
||||
h->child = h;
|
||||
// select the winner by size
|
||||
if ( comp1->size < comp2->size )
|
||||
comp1->growHistory( hptr, wp, -1, false );
|
||||
comp2->growHistory( hptr, wp, -1, false );
|
||||
|
||||
if( comp1->size < comp2->size )
|
||||
std::swap(comp1, comp2);
|
||||
|
||||
if( !comp1->history )
|
||||
if( comp2->size == 0 )
|
||||
{
|
||||
h->shortcut = h;
|
||||
h->stable = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
comp1->history->child = h;
|
||||
h->shortcut = comp1->history->shortcut;
|
||||
h->stable = comp1->history->stable;
|
||||
}
|
||||
if( comp2->history && comp2->history->stable > h->stable )
|
||||
h->stable = comp2->history->stable;
|
||||
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
|
||||
Rect capture( const Pixel* pix0, int step, vector<Point>& region ) const
|
||||
{
|
||||
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() )
|
||||
{
|
||||
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.push_back(Point(x, y));
|
||||
gray_level = comp1->gray_level;
|
||||
head = comp1->head;
|
||||
tail = comp1->tail;
|
||||
size = comp1->size;
|
||||
history = comp1->history;
|
||||
return;
|
||||
}
|
||||
|
||||
return Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
|
||||
CompHistory* h1 = comp1->history;
|
||||
CompHistory* h2 = comp2->history;
|
||||
|
||||
gray_level = std::max(comp1->gray_level, comp2->gray_level);
|
||||
history = comp1->history;
|
||||
wp.pix0[comp1->tail].setNext(comp2->head);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
PPixel head;
|
||||
PPixel tail;
|
||||
CompHistory* history;
|
||||
int grey_level;
|
||||
int gray_level;
|
||||
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,
|
||||
@ -296,7 +356,7 @@ public:
|
||||
heapbuf.resize(cols*rows + 256);
|
||||
histbuf.resize(cols*rows);
|
||||
Pixel borderpix;
|
||||
borderpix.setDir(4);
|
||||
borderpix.setDir(5);
|
||||
|
||||
for( j = 0; j < step; j++ )
|
||||
{
|
||||
@ -349,6 +409,12 @@ public:
|
||||
Pixel** heap[256];
|
||||
ConnectedComp comp[257];
|
||||
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][0] = 0;
|
||||
@ -359,9 +425,9 @@ public:
|
||||
heap[i][0] = 0;
|
||||
}
|
||||
|
||||
comptr->grey_level = 256;
|
||||
comptr->gray_level = 256;
|
||||
comptr++;
|
||||
comptr->grey_level = ptr->getGray(ptr0, imgptr0, mask);
|
||||
comptr->gray_level = ptr->getGray(ptr0, imgptr0, mask);
|
||||
ptr->setDir(1);
|
||||
int dir[] = { 0, 1, step, -1, -step };
|
||||
for( ;; )
|
||||
@ -427,48 +493,32 @@ public:
|
||||
|
||||
ptr = *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 );
|
||||
bboxvec.push_back(box);
|
||||
}
|
||||
comptr->growHistory( histptr++ );
|
||||
comptr[0].grey_level = curr_gray;
|
||||
}
|
||||
if( curr_gray < comptr[-1].gray_level )
|
||||
comptr->growHistory(histptr, wp, curr_gray, false);
|
||||
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(;;)
|
||||
{
|
||||
comptr--;
|
||||
ConnectedComp::merge(comptr+1, comptr, comptr, histptr++, ptr0);
|
||||
if( curr_gray <= comptr[0].grey_level )
|
||||
comptr->merge(comptr, comptr+1, histptr, wp);
|
||||
if( curr_gray <= comptr[0].gray_level )
|
||||
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
|
||||
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;
|
||||
comptr->growHistory(histptr, wp, curr_gray, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for( ; comptr->gray_level != 256; comptr-- )
|
||||
{
|
||||
comptr->growHistory(histptr, wp, 256, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
Mat tempsrc;
|
||||
|
@ -347,3 +347,97 @@ TEST( Features2d_DescriptorExtractor_AKAZE, regression )
|
||||
Hamming(), AKAZE::create());
|
||||
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 )
|
||||
DescriptorMatcher::create("FlannBased"), 0.04f );
|
||||
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 @@
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/imgproc/imgproc_c.h"
|
||||
|
||||
#if 0
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
class CV_MserTest : public cvtest::BaseTest
|
||||
#undef RENDER_MSERS
|
||||
#define RENDER_MSERS 0
|
||||
|
||||
#if defined RENDER_MSERS && RENDER_MSERS
|
||||
static void renderMSERs(const Mat& gray, Mat& img, const vector<vector<Point> >& msers)
|
||||
{
|
||||
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)
|
||||
{
|
||||
boxes.clear();
|
||||
FILE* f = fopen(path,"r");
|
||||
|
||||
if (f==NULL)
|
||||
cvtColor(gray, img, COLOR_GRAY2BGR);
|
||||
RNG rng((uint64)1749583);
|
||||
for( int i = 0; i < (int)msers.size(); i++ )
|
||||
{
|
||||
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))
|
||||
{
|
||||
CvBox2D box;
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
int CV_MserTest::CompareBoxes(const vector<CvBox2D>& boxes1,const vector<CvBox2D>& boxes2, float max_rel_diff)
|
||||
{
|
||||
if (boxes1.size() != boxes2.size())
|
||||
return 0;
|
||||
|
||||
for (int i=0; i<(int)boxes1.size();i++)
|
||||
{
|
||||
float rel_diff;
|
||||
if (!((boxes1[i].angle == 0.0f) && (abs(boxes2[i].angle) < max_rel_diff)))
|
||||
{
|
||||
float angle_diff = (float)fmod(boxes1[i].angle - boxes2[i].angle, 180);
|
||||
// for angular correctness, it makes no sense to use a "relative" error.
|
||||
// a 1-degree error around 5 degrees is equally bas as around 250 degrees.
|
||||
// in correct cases, angle_diff can now be a bit above 0 or a bit below 180
|
||||
if (angle_diff > 90.0f)
|
||||
{
|
||||
angle_diff -= 180.0f;
|
||||
}
|
||||
rel_diff = (float)fabs(angle_diff);
|
||||
if (rel_diff > max_rel_diff)
|
||||
return i;
|
||||
}
|
||||
|
||||
if (!((boxes1[i].center.x == 0.0f) && (abs(boxes2[i].center.x) < max_rel_diff)))
|
||||
{
|
||||
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)))
|
||||
{
|
||||
rel_diff = abs(boxes1[i].center.y-boxes2[i].center.y)/abs(boxes1[i].center.y);
|
||||
if (rel_diff > max_rel_diff)
|
||||
return i;
|
||||
}
|
||||
if (!((boxes1[i].size.width == 0.0f) && (abs(boxes2[i].size.width) < max_rel_diff)))
|
||||
{
|
||||
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)))
|
||||
{
|
||||
rel_diff = abs(boxes1[i].size.height-boxes2[i].size.height)/abs(boxes1[i].size.height);
|
||||
if (rel_diff > max_rel_diff)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void CV_MserTest::run(int)
|
||||
{
|
||||
string image_path = string(ts->get_data_path()) + "mser/puzzle.png";
|
||||
|
||||
Mat img = imread( image_path );
|
||||
if (img.empty())
|
||||
{
|
||||
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;
|
||||
cvtColor(img, yuv, COLOR_BGR2YCrCb);
|
||||
vector<vector<Point> > msers;
|
||||
MSER()(yuv, msers);
|
||||
|
||||
vector<CvBox2D> boxes;
|
||||
vector<CvBox2D> boxes_orig;
|
||||
for ( size_t i = 0; i < msers.size(); i++ )
|
||||
{
|
||||
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";
|
||||
string calc_boxes_path = string(ts->get_data_path()) + "mser/boxes.calc.txt";
|
||||
|
||||
if (!LoadBoxes(boxes_path.c_str(),boxes_orig))
|
||||
{
|
||||
SaveBoxes(boxes_path.c_str(),boxes);
|
||||
ts->printf( cvtest::TS::LOG, "Unable to open data file mser/boxes.txt\n");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISSING_TEST_DATA);
|
||||
return;
|
||||
}
|
||||
|
||||
const float dissimularity = 0.01f;
|
||||
int n_box = CompareBoxes(boxes_orig,boxes,dissimularity);
|
||||
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);
|
||||
const Point* pt = &msers[i][0];
|
||||
size_t j, n = msers[i].size();
|
||||
for( j = 0; j < n; j++ )
|
||||
img.at<Vec3b>(pt[j]) = color;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Features2d_MSER, DISABLED_regression) { CV_MserTest test; test.safe_run(); }
|
||||
|
||||
#endif
|
||||
|
||||
TEST(Features2d_MSER, cases)
|
||||
{
|
||||
uchar buf[] =
|
||||
{
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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
|
||||
};
|
||||
Mat big_image = imread(cvtest::TS::ptr()->get_data_path() + "mser/puzzle.png", 0);
|
||||
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;
|
||||
|
||||
RNG rng((uint64)123456);
|
||||
|
||||
for( int i = 0; i < 100; i++ )
|
||||
{
|
||||
bool use_big_image = rng.uniform(0, 7) != 0;
|
||||
bool invert = rng.uniform(0, 2) != 0;
|
||||
bool binarize = use_big_image ? rng.uniform(0, 5) != 0 : false;
|
||||
bool blur = rng.uniform(0, 2) != 0;
|
||||
int thresh = thresharr[rng.uniform(0, 5)];
|
||||
|
||||
/*if( i == 0 )
|
||||
{
|
||||
use_big_image = true;
|
||||
invert = binarize = blur = false;
|
||||
}*/
|
||||
|
||||
const Mat& src0 = use_big_image ? big_image : small_image;
|
||||
Mat src = src0.clone();
|
||||
|
||||
int kMinArea = use_big_image ? 256 : 10;
|
||||
int kMaxArea = (int)src.total()/4;
|
||||
|
||||
mserExtractor->setMinArea(kMinArea);
|
||||
mserExtractor->setMaxArea(kMaxArea);
|
||||
|
||||
if( invert )
|
||||
bitwise_not(src, src);
|
||||
if( binarize )
|
||||
threshold(src, src, thresh, 255, THRESH_BINARY);
|
||||
if( blur )
|
||||
GaussianBlur(src, src, Size(5, 5), 1.5, 1.5);
|
||||
|
||||
int minRegs = use_big_image ? 7 : 2;
|
||||
int maxRegs = use_big_image ? 1000 : 15;
|
||||
if( binarize && (thresh == 0 || thresh == 255) )
|
||||
minRegs = maxRegs = 0;
|
||||
|
||||
mserExtractor->detectRegions( src, msers, boxes );
|
||||
int nmsers = (int)msers.size();
|
||||
ASSERT_EQ(nmsers, (int)boxes.size());
|
||||
|
||||
if( maxRegs < nmsers || minRegs > nmsers )
|
||||
{
|
||||
printf("%d. minArea=%d, maxArea=%d, nmsers=%d, minRegs=%d, maxRegs=%d, "
|
||||
"image=%s, invert=%d, binarize=%d, thresh=%d, blur=%d\n",
|
||||
i, kMinArea, kMaxArea, nmsers, minRegs, maxRegs, use_big_image ? "big" : "small",
|
||||
(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
|
||||
}
|
||||
|
||||
ASSERT_LE(minRegs, nmsers);
|
||||
ASSERT_GE(maxRegs, nmsers);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user