SOL9 Sample: ImageStitcher
|
1 Screenshot
2 Source code
/*
* ImageStitcher.cpp
* Copyright (c) 2019 Antillia.com TOSHIYUKI ARAI. ALL RIGHTS RESERVED.
*/
//2017/09/25
// See: http://docs.opencv.org/3.2.0/d2/d8d/classcv_1_1Stitcher.html
// See: http://docs.opencv.org/3.2.0/d8/d19/tutorial_stitcher.html
//2017/12/01 Added save, saveAs methods to MainView.
#define _CONSOLE_
#include <sol/ModuleFileName.h>
#include <sol/Pair.h>
#include <sol/DropFiles.h>
#include <sol/LabeledTrackBar.h>
#include <sol/LabeledComboBox.h>
#include <sol/PushButton.h>
#include <sol/FileDialog.h>
#include <sol/opencv/OpenCVApplicationView.h>
#include <sol/opencv/OpenCVScrolledImageView.h>
#include <sol/opencv/OpenCVScaleComboBox.h>
#include "resource.h"
namespace SOL {
class MainView :public OpenCVApplicationView {
private:
////////////////////////////////////////////////////////////////////////////////////////
//Inner classes start.
class SourceImageView :public OpenCVScrolledImageView {
private:
cv::Mat originalImage;
public:
cv::Mat getClone()
{
return originalImage.clone();
}
public:
SourceImageView(View* parent, const char* name, Args& args)
:OpenCVScrolledImageView(parent, name, args)
{
try {
const char* filename = (const char*)args.get(XmNimageFileName);
int imageLoadingFlag = args.get(XmNimageLoadingFlag);
int scalingRatio = args.get(XmNimageScalingRatio);
loadImage(filename, imageLoadingFlag, scalingRatio);
} catch (SOL::Exception ex) {
caught(ex);
}
}
~SourceImageView()
{
}
void rescaleImage(int scalingRatio)
{
setScaledImage(originalImage, scalingRatio);
}
void loadImage(const char* filename, int imageLoadingFlag= CV_LOAD_IMAGE_COLOR, int scalingRatio=100)
{
try {
originalImage = readImage(filename, imageLoadingFlag);
setScaledImage(originalImage, scalingRatio);
refresh();
} catch (Exception& ex) {
caught(ex);
}
}
};
class StitchedImageView :public OpenCVScrolledImageView {
private:
cv::Mat originalImage;
public:
StitchedImageView(View* parent, const char* name, Args& args)
:OpenCVScrolledImageView(parent, name, args)
{
try {
;//Do nothing here.
} catch (SOL::Exception ex) {
caught(ex);
}
}
~StitchedImageView()
{
}
void rescaleImage(int scalingRatio)
{
if (!originalImage.empty()) {
setScaledImage(originalImage, scalingRatio);
}
}
void setStitchedImage(cv::Mat stitchedImage, int scalingRatio)
{
try {
originalImage = stitchedImage;
setScaledImage(originalImage, scalingRatio);
refresh();
} catch (Exception& ex) {
caught(ex);
}
}
};
//Inner classes end.
////////////////////////////////////////////////////////////////////////////////////////
static const int IMAGES_COUNT = 2;
static const int FIRST = 0;
static const int SECOND = 1;
StringT<char> imageFiles[IMAGES_COUNT];
StringT<char> savedImageFile;
int imageLoadingFlag;
int imageScalingRatio; //percentage
SmartPtr<SourceImageView> sourceImageView[IMAGES_COUNT];
SmartPtr<StitchedImageView> stitchedImageView;
int stitcherModeIndex;
cv::Stitcher::Mode mode;
SmartPtr<OpenCVScaleComboBox> scaleComboBox;
//2018/12/30 bool tryToUseGPU;
cv::Ptr<Stitcher> stitcher;
SmartPtr<LabeledComboBox> stitcherModeComboBox;
SmartPtr<PushButton> clearButton;
SmartPtr<PushButton> stitchButton;
FileDialog filedlg;
cv::Stitcher::Mode getStitcherMode(int index)
{
int n = 0;
Pair<int, cv::Stitcher::Mode> types[] = {
{n++, cv::Stitcher::PANORAMA },
{n++, cv::Stitcher::SCANS },
};
mode = cv::Stitcher::PANORAMA;
if (index >= 0 && index <n) {
mode = types[index].second;
}
return mode;
}
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 scaleChanged(Action& action)
{
imageScalingRatio = scaleComboBox->getScale();
sourceImageView[FIRST] -> rescaleImage(imageScalingRatio);
sourceImageView[SECOND] -> rescaleImage(imageScalingRatio);
stitchedImageView -> rescaleImage(imageScalingRatio);
}
void stitcherModeChanged(Action& event)
{
int index = stitcherModeComboBox -> getCurSel();
if (stitcherModeIndex != index) {
stitcherModeIndex = index;
mode = getStitcherMode(index);
//Rebuild stitcher.
stitcher = cv::Stitcher::create(mode);//2018/12/30 , tryToUseGPU);
stitchImages();
}
}
void resize(int w, int h)
{
try {
if (sourceImageView[FIRST] && sourceImageView[SECOND] && stitchedImageView ) {
static const int CPW = 170;
sourceImageView[FIRST] -> reshape( 2, 2, (w-CPW)/2-2, h/2-4);
sourceImageView[SECOND] -> reshape((w-CPW)/2+1, 2, (w-CPW)/2-2, h/2-4);
stitchedImageView -> reshape( 2, h/2, w-CPW-2 , h/2-4);
scaleComboBox -> reshape((w-CPW)+4, 2, CPW-10, 50);
stitcherModeComboBox -> reshape((w-CPW)+4, 60, CPW-10, 50);
//clearButton -> reshape(w-160+4, 250, 140, 30);
stitchButton -> reshape(w-CPW+10, 250, CPW-20, 30);
}
} catch (SOL::Exception& ex) {
caught(ex);
}
}
void openFile(int index, const char* filename)
{
try {
if (index >= FIRST && index <= SECOND) {
printf("filename: %s\n", filename);
// sourceImageView[fileOpenIndex]->invalidate();
sourceImageView[index]->loadImage(filename,
imageLoadingFlag,
imageScalingRatio);
imageFiles[index] = filename;
updateCaption();
savedImageFile = "";
}
} 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 == sourceImageView[FIRST]) {
openFile(FIRST, fileName);
} else if (view == sourceImageView[SECOND]) {
openFile(SECOND, fileName);
}
bringUp();
} else {
bringUp();
showErrorDialog("Invalid image filename", fileName, MB_OK);
}
}
return 0;
}
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 stitch(Action& action)
{
stitchImages();
}
/*
enum Status {
OK = 0,
ERR_NEED_MORE_IMGS = 1,
ERR_HOMOGRAPHY_EST_FAIL = 2,
ERR_CAMERA_PARAMS_ADJUST_FAIL = 3
*/
const char* getErrorMessage(cv::Stitcher::Status status)
{
static const Pair<const char*, cv::Stitcher::Status> errors[] = {
{"Need more images", cv::Stitcher::Status::ERR_NEED_MORE_IMGS},
{"Homography est fail", cv::Stitcher::Status::ERR_HOMOGRAPHY_EST_FAIL},
{"Camera params adjust fail", cv::Stitcher::Status::ERR_CAMERA_PARAMS_ADJUST_FAIL},
};
const char* err = "";
for (int i = 0; i <CountOf(errors); i++) {
if (status == errors[i].second) {
err = errors[i].first;
break;
}
}
return err;
}
void stitchImages()
{
//Detect connectedCommponentsWithStats on destImage
Mat labels, stats, centroids;
cv::Mat sourceImage[IMAGES_COUNT];
std::vector<cv::Mat> images;
images.push_back( sourceImageView[FIRST ]->getClone() );
images.push_back( sourceImageView[SECOND ]->getClone() );
cv::Mat panorama;
cv::Stitcher::Status status = stitcher->stitch(images, panorama);
if (status == cv::Stitcher::Status::OK) {
stitchedImageView->setStitchedImage(panorama, imageScalingRatio);
} else {
char message[1024];
const char* err = getErrorMessage(status);
sprintf_s(message, CountOf(message), "Failed to Stitcher::stitch method error=%s", err);
MessageBox(NULL, message, "Confirmation",
MB_OK|MB_ICONERROR);
}
}
public:
MainView(OpenCVApplication& applet, const char* name, Args& args)
:OpenCVApplicationView(applet, name, args)
,imageScalingRatio(50)
,mode (cv::Stitcher::PANORAMA)
//,tryToUseGPU(false)
{
try {
stitcher = cv::Stitcher::create(mode); //2018/12/30 , tryToUseGPU);
imageFiles[FIRST] = "../images/Lake2.png";
imageFiles[SECOND] = "../images/Lake1.png";
imageLoadingFlag = CV_LOAD_IMAGE_COLOR;
//1 Create two source image views.
Args ar;
for (int i = 0; i<IMAGES_COUNT; i++) {
ar.reset();
ar.set(XmNimageFileName, (const char*)imageFiles[i]);
ar.set(XmNimageLoadingFlag, imageLoadingFlag);
ar.set(XmNimageScalingRatio, imageScalingRatio);
sourceImageView[i] = new SourceImageView(this, "", ar);
}
//2 Create a stitched image view.
ar.reset();
stitchedImageView = new StitchedImageView(this, "", ar);
//3 Create a scale combobox
ar.reset();
ar.set(XmNdefaultScale, "50%");
scaleComboBox = new OpenCVScaleComboBox(this, "Scale", ar);
//Add a selChanged callback.
scaleComboBox -> addCallback(XmNselChangeCallback, this,
(Callback)&MainView::scaleChanged, NULL);
//4 Create a stitch mode combobox
ar.reset();
stitcherModeComboBox = new LabeledComboBox(this, "StitcherMode", ar);
const char* types[] = {
"Panorama",
"Scans",
};
for (int i = 0; i<CountOf(types); i++) {
stitcherModeComboBox -> addString(types[i]);
}
stitcherModeIndex = 0; //Panorama
stitcherModeComboBox -> setCurSel(stitcherModeIndex);
//Add a selChanged callback.
stitcherModeComboBox -> addCallback(XmNselChangeCallback, this,
(Callback)&MainView::stitcherModeChanged, NULL);
//5 Create a stitchButton.
ar.reset();
stitchButton = new PushButton(this, "Stitch", ar);
stitchButton -> addCallback(XmNactivateCallback, this,
(Callback)&MainView::stitch, NULL);
//6 If you wouldn't call the stitchImages method, since it will take a few seconds,
// you comment out the following line, and let the users click stitchButton.
stitchImages();
updateCaption();
ar.reset();
ar.set(XmNfilter, FileDialog::getImageFilesFilter());
filedlg.create(this, "OpenFile", ar);
addCallback(XmNmenuCallback, IDM_EXIT, this,
(Callback)&MainView::confirm, NULL);
addCallback(XmNmenuCallback, IDM_OPEN_FIRST_FILE, this,
(Callback)&MainView::openFile, (void*)FIRST);
addCallback(XmNmenuCallback, IDM_OPEN_SECOND_FILE, this,
(Callback)&MainView::openFile, (void*)SECOND);
bringUp();
postResizeRequest();
} 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 stitchedImage into the filename.
stitchedImageView->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 stitchedImage into the filename.
stitchedImageView->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: 1 Jan. 2019
Copyright (c) 2019 Antillia.com ALL RIGHTS RESERVED.