OpenCV2计算机编程手册(二)基于类的图像处理
1. 在算法设计中使用策略(Strategy)模式
策略设计模式的目标是将算法封装在类中。因此,可以更容易地替换一个现有的算法,或者组合使用多个算法以拥有更复杂的处理逻辑。此外,该模式将算法的复杂度隐藏在易用的编程接口背后,降低了算法的部署难度。
准备工作
比方说,我们需要构建一个简单的算法,它可以鉴别出图像中含有给定颜色的所有像素。该算法输入的是图像以及颜色,并返回表示含有指定颜色的像素的二值图像,该算法还需要指定另外一个参数,即对颜色偏差的容忍度。
实现方法
让我们写一个主函数,然后看看我们颜色检测算法的运行结果是什么样的:
int main()
{
//1. 创建图像处理对象
ColorDector cdetect;
//2. 读取输入图像
cv::Mat image = cv::imread("boldt.jpg");
cv::namedWindow("original", );
cv::imshow("original", image);
//3. 设置输入参数
cv::Mat result;
cdetect.setTargetColor(, , );//蓝天的颜色
//处理并显示结果
cv::namedWindow("result", );
cv::imshow("result", cdetect.process(image, result));
cv::waitKey(); return ;
}
作用原理
算法的核心部分非常简单,,包含一个遍历每个像素的简单循环,将像素的颜色与目标颜色比较。
cv::Mat_<uchar> process(cv::Mat &image)
{
cv::Mat result;
result.create(image.rows, image.cols, CV_8U);
//得到迭代器
cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
cv::Mat_<uchar>::iterator itout = result.begin<uchar>(); while (it != itend)
{
if (getDistance(*it) < minDist)
{
*itout = ;
}
else
{
*itout = ;
}
it++;//更新输入迭代器
itout++;;//更新输出迭代器
}
return result;
}
cv::Mat 类型的变量 image 表示输入图像,而 result 表示的是二值输出图像。因此,第一步包括初始化迭代器,之后循环遍历很容易实现。每个迭代器检测当前像素的颜色与目标颜色的差距,判断是否在 minDist 所定义的容忍度之内。如果判断为真,那么输出图像中的当前像素赋值为255(白色),否则赋值为0(黑色)。 gitDistance 方法用于计算两个颜色之间的距离。本例中,简单使用各通道像素差绝对值之和来计算颜色距离:
int getDistance(const cv::Vec3b &color) const
{
return abs(color[] - target[]) +
abs(color[] - target[]) +
abs(color[] - target[]);
}
这里process 方法执行时,会检查 输出图像是否需要重新分配大小, 也就是creat 方法是否会执行。
下面我们来把其他方法(包括变量set和get方法)或变量补全。
class ColorDector{
private:
//最小可接受距离
int minDist;
//目标色
cv::Vec3b target;
//结果图像
cv::Mat result;
public:
//构造函数
ColorDector() : minDist()
{
//初始化默认参数
target[] = target[] = target[] = ;
}
//设置彩色距离阈值,阈值须为非负数
void setColorDistanceThreshold(int distance)
{
if (distance < )
distance = ;
minDist = distance;
}
//获取彩色距离阈值
int getColorDistanceThreshold() const
{
return minDist;
}
//设置需检测的颜色
void setTargetColor(unsigned char red, unsigned char green, unsigned char blue)
{
target[] = red;
target[] = green;
target[] = blue;
}
//设置需检测的颜色
void setTargetColor(cv::Vec3b color)
{
target = color;
}
//获取需检测的颜色
cv::Vec3b getTargetColor() const
{
return target;
}
//二值化处理函数
cv::Mat_<uchar> process(cv::Mat &image, cv::Mat & result)
{
result.create(image.rows, image.cols, CV_8U);
//得到迭代器
cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
cv::Mat_<uchar>::iterator itout = result.begin<uchar>(); while (it != itend)
{
if (getDistance(*it) < minDist)
{
*itout = ;
}
else
{
*itout = ;
}
it++;//更新输入迭代器
itout++;;//更新输出迭代器
}
return result;
}
//计算颜色距离
int getDistance(const cv::Vec3b &color) const
{
return abs(color[] - target[]) +
abs(color[] - target[]) +
abs(color[] - target[]);
}
};
这里,我们提供用户两种 setTargetColor 方法。前一种的三个参数分别是三个颜色分量,而后一种使用 cv::Vec3b 来保存颜色值。
整个设计很简单,当算法愈发复杂是,策略模式才能够发挥真正的威力。
扩展阅读
为了计算两个颜色向量之间的距离,我们之前使用了简单的像素差绝对值之和。OpenCV内置了一个 norm 函数用于计算向量的欧斯距离:
return static_cast<int>(
cv::norm<int,>(cv::Vec3i(color[]-target[],
color[]-target[],
color[]-target[])));
这里我们使用的是 cv::Vec3i(包含三个整数的向量)作为norm函数的输入参数。
OpenCV提供了矩阵和向量结构的一些基本算术操作。所以我们可能会这样写:
return static_cast<int>(
cv::norm<uchar,>(color-target)); // wrong!
实际上,这是错误的,因为这些算术操作符都包含了对 static_cast 的调用。那么当target的值大于color时,返回的不是负数值,而是0。
正确的形式应该是:
cv::Vec3b dist;
cv::absdiff(color, target, dist);
return cv::sum(dist)[];
2. 使用控制器(Controller)实现模块间通信
当构建更复杂的应用程序时,你将需要创建多个算法,组合使用它们可以完成一些高级的任务。
准备工作
创建两个按钮的简单对话框应用,其中之一用于选择图像,另一个用于开始处理图像,如图所示:
实现方法
1. 定义colordetector.h
#include<iostream>
#include "opencv2/opencv.hpp" using namespace std; class ColorDetector{
private:
//最小可接受距离
int minDist;
//目标色
cv::Vec3b target;
//结果图像
cv::Mat result;
public:
//构造函数
ColorDetector() : minDist()
{
//初始化默认参数
target[] = target[] = target[] = ;
}
//设置彩色距离阈值,阈值须为非负数
void setColorDistabceThreshold(int distance)
{
if (distance < )
distance = ;
minDist = distance;
}
//获取彩色距离阈值
int getColorDistanceThreshold() const
{
return minDist;
}
//设置需检测的颜色
void setTargetColor(unsigned char red, unsigned char green, unsigned char blue)
{
target[] = red;
target[] = green;
target[] = blue;
}
//设置需检测的颜色
void setTargetColor(cv::Vec3b color)
{
target = color;
}
//获取需检测的颜色
cv::Vec3b getTargetColor() const
{
return target;
}
//二值化处理函数
cv::Mat_<uchar> process(cv::Mat &image)
{
result.create(image.rows, image.cols, CV_8U);
//得到迭代器
cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
cv::Mat_<uchar>::iterator itout = result.begin<uchar>(); while (it != itend)
{
if (getDistance(*it) < minDist)
{
*itout = ;
}
else
{
*itout = ;
}
it++;//更新输入迭代器
itout++;;//更新输出迭代器
}
return result;
}
//计算颜色距离
int getDistance(const cv::Vec3b &color) const
{
cv::Vec3b dist;
cv::absdiff(color, target, dist);
return cv::sum(dist)[];
}
}; class ColorDetectController
{
private:
ColorDetector* cdetect;//算法类
cv::Mat image;//待处理的图像
cv::Mat result;//结果
public:
ColorDetectController()
{
cdetect = new ColorDetector();
}
//设置色彩距离阈值
void setColorDistanceThreshold(int distance)
{
cdetect->setColorDistabceThreshold(distance);
}
//获取色彩距离阈值
int getColorDistancethreshold() const
{
return cdetect->getColorDistanceThreshold();
}
//设置要检测的颜色
void setTargetColor(unsigned char red,
unsigned char green, unsigned char blue)
{
cdetect->setTargetColor(red, green, blue);
}
//获取要检测的颜色
void getTargetColor(unsigned char& red, unsigned char& green, unsigned char& blue) const
{
cv::Vec3b color = cdetect->getTargetColor();
red = color[];
green = color[];
blue = color[];
}
//设置输入图像,通过文件读取
bool setInputImage(string filename)
{
image = cv::imread(filename);
if (!image.data)
return false;
else
return true;
}
//返回当前的输入图像
const cv::Mat getInputImage() const
{
return image;
}
//开始处理图像
void process()
{
result = cdetect->process(image);
}
//获取最近一次处理的结果
const cv::Mat getLastResult() const
{
return result;
}
//删除当前控制器创建的处理对象
~ColorDetectController()
{
delete cdetect;
}
};
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 a filename has been selected
if (dlg.DoModal() == IDOK) {
// get the path of the selected filename
CString strMfc = dlg.GetPathName();
std::string filename = CT2CA(strMfc.GetBuffer());
// set and display the input image
controller.setInputImage(filename);
cv::imshow("Input Image", controller.getInputImage());
}
// target color is hard-coded here
controller.setTargetColor(, , );
// process the input image and display result
controller.process();
cv::imshow("Output Result", controller.getLastResult());
3. 使用单件(Singleton)设计模式
单件是另外一种流行的设计模式,用于简化对一个类实例的访问,同时保证在程序执行期间只有一个实例存在。
准备工作
我们使用上面的 ColorDetectController 类。该类将被修改,以包含一个单件类。
实现方法
要做的第一件事是添加一个私有静态成员变量,他将保存对单个类实例的引用。同时,为了禁止创建额外的类实例,构造函数也是私有的:
class ColorDetectController{
private:
cv::Mat image;//待处理的图像
cv::Mat result;//结果
//单件指针
static ColorDetectController *singleton;
ColorDetector *cdetect;
//私有构造函数
ColorDetectController()
{
//初始化工作
cdetect = new ColorDetector();
}
...
}
此外,你还可以使复制构造函数和操作符 = 私有化,以确保无法创建独一无二的单件实例的拷贝。当一个用户的类要求单件类的一个实例时,它才被创建。这通过使用一个公有静态方法实现,如果实例不存在那么创建它,然后返回一个指向该实例的指针:
static ColorDetectController * getInstance()
{
if (singleton == )
singleton = new ColorDetectController();
return singleton;
}
需要注意的是,单件的实现并不是线程安全的。因此,在多线程情况下不应该使用它。
最后,因为单件实例是被动态创建,当不需要时用户必须删除它。这也是通过一个静态方法实现的:
static void destroy()
{
if (singleton != )
delete singleton;
singleton = ;
}
由于单件是一个静态成员变量,它必须在 .cpp 文件中定义,如下:
ColorDetectController *ColorDetectController::singleton = ;
作用原理
因为单件可以通过一个公共的静态方法获取,所有包括单件类声明的类都能访问它。这尤其适用于控制器对象,它被多个拥有复杂GUI的窗口控件类访问。其中的任何一个GUI类都不需要声明一个成员变量,这和前一节不同。对话框类的两个回调方法编写如下:
void CMy3_3单件设计模式Dlg::OnBnClickedOpen()
{
// TODO: 在此添加控件通知处理程序代码
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)
{
// get the path of the selected filename
CString strMfc = dlg.GetPathName();
std::string filename = CT2CA(strMfc.GetBuffer());
// set and display the input image
ColorDetectController::getInstance()->setInputImage(filename);
cv::imshow("Input Image", ColorDetectController::getInstance()->getInputImage());
}
} void CMy3_3单件设计模式Dlg::OnBnClickedProcess()
{
// TODO: 在此添加控件通知处理程序代码
ColorDetectController::getInstance()->setTargetColor(, , );
ColorDetectController::getInstance()->process();
cv::imshow("Output result", ColorDetectController::getInstance()->getLastResult());
}
当应用程序关闭时,单件的实例必须被释放:
void CMy3_3单件设计模式Dlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
ColorDetectController::getInstance()->destroy();
CDialog::OnOK();
}
如上所示,当一个控制器被封装在一个单件中,它变得更容易访问。然而,一个更好的实现需要一个更复杂的GUI。这将在下一节中实现。
4. 使用模型-试图-控制器(Model-View-Controller)架构设计应用程序
5. 颜色空间转换
下面先介绍几个不同的颜色空间:
RGB: 基于红、绿、蓝三原色的使用。
Lab: 在感知上均匀分布的色彩空间。
HSV: 代表色调,饱和度和值(Value)。
HLS: 代表色调(Hue),饱和度(Saturation)和亮度(Lightness)。
调用方式为:
cvtColor(image_, gray, COLOR_BGR2GRAY);
cvtColor(image_, hls, COLOR_BGR2HLS);
cvtColor(image_, hsv, COLOR_BGR2HSV);
cvtColor(image_, hsv, COLOR_BGR2Lab);
作用原理
当图像从一个颜色空间转换到另一个,线性或非线性变换将作用于每个输入像素,以产生输出像素。
注意,三原色红、绿、蓝在RGB次序或BGR次序转换到相同颜色空间是不一样的(灰度一样),比如转到HSV空间,H会相反。
#include <iostream>
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp" using namespace std;
using namespace cv; void Show_Image(Mat & input, char* name)
{
namedWindow(name, );
imshow(name, input); } int main()
{
Mat image = imread("waves.jpg");
Mat hsv;
cvtColor(image, hsv, COLOR_BGR2HSV);
Mat channel[];
split(hsv, channel);
for (int index = ; index < ; index++)
{
char* buff[] = { "H", "S", "V" }; Show_Image(channel[index], buff[index]);
} waitKey(); return ;
}
OpenCV2计算机编程手册(二)基于类的图像处理的更多相关文章
- OpenCV2计算机编程手册(一)操作像素
1. 引言 从根本上来说,一张图像是一个由数值组成的矩阵.这也是opencv中使用 代表黑色,代表白色.对于彩色图像(BGR三通道)而言,每个像素需要三个这样的8位无符号数来表示,这种情况下,矩阵的元 ...
- Scala 编程(二)类和对象
类,字段和方法 类是对象的蓝图.一旦定义了类,就可以用关键字new从类的蓝图里创建对象,类的定义: class ChecksumAccumulator { // class definition go ...
- JavaScript 面向对象的编程(二) 类的封装
类的定义 方式一 var Book = function(id, name, price){ //私有属性,外部不能直接访问 var num = 1; //私有方法, function checkId ...
- 并发编程(二)concurrent 工具类
并发编程(二)concurrent 工具类 一.CountDownLatch 经常用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工作. import java.util.concurren ...
- Django——基于类的视图源码分析 二
源码分析 抽象类和常用视图(base.py) 这个文件包含视图的顶级抽象类(View),基于模板的工具类(TemplateResponseMixin),模板视图(TemplateView)和重定向视图 ...
- 《Linux/UNIX系统编程手册》第56章 SOCKET:介绍
关键词: 1. socket基础 一个典型的客户端/服务器场景中,应用程序使用socket进行通信的方式如下: 各个应用程序创建一个socket.socket是一个允许通信的设备,两个应用程序都需要用 ...
- 《Linux/Unix系统编程手册》读书笔记8 (文件I/O缓冲)
<Linux/Unix系统编程手册>读书笔记 目录 第13章 这章主要将了关于文件I/O的缓冲. 系统I/O调用(即内核)和C语言标准库I/O函数(即stdio函数)在对磁盘进行操作的时候 ...
- 《Linux/Unix系统编程手册》读书笔记6
<Linux/Unix系统编程手册>读书笔记 目录 第9章 这章主要讲了一堆关于进程的ID.实际用户(组)ID.有效用户(组)ID.保存设置用户(组)ID.文件系统用户(组)ID.和辅助组 ...
- WCF编程系列(二)了解WCF
WCF编程系列(二)了解WCF 面向服务 服务是复用进化的结果,起初的复用是函数,面向对象编程的出现使复用从函数上升到对象,随后面向组件编程又将复用从对象上升到组件,现在面向服务编程将复用 ...
随机推荐
- winsock select 学习代码(1)
// SelectCli.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <winsock2.h> #incl ...
- ABP框架系列之二十三:(EF-MySql-Integration-EF-MySql-集成)
Introduction While our default templates designed to work with SQL Server, you can easily modify the ...
- LA 3026 && POJ 1961 Period (KMP算法)
题意:给定一个长度为n字符串s,求它每个前缀的最短循环节.也就是对于每个i(2<=i<=n),求一个最大整数k>1(如果存在),使得s的前i个字符组成的前缀是某个字符串重复得k次得到 ...
- 记一次web服务模块开发过程
一.前言 之前在分析WCS系统的过程中,也赶上要开发其中的一个模块,用于和AGV系统对接完成一些取货.配盘等任务:在这里将这次模块开发的全过程记录一下,以便自己以后开发时能够更加快速的明白流程. 二. ...
- ansible facts 获取硬件信息
facts 指的是 ansible_facts 变量,ansible 中使用 setup 模块来获取,包含系统的大部分基础硬件信息, [root@10_1_162_39 host_vars]# ll ...
- 如何使用Java执行cmd命令
用JAVA代码实现执行CMD命令的方法! Runtime rt = Runtime.getRuntime(); Process p = rt.exec(String[] cmdarray); ...
- 201709025工作日记--更新UI方法
1.handler+Thread 和 runOnUIThread 和 handler.post 方法 区别: 从实现原理上,两者别无二致,runOnUiThread也是借助Handler实现的. 对 ...
- 【OSGI】1.初识OSGI-到底什么是OSGI
目前,业内关于OSGI技术的学习资源或者技术文档还是很少的.我在某宝网搜索了一下“OSGI”的书籍,结果倒是有,但是种类少的可怜,而且几乎没有人购买. 因为工作的原因我需要学习OSGI,所以我不得不想 ...
- mysql 可重复执行添加列
DROP PROCEDURE IF EXISTS `add_column_if`; CREATE PROCEDURE `add_column_if`(IN v_table varchar(), IN ...
- Spring框架事务支持模型的优势
全局事务 全局事务支持对多个事务性资源的操作,通常是关系型数据库和消息队列.应用服务器通过JTA管理全局性事务,API非常烦琐.UserTransaction通常需要从JNDI获取,意味着需要与JND ...