added FAST<5/8> & FAST<7/12> (by Vincent Rabaud)
authorVadim Pisarevsky <vadim.pisarevsky@itseez.com>
Tue, 31 Jul 2012 13:17:58 +0000 (17:17 +0400)
committerVadim Pisarevsky <vadim.pisarevsky@itseez.com>
Tue, 31 Jul 2012 13:17:58 +0000 (17:17 +0400)
modules/features2d/include/opencv2/features2d/features2d.hpp
modules/features2d/perf/perf_fast.cpp
modules/features2d/src/fast.cpp
modules/features2d/src/features2d_init.cpp
modules/features2d/test/test_fast.cpp

index d8cd235..a191ca2 100644 (file)
@@ -473,12 +473,18 @@ protected:
 
 //! detects corners using FAST algorithm by E. Rosten
 CV_EXPORTS void FAST( InputArray image, CV_OUT vector<KeyPoint>& keypoints,
-                      int threshold, bool nonmaxSupression=true );
+                      int threshold, bool nonmaxSupression=true, int type = 2 );
 
 class CV_EXPORTS_W FastFeatureDetector : public FeatureDetector
 {
 public:
-    CV_WRAP FastFeatureDetector( int threshold=10, bool nonmaxSuppression=true );
+    enum
+    {
+      TYPE_5_8 = 0, TYPE_7_12 = 1, TYPE_9_16 = 2
+    };
+
+    CV_WRAP FastFeatureDetector( int threshold=10, bool nonmaxSuppression=true);
+    CV_WRAP FastFeatureDetector( int threshold, bool nonmaxSuppression, int type);
     AlgorithmInfo* info() const;
 
 protected:
@@ -486,6 +492,7 @@ protected:
 
     int threshold;
     bool nonmaxSuppression;
+    int type;
 };
 
 
index da75f9d..28f0ccc 100644 (file)
@@ -22,9 +22,13 @@ PERF_TEST_P(fast, detectForORB, testing::Values(FAST_IMAGES))
 
     declare.in(frame);
 
-    FastFeatureDetector fd(20, true);
+    FastFeatureDetector fd(20, true, FastFeatureDetector::TYPE_5_8);
     vector<KeyPoint> points;
 
     TEST_CYCLE() fd.detect(frame, points);
+    fd = FastFeatureDetector(20, true, FastFeatureDetector::TYPE_7_12);
+    TEST_CYCLE() fd.detect(frame, points);
+    fd = FastFeatureDetector(20, true, FastFeatureDetector::TYPE_9_16);
+    TEST_CYCLE() fd.detect(frame, points);
 }
 
index 9495d35..f496de3 100644 (file)
@@ -46,27 +46,93 @@ The references are:
 namespace cv
 {
 
-static void makeOffsets(int pixel[], int row_stride)
+static void makeOffsets(int pixel[], int row_stride, int patternSize)
 {
-    pixel[0] = 0 + row_stride * 3;
-    pixel[1] = 1 + row_stride * 3;
-    pixel[2] = 2 + row_stride * 2;
-    pixel[3] = 3 + row_stride * 1;
-    pixel[4] = 3 + row_stride * 0;
-    pixel[5] = 3 + row_stride * -1;
-    pixel[6] = 2 + row_stride * -2;
-    pixel[7] = 1 + row_stride * -3;
-    pixel[8] = 0 + row_stride * -3;
-    pixel[9] = -1 + row_stride * -3;
-    pixel[10] = -2 + row_stride * -2;
-    pixel[11] = -3 + row_stride * -1;
-    pixel[12] = -3 + row_stride * 0;
-    pixel[13] = -3 + row_stride * 1;
-    pixel[14] = -2 + row_stride * 2;
-    pixel[15] = -1 + row_stride * 3;
+    switch(patternSize) {
+      case 16:
+        pixel[0] = 0 + row_stride * 3;
+        pixel[1] = 1 + row_stride * 3;
+        pixel[2] = 2 + row_stride * 2;
+        pixel[3] = 3 + row_stride * 1;
+        pixel[4] = 3 + row_stride * 0;
+        pixel[5] = 3 + row_stride * -1;
+        pixel[6] = 2 + row_stride * -2;
+        pixel[7] = 1 + row_stride * -3;
+        pixel[8] = 0 + row_stride * -3;
+        pixel[9] = -1 + row_stride * -3;
+        pixel[10] = -2 + row_stride * -2;
+        pixel[11] = -3 + row_stride * -1;
+        pixel[12] = -3 + row_stride * 0;
+        pixel[13] = -3 + row_stride * 1;
+        pixel[14] = -2 + row_stride * 2;
+        pixel[15] = -1 + row_stride * 3;
+        break;
+      case 12:
+        pixel[0] = 0 + row_stride * 2;
+        pixel[1] = 1 + row_stride * 2;
+        pixel[2] = 2 + row_stride * 1;
+        pixel[3] = 2 + row_stride * 0;
+        pixel[4] = 2 + row_stride * -1;
+        pixel[5] = 1 + row_stride * -2;
+        pixel[6] = 0 + row_stride * -2;
+        pixel[7] = -1 + row_stride * -2;
+        pixel[8] = -2 + row_stride * -1;
+        pixel[9] = -2 + row_stride * 0;
+        pixel[10] = -2 + row_stride * 1;
+        pixel[11] = -1 + row_stride * 2;
+        break;
+      case 8:
+        pixel[0] = 0 + row_stride * 1;
+        pixel[1] = 1 + row_stride * 1;
+        pixel[2] = 1 + row_stride * 0;
+        pixel[3] = 1 + row_stride * -1;
+        pixel[4] = 0 + row_stride * -1;
+        pixel[5] = -1 + row_stride * -1;
+        pixel[6] = 0 + row_stride * 0;
+        pixel[7] = 1 + row_stride * 1;
+        break;
+    }
 }
 
-static int cornerScore(const uchar* ptr, const int pixel[], int threshold)
+/*static void testCorner(const uchar* ptr, const int pixel[], int K, int N, int threshold) {
+    // check that with the computed "threshold" the pixel is still a corner
+    // and that with the increased-by-1 "threshold" the pixel is not a corner anymore
+    for( int delta = 0; delta <= 1; delta++ )
+    {
+        int v0 = std::min(ptr[0] + threshold + delta, 255);
+        int v1 = std::max(ptr[0] - threshold - delta, 0);
+        int c0 = 0, c1 = 0;
+
+        for( int k = 0; k < N; k++ )
+        {
+            int x = ptr[pixel[k]];
+            if(x > v0)
+            {
+                if( ++c0 > K )
+                    break;
+                c1 = 0;
+            }
+            else if( x < v1 )
+            {
+                if( ++c1 > K )
+                    break;
+                c0 = 0;
+            }
+            else
+            {
+                c0 = c1 = 0;
+            }
+        }
+        CV_Assert( (delta == 0 && std::max(c0, c1) > K) ||
+                   (delta == 1 && std::max(c0, c1) <= K) );
+    }
+}*/
+
+template<int patternSize>
+int cornerScore(const uchar* ptr, const int pixel[], int threshold);
+
+template<>
+int cornerScore<16>(const uchar* ptr, const int pixel[], int threshold)
 {
     const int K = 8, N = 16 + K + 1;
     int k, v = ptr[0];
@@ -150,50 +216,170 @@ static int cornerScore(const uchar* ptr, const int pixel[], int threshold)
 #endif
 
 #if 0
-    // check that with the computed "threshold" the pixel is still a corner
-    // and that with the increased-by-1 "threshold" the pixel is not a corner anymore
-    for( int delta = 0; delta <= 1; delta++ )
+    testCorner(ptr, pixel, K, N, threshold);
+#endif
+    return threshold;
+}
+
+template<>
+int cornerScore<12>(const uchar* ptr, const int pixel[], int threshold)
+{
+    const int K = 6, N = 12 + K + 1;
+    int k, v = ptr[0];
+    short d[N];
+    for( k = 0; k < N; k++ )
+        d[k] = (short)(v - ptr[pixel[k]]);
+
+#if CV_SSE2
+    __m128i q0 = _mm_set1_epi16(-1000), q1 = _mm_set1_epi16(1000);
+    for( k = 0; k < 16; k += 8 )
     {
-        int v0 = std::min(ptr[0] + threshold + delta, 255);
-        int v1 = std::max(ptr[0] - threshold - delta, 0);
-        int c0 = 0, c1 = 0;
+        __m128i v0 = _mm_loadu_si128((__m128i*)(d+k+1));
+        __m128i v1 = _mm_loadu_si128((__m128i*)(d+k+2));
+        __m128i a = _mm_min_epi16(v0, v1);
+        __m128i b = _mm_max_epi16(v0, v1);
+        v0 = _mm_loadu_si128((__m128i*)(d+k+3));
+        a = _mm_min_epi16(a, v0);
+        b = _mm_max_epi16(b, v0);
+        v0 = _mm_loadu_si128((__m128i*)(d+k+4));
+        a = _mm_min_epi16(a, v0);
+        b = _mm_max_epi16(b, v0);
+        v0 = _mm_loadu_si128((__m128i*)(d+k+5));
+        a = _mm_min_epi16(a, v0);
+        b = _mm_max_epi16(b, v0);
+        v0 = _mm_loadu_si128((__m128i*)(d+k+6));
+        a = _mm_min_epi16(a, v0);
+        b = _mm_max_epi16(b, v0);
+        v0 = _mm_loadu_si128((__m128i*)(d+k));
+        q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
+        q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
+        v0 = _mm_loadu_si128((__m128i*)(d+k+7));
+        q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
+        q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
+    }
+    q0 = _mm_max_epi16(q0, _mm_sub_epi16(_mm_setzero_si128(), q1));
+    q0 = _mm_max_epi16(q0, _mm_unpackhi_epi64(q0, q0));
+    q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, 4));
+    q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, 2));
+    threshold = (short)_mm_cvtsi128_si32(q0) - 1;
+#else
+    int a0 = threshold;
+    for( k = 0; k < 12; k += 2 )
+    {
+        int a = std::min((int)d[k+1], (int)d[k+2]);
+        if( a <= a0 )
+            continue;
+        a = std::min(a, (int)d[k+3]);
+        a = std::min(a, (int)d[k+4]);
+        a = std::min(a, (int)d[k+5]);
+        a = std::min(a, (int)d[k+6]);
+        a0 = std::max(a0, std::min(a, (int)d[k]));
+        a0 = std::max(a0, std::min(a, (int)d[k+7]));
+    }
 
-        for( int k = 0; k < N; k++ )
-        {
-            int x = ptr[pixel[k]];
-            if(x > v0)
-            {
-                if( ++c0 > K )
-                    break;
-                c1 = 0;
-            }
-            else if( x < v1 )
-            {
-                if( ++c1 > K )
-                    break;
-                c0 = 0;
-            }
-            else
-            {
-                c0 = c1 = 0;
-            }
-        }
-        CV_Assert( (delta == 0 && std::max(c0, c1) > K) ||
-                   (delta == 1 && std::max(c0, c1) <= K) );
+    int b0 = -a0;
+    for( k = 0; k < 12; k += 2 )
+    {
+        int b = std::max((int)d[k+1], (int)d[k+2]);
+        b = std::max(b, (int)d[k+3]);
+        b = std::max(b, (int)d[k+4]);
+        if( b >= b0 )
+            continue;
+        b = std::max(b, (int)d[k+5]);
+        b = std::max(b, (int)d[k+6]);
+
+        b0 = std::min(b0, std::max(b, (int)d[k]));
+        b0 = std::min(b0, std::max(b, (int)d[k+7]));
     }
+
+    threshold = -b0-1;
+#endif
+
+#if 0
+    testCorner(ptr, pixel, K, N, threshold);
 #endif
     return threshold;
 }
 
+template<>
+int cornerScore<8>(const uchar* ptr, const int pixel[], int threshold)
+{
+    const int K = 4, N = 8 + K + 1;
+    int k, v = ptr[0];
+    short d[N];
+    for( k = 0; k < N; k++ )
+        d[k] = (short)(v - ptr[pixel[k]]);
+
+#if CV_SSE2
+    __m128i q0 = _mm_set1_epi16(-1000), q1 = _mm_set1_epi16(1000);
+    for( k = 0; k < 16; k += 8 )
+    {
+        __m128i v0 = _mm_loadu_si128((__m128i*)(d+k+1));
+        __m128i v1 = _mm_loadu_si128((__m128i*)(d+k+2));
+        __m128i a = _mm_min_epi16(v0, v1);
+        __m128i b = _mm_max_epi16(v0, v1);
+        v0 = _mm_loadu_si128((__m128i*)(d+k+3));
+        a = _mm_min_epi16(a, v0);
+        b = _mm_max_epi16(b, v0);
+        v0 = _mm_loadu_si128((__m128i*)(d+k+4));
+        a = _mm_min_epi16(a, v0);
+        b = _mm_max_epi16(b, v0);
+        v0 = _mm_loadu_si128((__m128i*)(d+k));
+        q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
+        q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
+        v0 = _mm_loadu_si128((__m128i*)(d+k+5));
+        q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
+        q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
+    }
+    q0 = _mm_max_epi16(q0, _mm_sub_epi16(_mm_setzero_si128(), q1));
+    q0 = _mm_max_epi16(q0, _mm_unpackhi_epi64(q0, q0));
+    q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, 4));
+    q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, 2));
+    threshold = (short)_mm_cvtsi128_si32(q0) - 1;
+#else
+    int a0 = threshold;
+    for( k = 0; k < 8; k += 2 )
+    {
+        int a = std::min((int)d[k+1], (int)d[k+2]);
+        if( a <= a0 )
+            continue;
+        a = std::min(a, (int)d[k+3]);
+        a = std::min(a, (int)d[k+4]);
+        a0 = std::max(a0, std::min(a, (int)d[k]));
+        a0 = std::max(a0, std::min(a, (int)d[k+5]));
+    }
+
+    int b0 = -a0;
+    for( k = 0; k < 12; k += 2 )
+    {
+        int b = std::max((int)d[k+1], (int)d[k+2]);
+        b = std::max(b, (int)d[k+3]);
+        if( b >= b0 )
+            continue;
+        b = std::max(b, (int)d[k+4]);
+
+        b0 = std::min(b0, std::max(b, (int)d[k]));
+        b0 = std::min(b0, std::max(b, (int)d[k+5]));
+    }
+
+    threshold = -b0-1;
+#endif
+
+#if 0
+    testCorner(ptr, pixel, K, N, threshold);
+#endif
+    return threshold;
+}
 
-void FAST(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool nonmax_suppression)
+template<int patternSize>
+void FAST_t(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool nonmax_suppression)
 {
     Mat img = _img.getMat();
-    const int K = 8, N = 16 + K + 1;
-    int i, j, k, pixel[N];
-    makeOffsets(pixel, (int)img.step);
-    for(k = 16; k < N; k++)
-        pixel[k] = pixel[k - 16];
+    const int K = patternSize/2, N = patternSize + K + 1, quarterPatternSize = patternSize/4;
+    int i, j, k, pixel[25];
+    makeOffsets(pixel, (int)img.step, patternSize);
+    for(k = patternSize; k < 25; k++)
+        pixel[k] = pixel[k - patternSize];
 
     keypoints.clear();
 
@@ -235,9 +421,9 @@ void FAST(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool
                 v0 = _mm_xor_si128(_mm_adds_epu8(v0, t), delta);
 
                 __m128i x0 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[0])), delta);
-                __m128i x1 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[4])), delta);
-                __m128i x2 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[8])), delta);
-                __m128i x3 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[12])), delta);
+                __m128i x1 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[quarterPatternSize])), delta);
+                __m128i x2 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[2*quarterPatternSize])), delta);
+                __m128i x3 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[3*quarterPatternSize])), delta);
                 m0 = _mm_and_si128(_mm_cmpgt_epi8(x0, v0), _mm_cmpgt_epi8(x1, v0));
                 m1 = _mm_and_si128(_mm_cmpgt_epi8(v1, x0), _mm_cmpgt_epi8(v1, x1));
                 m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x1, v0), _mm_cmpgt_epi8(x2, v0)));
@@ -279,7 +465,7 @@ void FAST(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool
                     {
                         cornerpos[ncorners++] = j+k;
                         if(nonmax_suppression)
-                            curr[j+k] = (uchar)cornerScore(ptr+k, pixel, threshold);
+                            curr[j+k] = (uchar)cornerScore<patternSize>(ptr+k, pixel, threshold);
                     }
             }
     #endif
@@ -317,7 +503,7 @@ void FAST(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool
                             {
                                 cornerpos[ncorners++] = j;
                                 if(nonmax_suppression)
-                                    curr[j] = (uchar)cornerScore(ptr, pixel, threshold);
+                                    curr[j] = (uchar)cornerScore<patternSize>(ptr, pixel, threshold);
                                 break;
                             }
                         }
@@ -339,7 +525,7 @@ void FAST(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool
                             {
                                 cornerpos[ncorners++] = j;
                                 if(nonmax_suppression)
-                                    curr[j] = (uchar)cornerScore(ptr, pixel, threshold);
+                                    curr[j] = (uchar)cornerScore<patternSize>(ptr, pixel, threshold);
                                 break;
                             }
                         }
@@ -375,19 +561,36 @@ void FAST(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool
     }
 }
 
-
+void FAST(InputArray _img, std::vector<KeyPoint>& keypoints, int threshold, bool nonmax_suppression, int type)
+{
+  switch(type) {
+    case FastFeatureDetector::TYPE_5_8:
+      FAST_t<8>(_img, keypoints, threshold, nonmax_suppression);
+      break;
+    case FastFeatureDetector::TYPE_7_12:
+      FAST_t<12>(_img, keypoints, threshold, nonmax_suppression);
+      break;
+    case FastFeatureDetector::TYPE_9_16:
+      FAST_t<16>(_img, keypoints, threshold, nonmax_suppression);
+      break;
+  }
+}
 /*
  *   FastFeatureDetector
  */
 FastFeatureDetector::FastFeatureDetector( int _threshold, bool _nonmaxSuppression )
-: threshold(_threshold), nonmaxSuppression(_nonmaxSuppression)
+    : threshold(_threshold), nonmaxSuppression(_nonmaxSuppression), type(FastFeatureDetector::TYPE_9_16)
 {}
 
+FastFeatureDetector::FastFeatureDetector( int _threshold, bool _nonmaxSuppression, int _type )
+: threshold(_threshold), nonmaxSuppression(_nonmaxSuppression), type(_type)
+{}
+    
 void FastFeatureDetector::detectImpl( const Mat& image, vector<KeyPoint>& keypoints, const Mat& mask ) const
 {
     Mat grayImage = image;
     if( image.type() != CV_8U ) cvtColor( image, grayImage, CV_BGR2GRAY );
-    FAST( grayImage, keypoints, threshold, nonmaxSuppression );
+    FAST( grayImage, keypoints, threshold, nonmaxSuppression, type );
     KeyPointsFilter::runByPixelsMask( keypoints, mask );
 }
 
index 0d884ef..6ecffeb 100644 (file)
@@ -58,7 +58,8 @@ CV_INIT_ALGORITHM(BriefDescriptorExtractor, "Feature2D.BRIEF",
 
 CV_INIT_ALGORITHM(FastFeatureDetector, "Feature2D.FAST",
                   obj.info()->addParam(obj, "threshold", obj.threshold);
-                  obj.info()->addParam(obj, "nonmaxSuppression", obj.nonmaxSuppression));
+                  obj.info()->addParam(obj, "nonmaxSuppression", obj.nonmaxSuppression);
+                  obj.info()->addParam(obj, "type", obj.type, FastFeatureDetector::TYPE_9_16));
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
index d991a29..671e66d 100644 (file)
@@ -58,6 +58,7 @@ CV_FastTest::~CV_FastTest() {}
 
 void CV_FastTest::run( int )
 {
+  for(int type=0; type <= 2; ++type) {
     Mat image1 = imread(string(ts->get_data_path()) + "inpaint/orig.jpg");
     Mat image2 = imread(string(ts->get_data_path()) + "cameracalibration/chess9.jpg");
     string xml = string(ts->get_data_path()) + "fast/result.xml";
@@ -74,8 +75,8 @@ void CV_FastTest::run( int )
 
     vector<KeyPoint> keypoints1;
     vector<KeyPoint> keypoints2;
-    FAST(gray1, keypoints1, 30);
-    FAST(gray2, keypoints2, 30);
+    FAST(gray1, keypoints1, 30, type);
+    FAST(gray2, keypoints2, 30, type);
 
     for(size_t i = 0; i < keypoints1.size(); ++i)
     {
@@ -109,17 +110,21 @@ void CV_FastTest::run( int )
     read( fs["exp_kps2"], exp_kps2, Mat() );
     fs.release();
 
+    // We only have testing data for 9_16 but it actually works equally well for 7_12
+    if ((type==1) || (type==2)){
     if ( 0 != norm(exp_kps1, kps1, NORM_L2) || 0 != norm(exp_kps2, kps2, NORM_L2))
     {
         ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
         return;
     }
+    }
 
/*   cv::namedWindow("Img1"); cv::imshow("Img1", image1);
   /*cv::namedWindow("Img1"); cv::imshow("Img1", image1);
     cv::namedWindow("Img2"); cv::imshow("Img2", image2);
     cv::waitKey(0);*/
+  }
 
-    ts->set_failed_test_info(cvtest::TS::OK);
+  ts->set_failed_test_info(cvtest::TS::OK);
 }
 
 TEST(Features2d_FAST, regression) { CV_FastTest test; test.safe_run(); }