OpenCV中的色彩空间

OpenCV中的色彩空间

我们将学习计算机视觉(Computer Vision)中使用的流行色彩空间,并将其用于基于颜色的分割。

像我这样的计算机视觉业余爱好者在进行颜色分割时,就不会考虑不同照明条件的影响。颜色分割代码在房间晚上工作得很好,但是白天在房间外面却消失了!我们在许多涉及基于颜色的分割(例如肤色检测,交通信号灯识别)的计算机视觉应用中都遇到了这个问题。让我们看看如何构建一个强大的颜色检测系统。

文章的结构安排如下:

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

不同的色彩空间

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

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

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

OpenCV中的色彩空间_第1张图片
两个立方体在不同的光照下

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

RGB色彩空间

RGB色彩空间具有以下属性

  • 它是一种加性色彩空间,其中颜色是通过红色,绿色和蓝色值的线性组合获得的。
  • 这三个通道与撞击表面的光量相关。
    让我们将两个图像分为R,G和B分量,然后观察它们,以更深入地了解色彩空间。
OpenCV中的色彩空间_第2张图片
BGR组件

图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)
OpenCV中的色彩空间_第3张图片
LAB元件

图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的蓝色分量多远)。8

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

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

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

#python 
brightYCB = cv2.cvtColor(bright, cv2.COLOR_BGR2YCrCb)
darkYCB = cv2.cvtColor(dark, cv2.COLOR_BGR2YCrCb)
OpenCV中的色彩空间_第4张图片
YCrCb成分

图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)
OpenCV中的色彩空间_第5张图片
HSV组件

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

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

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

最简单的方法

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

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

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

OpenCV中的色彩空间_第6张图片
不同颜色空间中的像素颜色

图片颜色空间检测代码

图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)
OpenCV中的色彩空间_第7张图片
室外图像上绿色的初始结果

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

更多结果

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

OpenCV中的色彩空间_第8张图片
室内图像上绿色的初始结果

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

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

OpenCV中的色彩空间_第9张图片
室外图像上黄色的初始结果

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

OpenCV中的色彩空间_第10张图片
室内图像上黄色的初始结果

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

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

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

OpenCV中的色彩空间_第11张图片
演示如何使用色彩空间

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

图片颜色空间正确阈值代码

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

步骤1:数据收集

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

OpenCV中的色彩空间_第12张图片
通过改变照明来改变颜色

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

第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])
观察结果:类似的照明
OpenCV中的色彩空间_第13张图片
密度图类似的照明

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

OpenCV中的色彩空间_第14张图片
密度图类似的照明

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

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

  • YCrCb和LAB比其他紧凑得多
  • 在HSV中,S方向(色纯度)有变化,但H方向几乎没有变化。
观察结果:不同的照明
OpenCV中的色彩空间_第15张图片
密度图不同的照明

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

OpenCV中的色彩空间_第16张图片
密度图不同的照明

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

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

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

最终结果

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

OpenCV中的色彩空间_第17张图片
演示图片

图17:演示图像1

OpenCV中的色彩空间_第18张图片
演示图像1的结果

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

OpenCV中的色彩空间_第19张图片
演示图像1上的结果

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

OpenCV中的色彩空间_第20张图片
演示图片

图20:演示图像2

OpenCV中的色彩空间_第21张图片
演示图像2上的结果

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

OpenCV中的色彩空间_第22张图片
演示图像2上的结果

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

OpenCV中的色彩空间_第23张图片
演示图片

图23:演示图像3

OpenCV中的色彩空间_第24张图片
演示图像3的结果

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

OpenCV中的色彩空间_第25张图片
演示图像3上的结果

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

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

你可能感兴趣的:(OpenCV中的色彩空间)