opencv(C++)处理图像颜色

文章目录

    • 介绍
    • 使用策略设计模式比较颜色
      • 实现方案
      • 计算两个颜色向量之间的距离
        • 1. 简单方法:曼哈顿距离计算(Manhattan Distance)
        • 2.使用 OpenCV 的 cv::norm 函数
        • 3.使用 OpenCV 的 cv::absdiff 函数
          • 错误示例
      • 使用 OpenCV 函数实现颜色检测
        • 实现方案
          • 阈值处理:cv::threshold
            • cv::THRESH_BINARY_INV 模式:
            • 其他有用的模式
      • 使用 cv::floodFill 函数
        • cv::floodFill 的工作原理
        • 实现方案
        • 应用场景
      • 使用函数对象
        • 实现方案
        • 适用场景
      • OpenCV 的算法基类:cv::Algorithm
        • cv::Algorithm 的核心功能
        • cv::Algorithm 的多态性
        • 优点
        • 适用场景
    • 使用GrabCut算法分割图像
      • 图像分割实现步骤
      • 实现方案
    • 转换颜色
      • RGB 颜色空间
      • RGB 的局限性
      • CIE Lab* 颜色空间
      • 颜色转换的步骤
      • 其他常用颜色空间转换
      • 工作原理
    • 用色调、饱和度和亮度表示颜色
      • 颜色空间转换及可视化HSV各分量
        • HSV颜色空间简介
          • 色调 (Hue)
          • 饱和度 (Saturation)
          • 亮度 (Value)
        • OpenCV 中的 HSV 实现
          • 生成 HSV 色彩表
          • 修改图像亮度
        • 将BGR图像转换为HSV颜色空间
        • 从HSV转回BGR颜色空间
        • 分离并可视化HSV各通道
      • 实现案例
      • 使用颜色进行肤色检测
        • 案例实现

介绍

  • 使用策略设计模式比较颜色
  • 使用GrabCut算法分割图像
  • 转换颜色表示法
  • 使用色调、饱和度和亮度表示颜色

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

假设需要构建一个简单的算法,用于识别图像中所有具有指定颜色的像素。为实现这一目标,该算法需要接受一张图像和一种颜色作为输入,并返回一张二值图像,显示哪些像素具有指定的颜色。此外,还需要在运行算法之前指定一个容差值(tolerance),以决定接受某种颜色的宽松程度。

为了实现这个目标,将使用策略设计模式(Strategy Design Pattern)。这是一种面向对象的设计模式,非常适合将算法封装到类中。通过这种方式,可以轻松替换某个算法,或将多个算法串联在一起以构建更复杂的过程。此外,这种模式通过隐藏尽可能多的复杂性,为算法提供了一个直观的编程接口,从而简化了算法的部署和使用。

实现方案

#include <opencv2/opencv.hpp>
#include <iostream>class ColorDetector 
{
public:void setTargetColor(int r, int g, int b);     // 设置目标颜色void setColorDistanceThreshold(int distance);  // 设置颜色距离阈值cv::Mat process(const cv::Mat &image);     // 处理图像并返回结果private:bool matchColor(const cv::Vec3b& pixel) const;  // 比较颜色private:cv::Vec3b targetColor;  // 目标颜色int tolerance{30};     // 容差,默认值
};void ColorDetector::setTargetColor(int r, int g, int b) 
{targetColor = cv::Vec3b(b, g, r);  // 注意OpenCV中的颜色顺序是BGR
}void setColorDistanceThreshold(int distance) {if (distance < 0) distance = 0;tolerance= distance;}bool ColorDetector::matchColor(const cv::Vec3b& pixel) const 
{return std::abs(pixel[0] - targetColor[0]) <= tolerance &&std::abs(pixel[1] - targetColor[1]) <= tolerance &&std::abs(pixel[2] - targetColor[2]) <= tolerance;
}cv::Mat ColorDetector::process(const cv::Mat &image) 
{cv::Mat result(image.size(), CV_8UC1);  // 输出的二值图像for (int i = 0; i < image.rows; ++i) {for (int j = 0; j < image.cols; ++j) {if (matchColor(image.at<cv::Vec3b>(i, j))) {result.at<uchar>(i, j) = 255;  // 白色表示匹配} else {result.at<uchar>(i, j) = 0;   // 黑色表示不匹配}}}return result;
}
#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main() 
{// 1. 创建图像处理器对象ColorDetector cdetect;// 2. 读取输入图像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) return 0;// 3. 设置输入参数cdetect.setTargetColor(230, 190, 130);  // 这里假设要检测蓝色天空// 4. 处理图像并显示结果cv::namedWindow("result");cv::Mat result = cdetect.process(image);cv::imshow("result", result);cv::imshow("image", image);cv::waitKey();return 0;
}

在这里插入图片描述

OpenCV提供了一些内置函数可以完成类似的任务,例如提取具有特定颜色的连通区域。此外,策略设计模式的实现还可以通过函数对象进一步完善。最后,OpenCV定义了一个基类 cv::Algorithm,它实现了策略设计模式的核心概念。

计算两个颜色向量之间的距离

1. 简单方法:曼哈顿距离计算(Manhattan Distance)

计算方式是将每个颜色通道的绝对差值相加。
优点是实现简单且计算效率高,但在某些情况下可能无法准确反映颜色之间的感知差异。

return abs(color[0] - target[0]) +abs(color[1] - target[1]) +abs(color[2] - target[2]);
2.使用 OpenCV 的 cv::norm 函数

OpenCV 提供了一个名为 cv::norm 的函数,可以用来计算向量的欧几里得范数(Euclidean Norm)。我们可以使用它来计算颜色向量之间的距离:

return static_cast<int>(cv::norm(cv::Vec3i(color[0] - target[0],color[1] - target[1],color[2] - target[2])));

这里使用了 cv::Vec3i(一个包含三个整数值的向量),因为颜色通道相减的结果可能是负数,而 cv::Vec3b 是无符号类型,无法正确表示负数
通过这种方式,可以获得更精确的结果。需要注意的是,cv::norm 默认计算的是欧几里得距离。

3.使用 OpenCV 的 cv::absdiff 函数
错误示例
return static_cast<int>(cv::norm<uchar, 3>(color - target)); // 错误!

OpenCV 的算术运算符会调用 saturate_cast 函数来确保结果始终在输入类型的范围内(在这里是 uchar 类型)。如果目标值大于对应的颜色值,结果会被截断为 0,而不是预期的负值。这会导致距离计算不准确。

为了避免上述问题,可以使用 OpenCV 提供的 cv::absdiff 函数来计算两个向量之间的绝对差值:

// cv::absdiff 会逐元素地计算两个向量之间的绝对差值。
// cv::sum 函数会对结果向量的所有元素求和,返回一个标量值。
cv::Vec3b dist;
cv::absdiff(color, target, dist);
return cv::sum(dist)[0];

这种方法可以正确计算距离,但它需要两次函数调用(cv::absdiff 和 cv::sum),因此效率较低。

尽管 cv::absdiff 和 cv::sum 提供了一种正确且通用的解决方案,但对于大规模图像处理任务来说,效率可能是一个问题。在这种情况下,手动实现距离计算通常是更好的选择(使用方法1)

使用 OpenCV 函数实现颜色检测

上文展示了一种使用 OpenCV 函数实现颜色检测的方法。与手动编写循环相比,这种方法可以更快速地构建复杂的应用程序,并且通常具有更高的效率(得益于 OpenCV 的优化)。需要注意的是,当涉及多个中间步骤时,可能会消耗更多的内存。

实现方案
cv::Mat ColorDetector::process(const cv::Mat &image) 
{cv::Mat output;// 1. 计算图像与目标颜色之间的绝对差值cv::absdiff(image, cv::Scalar(target), output);// 2. 将通道分离为三个独立的图像// 将 RGB 图像的三个通道分离出来,以便后续对每个通道单独处理std::vector<cv::Mat> images;cv::split(output, images);// 3. 将三个通道相加(可能发生饱和)// OpenCV 默认会对结果应用饱和操作(saturate_cast),因此如果某个像素的总和超过 255,它会被截断为 255。output = images[0] + images[1] + images[2];// 4. 应用阈值处理生成二值图像cv::threshold(output,               // 输入/输出图像output,               // 输出图像maxDist,              // 阈值(必须小于 256)255,                  // 最大值cv::THRESH_BINARY_INV // 阈值模式);return output;
}
阈值处理:cv::threshold
cv::THRESH_BINARY_INV 模式:
  • 像素值 ≤ 阈值时,设置为 255(白色)。
  • 像素值 > 阈值时,设置为 0(黑色)。
其他有用的模式
  • cv::THRESH_TOZERO:保留像素值大于阈值的部分,其余部分设为 0。
  • cv::THRESH_TOZERO_INV:保留像素值小于或等于阈值的部分,其余部分设为 0。

使用 cv::floodFill 函数

上述案例中,使用了逐像素比较的方式来识别图像中颜色接近目标颜色的像素。
OpenCV 提供了一个功能类似的函数 cv::floodFill。cv::floodFill 的核心思想是基于连通区域进行颜色提取,而不仅仅是逐像素判断。

cv::floodFill 的工作原理

1.种子点(Seed Point)

  • 需要指定一个起始像素位置(称为种子点),该点的颜色将作为参考颜色。
  • 从种子点开始,算法会检查其邻居像素,并根据容差参数决定是否接受这些邻居像素。

2.连通性(Connectivity)

  • 如果某个邻居像素被接受,则继续检查该像素的邻居,依此类推。
  • 这样,算法可以提取出一个连通的颜色区域。

3.颜色容差(Tolerance Parameters)

  • 容差参数决定了颜色相似度的标准。用户可以为高于和低于参考颜色的值分别设置不同的阈值。
  • 在固定范围模式(cv::FLOODFILL_FIXED_RANGE)下,所有测试的像素都会与种子点的颜色进行比较。
  • 在默认模式下,每个测试像素会与其邻居的颜色进行比较。

4.重新着色(Repainting)

  • 被接受的像素会被重新着色为指定的颜色(通过第三个参数指定)。
实现方案
cv::Mat image = cv::imread("image.jpg");// 使用 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);  // 固定范围模式// cv::FLOODFILL_FIXED_RANGE:表示所有测试的像素都与种子点的颜色进行比较。// 默认模式(未指定 cv::FLOODFILL_FIXED_RANGE):表示每个测试像素与邻居的颜色进行比较
#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main() 
{cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) return 0;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::imshow("image", image);cv::waitKey();return 0;
}

在这里插入图片描述
算法会从种子点开始,提取出一个连通的蓝色区域,并将其重新着色为白色。即使图像中其他地方存在颜色相似的像素,只要它们没有区域连通,就不会被识别。

应用场景
  • 连通区域提取: 提取图像中特定颜色的连通区域(如天空、水面等)。
  • 对象分割:将图像中的某个对象(如前景或背景)分割出来。
  • 图像修复:填充图像中的小区域或孔洞。
  • 交互式图像编辑:用户可以通过点击图像中的某个点来选择并操作特定区域。

使用函数对象

在 C++ 中,通过重载 operator() 运算符,可以创建一个类,使其实例像函数一样被调用。这种技术被称为函数对象(Functor) 。
优点是它们结合了函数的简洁性和类的状态管理能力,因此非常适合用于需要保存状态的算法。

实现方案
class ColorDetector 
{
public:// 构造函数ColorDetector(uchar blue, uchar green, uchar red, int maxDist = 100): maxDist(maxDist) {setTargetColor(blue, green, red);}// 设置目标颜色void setTargetColor(uchar blue, uchar green, uchar red) {targetColor = cv::Vec3b(blue, green, red);}// 获取最大距离int getMaxDist() const {return maxDist;}// 设置最大距离void setMaxDist(int dist) {maxDist = dist;}// 重载 operator(),实现函数对象行为cv::Mat operator()(const cv::Mat &image) {cv::Mat output;// 计算绝对差值cv::absdiff(image, cv::Scalar(targetColor), output);// 分离通道并相加std::vector<cv::Mat> channels;cv::split(output, channels);output = channels[0] + channels[1] + channels[2];// 应用阈值处理cv::threshold(output, output, maxDist, 255, cv::THRESH_BINARY_INV);return output;}private:cv::Vec3b targetColor; // 目标颜色int maxDist;           // 最大距离(容差)
};
// 创建 ColorDetector 实例,并初始化目标颜色和容差
ColorDetector colordetector(230, 190, 130, 100);// 使用函数对象检测颜色
cv::Mat image = cv::imread("image.jpg");
cv::Mat result = colordetector(image); // 像调用函数一样调用对象
适用场景
  • 需要保存状态的算法。
  • 动态配置参数的场景。
  • 与 STL 算法结合使用的场景。

OpenCV 的算法基类:cv::Algorithm

cv::Algorithm 的核心功能

1. 动态创建算法实例
所有继承自 cv::Algorithm 的算法都可以通过静态方法动态创建。
确保算法在创建时总是处于有效状态(即未指定的参数会被赋予默认值)。
例如,对于 cv::ORB(一种用于检测兴趣点的算法),可以通过以下方式创建实例:

cv::Ptr<cv::ORB> ptrORB = cv::ORB::create(); // 默认状态

2. 读取和写入算法状态
cv::Algorithm 提供了通用的 read 和 write 方法,用于加载或存储算法的状态。
这使得算法可以轻松地保存到文件中,并在需要时重新加载。

cv::FileStorage fs("orb_state.yml", cv::FileStorage::WRITE);
ptrORB->write(fs); // 将算法状态写入文件
fs.release();cv::Ptr<cv::ORB> newORB = cv::ORB::create();
cv::FileStorage fs2("orb_state.yml", cv::FileStorage::READ);
newORB->read(fs2.root()); // 从文件中读取算法状态
fs2.release();

3.专用方法
每个算法都有其特定的功能方法。例如,cv::ORB 提供了 detect 和 compute 方法,用于检测特征点并计算描述符。

std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
ptrORB->detectAndCompute(image, cv::Mat(), keypoints, descriptors);

这些方法是算法的核心计算单元,用户可以直接调用它们来执行特定任务。
4.参数设置与获取
算法通常包含多个内部参数,用户可以通过专用的 setter 和 getter 方法来调整这些参数。

ptrORB->setMaxFeatures(500); // 设置最大特征点数量
int maxFeatures = ptrORB->getMaxFeatures(); // 获取当前设置
cv::Algorithm 的多态性

如果将算法实例声明为 cv::Ptrcv::Algorithm 类型,则无法直接调用其专用方法(如 detect 或 compute)。这是因为 cv::Algorithm 是一个通用基类,它并不知道子类的具体方法。

cv::Ptr<cv::Algorithm> algo = cv::ORB::create();
// algo->detect(...) // 错误!detect 方法不可用

使用具体的子类类型(如 cv::Ptrcv::ORB)来声明指针,以便访问其专用方法。

cv::Ptr<cv::ORB> ptrORB = cv::ORB::create();
ptrORB->detectAndCompute(image, cv::Mat(), keypoints, descriptors); // 正确!
优点
  • 统一的接口,便于管理和使用。
  • 动态创建机制确保算法始终处于有效状态。
  • 支持灵活的参数调整和状态管理。
  • 易于扩展,适合开发新的算法。
适用场景
  • 需要动态加载或保存算法状态的场景。
  • 需要灵活调整算法参数的任务。
  • 开发新的算法并希望与 OpenCV 现有框架无缝集成。

使用GrabCut算法分割图像

GrabCut 是一种流行的图像分割算法,特别适用于从静态图像中提取前景对象(例如,从一张图片中剪切并粘贴一个对象到另一张图片)。尽管它是一种复杂且计算成本较高的算法,但它通常能产生非常精确的结果。

图像分割实现步骤

1.定义输入图像和矩形区域
定义一个矩形区域来包含前景对象。所有在这个矩形之外的像素将被标记为背景。

// 定义包围矩形
cv::Rect rectangle(5, 70, 260, 120);

2.初始化 GrabCut 所需的数据结构
创建几个矩阵来存储分割结果和算法内部使用的模型。

cv::Mat result; // 分割结果(4种可能值)          每个像素可以有四种状态之一
cv::Mat bgModel, fgModel; // 算法内部使用的模型   用于存储算法生成的背景和前景模型

3.调用 cv::grabCut 函数

// GrabCut 分割
cv::grabCut(image,        // 输入图像result,       // 分割结果rectangle,    // 包含前景对象的矩形bgModel,      // 背景模型fgModel,      // 前景模型5,            // 迭代次数cv::GC_INIT_WITH_RECT); // 使用矩形模式  使用定义的矩形来指定前景区域

4.解释分割结果
分割结果图像中的每个像素可以有以下四种状态之一:

  • cv::GC_BGD:背景像素(例如,在我们的例子中,矩形外的所有像素)。
  • cv::GC_FGD:前景像素(在我们的例子中没有)。
  • cv::GC_PR_BGD:可能是背景的像素。
  • cv::GC_PR_FGD:可能是前景的像素(即,初始状态下矩形内的所有像素)。

为了生成二值分割图像,我们需要提取那些具有 cv::GC_PR_FGD 值的像素:

// 获取可能属于前景的像素
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);

5.提取所有前景像素
如果要提取所有前景像素(包括 cv::GC_PR_FGD 和 cv::GC_FGD),可以通过检查第一个比特位来实现:

// 检查第一个比特位
result = result & 1; // 如果是前景,则值为1

因为 cv::GC_FGD 和 cv::GC_PR_FGD 的值分别为 1 和 3,而其他两个常量 cv::GC_BGD 和 cv::GC_PR_BGD 的值分别为 0 和 2。因此,通过与操作可以有效地提取前景像素。

实现方案

#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main() 
{cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "Error: Could not load image!" << std::endl;return -1;}// 定义包含前景对象的矩形区域cv::Rect rectangle(50, 50, 300, 200); // 根据实际情况调整矩形位置和大小// 创建用于存储分割结果和模型的矩阵cv::Mat result;       // 分割结果(4种可能值)cv::Mat bgModel, fgModel; // 背景和前景模型// 调用 GrabCut 算法cv::grabCut(image,          // 输入图像result,         // 分割结果rectangle,      // 包含前景的矩形bgModel,        // 背景模型fgModel,        // 前景模型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::imshow("Original Image", image);cv::imshow("Foreground", foreground);// 保存结果cv::imwrite("foreground.jpg", foreground);// 等待用户按键退出cv::waitKey(0);return 0;
}

在这里插入图片描述

转换颜色

RGB 颜色空间

RGB 颜色空间基于红(Red)、绿(Green)、蓝(Blue)三种加色原理。
在数字图像中,RGB 是默认的颜色空间,因为彩色图像是通过红、绿、蓝滤镜捕捉的。

RGB 的每个通道(红、绿、蓝)都经过归一化处理:当三个通道值相等时,会生成灰度颜色,从黑色(0,0,0)到白色(255,255,255)。然而,它并不适合用来直接衡量两种颜色之间的相似性。

RGB 的局限性

RGB 不是一个感知均匀的颜色空间。
在 RGB 空间中,两个颜色之间的距离可能无法准确反映它们在人眼中的视觉差异。例如:

  • 两个颜色之间的距离相同,但它们看起来可能非常相似。
  • 另外两个颜色之间的距离相同,但它们看起来却可能截然不同。

这种不一致性使得 RGB 颜色空间不适合用于需要精确比较颜色相似性的任务。

CIE Lab* 颜色空间

1.感知均匀性

  • 在 CIE Lab* 中,两个颜色之间的欧几里得距离可以很好地反映它们在人眼中的视觉相似性。
  • 距离越小,颜色看起来越相似;距离越大,颜色看起来差异越明显。

2.分量解释

  • L* 表示亮度(Lightness),从黑到白的变化。
  • a* 表示从绿色到红色的变化。
  • b* 表示从蓝色到黄色的变化。

3.适用性

  • 将图像从 RGB 转换到 CIE Lab* 后,可以直接用欧几里得距离来衡量颜色的相似性,而无需担心 RGB 空间的非均匀性问题。

颜色转换的步骤

1. 将图像从 BGR 转换到 CIE Lab 颜色空间

// 输入图像(假设为BGR格式)
cv::Mat inputImage = cv::imread("input.jpg");// 创建一个矩阵存储转换后的图像
cv::Mat labImage;// 使用 cv::cvtColor 进行颜色空间转换
cv::cvtColor(inputImage, labImage, cv::COLOR_BGR2Lab);

cv::COLOR_BGR2Lab 是 BGR 到 CIE Lab* 的转换代码。
转换后,labImage 包含了 Lab* 表示的图像数据。
2. 将目标颜色从 RGB 转换到 CIE Lab

void 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); // 注意顺序:BGR// 转换到 CIE L*a*b*cv::cvtColor(tmp, tmp, cv::COLOR_BGR2Lab);// 提取转换后的颜色值cv::Vec3b labColor = tmp.at<cv::Vec3b>(0, 0);
}

tmp 是一个单像素图像,用于存储目标颜色。
转换后,labColor 包含了目标颜色的 CIE Lab* 值。
3. 在 CIE Lab 空间中比较颜色
在 CIE Lab* 空间中,颜色的距离可以直接用欧几里得距离计算:

// 计算两个颜色之间的欧几里得距离
double colorDistance(const cv::Vec3b &color1, const cv::Vec3b &color2) 
{double deltaL = color1[0] - color2[0];double deltaA = color1[1] - color2[1];double deltaB = color1[2] - color2[2];return std::sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
}

color1 和 color2 是两个 CIE Lab* 颜色。
返回值是两个颜色之间的感知距离。

其他常用颜色空间转换

转换类型代码描述
BGR → 灰度cv::COLOR_BGR2GRAY将彩色图像转换为灰度图像
BGR → HSVcv::COLOR_BGR2HSV转换到 HSV 颜色空间
BGR → YCrCbcv::COLOR_BGR2YCrCb转换到 JPEG 压缩使用的 YCrCb 空间
BGR → CIE XYZcv::COLOR_BGR2XYZ转换到设备无关的 CIE XYZ 空间
BGR → CIE Luv*cv::COLOR_BGR2Luv转换到另一种感知均匀颜色空间
// 将彩色图像转换为灰度图像
cv::Mat grayImage;
cv::cvtColor(colorImage, grayImage, cv::COLOR_BGR2GRAY);

工作原理

线性与非线性变换

  • RGB ↔ XYZ 是线性变换。
  • RGB ↔ CIE Lab* 或 CIE Luv* 是非线性变换(为了实现感知均匀性)。

通道范围

  • CIE Lab*:
    1. L 通道:亮度,范围 [0, 100],在 8 位图像中映射为 [0, 255]。
    2. a 和 b 通道:色度,范围 [-127, 127],在 8 位图像中偏移为 [0, 255]。
  • 灰度图像:单通道,范围 [0, 255]。

精度损失
8 位图像在转换时会引入舍入误差,因此某些转换不可完全逆向。

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

最初考虑的是 RGB 颜色空间,尽管它在电子成像系统中是一种有效的颜色捕获和显示方式,但并不直观。
人们通常用色调、亮度或色彩浓度(即颜色是鲜艳还是柔和)来描述颜色。
因此,基于色调、饱和度和亮度的颜色空间被引入,以帮助用户通过更直观的属性来指定颜色。

颜色空间转换及可视化HSV各分量

HSV颜色空间简介

HSB 颜色空间通常用一个锥形来表示,其中内部的每个点对应一种特定的颜色。角度位置对应于颜色的色调,饱和度是与中心轴的距离,而亮度则由高度表示。锥形的顶点对应黑色,此时色调和饱和度是未定义的。
在这里插入图片描述

HSV(色调、饱和度、亮度)颜色空间是基于人类对颜色的自然感知而设计的。它将颜色分为三个直观属性:

色调 (Hue)

表示颜色的类型,例如红色、绿色、蓝色等。
在 HSV 中,色调用角度表示,范围为 [0, 360] 度。在 OpenCV 中,为了适应 8 位图像,范围被压缩到 [0, 180]。

饱和度 (Saturation)

表示颜色的纯度,即颜色中灰色的比例。
高饱和度对应鲜艳的颜色,低饱和度对应接近灰色的颜色。
范围为 [0, 1] 或 [0, 255](对于 8 位图像)。

亮度 (Value)

表示颜色的明暗程度。
在 OpenCV 中,亮度定义为 BGR 通道的最大值。

OpenCV 中的 HSV 实现

OpenCV 提供了两种主要的感知颜色空间:HSV 和 HLS。以下是它们的计算方式和实现细节:

  • 亮度 (Value):
    定义为 BGR 通道的最大值。
  • 饱和度 (Saturation):
    如果颜色为灰度(R=G=B),则饱和度为 0。
    计算公式基于 BGR 的最大值和最小值:

S = (max(R, G, B) - min(R, G, B))/ max(R, G, B)

  • 色调 (Hue):
    基于 BGR 的最大值和最小值,通过三角函数计算角度。
    红色对应 0 度,绿色对应 120 度,蓝色对应 240 度。
生成 HSV 色彩表
#include <opencv2/opencv.hpp>
#include <iostream>int main() 
{// 创建一个 128x360 的三通道图像cv::Mat hs(128, 360, CV_8UC3);for (int h = 0; h < 360; h++) {        // 遍历所有色调值for (int s = 0; s < 128; s++) {     // 遍历所有饱和度值// 设置每个像素的 HSV 值hs.at<cv::Vec3b>(s, h)[0] = h / 2;      // 色调 (0-180)hs.at<cv::Vec3b>(s, h)[1] = 255 - s * 2; // 饱和度 (从高到低)hs.at<cv::Vec3b>(s, h)[2] = 255;        // 恒定亮度}}// 将 HSV 图像转换为 BGR 格式以显示cv::Mat hsvToBgr;cv::cvtColor(hs, hsvToBgr, cv::COLOR_HSV2BGR);// 显示结果cv::imshow("HSV Color Table", hsvToBgr);cv::waitKey(0);return 0;
}

在这里插入图片描述

修改图像亮度
#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main() 
{// 读取输入图像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "无法加载图像,请检查路径!" << std::endl;return -1;}// 转换为 HSV 颜色空间cv::Mat hsvImage;cv::cvtColor(image, hsvImage, cv::COLOR_BGR2HSV);// 分离 HSV 通道std::vector<cv::Mat> channels;cv::split(hsvImage, channels);// 将亮度通道设置为 255channels[2] = 255;// 合并通道cv::merge(channels, hsvImage);// 转换回 BGR 颜色空间cv::Mat newImage;cv::cvtColor(hsvImage, newImage, cv::COLOR_HSV2BGR);// 显示结果cv::imshow("Original Image", image);cv::imshow("Modified Image", newImage);cv::waitKey(0);return 0;
}

在这里插入图片描述

将BGR图像转换为HSV颜色空间

要将一个BGR格式的图像转换到HSV(色调Hue、饱和度Saturation、亮度Value)颜色空间,可以使用如下方法:

// 假设'image'是你的输入BGR图像
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);

这里的CV_BGR2HSV是转换代码,它将BGR图像转换为HSV图像。
hsv变量现在存储了转换后的图像数据。

从HSV转回BGR颜色空间
cv::Mat bgr;
cv::cvtColor(hsv, bgr, CV_HSV2BGR);
分离并可视化HSV各通道

为了更好地理解HSV颜色空间中的每个组成部分,我们可以将HSV图像的三个通道拆分成独立的图像:

// 创建一个容器来保存三个通道
std::vector<cv::Mat> channels;
// 拆分HSV图像的三个通道
cv::split(hsv, channels);// 'channels[0]' 是色调(Hue)
// 'channels[1]' 是饱和度(Saturation)
// 'channels[2]' 是亮度(Value)

由于我们处理的是8位图像,OpenCV将这些通道的值调整到了0到255的范围(除了色调外,它的范围是0到180)。这使得我们可以像显示灰度图像一样显示这些通道。

实现案例

#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main() 
{// 读取输入图像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "无法加载图像,请检查路径!" << std::endl;return -1;}// 转换为HSV颜色空间cv::Mat hsvImage;cv::cvtColor(image, hsvImage, cv::COLOR_BGR2HSV);// 分离HSV的三个通道std::vector<cv::Mat> channels;cv::split(hsvImage, channels);// 提取各通道cv::Mat hue = channels[0];         // 色调 (Hue)cv::Mat saturation = channels[1]; // 饱和度 (Saturation)cv::Mat value = channels[2];      // 亮度 (Value)// 显示原始图像cv::imshow("Original Image", image);// 显示色调通道 (Hue)cv::imshow("Hue Channel", hue);// 显示饱和度通道 (Saturation)cv::imshow("Saturation Channel", saturation);// 显示亮度通道 (Value)cv::imshow("Value Channel", value);// 等待用户按键退出cv::waitKey(0);return 0;
}

在这里插入图片描述
在这里插入图片描述

使用颜色进行肤色检测

颜色信息可以用于特定对象的初步检测。
驾驶员辅助系统中,可以通过标准路标的颜色快速识别潜在的路标候选区域。
肤色检测中,可以用来判断图像中是否存在人类,并且常用于手势识别中通过肤色检测来定位手的位置。

使用颜色检测对象,通常需要以下步骤:
收集样本数据

  • 收集大量包含目标对象的图像样本,这些样本应从不同的视角和光照条件下捕获。
  • 这些样本将用于定义分类器的参数。

选择颜色表示方式

  • 对于肤色检测,研究表明不同种族的肤色在色调(Hue)和饱和度(Saturation)空间中具有良好的聚类特性。因此,我们将使用色调和饱和度值来识别肤色
案例实现
#include <opencv2/opencv.hpp>
#include <iostream>#define IMG_WOMAN "princess.jpeg"
#define IMG_OLD_MAN "old_man.jpeg"// 定义肤色检测函数
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::COLOR_BGR2HSV);// 分离 HSV 通道std::vector<cv::Mat> channels;cv::split(hsv, channels);// channels[0] 是色调 (Hue)// channels[1] 是饱和度 (Saturation)// channels[2] 是亮度 (Value)// 色调掩码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; // 如果区间未跨越零度轴elsehueMask = mask1 | mask2; // 如果区间跨越零度轴// 饱和度掩码cv::Mat satMask; // 饱和度掩码cv::inRange(channels[1], minSat, maxSat, satMask);// 组合掩码mask = hueMask & satMask;
}int main() 
{// 读取输入图像cv::Mat image = cv::imread(IMG_OLD_MAN);if (image.empty()) {std::cerr << "无法加载图像,请检查路径!" << std::endl;return -1;}// 定义肤色检测的色调和饱和度区间cv::Mat mask;detectHScolor(image, 160, 10, // 色调范围:320 度到 20 度(OpenCV 中缩放为 0-180)25, 166, // 饱和度范围:~0.1 到 ~0.65mask);// 显示检测结果cv::Mat detected(image.size(), CV_8UC3, cv::Scalar(0, 0, 0)); // 创建黑色背景image.copyTo(detected, mask); // 将检测到的区域复制到黑色背景上// 显示原始图像和检测结果cv::imshow("Original Image", image);cv::imshow("Detected Skin", detected);cv::waitKey(0);return 0;
}

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75733.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DOM解析XML:Java程序员的“乐高积木式“数据搭建

各位代码建筑师们&#xff01;今天我们要玩一个把XML变成内存乐高城堡的游戏——DOM解析&#xff01;和SAX那种"边看监控边破案"的刺激不同&#xff0c;DOM就像把整个乐高说明书一次性倒进大脑&#xff0c;然后慢慢拼装&#xff08;内存&#xff1a;你不要过来啊&…

Apache Nifi安装与尝试

Apache NIFI中文文档 地址&#xff1a;https://nifichina.github.io/ 下载安装配置 1、环境准备 Nifi的运行需要依赖于java环境&#xff0c;所以本机上需要安装java环境&#xff0c;并配置环境变量。 1.1查看本机是否已经存在java环境 请先执行以下命令找出系统中真实可用…

我可能用到的网站和软件

我可能用到的网站和软件 程序员交流的网站代码管理工具前端组件库前端框架在线工具人工智能问答工具学习的网站Windows系统电脑的常用工具 程序员交流的网站 csdn博客博客园 - 开发者的网上家园InfoQ - 软件开发及相关领域-极客邦掘金 (juejin.cn) 代码管理工具 GitHub 有时…

使用SSH解决在IDEA中Push出现403的问题

错误截图&#xff1a; 控制台日志&#xff1a; 12:15:34.649: [xxx] git -c core.quotepathfalse -c log.showSignaturefalse push --progress --porcelain master refs/heads/master:master fatal: unable to access https://github.com/xxx.git/: The requested URL return…

JavaScript异常机制与严格模式

目录 JavaScript 异常机制 1. 基本语法&#xff1a;try...catch...finally 2. 抛出异常&#xff1a;throw 3. 错误对象属性 4. 同步代码的异常处理 5. 异步代码的异常处理 5.1 回调函数 5.2 Promise 5.3 全局未捕获的 Promise 错误 6. 全局错误处理 7. 自定义错误与…

中厂算法岗面试总结

时间&#xff1a;2025.4.10 地点&#xff1a;上市的电子有限公司 面试流程&#xff1a; 1.由负责人讲解公司文化 2&#xff0c;由技术人员讲解公司的技术岗位&#xff0c;还有成果 3.带领参观各个工作位置&#xff0c;还有场所 4.中午吃饭 5.面试题&#xff0c;闭卷考试…

vue+flask图书知识图谱推荐系统

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01; 编号: F025 架构: vueflaskneo4jmysql 亮点&#xff1a;协同过滤推荐算法知识图谱可视化 支持爬取图书数据&#xff0c;数据超过万条&am…

MySQL NDB Cluster详解

MySQL NDB Cluster&#xff08;MNC&#xff09; 是MySQL提供的一种分布式数据库解决方案&#xff0c;旨在提供高可用性、高性能的数据库服务。它通过 NDB&#xff08;Network DataBase&#xff09; 存储引擎实现了高可用性和分布式存储&#xff0c;在NDB中&#xff0c;数据通过…

解决华硕主板Z890m下载ubuntu20.04后没有以太网问题

问题描述&#xff1a; 华硕主板Z890m下载双系统ubuntu20.04后&#xff0c;发现ubuntu不能打开以太网。 问题原因&#xff1a; 华硕主板的网卡驱动是r8125,而ubuntu20.04的驱动版本是r8169&#xff0c;所以是网卡驱动不匹配造成 解决方案 开机界面按下F2进入BOIS模式&#…

JS里对于集合的简单介绍

JS的集合 前言一、集合二、基本使用1. 创建集合2. 添加元素3. 删除元素4. 检查元素5. 清空集合6. 集合的大小 三、扩展使用1. 遍历集合2. 从数组创建集合3. 集合的应用场景 四、总结 前言 JS里对于集合的简单介绍 同数学的集合&#xff0c;有无序性、唯一性 注意&#xff1a;…

pytorch 反向传播

文章目录 概念计算图自动求导的两种模式 自动求导-代码标量的反向传播非标量变量的反向传播将某些计算移动到计算图之外 概念 核心&#xff1a;链式法则 深度学习框架通过自动计算导数(自动微分)来加快求导。 实践中&#xff0c;根据涉及号的模型&#xff0c;系统会构建一个计…

Kotlin日常使用函数记录

文章目录 前言字符串集合1.两个集合的差集2.集合转数组2.1.集合转基本数据类型数组2.2.集合转对象数组 Map1.合并Map1.1.使用 操作符1.2.使用 操作符1.3.使用 putAll 方法1.4.使用 merge 函数 前言 记录一些kotlin开发中&#xff0c;日常使用的函数和方式之类的&#xff0c;…

详解正则表达式中的?:、?= 、 ?! 、?<=、?<!

1、?: - 非捕获组 语法: (?:pattern) 作用: 创建一个分组但不捕获匹配结果&#xff0c;不会将匹配的文本存储到内存中供后续使用。 优势: 提高性能和效率 不占用编号&#xff08;不会影响后续捕获组的编号&#xff09; 减少内存使用 // 使用捕获组 let regex1 /(hell…

【无标题】spark编程

Value类型&#xff1a; 9) distinct ➢ 函数签名 def distinct()(implicit ord: Ordering[T] null): RDD[T] def distinct(numPartitions: Int)(implicit ord: Ordering[T] null): RDD[T] ➢ 函数说明 将数据集中重复的数据去重 val dataRDD sparkContext.makeRDD(Lis…

GPT-2 语言模型 - 模型训练

本节代码是一个完整的机器学习工作流程&#xff0c;用于训练一个基于GPT-2的语言模型。下面是对这段代码的详细解释&#xff1a; 文件目录如下 1. 初始化和数据准备 设置随机种子 random.seed(1002) 确保结果的可重复性。 定义参数 test_rate 0.2 context_length 128 tes…

架构师面试(二十九):TCP Socket 编程

问题 今天考察网络编程的基础知识。 在基于 TCP 协议的网络 【socket 编程】中可能会遇到很多异常&#xff0c;在下面的相关描述中说法正确的有哪几项呢&#xff1f; A. 在建立连接被拒绝时&#xff0c;有可能是因为网络不通或地址错误或 server 端对应端口未被监听&#x…

HTTP实现心跳模块

HTTP实现心跳模块 使用轻量级的cHTTP库cpp-httplib重现实现HTTP心跳模块 头文件HttplibHeartbeat.h #ifndef HTTPLIB_HEARTBEAT_H #define HTTPLIB_HEARTBEAT_H#include <string> #include <thread> #include <atomic> #include <chrono> #include …

openharmony—release—4.1开发环境搭建(踩坑记录)

环境开发需要分别在window以及ubuntu下进行相应设置 一、window 1.安装DevEco Device Tool OpenAtom OpenHarmony 二、ubuntu 1.将Ubuntu Shell环境修改为bash ls -l /bin/sh 2.打开终端工具&#xff0c;执行如下命令&#xff0c;输入密码&#xff0c;然后选择No&#xff0…

Go学习系列文章声明

本次学习是基于B站的视频&#xff0c;【Udemy高分热门付费课程】Golang&#xff1a;完整开发者指南&#xff08;基础知识和高级特性&#xff09;中英文字幕_哔哩哔哩_bilibili 本人会尝试输出视频中的内容&#xff0c;如有错误欢迎指出 next page: Go installation process

error: RPC failed; HTTP 408 curl 22 The requested URL returned error: 408

在git push时报错&#xff1a;error: RPC failed; HTTP 408 curl 22 The requested URL returned error: 408 原因&#xff1a;可能是推送的文件太大&#xff0c;要么是缓存不够&#xff0c;要么是网络不行。 解决方法&#xff1a; 将本地 http.postBuffer 数值调整到500MB&…