OpenCV学习笔记-用控制器设计模式实现功能模块间的通信

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

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

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

主要步骤

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

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

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

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

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

具体步骤

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

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

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第1张图片

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

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第2张图片

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

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

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

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第3张图片

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

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

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

#if !defined CD_CNTRLLR
#define CD_CNTRLLR
#include "stdafx.h"
#include <opencv2/highgui/highgui.hpp>
#include "colordetector.h" //添加对算法类头文件的包含

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

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

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

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

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

该类中首先添加对算法类ColorDetector的使用。另外需要有两个成员变量,作为输入输出结果的引用。
class ColorDetectController { //定义控制器类(开头部分代码)

  private:
   // the algorithm class
   ColorDetector *cdetect;
   
   cv::Mat image;   // The image to be processed
   cv::Mat result;  // The image result

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

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

  public:
	ColorDetectController() { // private constructor
		  //setting up the application
		  cdetect= new ColorDetector();

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

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

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

// Sets the colour distance threshold设置颜色差距的阈值
	  void setColorDistanceThreshold(int distance) {
		  cdetect->setColorDistanceThreshold(distance);
	  }

	  // Gets the colour distance threshold获取颜色差距阈值
	  int getColorDistanceThreshold() const {
		  return cdetect->getColorDistanceThreshold();
	  }

	  // Sets the colour to be detected设置目标颜色的方法
	  void setTargetColor(unsigned char red, unsigned char green, unsigned char blue) {
		  cdetect->setTargetColor(blue,green,red);
	  }

	  // Gets the colour to be detected获取目标颜色的方法
	  void getTargetColour(unsigned char &red, unsigned char &green, unsigned char &blue) const {
		  cv::Vec3b colour= cdetect->getTargetColor();
		  red= colour[2];
		  green= colour[1];
		  blue= colour[0];
	  }

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

在控制器类的定义中,需要定义如何读取图像的方法,以及如何获取输入的图像的方法。 
  // Sets the input image. Reads it from file.输入图像,从文件中读取
	  bool setInputImage(std::string filename) {
		  image= cv::imread(filename);
		  return !image.empty();
	  }

	  // Returns the current input image.返回当前的输入图像
	  const cv::Mat getInputImage() const {
		  return image;
	  }

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

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

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

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

需要一个方法来获取处理结果
 // Returns the image result from the latest processing.
	  const cv::Mat getLastResult() const {
		  return result;
	  }

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

最后,非常重要的一步是在程序结束时做内存清理,即释放Controller类,在程序结束时清理内存
 // Deletes all processor objects created by the controller.删除由控制器创建的对象
	  ~ColorDetectController() {
		  delete cdetect; // 释放动态分配的实例的内存
	  }
};     // 控制器类定义的结束
#endif  // 接最前面的预编译指令

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

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

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

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

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第4张图片

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

(2)open按钮的处理程序

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

void CopencvColorDetectControllerDlg::OnBnClickedOpen()
{
	// TODO: 在此添加控件通知处理程序代码
	// 选择bmp或jpg类型文件的MFC对话框
	CFileDialog dlg(TRUE,_T("*.bmp"),NULL,
		OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,
		_T("image file(*.bmp;*,jpg)|*.bmp;*.jpg|ALL Files(*.*)|*.*||"),NULL);

	dlg.m_ofn.lpstrTitle=_T("Open Image");

	//如果选中了一个文件名
	if(dlg.DoModal()==IDOK)
	{
		//取得选定文件名的完整路径
		//Cstring->std::string
	    CString cstrfilename = dlg.GetPathName();//.GetPathName返回的是CString类型的字符串
	    std::string filename;
        setlocale(LC_ALL, "chs");
        char *p = new char[256];
        wcstombs( p, cstrfilename, 256 );
        filename = p;
		
		//设置并显示输入的图像,注意controller变量已经在前文向对话框类中添加过了
		controller.setInputImage(filename); //.setInputImage方法输入的为std::string类型的字符串
		cv::imshow("Input Image",controller.getInputImage());
	}
}

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

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第5张图片

打开图片的效果

(3)process按钮的处理程序

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

void CopencvColorDetectControllerDlg::OnBnClickedProcess()
{
	// TODO: 在此添加控件通知处理程序代码
	//这里颜色采用硬编码
	//controller.setTargetColor(130,190,230);//源代码的目标颜色
controller.setTargetColor(140,174,209); //根据读入城堡图像调整了一下目标颜色

	//处理输入图像并显示结果
	controller.process();
	cv::imshow("Output Result",controller.getLastResult());
}
注意,书上ColorDetectController类型的成员变量定义为colordetect,而本文中定义的控制器实例名称为controller(即控制器变量名称)(见3.3.4节(1)小节),因此,注意将书中的名称colordetect改为controller。
通过process按钮的颜色检测结果

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

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

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

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

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

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

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

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

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

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

  //增加肤色检测方法 
  	void detectHScolor(const cv::Mat& image,	// input image 
		double minHue, double maxHue,	// Hue interval 
		double minSat, double maxSat,	     // saturation interval
		cv::Mat& mask) {				// output mask输出肤色掩码

		// convert into HSV space
		cv::Mat hsv;
		cv::cvtColor(image, hsv, CV_BGR2HSV);

		// split the 3 channels into 3 images
		std::vector<cv::Mat> channels;
		cv::split(hsv, channels);
		// channels[0] is the Hue        色调
		// channels[1] is the Saturation 饱和度
		// channels[2] is the Value      亮度

		// Hue masking
		cv::Mat mask1; // under maxHue
		cv::threshold(channels[0], mask1, maxHue, 255, cv::THRESH_BINARY_INV);
		cv::Mat mask2; // over minHue
		cv::threshold(channels[0], mask2, minHue, 255, cv::THRESH_BINARY);

		cv::Mat hueMask; // hue mask
		if (minHue < maxHue)
			hueMask = mask1 & mask2;
		else // if interval crosses the zero-degree axis
			hueMask = mask1 | mask2;

		// Saturation masking
		// under maxSat
		cv::threshold(channels[1], mask1, maxSat, 255, cv::THRESH_BINARY_INV);
		// over minSat
		cv::threshold(channels[1], mask2, minSat, 255, cv::THRESH_BINARY);

		cv::Mat satMask; // saturation mask
		satMask = mask1 & mask2;

		// combined mask组合掩码
		mask = hueMask&satMask;
}

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

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

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第6张图片

添加肤色检测按钮

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

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

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

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

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

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

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

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

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

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

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第7张图片

打开一幅有人物的图像

OpenCV学习笔记-用控制器设计模式实现功能模块间的通信_第8张图片

显示肤色检测结果

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

你可能感兴趣的:(设计模式,编程,opencv,计算机视觉,模块间通信)