VS+openCV 处理图像的颜色(上)用策略设计模式比较颜色

用策略设计模式比较颜色

【实现】

#pragma once
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<vector>
#include<iostream>
#include<random>

using namespace std;
using namespace cv;
class ColorDetector {
private:
	//允许的最小差值
	int maxDist;
	//目标颜色
	cv::Vec3b target;
	//存储二值影响结果的图像
	cv::Mat result;
public:
	//空构造函数
	//在此初始化默认参数
	ColorDetector();
	//另一种构造函数,使用目标颜色和颜色距离作为参数
	//有参构造1
	ColorDetector(uchar blue, uchar green, uchar red, int distance);
	//有参构造2
	ColorDetector(cv::Vec3b color, int distance);

	//设置目标颜色函数1
	void setTargetColor(uchar blue, uchar green, uchar red);

	//设置目标颜色函数2
	void setTargetColor(cv::Vec3b color);

	//设置颜色公差
	void setColorDistanceThreshold(int distance);

	//取颜色公差
	int getColorDistanceThreshold() const;

	//取目标颜色
	cv::Vec3b getTargetColor() const;

	//用城区距离计算颜色差距以供类内调用
	int getColorDistance(const cv::Vec3b& color1, const cv::Vec3b& color2) const;

	//计算与目标颜色差距
	int getDistanceToTargetColor(const cv::Vec3b& color) const;

	//用迭代器遍历并返回二值图像
	cv::Mat process(const cv::Mat& image);
};


ColorDetector::ColorDetector()
{
	this->maxDist = 100;
	this->target = cv::Vec3b(0, 0, 0);
}

ColorDetector::ColorDetector(uchar blue, uchar green, uchar red, int distance)
{
	this->maxDist = distance;
	this->target = cv::Vec3b(blue, green, red);
}

ColorDetector::ColorDetector(cv::Vec3b color, int distance)
{
	this->maxDist = distance;
	this->target = color;
}

//设置需要检测的颜色
void ColorDetector::setTargetColor(uchar blue,
								   uchar green,
								   uchar red) {
	//次序为BGR
	target = cv::Vec3b(blue, green, red);
}

//设置颜色差距的阈值
//阈值必须是正数,否则就设为0
void ColorDetector::setColorDistanceThreshold(int distance) {
	if (distance < 0) {
		distance = 0;
		this->maxDist = distance;
	}
}

//取得颜色差距的阈值
int ColorDetector::getColorDistanceThreshold() const {
	return maxDist;
}

//设置需要检测的颜色
void ColorDetector::setTargetColor(cv::Vec3b color) {
	target = color;
}

//取得需要检测的颜色
cv::Vec3b ColorDetector::getTargetColor() const {
	return target;
}

//计算与目标颜色的差距
int ColorDetector::getDistanceToTargetColor(const cv::Vec3b& color)const {
	return getColorDistance(color, target);
}

//计算两个颜色直接的城区距离
int ColorDetector::getColorDistance(const cv::Vec3b& color1, const cv::Vec3b& color2)const {
	return abs(color1[0] - color2[0]) +
		abs(color1[1] - color2[1]) +
		abs(color1[2] - color2[2]);
}

cv::Mat ColorDetector::process(const cv::Mat& image) {
	//必要时重新分配二值映像
	//与输入图像的尺寸相同,不过是单通道
	cv::Mat result;
	result.create(image.size(), 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>();
	//对于每个像素
	for (; it != itend; ++it, ++itout) {
		//比较与目标颜色的差距
		if (ColorDetector::getDistanceToTargetColor(*it) <= maxDist) {
			*itout = 255;
		}
		else {
			*itout = 0;
		}
	}
	return result;
}

int main()
{

	// 1.创建图像处理器对象
	ColorDetector cdetect;
	// 2.读取输入的图像
	cv::Mat image = cv::imread("bluesky.jpg");
	if (image.empty()) return 0;
	// 3.设置输入参数
	cdetect.setTargetColor(230, 190, 130); // 这里表示蓝天
	// 4.处理图像并显示结果
	cv::namedWindow("result");
	cv::Mat result = cdetect.process(image);
	cv::imshow("result", result);
	cv::waitKey();
	return 0;
}

【实现原理】

        首先,这个算法的核心过程非常简单,只是对每个像素进行循环扫描,把它的颜色和目标颜色做比较。如果<=则为白色,否则为黑色。

        本次的代码要封装一个ColorDetector,这里涉及到C++的知识。在这个类中,private里自然不必多说,public里的函数我将逐个进行说明。

        (1)ColorDetector(); 初始化默认参数。将最小差值maxDist设为100,目标颜色设为(0,0,0)即为黑色。这一步只是初始化了一下参数,相当于int i=1;

        (2)ColorDetector(uchar blue, uchar green, uchar red, int distance);有参构造1。这里将最小差值设置为目标输入的distance,将目标颜色设置为用户输入的颜色。

        (3)ColorDetector(cv::Vec3b color, int distance);有参构造2。这里的意义和有参构造1一致。

        (4)void setTargetColor(uchar blue, uchar green, uchar red);设置目标颜色函数1。这里将目标颜色设置为用户输入的BGR。

        (5)void setTargetColor(cv::Vec3b color);设置目标颜色函数2。作用同上。只是用户输入的形式不同。

        (6)void setColorDistanceThreshold(int distance);设置颜色公差。如果输入的distance<0则最小差值设为0。

        (7)int getColorDistanceThreshold() const;取颜色公差。返回最小差值。

        (8)cv::Vec3b getTargetColor() const;取目标颜色。返回目标颜色。

        (9)int getColorDistance(const cv::Vec3b& color1, const cv::Vec3b& color2) const;用城区距离计算颜色差距以供类内调用。返回BGR的距离之和。

        (10)int getDistanceToTargetColor(const cv::Vec3b& color) const;计算与目标颜色差距。用(9)的函数进行计算,计算用户输入颜色与目标颜色的差值。

        (11)cv::Mat process(const cv::Mat& image);用迭代器遍历并返回二值图像。这里首先要建立一个和image同样大小的图像:result。然后取得迭代器,将image的开始位置赋予it,结束位置赋予itend,将result的开始位置赋予itout。接下来就开始循环,比较图像中颜色与目标颜色的差距,<=最小差值的赋予白色,>的赋予黑色。

        说明完这些后,我们来看main函数。首先创建图像处理器对象,接着读取图像,设置一个输入参数。这里的意思是,将目标颜色变为(230,190,130),然后用process进行处理。

【扩展阅读】

        例子中的算法可识别出图像中与指定目标颜色足够接近的像素。过程中已经完成了计算步骤。OpenCV 中有一个具有类似功能的函数,可以从图像中提取出与特定颜色相关联的部件。另外,我们也可以用函数对象来补充策略设计模式。OpenCV 中定义了一个基类 cv::Algorithm,实现策略设计模式的概念。

(1)计算两个颜色向量间的距离

        在本例中我们用的方法是返回BGR的距离之和,OpenCV 中也有计算向量的欧几里得范数的函数,因此也可以这样计算距离。

return static_cast<int>( 
     cv::norm<int,3>(cv::Vec3i(color[0]-target[0], 
                               color[1]-target[1], 
                               color[2]-target[2]))); 

        这样得到的结果与原来的非常接近。这里之所以使用 cv::Vec3i(三个向量的整型数组),是因为减法运算得到的是整数值。

        还可以这样计算距离。

cv::Vec3b dist; 
cv::absdiff(color,target,dist); 
return cv::sum(dist)[0]; 

        不过在计算三个数组间距离时调用这两个函数的效率并不高。

(2)使用opencv函数

        调用 OpenCV 的系列函数也可以检测颜色

cv::Mat ColorDetector::process(const cv::Mat &image) { 
 cv::Mat output; 
 // 计算与目标颜色的距离的绝对值
cv::absdiff(image,cv::Scalar(target),output); 
 // 把通道分割进 3 幅图像
 std::vector<cv::Mat> images; 
 cv::split(output,images); 
 // 3 个通道相加(这里可能出现饱和的情况)
 output= images[0]+images[1]+images[2]; 
 // 应用阈值
 cv::threshold(output, // 相同的输入/输出图像
 output, 
 maxDist, // 阈值(必须<256)
 255, // 最大值
 cv::THRESH_BINARY_INV); // 阈值化模式
 return output; 
}

        absdiff 函数用来计算图像的像素与标量值之间差距的绝对值。该函数的第二个参数也可以不用标量值,而是改用另一幅图像,这样就可以逐个像素地计算差距。因此两幅图像的尺寸必须相同。然后,用 split 函数提取出存放差距的图像的单个通道以便求和。注意,累加值有可能超过 255,但因为饱和度对值范围有要求,所以最终结果不会超过 255。这里的 maxDist 参数也必须小于 256。如果你觉得这样不合理, 可以进行修改。

        最后一步是用 cv::threshold 函数创建一个二值图像。此函数通常用于将所有像素与某阈值(第3个参数)比较,并且在常规阈值化模式(cv::THRESH_BINARY)下,将所有>指定阈值的像素赋值为预定的最大值(第4个参数),将其他像素赋值为 0。这里用相反模式(cv::THRESH_BINARY_INV)把<=阈值的像素赋值为预定的最大值。还有 cv::THRESH_TOZERO 和 cv::THRESH_TOZERO_INV 模式,它们使大于或小于阈值的像素保持不变。

        一般来说,最好直接使用 OpenCV 函数。它可以快速建立复杂程序,减少潜在的错误,而且 程序的运行效率通常也比较高(得益于 OpenCV 项目参与者做的优化工作)。不过这样会执行很多的中间步骤,消耗更多内存。

(3)floodFill 函数

        cv::floodFill 函数的做法与ColorDetector 类 类似,但有一个很大的区别:它在判断 一个像素时,还要检查附近像素的状态,这是为了识别某种颜色的相关区域。用户只需指定一个起始位置和允许的误差,就可以找出颜色接近的连续区域。

        首先根据亚像素确定搜寻的颜色,并检查它旁边的像素,判断它们是否为颜色接近的像素; 然后,继续检查它们旁边的像素,并持续操作。这样就可以从图像中提取出特定颜色的区域。例如要从图中提取出蓝天,可以执行以下语句

cv::floodFill(image, // 输入/输出图像 
     cv::Point(100, 50), // 起始点
     cv::Scalar(255, 255, 255), // 填充颜色
     (cv::Rect*)0, // 填充区域的边界矩形
     cv::Scalar(35, 35, 35), // 偏差的最小/最大阈值
     cv::Scalar(35, 35, 35), // 正差阈值,两个阈值通常相等
     cv::FLOODFILL_FIXED_RANGE); // 与起始点像素比

        这种算法重绘了一个独立的连续区域(这里是把天空画成白色)。即使其他地方有颜色接近 的像素(例如水面),除非它们与天空相连,否则也不会被识别出来。

(4)仿函数或函数对象

        利用 C++的操作符重载功能,我们可以让类的实例表现得像函数。它的原理是重载 operator()方法,让调用类的处理方法就像调用纯粹的函数一样。这种类的实例被称为函数对 象或者仿函数(functor)。一个仿函数通常包含一个完整的构造函数,因此能够在创建后立即使 用。例如,可以在 ColorDetector 类中添加完整的构造函数:

// 完整的构造函数
ColorDetector(uchar blue, uchar green, uchar red, int maxDist=100): 
              maxDist(maxDist) { 
 // 目标颜色
 setTargetColor(blue, green, red); 
} 

//很显然,前面定义的获取方法和设置方法仍然可以使用。可以这样定义仿函数方法:
cv::Mat operator()(const cv::Mat &image) { 
 // 这里放检测颜色的代码
} 

//若想用仿函数方法检测指定的颜色,只需要用这样的代码片段:
ColorDetector colordetector(230,190,130, // 颜色
 100); // 阈值
cv::Mat result= colordetector(image); // 调用仿函数

//可以看到,这里对颜色检测方法的调用类似于对某个函数的调用。

(5)openCV的算法基类

        为实现计算机视觉的各项功能,OpenCV 提供了很多算法。为方便使用,大多数算法都被封装成了通用基类 cv::Algorithm 的子类。这体现了策略设计模式的一些概念。首先,所有算法都在专门的静态方法中动态地创建,以确保创建的算法总是有效的(即每个缺少的参数都有有效的默认值)。来看一个例子,即它的其中一个子类 cv::ORB。这里只把它作为一个算法示例。

//用下面的方法创建一个算法实例:
cv::Ptr<cv::ORB> ptrORB = cv::ORB::create(); // 默认状态

        算法一旦创建完毕,就可以开始使用,例如通用方法 read 和 write 可用于装载或存储算 法的状态值。算法也有一些专用方法(例如 ORB 的方法 detect 和 compute 用于触发它的主体 计算单元),也有专门用来设置内部参数的设置方法。需要注意的是,你可以把指针类型定为 cv::Ptr,但那样就无法使用它的专用方法了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值