OpenCV计算机视觉编程攻略(第3版)—— 第3章 处理图像的颜色

第3章 处理图像的颜色

3.2 用策略设计模式比较颜色

1、基本原理

假设我们要构建一个简单的算法,用来识别图像中具有某种颜色的所有像素。这个算法必须输入一幅图像和一个颜色,并且返回一个二值图像,显示具有指定颜色的像素。在运行算法前,还需要指定一个参数,即能够接受的颜色公差。

2、代码实现

/**
* 文件名:example_colorDetector.cpp
* 日期:2021/03/28
* 作者:南风无良
*/

#include <iostream>
#include <opencv2/opencv.hpp>

class ColorDetector {
private:
	int maxDist; // 允许的最小差距
	cv::Vec3b target; // 目标颜色
	cv::Mat result; // 存储二值映射结果的图像

public:
	ColorDetector() : maxDist(0.0), target(0, 0, 0) {}
	ColorDetector(uchar blue, uchar green, uchar red, int mxDist);
	void setColorDistanceThreshold(int distance);
	int getColorDistanceThreshold() const;
	void setTargetColor(uchar blue, uchar green, uchar red);
	void setTargetColor(cv::Vec3b color);
	cv::Vec3b getTargetColor() const;
	int getDistance2TargetColor(const cv::Vec3b& color) const;
	int getColorDistance(const cv::Vec3b& color, const cv::Vec3b& target) const;
	cv::Mat process(const cv::Mat& image);
	cv::Mat process(const std::string imageFile);
};

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

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

	this->maxDist = distance;
}

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

// 设置需要检测的颜色 - 重载
void ColorDetector::setTargetColor(uchar blue, uchar green, uchar red)
{
	this->target = cv::Vec3b(blue, green, red); // OpenCV颜色序列是BGR
}

void ColorDetector::setTargetColor(cv::Vec3b color)
{
	this->target = color;
}

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

// 计算与目标颜色之间的距离
int ColorDetector::getDistance2TargetColor(const cv::Vec3b& color) const
{
	return getColorDistance(color, this->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 (this->getDistance2TargetColor(*it) <= this->maxDist)
		{
		*itout = 255;
		}
		else
		{
		*itout = 0;
		}
	}

	return result;
}

cv::Mat ColorDetector::process(const std::string imageFile)
{
	cv::Mat image = cv::imread(imageFile);

	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);

	return image;
}

// main函数
int main(int argc, char* argv[])
{
    // 加载目标图像
    cv::Mat image = cv::imread("images/cow.jpg");
    cv::imshow("Image", image);
    
    std::cout << "cow: " << image.size() << std::endl;
    
    cv::Mat result;
    
    ColorDetector cold(30, 190, 30, 158);
    
    // 颜色比较
    const int64 start = cv::getTickCount();
    result = cold.process(image);
    double duration = (cv::getTickCount() - start) / cv::getTickFrequency();
    
    // 显示结果,并保存
    cv::imshow("colorDetector", result);
    cv::imwrite("output/colorDetector.jpg", result);
    cv::waitKey(0);
    
    std::cout << "wave: " << duration << "s" << std::endl;
    
    return 0;
}

3、验证结果

原图像中的背景以绿色和蓝色偏多,可以将阈值BGR分量中B与G设置的更高,而R分量降低。

(1)原图像在这里插入图片描述
(2)结果图在这里插入图片描述

3.3 用GrabCut算法分割图像

1、基本原理

利用输入信息,GrabCut算法通过以下步骤进行背景/前景分割。首先,把所有未标记的像素临时标为前景(cv::GC_PR_FGD)。基于当前的分类情况,算法把像素划分为多个颜色相似的组(即K个背景组和K个前景组)。下一步是通过引入前景和背景像素之间的边缘,确定背景/前景的分割,这将通过一个优化过程来实现。在此过程中,将试图连接具有相似标记的像素,并且避免边缘出现在强度相对均匀的区域。使用Graph Cuts算法可以高效地解决这个优化问题,它寻找最优解决方案的方法是:把问题表示成一幅连通的图形,然后在图形上进行切割,以形成最优的形态。分割完成后,像素会有新的标记。然后重复这个分组过程,找到新的最优分割方案,如此反复。因此,GrabCut算法是一个逐步改进分割结果的迭代过程。根据场景的复杂程度,找到最佳方案所需的迭代次数各不相同(如果情况简单,迭代一次就足够了)。

2、代码实现

/**
* 文件名:example_GrabCut.cpp
* 日期:2021/03/29
* 作者:南风无良
*/

#include <iostream>
#include <opencv2/opencv.hpp>

void testGrabCut(const std::string imageFile, cv::Mat& result)
{
	// 输入图像
	cv::Mat image = cv::imread(imageFile);
	cv::Mat imageBk;

	image.copyTo(imageBk);

	// 绘制矩形框
	cv::Rect rect(80, 100, 540, 315);
	cv::rectangle(imageBk, rect, cv::Scalar(255, 0, 0), 1, 8, 0);
	cv::namedWindow("rectangle");
	cv::imshow("rectangle", imageBk);
	cv::imwrite("output/GrabCut-rectangle.jpg", imageBk);

	// GrabCut分割
	cv::Mat bgModel1, fgModel1;
	cv::grabCut(image, result, rect, bgModel1, fgModel1, 5, cv::GC_INIT_WITH_RECT);
	cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ); // 比较分割结果,提取可能是前景目标的部分保存
	// 生成输出图像
	cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
	image.copyTo(foreground, result);
	cv::namedWindow("GrabCut");
	cv::imshow("GrabCut", result);
	cv::imwrite("output/GrabCut-mask.jpg", result);

	// 显示目标图像,需要注意的是结果图像result是二值图像
	cv::Mat object;//(result.rows, result.cols, CV_8UC3, cv::Scalar(0, 0, 0));
	image.copyTo(object);
	cv::Mat_<uchar>::const_iterator it = result.begin<uchar>();
	cv::Mat_<uchar>::const_iterator itend = result.end<uchar>();
	cv::Mat_<cv::Vec3b>::iterator itin = image.begin<cv::Vec3b>();
	cv::Mat_<cv::Vec3b>::iterator itout = object.begin<cv::Vec3b>();
	for (; it != itend; it++, itin++, itout++)
	{
		if ((*it) != 0)
		{
			(*itout)[0] = (*itin)[0];
			(*itout)[1] = (*itin)[1];
			(*itout)[2] = (*itin)[2];
		}
		else
		{
			(*itout)[0] = 0;
			(*itout)[1] = 0;
			(*itout)[2] = 0;
		}
	}

	cv::namedWindow("GrabCutOutput");
	cv::imshow("GrabCutOutput", object);
	cv::imwrite("output/GrabCut-output.jpg", object);
}

// main函数
int main(int argc, char* argv[])
{
    std::string imageFile = "images/cow.jpg";
    cv::Mat result;
    
    // GrabCut
    const int64 start = cv::getTickCount();
    testGrabCut(imageFile, result);
    double duration = (cv::getTickCount() - start) / cv::getTickFrequency();
    
    // 输出函数执行的时间
    std::cout << "wave: " << duration << "s" << std::endl;
    
    return 0;
}

3、验证结果

(1)原图像在这里插入图片描述
(2)前景框

在这里插入图片描述

(3)掩膜在这里插入图片描述
(4)分割结果在这里插入图片描述

3.4 转换颜色表示法

1、基本原理

RGB色彩空间的基础是对加色法三原色(红、绿、蓝)的应用,由于人类视锥细胞对这三原色感知能力的差异,因此,RGB色彩空间并不是感知均匀的色彩空间。为了解决这个问题,引入了CIE Lab*色彩模型,把图像转换到这种表示法后,就可以真正地使用图像像素与目标颜色之间的欧几里得距离,来度量颜色之间的视觉相似度。

2、代码实现

/**
* 文件名:example_colorDetectorLab.cpp
* 日期:2021/03/29
* 作者:南风无良
*/

#include <iostream>
#include <opencv2/opencv.hpp>

class ColorDetector {
private:
	int maxDist; 		// 允许的最小差距
	cv::Vec3b target; 	// 目标颜色
	cv::Mat result; 	// 存储二值映射结果的图像
	cv::Mat converted;	//颜色转换后图像
public:
	ColorDetector() : maxDist(0.0), target(0, 0, 0) {}
	ColorDetector(uchar blue, uchar green, uchar red, int mxDist);
	void setColorDistanceThreshold(int distance);
	int getColorDistanceThreshold() const;
	void setTargetColor(cv::Vec3b color);
	void setTargetColor(uchar blue, uchar green, uchar red);
	cv::Vec3b getTargetColor() const;
	int getDistance2TargetColor(const cv::Vec3b& color) const;
	int getColorDistance(const cv::Vec3b& color, const cv::Vec3b& target) const;
	cv::Mat process(const cv::Mat& image);
	cv::Mat process(const std::string imageFile);
};

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

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

	this->maxDist = distance;
}

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

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

void ColorDetector::setTargetColor(unsigned char red, unsigned char green, unsigned char blue)
{
	cv::Mat tmp(1, 1, CV_8UC3);
	tmp.at<cv::Vec3b>(0, 0) = cv::Vec3b(blue, green, red);	// OpenCV颜色序列是BGR
	cv::cvtColor(tmp, tmp, CV_BGR2Lab);
	this->target = tmp.at<cv::Vec3b>(0,0);
}

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

// 计算与目标颜色之间的距离
int ColorDetector::getDistance2TargetColor(const cv::Vec3b& color) const
{
	return getColorDistance(color, this->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::cvtColor(image, this->converted, CV_BGR2Lab);

	cv::Mat_<cv::Vec3b>::iterator it = this->converted.begin<cv::Vec3b>();
	cv::Mat_<cv::Vec3b>::iterator itend = this->converted.end<cv::Vec3b>();
	cv::Mat_<uchar>::iterator itout = result.begin<uchar>();

	// 处理每一个像素
	for (; it != itend; ++it, ++itout)
	{
		if (this->getDistance2TargetColor(*it) <= this->maxDist)
		{
		*itout = 255;
		}
		else
		{
		*itout = 0;
		}
	}

	return result;
}

cv::Mat ColorDetector::process(const std::string imageFile)
{
	cv::Mat image = cv::imread(imageFile);

	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);

	return image;
}

// main函数
int main(int argc, char* argv[])
{
    // 加载目标图像
    cv::Mat image = cv::imread("images/cow.jpg");
    cv::imshow("Image", image);
    
    std::cout << "cow: " << image.size() << std::endl;
    
    cv::Mat result;
    
    ColorDetector cold(30, 190, 30, 158);
    
    // 颜色比较
    const int64 start = cv::getTickCount();
    result = cold.process(image);
    double duration = (cv::getTickCount() - start) / cv::getTickFrequency();
    
    // 显示结果,并保存
    cv::imshow("colorDetectorLab", result);
    cv::imwrite("output/colorDetectorLab.jpg", result);
    cv::waitKey(0);
    
    std::cout << "wave: " << duration << "s" << std::endl;
    
    return 0;
}

3、验证结果

从原始图像中,可以观察到目标图像的颜色以棕黄色为主,可以适当提高黄色的颜色分量数值。

(1)原始图在这里插入图片描述
(2)效果图在这里插入图片描述

3.5 用色调、饱和度和亮度表示颜色

1、基本原理

引入色调、饱和度和亮度的色彩空间,更加方便地评价图像的品质。其中,色调(Hue)表示主色,我们使用的颜色名称(例如绿色、黄色和红色)就对应了不同的色调值;饱和度(Saturation)表示颜色的鲜艳程度,柔和的颜色饱和度较低,而彩虹的颜色饱和度就很高;最后,亮度(Brightness)是一个主观的属性,表示某种颜色的光亮程度。OpenCV建议的两种直觉色彩空间的实现是HSV和HLS色彩空间。同时,注意感知均匀的Lab和Luv色彩空间。

2、代码实现

/**
* 文件名:example_bgr2hsv.cpp
* 日期:2021/03/29
* 作者:南风无良
*/

#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

// main函数
int main(int argc, char* argv[])
{
    // 加载目标图像
    Mat image = imread("images/cow.jpg");
    
    // 转换色彩空间
    Mat hsv;
    cvtColor(image, hsv, COLOR_BGR2HSV);
    imshow("Image", hsv);
    imwrite("output/hsv.jpg", hsv);
    
    // 分离色彩空间中的三个颜色通道
    vector<Mat> channels;
    split(hsv, channels);
    
    // 色调
    imshow("Value", channels[0]);
    imwrite("output/value.jpg", channels[0]);
    
    // 饱和度
    imshow("Saturation", channels[1]);
    imwrite("output/saturation.jpg", channels[1]);
    
    // 亮度(灰度)
    imshow("Hue", channels[2]);
    imwrite("output/hue.jpg", channels[2]);
    
    // 色调、饱和度组合,亮度不变
    Mat hs(128, 360, CV_8UC3);
    for (int h = 0; h < 360; h++)
    {
    	for (int s = 0; s < 128; s++)
    	{
    		hs.at<Vec3b>(s, h)[0] = h / 2;	// 所有色调角度
    		hs.at<Vec3b>(s, h)[1] = 255 - s * 2;	// 饱和度从高到低
    		hs.at<Vec3b>(s, h)[2] = 255;
    	}
    }
    imshow("Hue/Saturation", hs);
    imwrite("output/hs.jpg", hs);
	// 色彩空间的相互转换
    channels[2] = 255;	// 将所有像素的亮度通道赋值为255
    merge(channels, hsv);
    
    Mat newImage;
    cvtColor(hsv, newImage, COLOR_HSV2BGR);
    imshow("newBGRImage", newImage);
    imwrite("output/newBGRImage.jpg", newImage);
    
    waitKey(0);
    
    return 0;
}

3、验证结果

(1)原图像在这里插入图片描述
(2)BGR2HSV在这里插入图片描述
(3)色调在这里插入图片描述
(4)饱和度在这里插入图片描述
(5)亮度在这里插入图片描述
(6)色调、饱和度组合,亮度不变在这里插入图片描述
(7)色彩空间的相互转换在这里插入图片描述

4、扩展

(1)实现过程
/**
* 文件名:example_skin.cpp
* 日期:2021/03/29
* 作者:南风无良
*/

#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

void detectHScolor(const Mat& image, double minHue, double maxHue, double minSat, double maxSat, Mat& mask);

// main函数
int main(int argc, char* argv[])
{
    // 加载目标图像
    Mat image = imread("images/cow.jpg");
    imshow("image", image);
    
    // 检测皮肤
    Mat mask;
    detectHScolor(image, 170, 10, 35, 166, mask);
    
    Mat detected(image.size(), CV_8UC3, Scalar(0, 0, 0));
    image.copyTo(detected, mask);
    imshow("detected", detected);
    imwrite("output/skin.jpg", detected);
    
    waitKey(0);
    
    return 0;
}

void detectHScolor(const Mat& image, double minHue, double maxHue, double minSat, double maxSat, Mat& mask)
{
	// 转换至HSV空间
	Mat hsv;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	
	vector<Mat> channels;
	split(hsv, channels);
	
	// 构造色调掩膜
	Mat mask1;
	threshold(channels[0], mask1, maxHue, 255, THRESH_BINARY_INV);
	
	Mat mask2;
	threshold(channels[0], mask2, minHue, 255, THRESH_BINARY);
	
	Mat hueMask;
	if (minHue < maxHue)
	{
		hueMask = mask1 & mask2;
	}
	else
	{
		hueMask = mask1 | mask2;
	}
	
	// 构造饱和度掩膜
	Mat satMask;
	inRange(channels[1], minSat, maxSat, satMask);
	mask = hueMask & satMask;
}
(2)结果图像

需要不断调节阈值,以期获取图像效果最优的结果图。
在这里插入图片描述

引用

[1]《OpenCV计算机视觉编程攻略(第3版)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南风无良

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值