diff --git a/modules/core/include/opencv2/core/persistence.hpp b/modules/core/include/opencv2/core/persistence.hpp index eec304d6b7..17686dd0d9 100644 --- a/modules/core/include/opencv2/core/persistence.hpp +++ b/modules/core/include/opencv2/core/persistence.hpp @@ -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& value); +CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector& 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& keypoints); +CV_EXPORTS void read(const FileNode& node, std::vector& matches); template static inline void read(const FileNode& node, Point_<_Tp>& value, const Point_<_Tp>& default_value) { diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index 002ff01d09..9c7b0f236b 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -5594,6 +5594,35 @@ void read(const FileNode& node, std::vector& keypoints) } } + +void write(FileStorage& fs, const String& objname, const std::vector& 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& 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; } diff --git a/modules/features2d/src/mser.cpp b/modules/features2d/src/mser.cpp index 7b920465f4..b37b69ba7b 100644 --- a/modules/features2d/src/mser.cpp +++ b/modules/features2d/src/mser.cpp @@ -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 >* msers; + vector* 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()); + vector& 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& 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()); - vector& 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()); - vector& 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; diff --git a/modules/features2d/test/test_descriptors_regression.cpp b/modules/features2d/test/test_descriptors_regression.cpp index e40fe9fb11..5ebf508d86 100644 --- a/modules/features2d/test/test_descriptors_regression.cpp +++ b/modules/features2d/test/test_descriptors_regression.cpp @@ -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 imgs, descriptors; + vector > keypoints; + int i, n = 6; + Ptr 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 fnames; + glob(pattern, fnames, false); + sort(fnames.begin(), fnames.end()); + + Ptr akaze = AKAZE::create(); + Ptr orb = ORB::create(); + Ptr kaze = KAZE::create(); + Ptr brisk = BRISK::create(); + size_t i, n = fnames.size(); + vector 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"); + } +} diff --git a/modules/features2d/test/test_matchers_algorithmic.cpp b/modules/features2d/test/test_matchers_algorithmic.cpp index ec71e5a5ad..8f66648599 100644 --- a/modules/features2d/test/test_matchers_algorithmic.cpp +++ b/modules/features2d/test/test_matchers_algorithmic.cpp @@ -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 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 ); +} diff --git a/modules/features2d/test/test_mser.cpp b/modules/features2d/test/test_mser.cpp index 29572870ae..89b8ddbade 100644 --- a/modules/features2d/test/test_mser.cpp +++ b/modules/features2d/test/test_mser.cpp @@ -41,171 +41,121 @@ //M*/ #include "test_precomp.hpp" -#include "opencv2/imgproc/imgproc_c.h" - -#if 0 +#include "opencv2/highgui.hpp" #include #include 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 >& msers) { -public: - CV_MserTest(); -protected: - void run(int); - int LoadBoxes(const char* path, vector& boxes); - int SaveBoxes(const char* path, const vector& boxes); - int CompareBoxes(const vector& boxes1,const vector& boxes2, float max_rel_diff = 0.01f); -}; - -CV_MserTest::CV_MserTest() -{ -} - -int CV_MserTest::LoadBoxes(const char* path, vector& 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& 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& boxes1,const vector& 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 > msers; - MSER()(yuv, msers); - - vector boxes; - vector 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(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 mserExtractor = MSER::create( kDelta ); + vector > msers; + vector 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); + } +}