用策略设计模式比较颜色 - sumpig/OpenCV GitHub Wiki
假设我们要构建一个简单的算法,用来识别图像中具有某种颜色的所有像素。这个算法必须输入一幅图像和一个颜色,并且返回一个二值图像,显示具有指定颜色的像素。在运行算法前,还要指定一个参数,即能接受的颜色的公差。
这个算法的核心过程非常简单,只是对每个像素进行循环扫描,把它的颜色和目标颜色做比较。可以这样写这个循环:
// 取得迭代器
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(getDistanceToTargetColor(*it) <= maxDist)
*itout = 255;
else
*itout = 0;
}
}
cv::Mat 类型的变量 image 表示输入图像,result 表示输出的二值图像。
在每个迭代步骤中计算当前像素的颜色与目标颜色的差距,检查它是否在公差(maxDist)范围之内。如果是,就在输出图像中赋值255(白色),否则就赋值0(黑色)。
为了增加灵活性,我们依据 getColorDistance 方法来编写 getDistanceToTargetColor 方法:
// 计算与目标颜色的差距
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 ColorDetector::process(const cv::Mat &image) {
// 必要时重新分配二值映像
// 与输入图像的尺寸相同,不过是单通道
result.create(image.size(),CV_8U);
// 在这里放前面的处理循环
...
return result;
}
OpenCV 中也有计算向量的欧几里得范数的函数,因此也可以这样计算距离:
static_cast<int>(cv::norm<int,3>(
cv::Vec3i(color[0]-target[0],
color[1]-target[1],
color[2]-target[2])));
检测颜色的方法还可以这样写:
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 函数计算图像的像素与标量值之间差距的绝对值。该函数的第二个参数也可以不用标量值,而是改用另一幅图像,这样就可以逐个像素地计算差距。因此两幅图像的尺寸必须相同。
用cv::threshold 函数创建一个二值图像。这个函数通常用于将所有像素与某个阈值(第三个参数)进行比较,并且在常规阈值化模式(cv::THRESH_BINARY)下,将所有大于指定阈值的像素赋值为预定的最大值(第四个参数),将其他像素赋值为0。
这里使用相反的模式(cv::THRESH_BINARY_INV)把小于或等于阈值的像素赋值为预定的最大值。此外还有cv::THRESH_TOZERO 和cv::THRESH_TOZERO_INV 模式,它们使大于或小于阈值的像素保持不变。
cv::floodFill 函数在判断一个像素时,还要检查附近像素的状态,这是为了识别某种颜色的相关区域。用户只需指定一个起始位置和允许的误差,就可以找出颜色接近的连续区域。
例如要从图中提取出蓝天,可以执行以下语句:
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); // 与起始点像素比较
图像中亚像素(100, 50)所处的位置是天空。函数会检查所有的相邻像素,颜色接近的像素会被重绘成第三个参数指定的新颜色。为了判断颜色是否接近,需要分别定义比参考色更高或更低的值作为阈值。这里使用固定范围模式,即所有像素都与亚像素的颜色进行对比,默认模式是将每个像素与和它邻近的像素进行对比。
这种算法重绘了一个独立的连续区域(这里是把天空画成白色)。即使其他地方有颜色接近的像素(例如水面),除非它们与天空相连,否则也不会被识别出来。