SOL9 Sample: FlannBasedMatcher

SOL9 2.0 Samples

1 Screenshot


2 Source code

/*
 * FlannBasedMatcher.cpp 
 * Copyright (c) 2015 Antillia.com TOSHIYUKI ARAI. ALL RIGHTS RESERVED. 
 */


//2017/10/15
//2017/12/01 Added save, saveAs methods to MainView

#define _CONSOLE_

#include <sol/ModuleFileName.h>
#include <sol/Static.h>
#include <sol/LabeledTrackBar.h>
#include <sol/LabeledComboBox.h>
#include <sol/Pair.h>
#include <sol/DropFiles.h>
#include <sol/FileDialog.h>
#include <sol/opencv/OpenCVApplicationView.h>
#include <sol/opencv/OpenCVImageView.h>


#include <sol/PushButton.h>

#include "resource.h"

namespace SOL {
  
static const int WM_FEATURE_MATCHING_REQUEST  = (WM_USER + 2011);
  

class MainView :public OpenCVApplicationView {
  
private:
  ////////////////////////////////////////////////////////////////////////////////////////
  //Inner classes start.
  class SourceImageView :public OpenCVImageView {
  private:
    cv::Mat sourceImage;
    
  public:
    //This is a mandatory method, because in parent class it's declared
    //as a pure virtual function.
    cv::Mat& getMat()
    {
      return sourceImage;
    }
    
    cv::Mat getMatClone()
    {
      return sourceImage.clone();
    }
    
    void display()
    {
      show(sourceImage);
    }
    
  public:
    SourceImageView(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);
      }
    }
    
    ~SourceImageView()
    {
    }
    
    void loadImage(const char* filename, int imageLoadingFlag= CV_LOAD_IMAGE_COLOR)
    {
      try {
        sourceImage = readImage(filename, imageLoadingFlag);
        refresh();
      } catch (Exception& ex) {
        caught(ex);
      }
    }
  };
  
  class MatchedImageView :public OpenCVImageView {
  private:
    cv::Mat matchedImage;
    
    cv::Mat& getMat()
    {
      return matchedImage;
    }
    
    void display()
    {
      if ( !matchedImage.empty()) {
        show(matchedImage);
      }
    }
    
  public:
    MatchedImageView(View* parent, const char* name, Args& args) //const cv::Mat& matchedMat)
    :OpenCVImageView(parent, name, args)
    {
      refresh();
    }
    
    ~MatchedImageView()
    {
    }
  
    void setMatched(cv::Mat& mat)
    {
      matchedImage.release();
      
      matchedImage = mat;
      refresh();
    }
  };
  //Inner classes end.
  ////////////////////////////////////////////////////////////////////////////////////////

private:
  typedef enum {
      /* AKAZEFeatureDetector */   DETECTOR_AKAZE=0,
      /* BRISKFeatureDetector */   DETECTOR_BRISK,
      /* ORBFeatureDetector   */   DETECTOR_ORB,
  } DETECTOR;

  static const int         IMAGES_COUNT = 2;
  
  static const int          FIRST  = 0;

  static const int          SECOND = 1;

  StringT<char>            imageFiles[IMAGES_COUNT];
  StringT<char>            savedImageFile;
  
  SmartPtr<SourceImageView>  sourceImage[IMAGES_COUNT];

  SmartPtr<MatchedImageView> matchedImage;

  int                       detectorIndex;  
  SmartPtr<LabeledComboBox> detectorComboBox;

  SmartPtr<Static>           matchedNumber;

  int                       bestTopNumberIndex;

  SmartPtr<LabeledComboBox> bestTopNumberComboBox;
  
  FileDialog                filedlg;

  SmartPtr<PushButton>      matchButton;
  
  void updateCaption()
  {
    char caption[MAX_PATH];
    sprintf_s(caption, CountOf(caption), "%s %s - %s", 
        (const char*)imageFiles[FIRST], 
        (const char*)imageFiles[SECOND], 
      getAppName() ); 
       
    setText(caption);
  }
  
  void resize(int w, int h)
  {
    if (sourceImage[FIRST] && sourceImage[SECOND] && 
        matchedImage && detectorComboBox &&
        bestTopNumberComboBox && matchButton &&
        matchedNumber ) {
   
      sourceImage[FIRST]       -> reshape(2,           2,  (w-160)/2-1,    h/2-4);
      sourceImage[SECOND]      -> reshape((w-160)/2+1, 2,  (w-160)/2-1,    h/2-4);

      matchedImage             -> reshape(2,  h/2-2,       (w-160)-1, h/2-1);

      detectorComboBox         -> reshape(w-160 + 4,   2, 140, 60);      

      bestTopNumberComboBox    -> reshape(w-160 + 4,  70, 140, 60);

      matchButton              -> reshape(w-160 + 4, 130, 140, 30);
      
      matchedNumber            -> reshape(w-160 + 4, 200, 150, 30);
    }
  }

  void openFile(int index, const char* filename)
  {
    try {
      if (index >= FIRST && index <= SECOND) {
        sourceImage[index]     -> loadImage(filename);
        const char* fname = strrchr(filename, '\\');
        if (fname) {
          fname++;
        }
        imageFiles[index] = fname;
        updateCaption();
      }
    } catch (Exception& ex) {
      caught(ex);
    }
  }
  
  //Callback for the bestTopNumberComboBox
  void bestTopNumberChanged(Action& action)
  {
    bestTopNumberIndex = bestTopNumberComboBox->getCurSel();
    
    post(WM_FEATURE_MATCHING_REQUEST);
  }
  
  //Event handler for WM_DROPFILES
  long dropFiles(Event& event)
  {
    View* view = (View*)event.getData();
    char fileName[MAX_PATH] = { 0 };
    DropFiles drop((HDROP)event.getWParam());
    //fileName[0] = ZERO;
    int num = drop.queryFile(0, fileName, CountOf(fileName));
    if(num > 0) {
      if (filedlg.isImageFileName(fileName)) {
        if (view == sourceImage[FIRST]) {
          openFile(FIRST, fileName);
        } else if (view == sourceImage[SECOND]) {
          openFile(SECOND, fileName);          
        }
        bringUp();
      } else {        
        bringUp(); 
        showErrorDialog("Invalid image filename", fileName,  MB_OK);
      }
    }    
    return 0;
  }

  const Pair<char*, DETECTOR>* getDetectorsList(int& count)
  {
    //We use only the following detectors which can be combined 
    //with cv::BFMatcher of NORM_HAMMING normType.
    
    //According OpenCV 3 Document,
    // NORM_HAMMING should be used with ORB, BRISK and BRIEF.
    
    static const Pair<char*, DETECTOR> detectors[] = {
      {"AKAZEFeatureDetector", DETECTOR_AKAZE},   
      {"BRISKFeatureDetector", DETECTOR_BRISK},
      {"ORBFeatureDetector",   DETECTOR_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);
    }
  }

  //Callback for the matchButton
  void match(Action& action)
  {
    post(WM_FEATURE_MATCHING_REQUEST);
  }
  
  void setMatchedNumber(int bestMatched, int totalMatchedNumber)
  {
    char number[128];
    sprintf_s(number, CountOf(number), "Matched Number: %d / %d", bestMatched, totalMatchedNumber);
    
    matchedNumber->setText(number);
  }
  
  
  //Event handler for WM_FEATURE_MATCHING_REQUEST
  long featureMatching(Event& event)
  {
    try {
      cv::Ptr<cv::Feature2D> feature = cv::ORB::create();

      switch (detectorIndex) {
        
      case DETECTOR_AKAZE:
            feature =  cv::AKAZE::create();
            break;
        
      case DETECTOR_BRISK:
            feature =  cv::BRISK::create();
            break;
        
        case DETECTOR_ORB:
            feature =  cv::ORB::create();  //use default argments
            break;
      default:
        break;
      } 
      std::vector<cv::KeyPoint> keypoints[IMAGES_COUNT]; 
      cv::Mat                   descriptors[IMAGES_COUNT];
      cv::Mat                   mats[IMAGES_COUNT];
    
      //Detect features and compute descriptors.
      for (int i = 0; i<IMAGES_COUNT; i++) {
        mats[i] = sourceImage[i] -> getMat();
        feature -> detect(mats[i], keypoints[i]);
        feature -> compute(mats[i], keypoints[i], descriptors[i]);
      }
      
      //FlannBasedMatcher
      cv::FlannBasedMatcher matcher(new flann::LshIndexParams(20, 10, 2));

      std::vector<cv::DMatch> matches;
      matcher.match(descriptors[FIRST], descriptors[SECOND], matches);

      int totalMatchedNumber = matches.size();
      
      int bestTop = (bestTopNumberIndex + 1) * 10;
    
      if (bestTop <= totalMatchedNumber) {
        std::nth_element(matches.begin(), matches.begin() + bestTop - 1, matches.end());
        matches.erase(matches.begin() + bestTop, matches.end());
      }
        
      int bestMatchedNumber = matches.size();
        
      Mat matchedImg;
      drawMatches(mats[FIRST],  keypoints[FIRST], 
                mats[SECOND], keypoints[SECOND], matches, matchedImg);
      
      matchedImage ->setMatched(matchedImg);
      
      setMatchedNumber(bestMatchedNumber, totalMatchedNumber);

    } catch (cv::Exception& ex) {
      //MessageBox(NULL, "Caught an excption", "Exception", MB_OK);
    }
    return 1;
  }
  
  //Callback for the detectorComboBox
  void detectorChanged(Action& action)
  {
    detectorIndex = (DETECTOR)detectorComboBox->getCurSel();
    
    post(WM_FEATURE_MATCHING_REQUEST);
  }

  //Callback for the event WM_DROPFILES
  void dropCallback(Action& action)
  {    
    View* view = (View*)action.getData();
    
    char fileName[MAX_PATH] = { 0 };
    DropFiles drop((HDROP)action.getWParam());
    int num = drop.queryFile(0, fileName, CountOf(fileName));
    if(num > 0) {
      if (filedlg.isImageFileName(fileName)) {
        if (view == sourceImage[FIRST]) {
          openFile(FIRST, fileName);
        } else if (view == sourceImage[SECOND]) {
          openFile(SECOND, fileName);          
        }
        bringUp();
      } else {        
        bringUp(); //Activate and raise this view
        showErrorDialog("Invalid image filename", fileName,  MB_OK);
      }
    }
  }
  
  void createImageView()
  {
    try {
      imageFiles[FIRST]  = "..\\images\\Tower1.png";
      imageFiles[SECOND] = "..\\images\\Tower2.png";
      int  imageLoadingFlag = CV_LOAD_IMAGE_COLOR;
      //1 Create two sourceImageViews.
      Args ar;
      for (int i = 0; i< IMAGES_COUNT; i++) {
        ar.reset();

        ar.set(XmNimageFileName, imageFiles[i]);
        ar.set(XmNimageLoadingFlag, imageLoadingFlag);
        sourceImage[i] = new SourceImageView(this, "", ar); 
        //Add XmNdropCallback to each sourceImage view.
        sourceImage[i] -> addCallback(XmNdropCallback, this,
          (Callback)&MainView::dropCallback, sourceImage[i]);
      }

      //2 Feature matching
      ar.reset();
      matchedImage = new MatchedImageView(this, "", ar);
    } catch (Exception& ex) {
      caught(ex); 
    }
  }
  
  //Create a feature detector selection comboBox.
  void createDetectorInterface()
  {
    Args ar;
    
    ar.reset();
    ar.set(XmNstyle, CBS_DROPDOWNLIST);
    detectorComboBox   = new LabeledComboBox(this, "FeatureDetector", 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_ORB;
    detectorComboBox -> setCurSel((int)detectorIndex);
    detectorComboBox -> addCallback(XmNselChangeCallback, this,
        (Callback)&MainView::detectorChanged, NULL);
  }

public:
  // Constructor
  MainView(OpenCVApplication& applet, const char* name, Args& args)
  :OpenCVApplicationView(applet, name, args)
  {
    try {
      //1 Create thress OpenCVImageViews.
      createImageView();
      
      //2 Create a detectorComboBox
      createDetectorInterface();
   
      Args ar;
      matchedNumber = new Static(this, "Matched: ", ar);
      
      //3 Create a detectButton
      ar.reset();
      matchButton = new PushButton(this, "Match", ar);      
      matchButton -> addCallback(XmNactivateCallback, this,
        (Callback)&MainView::match, NULL);
      
      //4 Create a bestTopNumbercombobox
      ar.reset();
      bestTopNumberComboBox = new LabeledComboBox(this, "BestTopNumber", ar);
      for (int i = 1; i<10; i++) {
        char number[20];
        sprintf_s(number, CountOf(number), "%d", i*10);
        bestTopNumberComboBox->addString(number);
      }
      //5 Create bestTopNumberComboBox
      bestTopNumberIndex = 1;
      bestTopNumberComboBox->setCurSel(bestTopNumberIndex);
      
      bestTopNumberComboBox -> addCallback(XmNselChangeCallback, this,
        (Callback)&MainView::bestTopNumberChanged, NULL);
        
      //6 Add menu callbacks
      addCallback(XmNmenuCallback, IDM_OPEN_FIRST_FILE, this,
          (Callback)&MainView::openFile, (void*)FIRST);
      
      addCallback(XmNmenuCallback, IDM_OPEN_SECOND_FILE, this,
          (Callback)&MainView::openFile, (void*)SECOND);

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

      updateCaption();  

      ar.reset();
      ar.set(XmNfilter, FileDialog::getImageFilesFilter());
      filedlg.create(this, "OpenFile", ar);

      //Add event handler for WM_FEATURE_MATCHING_REQUEST event,
      addEventHandler(WM_FEATURE_MATCHING_REQUEST, this,
        (Handler)&MainView::featureMatching, NULL);
      
      //Post a feature matching request event.
      post(WM_FEATURE_MATCHING_REQUEST);
      
    } catch (Exception& ex) {
      caught(ex);
    }
  }

  ~MainView()
  {
  }

  void openFile(Action& action)
  {
    Args ar;
    int index = (int)action.getData();
    
    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);
       
        openFile(index, filename);
      }
    } catch (Exception& ex) {
      caught(ex);
    }
  } 
  
  //2017/12/01
  void save(Action& action)
  {
    try {
      if (!savedImageFile.isEmpty()) {
        //Write binarized image into the filename.
        matchedImage->writeImage(savedImageFile); 
      } else {
        saveAs(action);
      }
    } catch (Exception& ex) {
      caught(ex);
    }
  }  

  void saveAs(Action& action)
  {
    Args ar;
    
    char dir[MAX_PATH] = { 0 };
    if (restoreFileFolder(dir, CountOf(dir))) {
      ar.set(XmNdirectory, dir);
      filedlg.setValues(ar);
    }
    
    try {    
      if(filedlg.save()) {
    
        const char* filename = filedlg.getFileName();
        saveFileFolder(filename);
        
        //Write binarized image into the filename.
        matchedImage->writeImage(filename);
        savedImageFile = filename;
      }
    } 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,  1000);
    args.set(XmNheight, 600);  
    MainView view(applet, name, args);
    view.realize();

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


Last modified: 2 Dec. 2017

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