在对特定物体做初步检测时,颜色信息非常有用。例如辅助驾驶程序中的路标检测功能,就要凭借标准路标的颜色快速识别可能是路标的信息。另一个例子是肤色检测,检测到的皮肤区域可作为图像中有人存在的标志。手势识别就经常使用肤色检测确定手的位置。
通常来说,为了用颜色来检测目标,首先需要收集一个存储有大量图像样本的数据库,每个样本包含从不同观察条件下捕捉到的目标,作为定义分类器的参数。你还需要选择一种用于分类的颜色表示法。肤色检测领域的大量研究已经表明,来自不同人种的人群的皮肤颜色,可以在色调-饱和度色彩空间中很好地归类。因此,在后面的图像中,我们将只使用色调和饱和度值来识别肤色。
我们定义了一个基于数值区间(最小和最大色调、最小和最大饱和度)的函数,把图像中的像素分为皮肤和非皮肤两类:
void detectHScolor(const cv::Mat& image, // 输入图像
double minHue, double maxHue, // 色调区间
double minSat, double maxSat, // 饱和度区间
cv::Mat& mask) { // 输出掩码
// 转换到 HSV 空间
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);
// 将 3 个通道分割到 3 幅图像
std::vector<cv::Mat> channels;
cv::split(hsv, channels);
// channels[0]是色调
// channels[1]是饱和度
// channels[2]是亮度
// 色调掩码
cv::Mat mask1; // 小于 maxHue
cv::threshold(channels[0], mask1, maxHue, 255,
cv::THRESH_BINARY_INV);
cv::Mat mask2; // 大于 minHue
cv::threshold(channels[0], mask2, minHue, 255, cv::THRESH_BINARY);
cv::Mat hueMask; // 色调掩码
if (minHue < maxHue)
hueMask = mask1 & mask2;
else // 如果区间穿越 0 度中轴线
hueMask = mask1 | mask2;
// 饱和度掩码
// 从 minSat 到 maxSat
cv::Mat satMask; // 饱和度掩码
cv::inRange(channels[1], minSat, maxSat, satMask);
// 组合掩码
mask = hueMask & satMask;
}
如果在处理时有了大量的皮肤(以及非皮肤)样本,我们就可以使用概率方法估算在皮肤样本中和非皮肤样本中发现指定颜色的可能性。此处,我们依据经验定义了一个合理的色调-饱和度区间,用于这里的测试图像(记住,8 位版本的色调在 0~180,饱和度在 0~255):
// 检测肤色
cv::Mat mask;
detectHScolor(image, 160, 10, // 色调为 320 度~20 度
25, 166, // 饱和度为~0.1~0.65
mask);
// 显示使用掩码后的图像
cv::Mat detected(image.size(), CV_8UC3, cv::Scalar(0, 0, 0));
image.copyTo(detected, mask);