SOL9 Sample: FlannBasedMatcher
|
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.