百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

如何使用OpenCV中的色彩空间进行颜色分割

toyiye 2024-07-06 00:35 33 浏览 0 评论




在本教程中,我们将学习Computer Vision中使用的流行色彩空间,并将其用于基于颜色的分割。

1975年,匈牙利专利HU170062引入了一种难题,在43,252,003,274,489,856,000(43亿亿)种可能性中,只有一种正确的解决方案。到2009年1月,这项被称为“魔方”的发明席卷全球,销量超过3.5亿。

因此,有位同学又建立基于计算机视觉的自动Rubik立方体求解器的想法,我对此很感兴趣。他试图使用颜色分割来查找多维数据集的当前状态。尽管他的颜色分割代码在他的房间晚上工作得很好,但是白天在他的房间外面却消失了!

他向我寻求帮助,我立即了解他出了问题。像许多其他计算机视觉业余爱好者一样,他在进行颜色分割时没有考虑不同照明条件的影响。我们在许多涉及基于颜色的分割(例如肤色检测,交通信号灯识别)的计算机视觉应用中都遇到了这个问题。让我们看看如何帮助他为他的机器人构建一个强大的颜色检测系统。

文章的结构安排如下:

  • 首先,我们将了解如何在OpenCV中读取图像并将其转换为不同的色彩空间,并了解每个色彩空间的不同通道为我们提供了哪些新信息。
  • 我们将应用Mark所做的一种简单的颜色分割算法,并思考其缺点。
  • 然后,我们将进入一些分析并使用系统的方法进行选择:
  • 正确的色彩空间。
  • 正确的细分阈值。
  • 查看结果

不同的色彩空间

在本节中,我们将介绍计算机视觉中使用的一些重要色彩空间。我们不会描述它们背后的理论,因为可以在Wikipedia上找到它。相反,我们将发展一个基本的直觉并学习一些重要的属性,这些属性对于以后的决策很有用。

让我们加载2个相同立方体的图像。默认情况下,它将以BGR格式加载。我们可以使用OpenCV函数cvtColor()在不同的色彩空间之间进行转换,如下所示。


#python 
bright = cv2.imread('cube1.jpg')
dark = cv2.imread('cube8.jpg')

//C++ 
bright = cv::imread('cube1.jpg')
dark = cv::imread('cube8.jpg')

第一张图像是在室外环境下在明亮的阳光下拍摄的,第二张图像是在正常照明条件下在室内拍摄的。



图1:在不同照明下拍摄的同一立方体的两个图像

RGB色彩空间

RGB色彩空间具有以下属性

  • 它是一种加色空间,其中颜色是通过红色,绿色和蓝色值的线性组合获得的。
  • 这三个通道与撞击表面的光量相关。

让我们将两个图像分为R,G和B分量,然后观察它们,以更深入地了解色彩空间。



图2:分别显示的RGB颜色空间的不同通道蓝色(B),绿色(G),红色(R)

建议

如果您查看蓝色通道,可以看到在室内照明条件下,第二张图像中的蓝色和白色部分看起来相似,但是第一张图像中有明显的不同。这种不均匀性使得在这种颜色空间中基于颜色的分割非常困难。此外,两个图像的值之间存在总体差异。下面我们总结了与RGB颜色空间相关的固有问题:

  • 重大的感知不一致性。
  • 色度(颜色相关信息)和亮度(强度相关信息)数据的混合。

LAB颜色空间

Lab颜色空间包含三个组成部分。

  1. L –亮度(强度)。
  2. a –颜色成分从绿色到洋红色。
  3. b –颜色分量,从蓝色到黄色。


Lab颜色空间与RGB颜色空间完全不同。在RGB颜色空间中,颜色信息分为三个通道,但是相同的三个通道也编码亮度信息。另一方面,在Lab颜色空间中,L通道与颜色信息无关,并且仅编码亮度。其他两个通道对颜色进行编码。

它具有以下属性。

  • 感知均匀的色彩空间,近似我们对色彩的感知方式。
  • 与设备无关(捕获或显示)。
  • 在Adobe Photoshop中广泛使用。
  • 通过复杂的转换方程与RGB颜色空间相关。

让我们查看Lab颜色空间中的两个图像,这些图像分为三个通道。


#python 
brightLAB = cv2.cvtColor(bright, cv2.COLOR_BGR2LAB)
darkLAB = cv2.cvtColor(dark, cv2.COLOR_BGR2LAB)

//C++ 
cv::cvtColor(bright, brightLAB, cv::COLOR_BGR2LAB);
cv::cvtColor(dark, darkLAB, cv::COLOR_BGR2LAB);



图3:LAB颜色空间中的亮度(L)和颜色分量(A,B)。

建议

从图中可以很明显地看出,照度的变化主要影响了L分量。

包含颜色信息的A和B组件没有发生大的变化。

在B分量中绿色,橙色和红色的相应值(A分量的极值)不变,在蓝色分量中蓝色和黄色(B分量的极值)的相应值不变。组件。

YCrCb颜色空间

YCrCb颜色空间是从RGB颜色空间派生的,并且具有以下三个组件。

  1. Y –伽玛校正后从RGB获得的亮度或亮度分量。
  2. Cr = R – Y(距离Luma的红色分量多远)。
  3. Cb = B – Y(距离Luma的蓝色分量多远)。

此色彩空间具有以下属性。

  • 将亮度和色度分量分成不同的通道。
  • 通常用于电视传输的压缩(Cr和Cb成分)。
  • 取决于设备。

YCrCb颜色空间中的两个图像分为两个通道,如下所示


#python 
brightYCB = cv2.cvtColor(bright, cv2.COLOR_BGR2YCrCb)
darkYCB = cv2.cvtColor(dark, cv2.COLOR_BGR2YCrCb)

//C++ 
cv::cvtColor(bright, brightYCB, cv::COLOR_BGR2YCrCb);
cv::cvtColor(dark, darkYCB, cv::COLOR_BGR2YCrCb);



图4:YCrCb颜色空间中的亮度(Y)和色度(Cr,Cb)分量。

建议

关于亮度变化,可以对强度和颜色分量进行与LAB类似的观察。

与LAB相比,即使在室外图像中,红色和橙色之间的感知差异也较小。

白色的所有三个部分均发生了变化。

HSV色彩空间

HSV颜色空间具有以下三个组成部分

  1. H –色相(主波长)。
  2. S –饱和度(纯度/颜色阴影)。
  3. V –值(强度)。

让我们枚举其一些属性。

  • 最好的事情是,它仅使用一个通道来描述颜色(H),从而非常直观地指定颜色。
  • 取决于设备。

这两个图像的H,S和V分量如下所示。


#python 
brightHSV = cv2.cvtColor(bright, cv2.COLOR_BGR2HSV)
darkHSV = cv2.cvtColor(dark, cv2.COLOR_BGR2HSV)

//C++ 
cv::cvtColor(bright, brightHSV, cv::COLOR_BGR2HSV);
cv::cvtColor(dark, darkHSV, cv::COLOR_BGR2HSV);



图5:HSV颜色空间中的色相(H),饱和度(S)和值(V)分量

建议

两个图像中的H分量非常相似,这表明即使在光照变化下颜色信息也完整无缺。

  • 在两个图像中,S分量也非常相似。
  • V分量捕获落在其上的光量,因此由于照明的变化而发生变化。
  • 室外红色图像和室内图像的红色值之间存在巨大差异。这是因为色相表示为圆圈,红色表示起始角度。因此,它可能取[300,360]到[0,60]之间的值。

如何使用这些色彩空间进行细分

最简单的方法

现在我们对不同的色彩空间有了一些了解,让我们首先尝试使用它们从多维数据集中检测绿色。

步骤1:获取特定颜色的颜色值

找到每个颜色空间的绿色值的近似范围。为此,我制作了一个交互式GUI,您可以通过将鼠标悬停在图像上来检查每个像素的所有颜色空间的值,如下所示:



图6:演示显示了户外图像在不同颜色空间中的像素及其值。

第2步:应用细分阈值

从图像中提取值接近绿色像素值的所有像素。我们可以为每个色彩空间采用+/- 40的范围,并检查结果的外观。我们将使用inRange中的opencv函数查找绿色像素的蒙版,然后使用bitwise_and操作使用该蒙版从图像中获取绿色像素。

另请注意,要将一个像素转换为另一种颜色空间,我们首先需要将1D阵列转换为3D阵列。


#python
bgr = [40, 158, 16]
thresh = 40
 
minBGR = np.array([bgr[0] - thresh, bgr[1] - thresh, bgr[2] - thresh])
maxBGR = np.array([bgr[0] + thresh, bgr[1] + thresh, bgr[2] + thresh])
 
maskBGR = cv2.inRange(bright,minBGR,maxBGR)
resultBGR = cv2.bitwise_and(bright, bright, mask = maskBGR)
 
#convert 1D array to 3D, then convert it to HSV and take the first element 
# this will be same as shown in the above figure [65, 229, 158]
hsv = cv2.cvtColor( np.uint8([[bgr]] ), cv2.COLOR_BGR2HSV)[0][0]
 
minHSV = np.array([hsv[0] - thresh, hsv[1] - thresh, hsv[2] - thresh])
maxHSV = np.array([hsv[0] + thresh, hsv[1] + thresh, hsv[2] + thresh])
 
maskHSV = cv2.inRange(brightHSV, minHSV, maxHSV)
resultHSV = cv2.bitwise_and(brightHSV, brightHSV, mask = maskHSV)
 
#convert 1D array to 3D, then convert it to YCrCb and take the first element 
ycb = cv2.cvtColor( np.uint8([[bgr]] ), cv2.COLOR_BGR2YCrCb)[0][0]
 
minYCB = np.array([ycb[0] - thresh, ycb[1] - thresh, ycb[2] - thresh])
maxYCB = np.array([ycb[0] + thresh, ycb[1] + thresh, ycb[2] + thresh])
 
maskYCB = cv2.inRange(brightYCB, minYCB, maxYCB)
resultYCB = cv2.bitwise_and(brightYCB, brightYCB, mask = maskYCB)
 
#convert 1D array to 3D, then convert it to LAB and take the first element 
lab = cv2.cvtColor( np.uint8([[bgr]] ), cv2.COLOR_BGR2LAB)[0][0]
 
minLAB = np.array([lab[0] - thresh, lab[1] - thresh, lab[2] - thresh])
maxLAB = np.array([lab[0] + thresh, lab[1] + thresh, lab[2] + thresh])
 
maskLAB = cv2.inRange(brightLAB, minLAB, maxLAB)
resultLAB = cv2.bitwise_and(brightLAB, brightLAB, mask = maskLAB)
 
cv2.imshow("Result BGR", resultBGR)
cv2.imshow("Result HSV", resultHSV)
cv2.imshow("Result YCB", resultYCB)
cv2.imshow("Output LAB", resultLAB)

//C++ code
cv::Vec3b bgrPixel(40, 158, 16);
// Create Mat object from vector since cvtColor accepts a Mat object
Mat3b bgr (bgrPixel);
 
//Convert pixel values to other color spaces.
Mat3b hsv,ycb,lab;
cvtColor(bgr, ycb, COLOR_BGR2YCrCb);
cvtColor(bgr, hsv, COLOR_BGR2HSV);
cvtColor(bgr, lab, COLOR_BGR2Lab);
//Get back the vector from Mat
Vec3b hsvPixel(hsv.at<Vec3b>(0,0));
Vec3b ycbPixel(ycb.at<Vec3b>(0,0));
Vec3b labPixel(lab.at<Vec3b>(0,0));
 
int thresh = 40;
 
cv::Scalar minBGR = cv::Scalar(bgrPixel.val[0] - thresh, bgrPixel.val[1] - thresh, bgrPixel.val[2] - thresh) 
cv::Scalar maxBGR = cv::Scalar(bgrPixel.val[0] + thresh, bgrPixel.val[1] + thresh, bgrPixel.val[2] + thresh) 
 
cv::Mat maskBGR, resultBGR;
cv::inRange(bright, minBGR, maxBGR, maskBGR);
cv::bitwise_and(bright, bright, resultBGR, maskBGR);
 
cv::Scalar minHSV = cv::Scalar(hsvPixel.val[0] - thresh, hsvPixel.val[1] - thresh, hsvPixel.val[2] - thresh) 
cv::Scalar maxHSV = cv::Scalar(hsvPixel.val[0] + thresh, hsvPixel.val[1] + thresh, hsvPixel.val[2] + thresh) 
 
cv::Mat maskHSV, resultHSV;
cv::inRange(brightHSV, minHSV, maxHSV, maskHSV);
cv::bitwise_and(brightHSV, brightHSV, resultHSV, maskHSV);
 
cv::Scalar minYCB = cv::Scalar(ycbPixel.val[0] - thresh, ycbPixel.val[1] - thresh, ycbPixel.val[2] - thresh) 
cv::Scalar maxYCB = cv::Scalar(ycbPixel.val[0] + thresh, ycbPixel.val[1] + thresh, ycbPixel.val[2] + thresh) 
 
cv::Mat maskYCB, resultYCB;
cv::inRange(brightYCB, minYCB, maxYCB, maskYCB);
cv::bitwise_and(brightYCB, brightYCB, resultYCB, maskYCB);
 
cv::Scalar minLAB = cv::Scalar(labPixel.val[0] - thresh, labPixel.val[1] - thresh, labPixel.val[2] - thresh) 
cv::Scalar maxLAB = cv::Scalar(labPixel.val[0] + thresh, labPixel.val[1] + thresh, labPixel.val[2] + thresh) 
 
cv::Mat maskLAB, resultLAB;
cv::inRange(brightLAB, minLAB, maxLAB, maskLAB);
cv::bitwise_and(brightLAB, brightLAB, resultLAB, maskLAB);
 
cv2::imshow("Result BGR", resultBGR)
cv2::imshow("Result HSV", resultHSV)
cv2::imshow("Result YCB", resultYCB)
cv2::imshow("Output LAB", resultLAB)



图7:RGB看起来不错,也许我们只是在浪费时间

因此,似乎RGB和LAB足以检测颜色,我们不需要花太多时间。让我们看看更多结果



图8:对室内图像应用相同的阈值无法检测所有颜色空间中的绿色立方体。

因此,相同的阈值不适用于深色图像。进行相同的实验以检测黄色,得出以下结果。



图9:使用相同的技术和从明亮图像获得的阈值(对于黄色)检测黄色碎片。HSV和YCrCb仍然表现不佳。

室内图像上黄色的初始结果


图10:尝试使用从明亮的立方体获得的阈值检测黄色碎片。所有颜色空间再次失败。

但是为什么结果这么差呢?这是因为我们对阈值进行了40个疯狂的猜测。我制作了另一个交互式演示,您可以在其中演示这些值,并尝试找到一个适用于所有图像的演示。查看屏幕截图。但是,在某些情况下,还会有其他图像出现而无法再次使用。我们不能只是盲目地尝试和尝试一些门槛。我们这样做并不是在利用色彩空间的力量。

我们需要有一些系统的方法来找到正确的阈值。



图11:演示的屏幕快照,用于处理不同的值以检测给定图像在所有颜色空间中的特定颜色。


一些数据分析以寻求更好的解决方案

步骤1:数据收集

我收集了10个在不同照明条件下的立方体图像,并分别裁剪每种颜色以获得6种不同颜色的6个数据集。您可以直观地看到颜色的变化。

图片:显示由于光照条件变化而引起的颜色变化


第2步:计算密度图

检查特定颜色(例如蓝色或黄色)在不同颜色空间中的分布。密度图或2D直方图给出了有关给定颜色的值变化的想法。例如,理想情况下,蓝色图像的蓝色通道应始终具有255的值。但是实际上,它的分布范围是0到255。

我只显示BGR颜色空间的代码。您需要针对所有颜色空间进行此操作。

我们将首先加载所有蓝色或黄色的图像


#python
B = np.array([])
G = np.array([])
R = np.array([])
im = cv2.imread(fi)

分隔通道,并通过附加每个图像的值为每个通道创建和排列。


#python
b = im[:,:,0]
b = b.reshape(b.shape[0]*b.shape[1])
g = im[:,:,1]
g = g.reshape(g.shape[0]*g.shape[1])
r = im[:,:,2]
r = r.reshape(r.shape[0]*r.shape[1])
B = np.append(B,b)
G = np.append(G,g)
R = np.append(R,r)

使用matplotlib中的直方图绘制2D直方图


#python
nbins = 10
plt.hist2d(B, G, bins=nbins, norm=LogNorm())
plt.xlabel('B')
plt.ylabel('G')
plt.xlim([0,255])
plt.ylim([0,255])

观察结果:类似的照明



图13:密度图显示了2个相似的蓝色明亮图像在彩色通道中的值变化


图14:密度图显示了黄色的2张相似的明亮图像在彩色通道中的值变化

可以看出,在相似的照明条件下,所有地块都非常紧凑。需要注意的几点是:

  • YCrCb和LAB比其他紧凑得多
  • 在HSV中,S方向(色纯度)有变化,但H方向几乎没有变化。

观察结果:不同的照明



图15:密度图显示了在蓝色变化的光照下颜色通道中值的变化


图16:密度图显示了在黄色变化的光照下颜色通道中值的变化

随着光照变化很大,我们可以看到:

  • 理想情况下,我们希望使用色彩空间具有最紧凑/最密集的颜色通道的色彩空间。
  • RGB的密度图急剧增大。这意味着通道值的变化非常大,固定阈值是一个大问题。固定较高的范围将检测到与所需颜色相似的颜色(假阳性),而较低的范围将无法在不同的照明下检测所需颜色(假阴性)。
  • 在HSV中,由于仅H分量包含有关绝对颜色的信息。因此,与YCrCb(Cr和Cb)和LAB(A和B)中的2个旋钮相比,我只能微调一个旋钮(H)来指定颜色,这成为我色彩空间的首选。
  • 比较YCrCb和LAB的图,在LAB的情况下显示出更高的紧密度。因此,对我而言,下一个最佳选择是LAB颜色空间。


最终结果

在最后一部分中,我将展示通过检测密度图中的阈值并将其应用到各个颜色空间中来检测蓝色和黄色部分的结果,方法与第二部分相同。在HSV,YCrCb和LAB颜色空间中工作时,我们不必担心强度分量。我们只需要指定颜色分量的阈值即可。图中显示了我为生成结果所采用的值。



图17:演示图像1


图18:演示图像1上的黄色检测结果


图19:演示图像1上的蓝色检测结果


图20:演示图像2


图21:演示图像2上的黄色检测结果


图22:演示图像2上的蓝色检测结果


图23:演示图像3


图24:演示图像3上的黄色检测结果


图25:演示图像3上的蓝色检测结果

在以上结果中,我直接从密度图中获取了值。我们还可以选择采用属于密度图中最密集区域的值,这将有助于更严格地控制颜色范围。这将留下一些孔和杂散的像素,可以使用“侵蚀和扩散”以及“过滤”将其清洁。

色彩空间的其他有用应用

  • 直方图均衡化通常在灰度图像上完成。但是,可以通过将RGB图像转换为YCbCr并仅对Y通道进行直方图均衡来对彩色图像进行均衡。
  • 通过将图像转换为Lab色彩空间,在两个图像之间进行颜色转移。
  • 智能手机相机应用程序(例如Google相机或Instagram)中的许多滤镜都利用这些颜色空间转换来创建这些炫酷效果!

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码