OZ++ Sample: ShapeMatcher

/******************************************************************************  *  * Copyright (c) 2019 Antillia.com TOSHIYUKI ARAI. ALL RIGHTS RESERVED.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  * 1. Redistributions of source code must retain the above copyright  *    notice, this list of conditions, and the following disclaimer.  *  * 2. The name of the author may not be used to endorse or promote products  *    derived from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  *  *  * ShapeMatcher.cpp  *  *****************************************************************************/ //2017/05/15 //2019/01/03 #include <opencv2/stitching.hpp> #include <oz++/Pair.h> #include <oz++/motif/Label.h> #include <oz++/motif/RowColumn.h> #include <oz++/motif/LabeledTrackBar.h> #include <oz++/opencv/OpenCVScaleComboBox.h> #include <oz++/opencv/OpenCVMainView.h> #include <oz++/opencv/OpenCVImageView.h> #include <oz++/motif/FileOpenDialog.h> #include "PrivateFileMenu.h" namespace OZ { class MainView :public OpenCVMainView { private:   ///////////////////////////////////////////////   //Inner classes start.   class BinarizedImageView: public OpenCVImageView {   private:     cv::Mat originalImage;      cv::Mat destImage;      cv::Mat grayImage;      cv::Mat scaledImage;     //The scale image is displayed on this image view.     virtual void display()     {       show(scaledImage);     }     public:     BinarizedImageView(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);         int scalingRatio = (int)args.get(XmNimageScalingRatio);         loadImage(filename, imageLoadingFlag, scalingRatio);       } catch (OZ::Exception ex) {         caught(ex);       }     }      ~BinarizedImageView()     {     }          void loadImage(const char* filename,          int imageLoadingFlag= CV_LOAD_IMAGE_COLOR,                 int scalingRatio=100)     {       originalImage = readImage(filename, imageLoadingFlag);       cv::cvtColor( originalImage, grayImage, COLOR_BGR2GRAY);        refresh();      }         void rescale(int scalingRatio)     {       scaledImage.release();       scaleImage(destImage, scaledImage, scalingRatio);     }     cv::Mat& getOriginalImage()     {       return originalImage;     }     cv::Mat& getBinarizedImage()     {       return destImage;     }     void binarize(int thresholdType, int thresholdValue, int scalingRatio)     {       thresholdValue = (thresholdValue/2)*2 + 1;       try {         cv::threshold( grayImage, destImage, thresholdValue,               THRESHOLD_VALUE_MAX, thresholdType );       } catch (cv::Exception& ex) {         //Sometimes we get an exception; I don't know the reason why it happens.       }             scaleImage(destImage, scaledImage, scalingRatio);     }   };   class MatchedImageView: public OpenCVImageView {   private:     cv::Mat matchedImage;      cv::Mat scaledImage;     //The scale image is displayed on this image view.     virtual void display()     {       show(scaledImage);     }     public:     MatchedImageView(View* parent, const char* name, Args& args)     :OpenCVImageView(parent, name, args)     {       refresh();     }      ~MatchedImageView()     {     }          void rescale(int scalingRatio)     {       scaledImage.release();       scaleImage(matchedImage, scaledImage, scalingRatio);     }     void setMatched(cv::Mat& mat, int scalingRatio)     {       matchedImage.release();              matchedImage = mat;       scaleImage(matchedImage, scaledImage, scalingRatio);       refresh();     }   };   //Inner classes end.      private:   static const int          IMAGES_COUNT = 2;   static const int          FIRST  = 0;   static const int          SECOND = 1;   StringT<char>             imageFiles[IMAGES_COUNT];      int                       fileOpenIndex;   int                          imageLoadingFlag;   int                          imageScalingRatio; //percentage    SmartPtr<Label>              label;      SmartPtr<BinarizedImageView>  binarizedImage[IMAGES_COUNT];   SmartPtr<MatchedImageView>   matchedImage;   SmartPtr<RowColumn>          controlPane;   SmartPtr<OpenCVScaleComboBox>  scaleComboBox;   int                          thresholdTypeIndex;   SmartPtr<LabeledComboBox>    thresholdTypeComboBox;      int                          thresholdValue;   SmartPtr<LabeledTrackBar>    thresholdValueTrackBar;   SmartPtr<Label>              space;   int                          minimumSize;   SmartPtr<LabeledTrackBar>    minimumSizeTrackBar;   int                          maximumSize;   SmartPtr<LabeledTrackBar>    maximumSizeTrackBar;   SmartPtr<PushButton>         clearButton;   SmartPtr<PushButton>         matchButton;     SmartPtr<FileOpenDialog>     fileDialog;   static const int THRESHOLD_VALUE_MAX = 255;   static int getThresholdType(int index)    {     int n = 0;     //We don't include the THRESH_OTSU type     Pair<int, int> types[] = {       {n++, THRESH_BINARY     },       {n++, THRESH_BINARY_INV },       {n++, THRESH_TRUNC      },       {n++, THRESH_TOZERO     },       {n++, THRESH_TOZERO_INV },       {n++, THRESH_OTSU       },      };     int type = THRESH_BINARY;      if (index >= 0 && index <n) {       type = types[index].second;     }      return type;   } public:   void scaleChanged(Action& action)   {     int val = scaleComboBox->getScale();     if (val > 0 && imageScalingRatio != val) {       imageScalingRatio = val;       binarizedImage[FIRST] -> rescale(imageScalingRatio);        binarizedImage[SECOND] -> rescale(imageScalingRatio);        matchedImage -> rescale(imageScalingRatio);      }   }     void thresholdTypeSelChanged(Action& action)   {     thresholdTypeIndex  = thresholdTypeComboBox -> getSelectedPosition();          thresholdValue      = thresholdValueTrackBar -> getPosition();     binarizedImage[FIRST] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     binarizedImage[SECOND] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     shapeMatching();   }   void trackBarScrolled(Action& action)   {     thresholdTypeIndex  = thresholdTypeComboBox -> getSelectedPosition();       thresholdValue      = thresholdValueTrackBar -> getPosition();     binarizedImage[FIRST] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     binarizedImage[SECOND] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     shapeMatching();   }   void minimumSizeScrolled(Action& action)   {     minimumSize         = minimumSizeTrackBar ->getPosition();     thresholdTypeIndex  = thresholdTypeComboBox -> getSelectedPosition();     thresholdValue      = thresholdValueTrackBar -> getPosition();     binarizedImage[FIRST] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     binarizedImage[SECOND] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     shapeMatching();   }   void maximumSizeScrolled(Action& action)   {     maximumSize         = maximumSizeTrackBar ->getPosition();     thresholdTypeIndex  = thresholdTypeComboBox -> getSelectedPosition();     thresholdValue      = thresholdValueTrackBar -> getPosition();     binarizedImage[FIRST] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     binarizedImage[SECOND] -> binarize( getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio);     shapeMatching();   }   void fileOpen(Action& action)   {     fileDialog->popup();   }   void updateLabel()   {     char path[PATH_MAX];     sprintf(path, "%s, %s",                 (const char*)imageFiles[0], (const char*)imageFiles[1]);     label -> setText(path);   }   //OK button callback for FileOpenDialog    void ok(Action& action)   {     try {         imageFiles[fileOpenIndex]  = fileDialog->getFileName();       const char* filename = (const char*)imageFiles[fileOpenIndex];       printf("filename: %s\n", filename);       fileDialog->popdown();       binarizedImage[fileOpenIndex]->invalidate();       binarizedImage[fileOpenIndex]->loadImage(filename,          imageLoadingFlag, imageScalingRatio);       binarizedImage[fileOpenIndex] -> binarize(                                 getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio );       updateLabel();       resize(width(), height());       flush();     } catch (OZ::Exception& ex) {       caught(ex);     }    }   void resize(Dimension w, Dimension h)   {     int CP_WIDTH = 220;     int LB_HEIGHT = 30;     int ww =  w-CP_WIDTH;     int hh = h - LB_HEIGHT/2;     if (label && binarizedImage[FIRST] &&                   binarizedImage[SECOND] &&                   matchedImage && controlPane       ) {       label        -> reshape(0, 0, w, LB_HEIGHT);        binarizedImage[FIRST]-> reshape(0, LB_HEIGHT, ww/2, hh/2);       binarizedImage[SECOND] -> reshape(ww/2, LB_HEIGHT, ww/2-1, hh/2);       matchedImage          -> reshape(0, hh/2+1, ww, hh/2-2);         controlPane  -> reshape(ww-1, LB_HEIGHT, CP_WIDTH+1, hh);       controlPane -> unmap();       controlPane -> map();     }     flush();   }   void clear(Action& action)   {     //Detect connectedCommponentsWithStats on destImage      cv::Mat& originalImage = binarizedImage[FIRST ]->getOriginalImage();     cv::Mat destImage      = originalImage.clone();      matchedImage->setMatched(destImage, imageScalingRatio);    }   void match(Action& action)   {     shapeMatching();   }   void shapeMatching()   {     //Detect connectedCommponentsWithStats on destImage      Mat labels, stats, centroids;      cv::Mat& originalImage = binarizedImage[FIRST ]->getOriginalImage();     cv::Mat& srcBin = binarizedImage[FIRST ]->getBinarizedImage();     cv::Mat& tmpBin = binarizedImage[SECOND]->getBinarizedImage();     cv::connectedComponentsWithStats(srcBin,             labels, stats, centroids);           int tw = tmpBin.rows;     int th = tmpBin.cols;             cv::Mat destImage = originalImage.clone();       const double MATCHING_THRESHOLD = 0.005;     cv::Rect minimum(0, 0, 0, 0);     double MIN_SIMILARITY = 1.0;     bool found = false;       for(int i=0; i < stats.rows; i++)  {        int x = stats.at<int>(Point(0, i));        int y = stats.at<int>(Point(1, i));        int w = stats.at<int>(Point(2, i));        int h = stats.at<int>(Point(3, i));        cv::Rect rect(x, y, w, h);       cv::Mat rectOfInterest = srcBin(rect);        double similarity = cv::matchShapes(tmpBin,                 rectOfInterest, CV_CONTOURS_MATCH_I1, 0);       if ( (w >= minimumSize || h >= minimumSize ) &&             (w <= maximumSize || h <= maximumSize )) {          if (similarity <= MIN_SIMILARITY) {           MIN_SIMILARITY = similarity;                  minimum = rect;             printf("matching similarity=%.04f  x=%d y=%d w=%d h=%d\n",               similarity, x, y, w, h);           found = true;         }       }     }      if (found) {       cv::rectangle(destImage, minimum, CV_RGB(255, 0, 0), 3);     }     matchedImage->setMatched(destImage, imageScalingRatio);    }   void fileOpen(int index, Action& action)   {     fileOpenIndex = index;     fileDialog->popup();   }   //Callback to handle file pulldown events.    virtual void privateFileMenu(Action& action)   {     menuId = action.getClientIntData();     switch(menuId) {     case NEW:          fileNew(action);         break;              case OPEN_FILE1:          fileOpen(FIRST, action);         break;             case OPEN_FILE2:          fileOpen(SECOND, action);         break;             case SAVE:          fileSave(action);         break;              case SAVE_AS:          fileSaveAs(action);         break;              case EXIT:          confirm(action);          break;     }   }     virtual void addFilePulldownMenu()   {     //1 Recreate pulldown menu for File.     MenuBar* menubar = getMenuBar();     getFileMenuButton() -> recreatePulldownMenu(menubar, "file");     //2 Add PrivateFileItems to the FilePulldown.     getFileMenuButton() -> addItems(PrivateFileItems, XtNumber(PrivateFileItems),                     this, (Callback)&MainView::privateFileMenu);   } public:   MainView(OpenCVApplication& applet, const char* name, Args& args)   :OpenCVMainView(applet, name, args)    {     addFilePulldownMenu();     BulletinBoard* bboard = getBulletinBoard();     imageFiles[FIRST]  = "../images/CatImage.png";     imageFiles[SECOND] = "../images/CatFace.png";     imageLoadingFlag = CV_LOAD_IMAGE_COLOR;     imageScalingRatio = 60; //%     try {       Args ar;       ar.set(XmNalignment, XmALIGNMENT_BEGINNING);        label = new Label(bboard, "", ar);       updateLabel();       for (int i = 0; i<IMAGES_COUNT; i++) {         ar.reset();         ar.set(XmNimageFileName,  imageFiles[i]);         ar.set(XmNimageLoadingFlag, imageLoadingFlag);         ar.set(XmNimageScalingRatio, imageScalingRatio);         binarizedImage[i] = new BinarizedImageView(bboard, "", ar);       }       ar.reset();       matchedImage = new MatchedImageView(bboard, "", ar);       ar.reset();       controlPane = new RowColumn(bboard, "", ar);       //Create a scaleComboBox in the controlPane       const char* defaultScale = "60%";       ar.reset();       CompoundString scaler("Scale");       ar.set(XmNlabelString, scaler);       ar.set(XmNdefaultScale, defaultScale);       scaleComboBox = new OpenCVScaleComboBox(controlPane, "", ar);       scaleComboBox->addCallback(XmNselectionCallback, this,         (Callback)&MainView::scaleChanged, NULL);             //Create a thresholdTypeComboBox in controlPane.       const char* types[] = {         "Binary",         "Binary Inverted",         "Threshold Truncated",         "Threshold to Zero",         "Threshold to Zero Inverted",         //"Mask",          "Otsu",        };       CompoundString thresholdcs("ThresholdType");        thresholdTypeIndex = 0; // Binary       CompoundStringList typescsl(types, CountOf(types));       ar.reset();       ar.set(XmNitems, typescsl);       ar.set(XmNselectedPosition, thresholdTypeIndex);       ar.set(XmNitemCount, CountOf(types));       ar.set(XmNlabelString, thresholdcs);       thresholdTypeComboBox = new LabeledComboBox(controlPane, "", ar);       thresholdTypeComboBox -> addCallback(XmNselectionCallback, this,         (Callback)&MainView::thresholdTypeSelChanged, NULL);             //Create a blockSizeTrackBar in controlPane.       CompoundString titlecs("ThresholdValue:[0,255]");              //Create a thresholdValueTrackBar       thresholdValue = 60;       ar.reset();       ar.set(XmNminimum, 0);       ar.set(XmNmaximum, THRESHOLD_VALUE_MAX);       ar.set(XmNvalue, thresholdValue);       ar.set(XmNtitleString, titlecs);       thresholdValueTrackBar = new LabeledTrackBar(controlPane, "ThresholdValue", ar);       thresholdValueTrackBar -> addCallback(XmNvalueChangedCallback, this,         (Callback)&MainView::trackBarScrolled, NULL);         //Create a minimumSizeTrackBar       CompoundString minsizecs("MatchMinSize:[10,100]");        minimumSize = 60;       ar.reset();       ar.set(XmNminimum, 10);       ar.set(XmNmaximum, 100);       ar.set(XmNvalue,   minimumSize);       ar.set(XmNtitleString, minsizecs);       minimumSizeTrackBar = new LabeledTrackBar(controlPane, "MinimumSize", ar);       minimumSizeTrackBar -> addCallback(XmNvalueChangedCallback, this,         (Callback)&MainView::minimumSizeScrolled, NULL);       //Create a minimumSizeTrackBar       CompoundString maxsizecs("MatchMaxSize:[200,400]");           maximumSize = 240;       ar.reset();       ar.set(XmNminimum, 100);       ar.set(XmNmaximum, 400);       ar.set(XmNvalue,   maximumSize);       ar.set(XmNtitleString, maxsizecs);       maximumSizeTrackBar = new LabeledTrackBar(controlPane, "MaximumSize", ar);       maximumSizeTrackBar -> addCallback(XmNvalueChangedCallback, this,         (Callback)&MainView::maximumSizeScrolled, NULL);       ar.reset();       clearButton = new PushButton(controlPane, "Clear", ar);       clearButton -> addCallback(XmNactivateCallback, this,         (Callback)&MainView::clear, NULL);         ar.reset();       matchButton = new PushButton(controlPane, "Match", ar);       matchButton -> addCallback(XmNactivateCallback, this,         (Callback)&MainView::match, NULL);         binarizedImage[FIRST] -> binarize(                                 getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio );       binarizedImage[SECOND] -> binarize(                                 getThresholdType(thresholdTypeIndex),                                 thresholdValue,                                 imageScalingRatio );       shapeMatching();       ar.reset();       fileDialog = new FileOpenDialog(this, "FileOpenDialog", ar);       fileDialog  -> getOkButton()                   -> addCallback(XmNactivateCallback, this,                           (Callback)&MainView::ok, NULL);        sendConfigureEvent();      } catch(OZ::Exception& ex) {       caught(ex);     }   }   ~MainView()   {   } }; } // int main(int argc, char** argv)  {   try {     const char*  appclass = argv[0];     OpenCVApplication applet(appclass, argc, argv);     Args args;     args.set(XmNwidth,  900);     args.set(XmNheight, 560);     MainView view(applet, argv[0], args);     view.realize();     applet.run();        } catch (OZ::Exception& ex) {     caught(ex);   }   return 0; }