1 Screenshot
2 Source code
/*
* OpticalFlow.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 <opencv2/superres/optical_flow.hpp>
#include <sol/PushButton.h>
#include "resource.h"
namespace SOL {
static const int WM_CALCULATE_FLOW_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 TrackedImageView :public OpenCVImageView {
private:
cv::Mat trackedImage;
cv::Mat& getMat()
{
return trackedImage;
}
void display()
{
if ( !trackedImage.empty()) {
show(trackedImage);
}
}
public:
TrackedImageView(View* parent, const char* name, Args& args) //const cv::Mat& matchedMat)
:OpenCVImageView(parent, name, args)
{
refresh();
}
~TrackedImageView()
{
}
void setTracked(cv::Mat& mat)
{
trackedImage.release();
trackedImage = mat;
refresh();
}
};
//Inner classes end.
////////////////////////////////////////////////////////////////////////////////////////
private:
typedef enum {
// SimpleFlow=0,
TV_L1 = 0,
Farneback,
} ALGORITHM;
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<TrackedImageView> trackedImage;
int algorithmIndex;
SmartPtr<LabeledComboBox> algorithmComboBox;
/*
SmartPtr<Static> matchedNumber;
int bestTopNumberIndex;
SmartPtr<LabeledComboBox> bestTopNumberComboBox;
*/
FileDialog filedlg;
SmartPtr<PushButton> trackButton;
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] &&
trackedImage ) {
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);
trackedImage -> reshape(2, h/2-2, (w-160)-1, h/2-1);
algorithmComboBox -> reshape(w-160 + 4, 2, 140, 60);
trackButton -> reshape(w-160 + 4, 130, 140, 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);
}
}
//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*, ALGORITHM>* getDetectorsList(int& count)
{
static const Pair<char*, ALGORITHM> algorithms[] = {
// {"Simple", Simple},
{"TV-L1", TV_L1},
{"Farnback", Farneback},
};
count = CountOf(algorithms);
return algorithms;
}
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 trackButton
void track(Action& action)
{
post(WM_CALCULATE_FLOW_REQUEST);
}
//Event handler for WM_CALCULATE_FLOW_REQUEST
long calculateFlow(Event& event)
{
try {
cv::Ptr<cv::superres::DenseOpticalFlowExt> opticalFlow;
switch (algorithmIndex) {
// case Simple:
// opticalFlow = cv::superres::createOptFlow_Simple();
// break;
case TV_L1:
opticalFlow = cv::superres::createOptFlow_DualTVL1();
break;
case Farneback:
opticalFlow = cv::superres::createOptFlow_Farneback();
break;
default:
throw IException("Invalid algorithm.");
}
cv::Mat& source1 = sourceImage[FIRST] ->getMat();
cv::Mat& source2 = sourceImage[SECOND]->getMat();
cv::Mat gray1, gray2;
cvtColor(source1, gray1, COLOR_BGR2GRAY);
cvtColor(source2, gray2, COLOR_BGR2GRAY);
cv::Mat flow;
opticalFlow->calc(gray1, gray2, flow);
cv::Mat tracked = sourceImage[SECOND]->getMatClone();
for (int y = 0; y < source2.rows; y += 10) {
for (int x = 0; x < source2.cols; x += 10) {
const Point2f point = flow.at<Point2f>(y, x) * 1;
line(tracked, Point(x, y), Point(cvRound(x + point.x), cvRound(y + point.y)), Scalar(0, 0, 255)); //red
circle(tracked, Point(x, y), 1, Scalar(0, 0, 0), -1);
}
}
trackedImage ->setTracked(tracked);
} catch (cv::Exception& ex) {
//MessageBox(NULL, "Caught an excption", "Exception", MB_OK);
}
return 1;
}
//Callback for the algorithmComboBox
void algorithmChanged(Action& action)
{
algorithmIndex = (ALGORITHM)algorithmComboBox->getCurSel();
post(WM_CALCULATE_FLOW_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);
}
}
}
public:
// Constructor
MainView(OpenCVApplication& applet, const char* name, Args& args)
:OpenCVApplicationView(applet, name, args)
{
try {
//1 Create thress OpenCVImageViews.
imageFiles[FIRST] = "..\\images\\Dog0.jpg";
imageFiles[SECOND] = "..\\images\\Dog2.jpg";
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 Create a tracked image view
ar.reset();
trackedImage = new TrackedImageView(this, "", ar);
//3 Create a algorithmComboBox
ar.reset();
ar.set(XmNstyle, CBS_DROPDOWNLIST);
algorithmComboBox = new LabeledComboBox(this, "FeatureDetector", ar);
int count = 0;
const Pair<char*, ALGORITHM>* detectors = getDetectorsList(count);
for (int i =0; i<count; i++) {
algorithmComboBox->addString(detectors[i].first);
}
algorithmIndex = Farneback;
algorithmComboBox -> setCurSel((int)algorithmIndex);
algorithmComboBox -> addCallback(XmNselChangeCallback, this,
(Callback)&MainView::algorithmChanged, NULL);
//4 Create a trackButton
ar.reset();
trackButton = new PushButton(this, "Track", ar);
trackButton -> addCallback(XmNactivateCallback, this,
(Callback)&MainView::track, NULL);
//5 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_CALCULATE_FLOW_REQUEST event,
addEventHandler(WM_CALCULATE_FLOW_REQUEST, this,
(Handler)&MainView::calculateFlow, NULL);
//Post a calculate flow request event.
post(WM_CALCULATE_FLOW_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 tracked image into the filename.
trackedImage->writeImage(savedImageFile);
} else {
saveAs(action);
}
} catch (Exception& ex) {
caught(ex);
}
}
//2017/12/01
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 tracked image into the filename.
trackedImage->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.