4.24 How to detect features in an image in OpenCV?

The following SolFeatureDetector is a simple example to detect featues in an image by using various feature detector classes of OpenCV. In the latest OpenCV3.2.0, the following feature detector classes are available, which are implemented as subclasses derived from Feature2D class. For overview, please read OpenCV 3.2.0 Transition guide :Changes overview.
It says that in OpenCV 3.2.0, the instances of classes derived from Algorithm class must be created with cv::makePtr or corresponding static factory method something like create:

Ptr<SomeAlgorithm> instance = makePtr<SomeAlgorithm>(...);
Ptr<SomeAlgoritym> instance = SomeAlgorithm::create(...);

For detail, see also C:\opencv3.2\build\include\opencv2\feature2d.hpp.
Furthermore, please note that some features detection algorithms (FREAK, BRIEF, SIFT, SURF) have been moved to opencv_contrib repository, to xfeatures2d module.



This SolFeatureDetector is a very simple and elementary GUI tool, but may be useful to evaluate characteristics of above feature detectors for various images. You need the following operations to use this program.

1 Open a target image file by using a file open dialog which can be popped up by Open menu item.
2 Select a detector from detectors combobox.
3 Select a a color from a combobox color chooser.
4 Click Detect pushbutton.
5 Click Clear pushbutton to clear detected keypoints.

In this program, we create a detector object for a selected item from the detector combobox. For example, if AgastFeatureDector were selected, we create the corresponding detector object and call detect method of the object to detect features in an image in the following way:
   void detect(DETECTOR detectorIndex, BYTE b, BYTE g, BYTE r)
    {
      cv::Mat image = originalImage.clone();
      detectedImage.release();
      
      std::vector keypoints;
 
      switch (detectorIndex) {
        case DETECTOR_AGAST: {      
              //Create an instance of AgastFeatureDetector class
               Ptr<AgastFeatureDetector> detector =  cv::AgastFeatureDetector::create(); 
              //Call detect method.
              detector->detect(image, keypoints);
            }
            ...
        }
      //Draw detected keypoints on detectedImage  by a cv::Scalar(b, g,r ). 
      drawKeypoints(image, keypoints, detectedImage, cv::Scalar(b, g, r));
      refresh();
    }      

The following are some examples of this feature detection program. The left pane is an orignal image, and the right pane is a detected image.

Feature detection for a geometry image by GFTTDetector


Feature detection for a cloud image by BRISKFeatureDetector


Feature detection for a building image by MSERFeatureDetector


/*
 * SolFeatureDetector.cpp 
 * Copyright (c) 2017 Antillia.com TOSHIYUKI ARAI. ALL RIGHTS RESERVED. 
 */
//2017/04/10

#define _CONSOLE_
#include <sol/Pair.h>
#include <sol/ModuleFileName.h>
#include <sol/Label.h>
#include <sol/ComboBox.h>
#include <sol/ComboBoxColorChooser.h>
#include <sol/PushButton.h>

#include <sol/FileDialog.h>
#include <sol/opencv/OpenCVApplicationView.h>
#include <sol/opencv/OpenCVImageView.h>

namespace SOL {

class MainView :public OpenCVApplicationView {
private:
  typedef enum {
      /* AgastFeatureDetector */   DETECTOR_AGAST,
      /* AKAZEFeatureDetector */   DETECTOR_AKAZE,
      /* BRISKFeatureDetector */   DETECTOR_BRISK,
      /* FastFeatureDetector  */   DETECTOR_FAST,
      /* GFTTDetector         */   DETECTOR_GFTT,
      /* KAZEFeatureDetector  */   DETECTOR_KAZE,
      /* MSERFeatureDetector  */   DETECTOR_MSER,
      /* ORBFeatureDetector   */   DETECOR_ORB,
  } DETECTOR;
  
private:
  ////////////////////////////////////////////////////////////////////////////////////////
  //Inner class starts.
  class OriginalImageView :public OpenCVImageView {
  private:
    cv::Mat originalImage;
    
    //This is a mandatory method, because in parent class it's declared
    //as a pure virtual function.
    cv::Mat& getMat()
    {
      return originalImage;
    }
    
    void display()
    {
      show(originalImage);
    }
    
  public:
    OriginalImageView(View* parent, const char* name, Args& args)
    :OpenCVImageView(parent, name, args)
    {
      try {
        const char* filename = (const char*)args.get(XmNimageFileName);
        int imageLoadingFlag = args.get(XmNimageLoadingFlag);
        loadImage(filename, imageLoadingFlag);
      } catch (SOL::Exception ex) {
        caught(ex);
      }
    }
    
    ~OriginalImageView()
    {
    }
    
    void loadImage(const char* filename, int imageLoadingFlag= CV_LOAD_IMAGE_COLOR)
    {
      try {
        originalImage = readImage(filename, imageLoadingFlag);
        refresh();
      } catch (Exception& ex) {
        caught(ex);
      }
    }
    
  };
  
  class DetectedImageView :public OpenCVImageView {
  private:
    cv::Mat originalImage;
    cv::Mat detectedImage;
    
    cv::Mat& getMat()
    {
      return detectedImage;
    }
    
    void display()
    {
      show(detectedImage);
    }
    
  public:
    DetectedImageView(View* parent, const char* name, Args& args)
    :OpenCVImageView(parent, name, args)
    {
      try {
        const char* filename = (const char*)args.get(XmNimageFileName);
        int imageLoadingFlag = args.get(XmNimageLoadingFlag);
        loadImage(filename, imageLoadingFlag);
      } catch (SOL::Exception ex) {
        caught(ex);
      }
    }
    
    ~DetectedImageView()
    {
    }
    
    void loadImage(const char* filename, int imageLoadingFlag= CV_LOAD_IMAGE_COLOR)
    {
      try {
        originalImage = readImage(filename, imageLoadingFlag);
        detectedImage  = originalImage;
        refresh();
      } catch (Exception& ex) {
        caught(ex);
      }
    }
    
    void clear()
    {
      cv::Mat image = originalImage.clone();
      detectedImage = image;
      refresh();
    }
    
    void detect(DETECTOR detectorIndex, BYTE b, BYTE g, BYTE r)
    {
      cv::Mat image = originalImage.clone();
      detectedImage.release();// = cv::Mat::ones(originalImage.size(), originalImage.type() );
      
      std::vector<cv::KeyPoint> keypoints;
      switch (detectorIndex) {
        case DETECTOR_AGAST: {      
              Ptr<AgastFeatureDetector> detector =  cv::AgastFeatureDetector::create(); 
              detector->detect(image, keypoints);
            }
            break;
        
      case DETECTOR_AKAZE:{
              Ptr<AKAZE> detector =  cv::AKAZE::create();
              detector->detect(image, keypoints);
            }
            break;
        
      case DETECTOR_BRISK: {
              Ptr<BRISK> detector =  cv::BRISK::create();
              detector->detect(image, keypoints);
            }
            break;
        
      case DETECTOR_FAST: {
              Ptr<FastFeatureDetector> detector =  cv::FastFeatureDetector::create();
              detector->detect(image, keypoints);
            }
            break;
        
      case DETECTOR_GFTT:{
              Ptr<GFTTDetector> detector =  cv::GFTTDetector::create();
              detector->detect(image, keypoints);
            }
            break;
        
      case DETECTOR_KAZE:{
              Ptr<KAZE> detector =  cv::KAZE::create();
              detector->detect(image, keypoints);              
            }
            break;
      case DETECTOR_MSER: {
              Ptr<MSER> detector =  cv::MSER::create();
              detector->detect(image, keypoints);
            }
            break;
      case DETECOR_ORB:{
              Ptr<ORB> detector =  cv::ORB::create();  //use default argments
              detector->detect(image, keypoints);
            }
            break;
      default:
        break;
      } 
      drawKeypoints(image, keypoints, detectedImage, cv::Scalar(b, g, r));
      refresh();
    }
 
  };
  //Inner class ends.
  ////////////////////////////////////////////////////////////////////////////////////////
  
  
  StringT<char>        imageFile;
  
  SmartPtr<OriginalImageView>     originalImage;
  SmartPtr<DetectedImageView>     detectedImage;
  
  SmartPtr<Label>                 detectorLabel;
  SmartPtr<ComboBox>              detectorComboBox;
  
  SmartPtr<Label>                 keyPointColorLabel;
  SmartPtr<ComboBoxColorChooser>  keyPointColorChooser;
  
  DETECTOR                        detectorIndex;
  COLORREF                        keyPointColorRGB;
  BYTE                            keyPointColorRed;
  BYTE                            keyPointColorGreen;
  BYTE                            keyPointColorBlue;
  
  SmartPtr<PushButton>            clearButton;
  SmartPtr<PushButton>            detectButton;
  
  FileDialog                      filedlg;

  void updateCaption()
  {
    char caption[MAX_PATH];
    sprintf_s(caption, CountOf(caption), "%s - %s", 
        (const char*)imageFile, 
        getAppName() ); 
 
    setText(caption);
  }

  
  void resize(int w, int h)
  {
    int workw = 170;
    
    if (originalImage && detectedImage) {
      originalImage        -> reshape(2,              2,  (w-workw)/2-1,    h-4);
      detectedImage        -> reshape((w-workw)/2+1,  2,  (w-workw)/2-1,    h-4);
      
      detectorLabel       -> reshape(w - workw+5,  10,  workw-20, 28);
      detectorComboBox    -> reshape(w - workw+5,  35,  workw-20, 120);
      
      keyPointColorLabel   -> reshape(w - workw+5, 80,  workw-20, 28);
      keyPointColorChooser -> reshape(w - workw+5, 105,  workw-20, 180);
      
      clearButton         -> reshape(w - workw+20, 160,  120,      30);
      detectButton         -> reshape(w - workw+20, 210,  120,      30);
      
    }
  }

  const Pair<char*, DETECTOR>* getDetectorsList(int& count)
  {
    static const Pair<char*, DETECTOR> detectors[] = {
      {"AgastFeatureDetector", DETECTOR_AGAST},
      {"AKAZEFeatureDetector", DETECTOR_AKAZE},
      {"BRISKFeatureDetector", DETECTOR_BRISK},
      {"FastFeatureDetector",  DETECTOR_FAST},
      
      {"GFTTDetector",         DETECTOR_GFTT},
      {"KAZEFeatureDetector",  DETECTOR_KAZE},
      {"MSERFeatureDetector",  DETECTOR_MSER},
      {"ORBFeatureDetector",   DETECOR_ORB},
    };
    
    count = CountOf(detectors);
    return detectors;
  }
  
  void confirm(Action& action)
  {
    int rc = MessageBox(NULL, "Are you sure to close this window?", "Confirmation", 
                MB_OKCANCEL|MB_ICONEXCLAMATION);
    if (rc == IDOK) {
      exit(action);
    }
  }
  
  void   createImageView()
  {  
    imageFile = "..\\images\\Geometry.png";
      
    int  imageLoadingFlag = CV_LOAD_IMAGE_COLOR;
    Args ar;
    ar.set(XmNimageFileName, imageFile);
    ar.set(XmNimageLoadingFlag, imageLoadingFlag);
    originalImage = new OriginalImageView(this, "cvwindow1", ar); 

    ar.reset();
    ar.set(XmNimageFileName, imageFile);
    ar.set(XmNimageLoadingFlag, imageLoadingFlag);
    detectedImage = new DetectedImageView(this, "cvwindow2", ar); 
  }
  
  void detectorChanged(Action& action)
  {
    detectorIndex = (DETECTOR)detectorComboBox->getCurSel();
    //char text[128];
    //sprintf_s(text, CountOf(text), "detector %d", detectorIndex);
    if (detectedImage) {
      detectedImage -> detect(detectorIndex, keyPointColorBlue,  keyPointColorGreen, keyPointColorRed);
    }
  }
  
  void createDetectorInterface()
  {
    Args ar;
    detectorLabel      = new Label(this, "Detector", ar);
    
    ar.reset();
    ar.set(XmNstyle, CBS_DROPDOWNLIST);
    detectorComboBox   = new ComboBox(this, "", ar);
    
    int count = 0;
    const Pair<char*, DETECTOR>* detectors = getDetectorsList(count);

    for (int i =0; i<count; i++) {
      detectorComboBox->addString(detectors[i].first);
    }
    detectorIndex  = DETECTOR_AGAST;
    detectorComboBox -> setCurSel((int)detectorIndex);
    detectorComboBox -> addCallback(XmNselChangeCallback, this,
        (Callback)&MainView::detectorChanged, NULL);
  }
  
  
  long colorChoosed(Event& event)
  {
    //This is not a BGR
    keyPointColorRGB = (COLORREF)event.getLParam();
    keyPointColorRed   = GetRValue(keyPointColorRGB);
    keyPointColorGreen = GetGValue(keyPointColorRGB);
    keyPointColorBlue  = GetBValue(keyPointColorRGB);
    
    if (detectedImage) {
      detectedImage -> detect(detectorIndex, keyPointColorBlue,  keyPointColorGreen, keyPointColorRed);
    }
    return 1;
  }
  
  void createColorSettingInterface()
  {
    Args ar;
    keyPointColorLabel = new Label(this, "KeyPointColor", ar);
    
    ar.reset();
    //ar.set(XmNstyle, CBS_DROPDOWNLIST);
    ar.set(XmNstyle, WS_VISIBLE);
    ar.set(XmNitemHeight, 32);
    ar.set(XmNrgbStep, 0x128);
    //ar.set(XmNrgbStep, 0x20);
    keyPointColorChooser = new ComboBoxColorChooser(this, "", ar);
    keyPointColorChooser -> setCurrentSelection("#FF0000"); //Red
    
    keyPointColorRGB   = RGB(0xff, 00, 00);
    keyPointColorRed   = GetRValue(keyPointColorRGB);
    keyPointColorGreen = GetGValue(keyPointColorRGB);
    keyPointColorBlue  = GetBValue(keyPointColorRGB);
    
    addEventHandler(WM_COMBOBOX_COLORCHOOSER_SELCHANGED, this,
      (Handler)&MainView::colorChoosed, NULL);
  }
  
  void clear(Action& action)
  {
    if (detectedImage) {
      detectedImage -> clear();
    }
    
  }
  
  void detect(Action& action)
  {
    if (detectedImage) {
      detectedImage -> detect(detectorIndex, keyPointColorBlue,  keyPointColorGreen, keyPointColorRed);
    }
  }
  
  void createOperationInterface()
  {
    Args ar;
    clearButton = new PushButton(this, "Clear", ar);
    clearButton -> addCallback(XmNactivateCallback, this,
      (Callback)&MainView::clear, NULL);
    
    ar.reset();
    detectButton = new PushButton(this, "Detect", ar);
    detectButton -> addCallback(XmNactivateCallback, this,
      (Callback)&MainView::detect, NULL);
  }
    
public:
  MainView(OpenCVApplication& applet, const char* name, Args& args)
  :OpenCVApplicationView(applet, name, args)
  {
    try {
      createImageView();
      
      createDetectorInterface();

      createColorSettingInterface();

      addCallback(XmNmenuCallback, IDM_EXIT, this,
          (Callback)&MainView::confirm, NULL);

      createOperationInterface();
      
      detectedImage -> detect(detectorIndex, keyPointColorBlue,  keyPointColorGreen, keyPointColorRed);
   
      updateCaption(); 
      
      Args ar;
      ar.reset();
      ar.set(XmNfilter, FileDialog::getImageFilesFilter());
      filedlg.create(this, "OpenFile", ar);
      
    } catch (Exception& ex) {
      caught(ex);
    }
  }

  ~MainView()
  {
  }

  void open(Action& action)
  {
    Args ar;
    
    char dir[MAX_PATH] = { 0 };
    if (restoreFileFolder(dir, CountOf(dir))) {
      ar.set(XmNdirectory, dir);
      filedlg.setValues(ar);
    }
    
    try {    
      if(filedlg.open()) {
    
        const char* filename = filedlg.getFileName();
        saveFileFolder(filename);
        
        originalImage -> loadImage(filename);
        detectedImage  -> loadImage(filename);
        const char* fname = strrchr(filename, '\\');
        if (fname) {
          fname++;
        }
        imageFile = fname;

      detectedImage -> detect(detectorIndex, keyPointColorBlue,  keyPointColorGreen, keyPointColorRed);

        updateCaption();  

      }
    } catch (Exception& ex) {
      caught(ex);
    }
  }  
};
}

//
void main(int argc, char** argv) 
{
  try {
    ModuleFileName module(argv[0]);
    
    const char*  name = module.getAppName();
        
    OpenCVApplication applet(name, argc, argv);

    Args args;
    args.set(XmNwidth,  900);
    args.set(XmNheight, 380);
    args.set(XmNbackground, (LONG_PTR)(COLOR_BTNFACE+1));
    args.set(XmNexStyle, (LONG_PTR)WS_EX_CONTROLPARENT);
  
    MainView view(applet, name, args);
    view.realize();

    applet.run();
    
  } catch (SOL::Exception& ex) {
    caught(ex);
  }
}

Last modified: 8 Apr. 2017

Copyright (c) 2017 Antillia.com ALL RIGHTS RESERVED.