A step-by-step guide to the use of the Intel OpenCV 1 library

Processing several images

Now, to process a series of images, we need to be able to select multiple images from the dialog box. This can be done by specifying the OFN_ALLOWMULTISELECT flag when constructing the CFileDialog. However, the default size of the buffer created to contain the selected filenames is quite small by default. It is therefore preferable to allocate yourself this buffer. A pointer to the buffer can be obtained using the member variable lpstrFile; one has therefore to dynamically allocate the suitable amount memory for the buffer. You can base your choice on the MAX_PATH constant that specifies the maximal number of characters that a path is allowed to contain. The new OnOpen method is therefore as follows:
 
void CcvisionDlg::OnOpen()
{
  CFileDialog dlg(TRUE, _T("*.avi"), "",                   
   OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|
   OFN_HIDEREADONLY|OFN_ALLOWMULTISELECT ,
   "Image files (*.bmp;*.jpg;*.jpeg;*.png;*.pgm;*.ppm;*.tif;*.tiff)|
                  *.bmp;*.jpg;*.jpeg;*.png;*.pgm;*.ppm;*.tif;*.tiff|
                      All Files (*.*)|*.*||",NULL);
 
  char title[]= {"Open Image Sequence"};
 
  const int maxFiles = 200; // you can select a maximum of 200 files
  // MAX_FILE is constant specifying
  // the maximal number of characters in a path
  const int bufferSize = (maxFiles * (MAX_PATH + 1)) + 1; 
  dlg.m_ofn.lpstrTitle= title;
  dlg.m_ofn.lpstrFile= new TCHAR[bufferSize];
  dlg.m_ofn.lpstrFile[0]= '\0';              
  dlg.m_ofn.nMaxFile = bufferSize;  // should be maxFiles but 
                                    // there is a bug in this class...              
                   
 
  if (dlg.DoModal() == IDOK) {
 
      std::vector<CString> files;
 
      // This is an iterator that extracts all filename
      POSITION pos= dlg.GetStartPosition();
      while (pos) {
 
            files.push_back(dlg.GetNextPathName(pos));
      }
 
      // do something with the vector of filename
  }
 
  delete[] dlg.m_ofn.lpstrFile;
}
Then, when you select multiple files using the dialog, all the filenames are inserted into the buffer. The CFileDialog class also offers functions to iterate over the buffer and then extract all the individual filenames. In our example, these filenames are inserted into a vector of CString in order to facilitate their manipulation. The next step is to go over this vector, open each corresponding image and process it. Following our good programming practice, we will create a class that will accomplish this specific task.

A vector of strings is given to this class (using the corresponding getter method) and by calling a run() method, the processing of each image is started. For now, lets just simply display these images. Also, since the processing time can be quite fast in some cases, we have added a pause parameter that causes the process to sleep for a given duration between each processing. Here is the class as described:

 
class Multim {
 
        std::vector<CString> files;
        DWORD pause;
 
  public:
 
        Multim() : pause(0) {
 
              cvNamedWindow("Image");
        }
 
        ~Multim() {
 
              cvDestroyWindow("Image");
        }
 
        void setFiles(std::vector<CString> &f) {
 
              files= f;
        }
 
        void setPause(DWORD p) {
       
              pause= p;
        }
 
        DWORD getPause() {
       
              return pause;
        }
 
        void run() {
 
            IplImage *image;
 
            for (int i=0; i<files.size(); i++) {
 
                  image= cvLoadImage(files[i],-1);
                  cvShowImage( "Image", image );
                  HWND hWnd= (HWND)cvGetWindowHandle("Image");
                  UpdateWindow(hWnd); // force the window to repaint
                  Sleep(pause);
                  cvReleaseImage(&image);
            }
        }
};
Note how, in the run() method, the image display window is forced to repaint by sending the appropriate window message. This is required since window repainting has a low priority and will not normally be made until the processing loop is completed.

Check point #6: source code of the above example.

The Multim class allows us to loop over a series of images. In order to process these images we must add a call to a processing function. We will pass this function to the Multim class through a function pointer. Lets add a setter method in the class to set this function pointer:

  
        void Multim::setImageProcess(void (*p)(IplImage *)) {
 
              process= p;
        }
where process is defined as:
  
        void (*process)(IplImage *);
We then add the call to process in the run() method:
  
        void run() {
 
            IplImage *image;
 
            for (int i=0; i<files.size(); i++) {
 
                  image= cvLoadImage(files[i],-1);
 
                  process(image); // process the image
 
                  cvShowImage( "Image", image );
                  HWND hWnd= (HWND)cvGetWindowHandle("Image");
                  UpdateWindow(hWnd); // force the window to repaint
                  Sleep(pause);
                  cvReleaseImage(&image);
            }
        }
The process function can be defined as follows:
   
Sobel *sobel;
 
void process(IplImage* img) {
 
      sobel->processImage(img);
      cvShowImage( "Result", sobel->getOutputImage());
      HWND hWnd= (HWND)cvGetWindowHandle("Result");
      UpdateWindow(hWnd); // force the window to repaint
}
Since the instance of the processing class (here Sobel) must be accessible from both the process function and the dialog class, we use a global variable (argh!) that points to an instance of the Sobel class. This instance is created in the dialog (in the OnInitDialog method). The dialog is also responsible for setting the parameter of the processing class; there is none in the case of the Sobel class but Multim class has some parameters to set:
    
BOOL CcvisionDlg::OnInitDialog()
{
      .
      .
      .
 
      sobel= new Sobel();
      multi.setImageProcess(process);
      multi.setPause(1000); // number of ms between each processing
 
      return TRUE;
}
Obviously, most of the time, we would like the user to interactively set (some of ) the parameter values. With Visual C++, an edit control can be added to the dialog using which the user can specify the values he wishes. Just go to the Ressource view and using the Toolbox, drag and drop an Edit Control. You can also drop a Static Text to add an appropriate label to this text field.

You can associate a variable with this Edit Control by right-clicking on it and select the Add Variable... option. You select Value for the Category and you have also to specify the Variable Type and the Variable Name for the variable that will be created. In our case, we will give the user the possibility to specify the pause duration between each image processing, so we select the int type.

Once you click finish, go to the xxxDialog.h file and you will notice that a new variable has been created.
     
class CcvisionDlg : public CDialog
{
.
.
.
public:
      afx_msg void OnOpen();
      afx_msg void OnBnClickedOk();
      afx_msg void OnBnClickedCancel();
      afx_msg void OnProcess();
      int duration;
};
This variable and the Edit Control are linked together and the function UpdateData is used to move the content of one into the other. If you call this function with false as argument, this will display the value of the variable onto the Edit Control. This is useful to provide default value that will appear when you start the dialog. For example, with this in the OnInitDialog:
     
      duration= 1000; // default value
      UpdateData(false); // display the content of 
                         // the variable in Edit Control
You obtain:

Reciprocally, calling UpdateData with true will copy the value currently in the Edit Control into the variable. You do this before performing the processing, i.e. when you push the Process button:
      
void CcvisionDlg::OnProcess()
{
      UpdateData(true); // move value from Edit Control to variable
      multi.setPause(duration); // ms between each processing
 
      multi.run();
}

Check point #7: source code of the above example.

We will now redesign the preceding example. One of the problems with the previous program relies in the fact that the application is intrinsically coupled with the graphical interface. A better approach consists in creating a controller class that is responsible for mapping the user action to the application model. The controller class is a bridge that links the user interface with the core application; it defines the application behavior and the way users can interact with it (that is the API). It greatly facilitates the maintenance of both the GUI and the application and it is part of the more general Model-View-Controller design pattern.

If we continue with our application example that computes the Sobel of a series of images, the corresponding controller would offer to the user the possibility to specify the filenames to be processed , to set (and get) the pause between each processing and to start the processing loop. Here is this controller:

      
class SobelController {
 
  private:
 
      Multim *multi;
      Sobel *sobel;
 
     
  public:
 
      SobelController() {
 
              //setting up the application
              multi= new Multim();
              sobel= new Sobel();
              multi->setImageProcess(process);
      }
 
        void setPause(int ms) {
 
              multi->setPause(ms);
        }
 
        int getPause() {
 
              multi->getPause();
        }
 
        void setFiles(std::vector<CString> &f) {
 
              multi->setFiles(f);
        }
 
        void run() {
 
              multi->run();
        }
 
        // to be called by the callback
        inline void processImage(IplImage *image) {
 
              sobel->processImage(image);
        }
       
        inline IplImage* getOutputImage() {
 
              return sobel->getOutputImage();
        }
 
        ~SobelController() {
 
              delete multi;
              delete sobel;
        }
};
As you can see, this class is also responsible for constructing the class instances of the application. All the methods are quite simple since they simply delegate the user request to the appropriate class in the application; this is exactly the responsibility of the controller class. Note that one of the methods, processImage, is there to be called by the process callback function and not by the user.

Now, if we want to avoid the use of the inelegant global variable of the previous example, we will use another well-known design pattern: the singleton. Using the singleton will also guarantee that only one controller will exist as it should. The key idea is simple; the constructor of the class is made private such that no instance of it can be created by an external class or function. A static variable, here called singleton, contains the address of the unique singleton instance that is accessible through a public static method, called get Instance:

       
        static Controller *getInstance() {
 
              if (singleton == 0)
                  singleton= new Controller;
 
              return singleton;
        }
This method creates the class instance when called for the first time and simply return it for the subsequent calls. Once added to our controller, the complete class looks as follows:
       
#if !defined CNTRLLR
#define CNTRLLR
 
#include "cv.h"
#include "multim.h"
#include "sobel.h"
#include "process.h"
 
#include <vector> 
 
class SobelController {
 
  private:
 
      static SobelController *singleton; // pointer to the singleton
 
      Multim *multi;
      Sobel *sobel;
 
      SobelController() { // private constructor
 
              //setting up the application
              multi= new Multim();
              sobel= new Sobel();
            multi->setImageProcess(process);
      }
     
  public:
 
        void setPause(int ms) {
 
              multi->setPause(ms);
        }
 
        int getPause() {
 
              multi->getPause();
        }
 
        void setFiles(std::vector<CString> &f) {
 
              multi->setFiles(f);
        }
 
        void run() {
 
              multi->run();
        }
 
        // to be called by the callback
        inline void processImage(IplImage *image) {
 
              sobel->processImage(image);
        }
       
        inline IplImage* getOutputImage() {
 
              return sobel->getOutputImage();
        }
 
        ~SobelController() {
 
              delete multi;
              delete sobel;
        }
 
        // Singleton static members
        static SobelController *getInstance() {
 
              if (singleton == 0)
                  singleton= new SobelController;
 
              return singleton;
        }
 
        static void destroy() {
 
              if (singleton != 0) {
                    delete singleton;
                    singleton= 0;
              }
        }
};
#endif
The process function then becomes (note how the singleton class is accessed):
        
void process(IplImage* img) {
 
      SobelController::getInstance()->processImage(img);
      cvShowImage( "Result",
                  SobelController::getInstance()->getOutputImage());
      HWND hWnd= (HWND)cvGetWindowHandle("Result");
      UpdateWindow(hWnd); // force the window to repaint
}
And the OnProcess method of the GUI becomes:
        
void CcvisionDlg::OnProcess()
{
      // display the content of the variable in Edit Control
      UpdateData(true); 
      // number of ms between each image processing
      SobelController::getInstance()->setPause(duration); 
      SobelController::getInstance()->run();
}
And it is important to call the destroy method before the application is closed:
        
      SobelController::getInstance()->destroy();

Check point #8: source code of the above example.

Top of the page

 

(c) Robert Laganiere 2011