本文概述
可能是深度学习和大数据时代, 复杂的算法通过向图像显示数以百万计的图像来分析图像, 但是色彩空间仍然令人惊讶地对图像分析有用。简单的方法仍然很强大。
在本文中, 你将学习如何使用OpenCV在Python中基于颜色简单地从图像中分割对象。 OpenCV是用C / C ++编写的流行的计算机视觉库, 带有Python绑定, 它提供了操作颜色空间的简便方法。
尽管你不需要已经熟悉OpenCV或本文中使用的其他帮助程序包, 但假定你至少具有Python编码的基本知识。
免费红利:单击此处以获得Python人脸检测和OpenCV示例迷你指南, 该指南向你展示了现实世界中Python计算机视觉技术的实用代码示例。
移除广告
什么是色彩空间?
在最常见的颜色空间RGB(红色绿色蓝色)中, 颜色以红色, 绿色和蓝色分量表示。用更专业的术语来说, RGB将颜色描述为三个组成部分的元组。每个分量可以取0到255之间的值, 其中元组(0, 0, 0)表示黑色, 而(255, 255, 255)表示白色。
RGB被认为是“加法”色彩空间, 可以想象颜色是由大量的红色, 蓝色和绿色的光照射到黑色背景上产生的。
以下是RGB颜色的更多示例:
颜色 | RGB值 |
---|---|
Red | 255, 0, 0 |
Orange | 255, 128, 0 |
Pink | 255, 153, 255 |
RGB是五个主要色彩空间模型之一, 每个模型都有许多分支。颜色空间太多, 因为不同的颜色空间可用于不同的目的。
在印刷领域, CMYK很有用, 因为它描述了从白色背景产生颜色所需的颜色组合。 RGB中的0元组是黑色, 而CMYK中的0元组是白色。我们的打印机包含青色, 品红色, 黄色和黑色的墨水罐。
在某些类型的医学领域中, 装有载有污渍的组织样本的载玻片会被扫描并保存为图像。可以在HED空间中对它们进行分析, 这是应用于原始组织的染色类型(苏木精, 曙红和DAB)的饱和度的表示。
HSV和HSL是色调, 饱和度和亮度/亮度的描述, 对于识别图像的对比度特别有用。这些色彩空间常用于软件的选色工具和Web设计中。
实际上, 颜色是一种连续现象, 这意味着有无限多种颜色。但是, 色彩空间通过离散结构(固定数量的整数整数值)表示颜色, 这是可以接受的, 因为人眼和感知也受到限制。颜色空间完全能够代表我们能够区分的所有颜色。
现在我们了解了色彩空间的概念, 我们可以继续在OpenCV中使用它们。
使用色彩空间的简单分割
为了演示颜色空间分割技术, 我们在Real Python资料存储库中提供了一个小小丑鱼图像数据集, 供你下载和使用。小丑鱼的亮橙色很容易识别, 因此很适合进行细分。让我们看看我们在图像中找到Nemo的能力。
你需要遵循的关键Python软件包是NumPy, 它是Python中科学计算的最重要的软件包, Matplotlib, 绘图库, 当然还有OpenCV。本文使用OpenCV 3.2.0, NumPy 1.12.1和Matplotlib 2.0.2。在遵循和掌握概念方面, 略有不同的版本不会产生重大变化。
如果你不熟悉NumPy或Matplotlib, 则可以在NumPy官方指南和Brad Solomon在Matplotlib上的出色文章中了解它们。
OpenCV中的色彩空间和读取图像
首先, 你需要设置你的环境。本文将假定你在系统上安装了Python3.x。请注意, 尽管OpenCV的当前版本为3.x, 但是要导入的软件包的名称仍为cv2:
>>>
>>> import cv2
如果你以前未在计算机上安装过OpenCV, 则导入将失败, 直到你先执行该操作。你可以在此处找到用于在不同操作系统上进行安装的用户友好型教程, 以及OpenCV自己的安装指南。成功导入OpenCV后, 你可以查看OpenCV提供的所有色彩空间转换, 并将它们全部保存到变量中:
>>>
>>> flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
根据你的OpenCV版本, 标志的列表和数量可能会略有不同, 但是无论如何, 会有很多!查看你有多少个标志:
>>>
>>> len(flags)
258
>>> flags[40]
'COLOR_BGR2RGB'
COLOR_之后的前几个字符表示原色空间, 而2之后的字符是目标颜色空间。该标志表示从BGR(蓝色, 绿色, 红色)到RGB的转换。如你所见, 两个颜色空间非常相似, 只交换了第一个和最后一个通道。
你将需要使用matplotlib.pyplot查看图像, 并需要使用NumPy进行某些图像处理。如果尚未安装Matplotlib或NumPy, 则在尝试导入之前, 需要先pip3安装matplotlib和pip3安装numpy:
>>>
>>> import matplotlib.pyplot as plt
>>> import numpy as np
现在你可以加载和检查图像了。请注意, 如果你在命令行或终端上工作, 则图像将出现在弹出窗口中。如果你正在使用Jupyter笔记本或类似产品, 它们将仅显示在下面。无论你进行何种设置, 都应该看到show()命令生成的图像:
>>>
>>> nemo = cv2.imread('./images/nemo0.jpg')
>>> plt.imshow(nemo)
>>> plt.show()
嘿, 尼莫(Nemo)还是多莉(Dory)?你会注意到, 蓝色和红色通道似乎混合在一起了。实际上, 默认情况下, OpenCV读取BGR格式的图像。你可以使用cvtColor(image, flag)和我们在上面查看的标志来解决此问题:
>>>
>>> nemo = cv2.cvtColor(nemo, cv2.COLOR_BGR2RGB)
>>> plt.imshow(nemo)
>>> plt.show()
现在, 尼莫看起来更像他自己。
移除广告
在RGB颜色空间中可视化Nemo
HSV是用于按颜色细分的颜色空间的不错选择, 但是要了解为什么, 让我们通过可视化其像素的颜色分布来比较RGB和HSV颜色空间中的图像。一个3D图很好地显示了这一点, 每个轴代表色彩空间中的一个通道。如果你想知道如何制作3D图, 请查看折叠部分:
如何显示/隐藏彩色3D散点图
要进行绘图, 你将需要更多的Matplotlib库:
>>>
>>> from mpl_toolkits.mplot3d import Axes3D
>>> from matplotlib import cm
>>> from matplotlib import colors
这些库提供了绘图所需的功能。你要根据其成分将每个像素放置在其位置, 并通过其颜色对其进行着色。 OpenCV split()在这里非常方便;它将图像分割成其组成通道。以下几行代码拆分图像并设置3D图:
>>>
>>> r, g, b = cv2.split(nemo)
>>> fig = plt.figure()
>>> axis = fig.add_subplot(1, 1, 1, projection="3d")
现在你已经设置好绘图, 你需要设置像素颜色。为了根据每个像素的真实颜色对它们进行着色, 需要进行一些调整和归一化处理。它看起来很杂乱, 但是从本质上讲, 你需要将与图像中每个像素相对应的颜色展平到一个列表中并进行规范化, 以便可以将它们传递给Matplotlib scatter()的facecolors参数。
规范化只是意味着按照facecolors参数的要求压缩从0-255到0-1的颜色范围。最后, facecolors需要一个列表, 而不是NumPy数组:
>>>
>>> pixel_colors = nemo.reshape((np.shape(nemo)[0]*np.shape(nemo)[1], 3))
>>> norm = colors.Normalize(vmin=-1., vmax=1.)
>>> norm.autoscale(pixel_colors)
>>> pixel_colors = norm(pixel_colors).tolist()
现在, 我们已经准备好绘制所有组件:每个轴的像素位置及其对应的颜色, 以facecolors期望的格式。你可以构建散点图并查看它:
>>>
>>> axis.scatter(r.flatten(), g.flatten(), b.flatten(), facecolors=pixel_colors, marker=".")
>>> axis.set_xlabel("Red")
>>> axis.set_ylabel("Green")
>>> axis.set_zlabel("Blue")
>>> plt.show()
这是RGB中Nemo图像的彩色散点图:
从该图可以看到, 图像的橙色部分几乎覆盖了红色, 绿色和蓝色值的整个范围。由于Nemo的各个部分在整个图上伸展, 因此根据RGB值的范围在RGB空间中分割Nemo并不容易。
在HSV颜色空间中可视化Nemo
我们在RGB空间中看到了Nemo, 所以现在让我们在HSV空间中查看他并进行比较。
如上所述, HSV代表色相, 饱和度和值(或亮度), 并且是圆柱形的色彩空间。颜色或色调建模为围绕中心垂直轴旋转的角度尺寸, 该轴代表值通道。值从暗(底部为0)到顶部亮。第三个轴是饱和度, 它定义了从垂直轴上的最小饱和度到离中心最远的最大饱和度的色调阴影:
图片:维基百科
要将图像从RGB转换为HSV, 可以使用cvtColor():
>>>
>>> hsv_nemo = cv2.cvtColor(nemo, cv2.COLOR_RGB2HSV)
现在, hsv_nemo将Nemo的表示形式存储在HSV中。使用与上述相同的技术, 我们可以查看由以下折叠部分生成的HSV中的图像图:
在HSV显示/隐藏中为图像生成彩色3D散点图
在HSV中显示图像的代码与RGB相同。请注意, 你使用相同的pixel_colors变量为像素着色, 因为Matplotlib期望值以RGB表示:
>>>
>>> h, s, v = cv2.split(hsv_nemo)
>>> fig = plt.figure()
>>> axis = fig.add_subplot(1, 1, 1, projection="3d")
>>> axis.scatter(h.flatten(), s.flatten(), v.flatten(), facecolors=pixel_colors, marker=".")
>>> axis.set_xlabel("Hue")
>>> axis.set_ylabel("Saturation")
>>> axis.set_zlabel("Value")
>>> plt.show()
在HSV空间中, Nemo的橙子更加本地化并且在视觉上可分离。橙子的饱和度和值确实会有所不同, 但它们大多位于沿色相轴的较小范围内。这是可用于细分的关键点。
挑选范围
让我们根据一个简单的橙色范围来设定Nemo的阈值。你可以通过注视上方的图或在线使用颜色选择应用程序(例如RGB to HSV工具)来选择范围。此处选择的色板是浅橙色和几乎是红色的深橙色:
>>>
>>> light_orange = (1, 190, 200)
>>> dark_orange = (18, 255, 255)
如果要使用Python显示所选颜色, 请单击折叠部分:
显示选择显示/隐藏的HSV颜色
在Python中显示颜色的一种简单方法是制作所需颜色的小正方形图像, 然后在Matplotlib中进行绘制。 Matplotlib仅解释RGB中的颜色, 但是为主要颜色空间提供了方便的转换功能, 以便我们可以在其他颜色空间中绘制图像:
>>>
>>> from matplotlib.colors import hsv_to_rgb
然后, 构建一个10x10x3的小正方形, 并填充相应的颜色。你可以使用NumPy轻松用颜色填充正方形:
>>>
>>> lo_square = np.full((10, 10, 3), light_orange, dtype=np.uint8) / 255.0
>>> do_square = np.full((10, 10, 3), dark_orange, dtype=np.uint8) / 255.0
最后, 你可以通过将它们转换为RGB进行查看来将它们绘制在一起:
>>>
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(hsv_to_rgb(do_square))
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(hsv_to_rgb(lo_square))
>>> plt.show()
产生这些图像, 并填充所选的颜色:
一旦获得合适的色彩范围, 就可以使用cv2.inRange()尝试对Nemo设置阈值。 inRange()具有三个参数:图像, 较低范围和较高范围。它返回图像大小的二进制掩码(1和0的ndarray), 其中值1表示范围内的值, 零值表示范围外的值:
>>>
>>> mask = cv2.inRange(hsv_nemo, light_orange, dark_orange)
要将遮罩强加于原始图像的顶部, 可以使用cv2.bitwise_and(), 如果遮罩中的对应值为1, 则它将保留给定图像中的每个像素:
>>>
>>> result = cv2.bitwise_and(nemo, nemo, mask=mask)
要查看其效果, 让我们同时查看蒙版和原始图像(蒙版位于顶部):
>>>
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(mask, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(result)
>>> plt.show()
你有它!这已经很好地捕获了鱼的橙色部分。唯一的问题是Nemo也有白色条纹……幸运的是, 添加第二个看起来像白色的面具与你对橘子所做的非常相似:
>>>
>>> light_white = (0, 0, 200)
>>> dark_white = (145, 60, 255)
指定颜色范围后, 你可以查看选择的颜色:
显示白人表演/隐藏
要显示白色, 可以采用与之前使用橙色相同的方法:
>>>
>>> lw_square = np.full((10, 10, 3), light_white, dtype=np.uint8) / 255.0
>>> dw_square = np.full((10, 10, 3), dark_white, dtype=np.uint8) / 255.0
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(hsv_to_rgb(lw_square))
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(hsv_to_rgb(dw_square))
>>> plt.show()
我在这里选择的上限是非常蓝的白色, 因为白色的阴影中确实有蓝色。让我们创建第二个遮罩, 看看它是否捕获了Nemo的条纹。你可以像制作第一个蒙版一样制作第二个蒙版:
>>>
>>> mask_white = cv2.inRange(hsv_nemo, light_white, dark_white)
>>> result_white = cv2.bitwise_and(nemo, nemo, mask=mask_white)
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(mask_white, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(result_white)
>>> plt.show()
不错!现在, 你可以组合蒙版了。将两个蒙版加在一起将得到橙色或白色的1个值, 这正是需要的值。让我们将蒙版加在一起并绘制结果:
>>>
>>> final_mask = mask + mask_white
>>> final_result = cv2.bitwise_and(nemo, nemo, mask=final_mask)
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(final_mask, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(final_result)
>>> plt.show()
本质上, 你在HSV颜色空间中对Nemo进行了粗略的细分。你会注意到, 在分割边界上有一些杂散像素, 如果愿意, 你可以使用高斯模糊来整理少量的误检。
高斯模糊是一种图像过滤器, 它使用一种称为高斯的函数来变换图像中的每个像素。其结果是消除图像噪点并减少细节。这是对我们的图片应用模糊效果的样子:
>>>
>>> blur = cv2.GaussianBlur(final_result, (7, 7), 0)
>>> plt.imshow(blur)
>>> plt.show()
移除广告
这种细分是否可以归结为Nemo的亲戚?
只是为了好玩, 让我们来看看这种分割技术在其他小丑鱼图像中的应用效果如何。在该存储库中, 有六张Google的小丑鱼图像供公众使用, 可供选择。图像位于子目录中, 并被索引为nemoi.jpg, 其中i是0-5的索引。
首先, 将所有Nemo的亲戚载入列表:
path = "./images/nemo"
nemos_friends = []
for i in range(6):
friend = cv2.cvtColor(cv2.imread(path + str(i) + ".jpg"), cv2.COLOR_BGR2RGB)
nemos_friends.append(friend)
你可以将上面使用的所有代码组合起来, 将一条鱼分割成一个函数, 该函数将图像作为输入并返回分割后的图像。展开本节以查看外观:
段鱼功能显示/隐藏
这是segment_fish()函数:
def segment_fish(image):
''' Attempts to segment the clownfish out of the provided image '''
# Convert the image into HSV
hsv_image = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
# Set the orange range
light_orange = (1, 190, 200)
dark_orange = (18, 255, 255)
# Apply the orange mask
mask = cv2.inRange(hsv_image, light_orange, dark_orange)
# Set a white range
light_white = (0, 0, 200)
dark_white = (145, 60, 255)
# Apply the white mask
mask_white = cv2.inRange(hsv_image, light_white, dark_white)
# Combine the two masks
final_mask = mask + mask_white
result = cv2.bitwise_and(image, image, mask=final_mask)
# Clean up the segmentation using a blur
blur = cv2.GaussianBlur(result, (7, 7), 0)
return blur
有了这个有用的功能, 你就可以分割所有鱼:
results = [segment_fish(friend) for friend in nemos_friends]
让我们将它们绘制成一个循环来查看所有结果:
for i in range(1, 6):
plt.subplot(1, 2, 1)
plt.imshow(nemos_friends[i])
plt.subplot(1, 2, 2)
plt.imshow(results[i])
plt.show()
前景小丑鱼的橙色阴影比我们的范围深。
Nemo侄子的阴影下半部分被完全排除, 但是背景中的紫色海葵碎片看起来非常像Nemo的蓝色条纹……
总体而言, 这种简单的细分方法已成功定位了Nemo的大多数亲戚。但是很明显, 用特定的光照和背景分割一个小丑鱼不一定能很好地推广到所有小丑鱼的分割。
结论
在本教程中, 你已经了解了几种不同的色彩空间, 如何在RGB和HSV色彩空间中分布图像, 以及如何使用OpenCV在色彩空间之间进行转换并划分范围。
总体而言, 你已经了解了如何基本了解OpenCV中的色彩空间如何用于图像中的对象分割, 并希望看到它也可以用于执行其他任务。在控制照明和背景的地方(例如在实验环境中或使用更均匀的数据集), 这种分割技术简单, 快速且可靠。