OpenCV转换色彩空间

444 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

前言

RGB 颜色空间使用红色、绿色和蓝色加色原色。选择这些颜色是因为当它们组合在一起时,可以产生不同颜色的宽色域。事实上,人类视觉系统也是基于对颜色的三原色感知,视锥细胞的敏感性位于红、绿、蓝光谱周围。RGB 颜色空间通常是数字图像中的默认色彩空间,因为这正是获取数字图像的方式(捕获的光通过红色、绿色和蓝色滤光片)。此外,在数字图像中,红色、绿色和蓝色通道会被调整,以便在等量组合时获得灰度级强度,即从黑色 (0,0,0) 到白色 (255,255,255)

但是,使用 RGB 颜色空间计算颜色之间的距离并不是衡量两种给定颜色之间相似性的最佳方法,这是由于 RGB 并不是一个感知上均匀的色彩空间。这意味着在给定距离处的两种颜色可能看起来非常相似,而具有相同距离的其他两种颜色可能看起来非常不同。

为了解决这个问题,引入了具有感知均匀特性的其他颜色表示,例如,CIE L*a*b* 颜色空间就是这样一种颜色模型。通过将图像转换为这种表示,图像像素和目标颜色之间的欧几里德距离将成为两种颜色之间视觉相似性的度量。在本节中,我们将学习如何将图像颜色转换到 CIE L*a*b* 色彩空间。

转换色彩空间

通过使用 cv::cvtColor 函数可以轻松完成不同颜色空间之间的图像转换。

1. 首先,在 process 方法开始时将输入图像转换为 CIE L*a*b* 颜色空间:

cv::Mat ColorDetector::process(const cv::Mat& image) {
    result.create(image.size(), CV_8U);
    // 转换色彩空间
    cv::cvtColor(image, converted, cv::COLOR_BGR2Lab);
    // 迭代器
    cv::Mat_<cv::Vec3b>::const_iterator it = converted.begin<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::const_iterator itend = converted.end<cv::Vec3b>();
    cv::Mat_<uchar>::iterator itout = result.begin<uchar>();
    for (; it != itend; ++it, ++itout) {
        // 像素处理
        ...
    }
    return result;
}

2. converted 变量包含颜色转换后的图像。在 ColorDetector 类中,它被定义为一个类属性:

class ColorDetector {
    private:
        // 转换色彩后的图像
        cv::Mat converted;

3. 我们还需要转换输入的目标颜色,可以通过创建仅包含一个像素的临时图像来实现此目的:

// 设置待检测颜色
void setTargetColor(uchar blue, uchar green, uchar red) {
    target = cv::Vec3b(blue, green, red);
    cv::Mat tmp(1, 1, CV_8UC3);
    tmp.at<cv::Vec3b>(0, 0) = cv::Vec3b(blue, green, red);
    // 将目标颜色转换到 Lab 色彩空间
    cv::cvtColor(tmp, tmp, cv::COLOR_BGR2Lab);
    target = tmp.at<cv::Vec3b>(0, 0);
    }
}

当图像从一种颜色空间转换到另一种颜色空间时,会对每个输入像素应用线性或非线性变换以产生输出像素,输出图像的像素类型与其中一个输入图像相同。即使我们大部分时间使用 8 位像素,我们也可以对浮点图像(像素值通常在 01.0 之间变化)或整数图像(像素通常在 065535 之间变化)执行转换。但是,像素值的确切域取决于特定的色彩空间和目标图像类型。例如,在 CIE L*a*b* 颜色空间中,代表每个像素亮度的 L 通道在 0100 之间变化,在 8 位图像的情况下,它会重新缩放至 0255 之间。ab 通道对应于色度分量,这些通道包含有关像素颜色的信息,与其亮度无关,它们的值在 -127127 之间变化;对于 8 位图像,每个值都加上 128,以使其缩放到 0255 区间内。但是,颜色转换会引入舍入误差,因此转换不完全可逆。