SOL9 Sample: OpticalFlow

SOL9 2.0 Samples

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.