目录
第5章 颜色检测
5.1 实现原理
5.2 Lab颜色模型
5.3 cv :: threshold() 阈值函数
5.4 计算图像之间的距离
5.4.1 公式计算
5.4.2 cv::absdiff()
5.4.3 cv::floodFill()
5.5 完整代码
Github代码地址:GitHub - Qinong/OpenCV
第5章 颜色检测
颜色检测用来识别图像中所有像素的某种颜色。这个算法必领输人一幅图像和一个颜色,并且返回一个二值图像,显示具有指定颜色的像素。在运行算法前,还要指定一个阈值,即能接受的颜色的公差。
5.1 实现原理
算法的核心对每个像素进行循环扫描,把像素颜色和目标颜色做比较。可以这样写这个循环:
cv::Mat ColorDetector::process(const cv::Mat &image) {
result.create(image.size(),CV_8U);
// 转换成Lab色彩空间
if (useLab)
cv::cvtColor(image, converted, CV_BGR2Lab);
// 获取迭代器
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>();
// 获取转换图像的迭代器
if (useLab) {
it = converted.begin<cv::Vec3b>();
itend = converted.end<cv::Vec3b>();
}
// 遍历每个像素
for ( ; it!= itend; ++it, ++itout) {
// 通过像素与阈值比较
// getDistanceToTargetColor()见完整代码
if (getDistanceToTargetColor(*it)<maxDist) {
*itout= 255;
}
else {
*itout= 0;
}
}
return result;
}
要先创建迭代器实现扫描循环,在每个迭代步骤中计算当前像素的颜色与目标颜色的距离,检查它是否在公差(maxDist )范围之内。如果是,就在输出图像中赋值 255( 白色),否则就赋值 0( 黑色)。
5.2 Lab颜色模型
Lab颜色模型弥补了RGB和CMYK两种色彩模式的不足。它是一种设备无关的颜色模型,也是一种基于生理特征的颜色模型。 Lab颜色模型由三个要素组成,一个要素是亮度(L),a 和b是两个颜色通道。a包括的颜色是从深绿色(低亮度值)到灰色(中亮度值)再到亮粉红色(高亮度值);b是从亮蓝色(低亮度值)到灰色(中亮度值)再到黄色(高亮度值)。因此,这种颜色混合后将产生具有明亮效果的色彩。
Lab模式既不依赖光线,也不依赖于颜料,它是CIE组织确定的一个理论上包括了人眼可以看见的所有色彩的色彩模式。Lab模式弥补了RGB和CMYK两种色彩模式的不足。同RGB颜色空间相比,Lab是一种不常用的色彩空间。它是在1931年国际照明委员会(CIE)制定的颜色度量国际标准的基础上建立起来的。
Lab颜色空间中的取值范围:
- L分量用于表示像素的亮度,取值范围是[0,100],表示从纯黑到纯白;
- a表示从红色到绿色的范围,取值范围是[127,-128];
- b表示从黄色到蓝色的范围,取值范围是[127,-128]。
下图所示为Lab颜色空间的图示:
5.3 cv :: threshold() 阈值函数
cv::threshold()函数创建一个二值图像。这个函数通常用于将所有像素与某个阈值(第三个参数)进行比较,在常规阈值化模式 (CV::THRESH_BINARY)下,将所有大于指定阈值的像素赋值为预定的最大值(第四个参数),将其他像素赋值为 0。
double cv::threshold(
cv::InputArray src,
cv::OutputArray dst,
double thresh,
double maxValue,
int thresholdType
);
- src: 多通道的输入图像,8bits或者32bits float
- dst: 与src的同尺寸、类型、通道数的输出图像
- thresh: 阈值(介于最大值和最小值之间的一个值)
- maxval: 与两种阈值类型(THRESH_BINARY & THRESH_BINARY_INV)结合使用,用于指定最大的值
- type: 阈值的类型
阈值的类型 | 作用 |
THRESH_BINARY | 最常见的作法,设置一个阈值,大于的时候取最大值(maxval是threshold的参数),否则取0。 |
THRESH_BINARY_INV | 与THRESH_BINARY相反,大于阈值取0,否则取最大值,maxval对于THRESH_BINARY和THRESH_BINARY_INV有意义。 |
THRESH_TRUNC | 截断型,大于阈值的统一将为阈值,其余不变。 |
THRESH_TOZERO | 过滤型,大于阈值的保持不变,其余设置为0。 |
THRESH_TOZERO_INV | 大于阈值0,其他不变。 |
THRESH_MASK | |
THRESH_OTSU | 大津阈值 |
THRESH_TRIANGLE | 矩形阈值 |
5.4 计算图像之间的距离
5.4.1 公式计算
要计算两个颜色向量间的距离,可使用这个简单的公式:
return abs (color [0]-target [0])+
abs (color [1]-target [1])+
abs (color [2]-target [2]);
(1)公式计算颜色检测
// 计算两个颜色之间的城区距离
int 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]);
}
5.4.2 cv::absdiff()
计算两个数组(图像)差的绝对值: dst(I)c = abs(src1(I) - src2(I)c),所有数组(图像)必须有相同的数据类型、相同的大小(或ROI大小)。
void cv::absdiff( const CvArr* src1, const CvArr* src2, CvArr* dst );
- src1:第一个原数组
- src2:第二个原数组
- dst:输出数组
5.4.3 cv::floodFill()
cv::floodFill()在判断一个像素时,要检查附近像素的状态,这是为了识别某种颜色的相关区域。用户只需指定一起始位置和允许的误差,就可以找出颜色接近的连续区域。
// 构造函数1
int floodFill(InputArray image,
Point seedPoint,
Scalar newVal,
Rect* rect=0,
Scalar ioDiff=Scalar(),
Scalar upDiff=Scalar(),
int flags=4)
// 构造函数2
int floodFill(InputArray image,
InputArray mask,
Point seedPoint,
Scalar newVal,
Rect* rect=0,
Scalar ioDiff=Scalar(),
Scalar upDiff=Scalar(),
int flags=4)
- image:输入图像,1个或3个通道的8位或浮点图像。
- mask:只有第二个版本才有该参数,表示作为掩膜。它应该是单通道,8位,长和宽都比输入图像大两个点的图像。因为漫水填充需要使用以及更细掩膜,所以对这个mask参数,我们一定要将其准本好并填在此处。需要注意的是,漫水填充不会填充mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。需要注意的是,mask会比需填充的图像大,所以mask与输入图像(x, y)像素点对应的坐标为(x,+1 y+1)。
- seedPoint:漫水填充算法的起始点。
- newVal:像素被染色的值。
- rect:默认值是0,可选参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。
- loDiff:默认值是Scalar(),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差的最大值。
- upDiff:默认值是Scalar(),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差的最大值。
- flags:操作标志符。
(1) cv::absdiff()检测颜色
// 使用cv::floodFill函数
ColorDetector colordetector(230, 190, 130, 45, true); // 第3个构造函数
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); // 与起始点像素比较
cv::namedWindow("Image2");
cv::Mat image2 = colordetector(image);
cv::imshow("Image2", image2);
cv::waitKey();
5.5 完整代码
/*
colordetector.h头文件
*/
#if !defined COLORDETECT
#define COLORDETECT
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class ColorDetector {
private:
int maxDist; // 允许的最小差距
cv::Vec3b target; // 目标颜色
cv::Mat converted; // 转换的图像
bool useLab; // 是否使用Lab色彩空间
cv::Mat result; // 存储二值映射结果的图像
public:
// 不同的构造函数
// 在此初始化默认参数
ColorDetector() : maxDist(100), target(0,0,0), useLab(false) {}
ColorDetector(bool useLab) : maxDist(100), target(0,0,0), useLab(useLab) {}
ColorDetector(uchar blue, uchar green, uchar red, int mxDist=100, bool useLab=false): maxDist(mxDist), useLab(useLab) {
// 设置需要检测的颜色
setTargetColor(blue, green, red);
}
// 计算与目标颜色的距离
int getDistanceToTargetColor(const cv::Vec3b& color) const {
return getColorDistance(color, target);
}
// 计算两个颜色之间的城区距离
int 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 process(const cv::Mat &image);
cv::Mat operator()(const cv::Mat &image) {
cv::Mat input;
if (useLab) {
cv::cvtColor(image, input, CV_BGR2Lab); // 转换成Lab色彩空间
}
else {
input = image;
}
cv::Mat output;
cv::absdiff(input,cv::Scalar(target),output); // 计算目标图像的绝对距离
std::vector<cv::Mat> images;
cv::split(output,images); // 分割图像的通道
output= images[0]+images[1]+images[2]; // 合并3个通道
// 设定阈值
cv::threshold(output, // 输入图像
output, // 输出图像
maxDist, // 阈值
255, // 最大值
cv::THRESH_BINARY_INV); // 阈值类型
return output;
}
//设置颜色距离的阈值
void setColorDistanceThreshold(int distance) {
if (distance<0)
distance=0;
maxDist= distance;
}
// 获取颜色距离的阈值
int getColorDistanceThreshold() const {
return maxDist;
}
// 设置需要检测的颜色
void setTargetColor(uchar blue, uchar green, uchar red) {
// BGR的次序
target = cv::Vec3b(blue, green, red);
if (useLab) {
// 创建临时的单像素图像
cv::Mat tmp(1, 1, CV_8UC3);
tmp.at<cv::Vec3b>(0, 0) = cv::Vec3b(blue, green, red);
// 转换成Lab色彩空间
cv::cvtColor(tmp, tmp, CV_BGR2Lab);
target = tmp.at<cv::Vec3b>(0, 0);
}
}
// 设置需要检测的颜色
void setTargetColor(cv::Vec3b color) {
target= color;
}
// 获取需要检测的颜色
cv::Vec3b getTargetColor() const {
return target;
}
};
#endif
/*
颜色检测处理函数
*/
#include "colordetector.h"
#include <vector>
cv::Mat ColorDetector::process(const cv::Mat &image) {
result.create(image.size(),CV_8U);
// 转换成Lab色彩空间
if (useLab)
cv::cvtColor(image, converted, CV_BGR2Lab);
// 获取迭代器
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>();
// 获取转换图像的迭代器
if (useLab) {
it = converted.begin<cv::Vec3b>();
itend = converted.end<cv::Vec3b>();
}
// 遍历每个像素
for ( ; it!= itend; ++it, ++itout) {
// 通过像素与阈值比较
if (getDistanceToTargetColor(*it)<maxDist) {
*itout= 255;
}
else {
*itout= 0;
}
}
return result;
}
/*
主函数
*/
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "colordetector.h"
int main()
{
// step1
// 创建检测图像的对象
ColorDetector cdetect;
// step2
// 读取原图像
cv::Mat image= cv::imread("Ferrar_F8.png");
if (image.empty())
return 0;
cv::namedWindow("Image");
cv::imshow("Image", image);
// step3
// 设置输入参数与输出图像处理效果
cdetect.setTargetColor(230,190,130);
cv::namedWindow("Image1");
cv::Mat image1 = cdetect.process(image);
cv::imshow("Image1",image1);
// 使用cv::floodFill函数
ColorDetector colordetector(230, 190, 130, 45, true); // 第3个构造函数
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); // 与起始点像素比较
cv::namedWindow("Image2");
cv::Mat image2 = colordetector(image);
cv::imshow("Image2", image2);
cv::waitKey();
return 0;
}