计算机视觉
图像处理

OpenCV—用控制器设计模式实现功能模块间的通信

文章目录

Opencv计算机视觉编程攻略(第二版)》书中例子只给出了部分关键代码,将实现例子的笔记整理出来做为学习笔记。

本文是书中3.3节:用控制器设计模式实现功能模块间的通信的内容。

主要步骤

①创建一个基于对话框的MFC工程并绘制所需按钮

②在工程中添加3.2节编写的算法类头文件colordetector.h

③在MFC工程头文件中定义控制器类ColorDetectController

④在对话框类的定义中添加控制器类变量controller(书中变量名为colordetect)

⑤添加两个按钮open image和process的响应代码

具体步骤

1.创建工程并绘制对话窗

新建基于对话框的MFC工程opencv-ColorDetectController。并配置好opencv。

在对话框中添加按钮如下图所示,将open和process按钮的ID分别修改为ID_OPEN和ID_PORCESS。

 2.添加算法类头文件colordetector.h

(1)添加算法类头文件至工程

将第3.2节中编写的算法类头文件colordetector.h拷贝到本MFC工程目录下(该头文件中封装了ColorDetector算法类代码),并在解决方案资源管理器中添加该头文件。此步骤相当于将编写好的算法以类的方式添加进MFC工程。

将算法类头文件添加进工程

(2)在MFC工程头文件中包含算法类头文件

因为用到之前编写的ColorDetector算法类,因此需要在MFC工程的头文件opencv-ColorDetectController.h中添加对该算法类头文件colordetector.h的包含。

  1. #if !defined CD_CNTRLLR  
  2. #define CD_CNTRLLR  
  3. #include “stdafx.h”  
  4. #include <opencv2/highgui/highgui.hpp>  
  5. #include “colordetector.h” //添加对算法类头文件的包含  

3.在MFC工程头文件中定义Controller类

利用Controller类,开发人员可以很容易的构建执行算法的程序接口,既不需要理解类与类是如何连接的,也不需要知道为了让所有的类正确运行需要调用哪个类的哪个方法。所有的这些工作都由Controller类来完成。唯一需要做的就是定义一个Controller类的实例。下文将介绍如何在应用程序中定义Controller类。

在本MFC工程头文件opencv-colorDetectController.h中定义Controller类。Controller类的首要任务是创建执行程序所需的类。本例中只有一个控制器类ColorDetectController,但更复杂的程序会有多个类。

以下将逐步讲解头文件中的ColorDetectController类定义。

(1)定义控制器类私有变量:算法和图像缓存

该类中首先添加对算法类ColorDetector的使用。另外需要有两个成员变量,作为输入输出结果的引用。

  1. class ColorDetectController { //定义控制器类(开头部分代码)  
  2.   
  3.   private:  
  4.    // the algorithm class  
  5.    ColorDetector *cdetect;  
  6.      
  7.    cv::Mat image;   // The image to be processed  
  8.    cv::Mat result;  // The image result  

(2)创建控制器类构造函数

创建ColorDetectController类的构造函数。这里采用动态分配类的方式,也可以简单地声明类的变量。

  1. public:  
  2. olorDetectController() { // private constructor  
  3.   //setting up the application  
  4.   cdetect= new ColorDetector();  

(3)在控制器类中定义所有设置和获取方法

为了部署算法,需要在控制器类ColorDetectController中定义用于控制程序的所有设置方法和获取方法。通常,这些方法都只需要简单的调用相关算法类的中对应的方法。这个例子只用到一个算法类ColorDetector,实际开发中会遇到多个。因此,controller的作用就是讲请求重新定向到相关类(在面想对象的编程中,这种机制称为委托)。

本例中需要定义的方法包括(均在控制器类定义的public下):

  1. // Sets the colour distance threshold设置颜色差距的阈值  
  2.       void setColorDistanceThreshold(int distance) {  
  3.           cdetect->setColorDistanceThreshold(distance);  
  4.       }  
  5.   
  6.       // Gets the colour distance threshold获取颜色差距阈值  
  7.       int getColorDistanceThreshold() const {  
  8.           return cdetect->getColorDistanceThreshold();  
  9.       }  
  10.   
  11.       // Sets the colour to be detected设置目标颜色的方法  
  12.       void setTargetColor(unsigned char red, unsigned char green, unsigned char blue) {  
  13.           cdetect->setTargetColor(blue,green,red);  
  14.       }  
  15.   
  16.       // Gets the colour to be detected获取目标颜色的方法  
  17.       void getTargetColour(unsigned char &red, unsigned char &green, unsigned char &blue) const {  
  18.           cv::Vec3b colour= cdetect->getTargetColor();  
  19.           red= colour[2];  
  20.           green= colour[1];  
  21.           blue= colour[0];  
  22.       }  

(4)在控制器类中定义输入图像和获得该图像的方法

在控制器类的定义中,需要定义如何读取图像的方法,以及如何获取输入的图像的方法。 

  1. // Sets the input image. Reads it from file.输入图像,从文件中读取  
  2.  bool setInputImage(std::string filename) {  
  3.   image= cv::imread(filename);  
  4.   return !image.empty();  
  5.  }  
  6.   
  7.  // Returns the current input image.返回当前的输入图像  
  8.  const cv::Mat getInputImage() const {  
  9.   return image;  
  10.  }  

(5)在控制器类中定义启动处理过程方法

定义一个启动处理过程的方法,供以后调用。

 
  1. // Performs image processing.启动处理过程的方法  
  2.   void process() {  
  3.       result= cdetect->process(image);  
  4.   }  

(6)在控制器类中定义获取处理结果的方法

需要一个方法来获取处理结果

  1. // Returns the image result from the latest processing.  
  2.   const cv::Mat getLastResult() const {  
  3.       return result;  
  4.   }  

(7)在控制器类中定义析构函数

最后,非常重要的一步是在程序结束时做内存清理,即释放Controller类,在程序结束时清理内存

  1.  // Deletes all processor objects created by the controller.删除由控制器创建的对象  
  2.       ~ColorDetectController() {  
  3.           delete cdetect; // 释放动态分配的实例的内存  
  4.       }  
  5. };     // 控制器类定义的结束  
  6. #endif  // 接最前面的预编译指令  

4.在MFC工程对话框类中添加Controller类成员变量

现在只需要在对话框类(这里为Copencv-ColorDetectControllerDlg类)中添加一个ColorDetectController成员变量controller,即可创建一个最基本的使用了控制器的对话框程序了。

(1)向对话框类Copencv-ColorDetectControllerDlg中添加Controller成员变量

通过类向导向Copencv-ColorDetectControllerDlg类中添加ColorDetectController类型的成员变量controller。添加完控制器变量后,该对话框类定义代码的最后将自动添加了如下图红框所示的代码。

向对话框类中添加控制器类成员变量controller

(2)open按钮的处理程序

在对话框上双击open按钮,添加打开图片的消息处理函数。在MFC对话框中的消息处理函数如下。

  1. void CopencvColorDetectControllerDlg::OnBnClickedOpen()  
  2. {  
  3.     // TODO: 在此添加控件通知处理程序代码  
  4.     // 选择bmp或jpg类型文件的MFC对话框  
  5.     CFileDialog dlg(TRUE,_T(“*.bmp”),NULL,  
  6.         OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,  
  7.         _T(“image file(*.bmp;*,jpg)|*.bmp;*.jpg|ALL Files(*.*)|*.*||”),NULL);  
  8.   
  9.     dlg.m_ofn.lpstrTitle=_T(“Open Image”);  
  10.   
  11.     //如果选中了一个文件名  
  12.     if(dlg.DoModal()==IDOK)  
  13.     {  
  14.         //取得选定文件名的完整路径  
  15.         //Cstring->std::string  
  16.         CString cstrfilename = dlg.GetPathName();//.GetPathName返回的是CString类型的字符串  
  17.         std::string filename;  
  18.         setlocale(LC_ALL, “chs”);  
  19.         char *p = new char[256];  
  20.         wcstombs( p, cstrfilename, 256 );  
  21.         filename = p;  
  22.           
  23.         //设置并显示输入的图像,注意controller变量已经在前文向对话框类中添加过了  
  24.         controller.setInputImage(filename); //.setInputImage方法输入的为std::string类型的字符串  
  25.         cv::imshow(“Input Image”,controller.getInputImage());  
  26.     }  
  27. }  

这里要注意的是,书中使用.GetPathName()方法,返回的是CString类型的字符串表示的文件路径,而.setInputImage方法输入要求为std::string类型的字符串,因此需要转换。转化方法为网上下载,应该有更简单的方法。

打开图片的效果

(3)process按钮的处理程序

双击process按钮进入其处理程序代码位置。添加处理代码如下:

  1. void CopencvColorDetectControllerDlg::OnBnClickedProcess()  
  2. {  
  3.     // TODO: 在此添加控件通知处理程序代码  
  4.     //这里颜色采用硬编码  
  5.     //controller.setTargetColor(130,190,230);//源代码的目标颜色  
  6. controller.setTargetColor(140,174,209); //根据读入城堡图像调整了一下目标颜色  
  7.   
  8.     //处理输入图像并显示结果  
  9.     controller.process();  
  10.     cv::imshow(“Output Result”,controller.getLastResult());  
  11. }  

注意,书上ColorDetectController类型的成员变量定义为colordetect,而本文中定义的控制器实例名称为controller(即控制器变量名称)(见3.3.4节(1)小节),因此,注意将书中的名称colordetect改为controller。

通过process按钮的颜色检测结果

5.使用MVC架构为程序增加肤色检测功能

书中的扩展阅读介绍了MVC架构。MVC架构的目的是生成一个能把程序逻辑与用户接口清晰地隔离的程序。MVC主要包含三个组件:模型、视图、控制器。

模型存放与应用程序有关的信息。他控制着程序的所有数据,当产生新数据时,它会通知控制器,然后控制器通知视图显示新结果。

视图相当于用户接口。它由不同的窗口组成,窗口可以向用户展示数据并且允许用户与程序交互。视图的功能之一就是将用户发出的命令发送给控制器。当有新数据时,视图会刷新以显示新的信息。

控制器是连接视图和模型的模块。它从视图接收请求,并转发给模型中对应的方法。模型状态发生变化时会通知控制器,然后控制器通知视图进行刷新,以显示新信息。

下面我们将扩展阅读介绍的不同的色彩空间的知识,以及在HSV色彩空间中搜寻特定颜色物体的例子添加到MFC程序中,为程序增加一项检测肤色的功能。

注:在对特定物体做初步检测时,颜色信息非常有用。例如在辅助驾驶程序中检测路标,就要凭借标准路标的颜色快速地提取可能是路标的信息。另一个例子是检测皮肤的颜色,检测到的皮肤区域可作为图像中有人存在的标志。在手势识别中经常使用这个方法,用肤色检测来确定手的位置。

下面将书中的肤色检测的例子按照MVC模式,添加到刚才编写的MFC程序中。

(1)模型层:在算法类中定义肤色检测方法

为了给算法添加肤色检测功能,需要在ColorDetecor算法类中定义一个肤色检测方法。打开包含算法类的头文件colordetector.h,在类定义中添加如下肤色检测代码:

  1. //增加肤色检测方法   
  2.     void detectHScolor(const cv::Mat& image,    // input image   
  3. double minHue, double maxHue,   // Hue interval   
  4. double minSat, double maxSat,        // saturation interval  
  5. cv::Mat& mask) {                // output mask输出肤色掩码  
  6.   
  7. // convert into HSV space  
  8. cv::Mat hsv;  
  9. cv::cvtColor(image, hsv, CV_BGR2HSV);  
  10.   
  11. // split the 3 channels into 3 images  
  12. std::vector<cv::Mat> channels;  
  13. cv::split(hsv, channels);  
  14. // channels[0] is the Hue        色调  
  15. // channels[1] is the Saturation 饱和度  
  16. // channels[2] is the Value      亮度  
  17.   
  18. // Hue masking  
  19. cv::Mat mask1; // under maxHue  
  20. cv::threshold(channels[0], mask1, maxHue, 255, cv::THRESH_BINARY_INV);  
  21. cv::Mat mask2; // over minHue  
  22. cv::threshold(channels[0], mask2, minHue, 255, cv::THRESH_BINARY);  
  23.   
  24. cv::Mat hueMask; // hue mask  
  25. if (minHue < maxHue)  
  26.     hueMask = mask1 & mask2;  
  27. else // if interval crosses the zero-degree axis  
  28.     hueMask = mask1 | mask2;  
  29.   
  30. // Saturation masking  
  31. // under maxSat  
  32. cv::threshold(channels[1], mask1, maxSat, 255, cv::THRESH_BINARY_INV);  
  33. // over minSat  
  34. cv::threshold(channels[1], mask2, minSat, 255, cv::THRESH_BINARY);  
  35.   
  36. cv::Mat satMask; // saturation mask  
  37. satMask = mask1 & mask2;  
  38.   
  39. // combined mask组合掩码  
  40. mask = hueMask&satMask;  

(2)视图层:在对话框中添加detect skin按钮

在对话框中使用工具添加按钮,增加一个处理按钮,并名称和ID分别改为detect skin和ID_DETECTSKIN。

添加肤色检测按钮

添加完按钮后,双击按钮进入响应函数编写,添加如下按钮响应代码:

  1. void CopencvColorDetectControllerDlg::OnBnClickedDetectskin()  
  2. {  
  3.     // TODO: 在此添加控件通知处理程序代码  
  4.     controller.detectHScolor();//调用检测肤色的算法  
  5.     cv::imshow(“Output detected skin”,controller.getLastResult());//调用获取检测结果的方法显示结果  
  6. }  

上面的代码表示,视图层通过按钮来调用控制器层运行检测肤色的方法controller.detectHScolor(),按照上文在控制器中定义方法的命名规则,我们也将该方法命名为detectHScolor()。该方法的算法实现为算法类中添加的detectHScolor()方法,控制器只是将视图层的请求重新定向到算法类(即实现委托)。

(3)控制器层:在控制器类中定义肤色检测方法

如何将算法层与视图层连接起来呢?这就要通过控制器层,下面将介绍控制器层如何实现它在MVC架构中的作用。

控制器是连接视图和模型的模块。它从视图接收请求,并转发给模型中对应的方法。模型状态发生变化时会通知控制器,然后控制器通知视图进行刷新,以显示新信息。

这里我们在控制器类中添加检测肤色的方法供视图层调用,而实际的处理则是由控制器层通知模型层的算法来处理。

在控制器类ColorDetectController中定义肤色检测方法,代码如下:

  1. void detectHScolor() {  
  2.        
  3.       cv::Mat  mask;  
  4.       cdetect->detectHScolor(image,160,10,25,166,mask);//调用算法类中的肤色检测算法,返回mask  
  5.       //显示掩码后的图像  
  6.       image.copyTo(result,mask);  
  7.   }  

控制器类中的肤色检测方法detectHScolor将输入图像image传递给模型层的算法cdetect->detectHScolor(…),由算法检测肤色后返回检测结果掩码mask。接下来,控制器的detectHScolor方法再将mask应用到输入图像上image.copyTo(result,mask),生成只有肤色部分的图像result。这时,视图层调用方法controller.getLastResult()就可以从控制器层获得最终的肤色检测结果了。

打开一幅有人物的图像

显示肤色检测结果

肤色检测的参数设置在控制器层的controller.detectHScolor()方法中,下一步还可以将参数设置改到视图层作为用户与程序的交互功能。

转载注明来源:CV视觉网 » OpenCV—用控制器设计模式实现功能模块间的通信

分享到:更多 ()
扫描二维码,给作者 打赏
pay_weixinpay_weixin

请选择你看完该文章的感受:

0不错 1超赞 0无聊 0扯淡 0不解 0路过

评论 抢沙发

评论前必须登录!