1 前言
这是我们关于形状检测和分析的三部分系列的最后一篇文章。
以前,我们学习了如何:
今天,我们将对图像中的对象执行形状检测和颜色标记。
在这一点上,我们理解图像的区域可以通过颜色直方图和基本颜色通道统计信息(例如均值和标准差)来表征。
但是,尽管我们可以计算这些统计数据,但它们无法为我们提供实际的标签,例如将区域标记为包含特定颜色的“红色”,“绿色”,“蓝色”或“黑色”。
在此博客文章中,我将详细介绍如何利用L*a*b*颜色空间以及欧几里德距离来使用Python和OpenCV标记,标注和确定图像中对象的颜色。
2 使用OpenCV确定对象颜色
在深入研究任何代码之前,让我们简要回顾一下我们的项目结构:
|--- pyimagesearch
| |--- __init__.py
| |--- colorlabeler.py
| |--- shapedetector.py
|--- detect_color.py
|--- example_shapes.png
注意我们如何重用我们先前博客文章中的shapedetector.py
和ShapeDetector
类。我们还将创建一个新文件colorlabeler.py
,该文件将使用颜色的文本标签标记图像区域。
最后,将使用detect_color.py
驱动程序脚本将所有片段粘合在一起。
在继续阅读本文之前,请确保已在系统上安装了imutils Python软件包:
$ pip install imutils
在本课程的其余部分中,我们将在该库中使用各种功能。
2.1 标记图像中的颜色
该项目的第一步是创建一个Python类,该类可用于用其关联的颜色标记图像中的形状。
为此,我们在colorlabeler.py
文件中定义一个名为ColorLabeler
的类:
# import the necessary packages
from scipy.spatial import distance as dist
from collections import OrderedDict
import numpy as np
import cv2
class ColorLabeler:
def __init__(self):
# initialize the colors dictionary, containing the color
# name as the key and the RGB tuple as the value
colors = OrderedDict({
"red": (255, 0, 0),
"green": (0, 255, 0),
"blue": (0, 0, 255)})
# allocate memory for the L*a*b* image, then initialize
# the color names list
self.lab = np.zeros((len(colors), 1, 3), dtype="uint8")
self.colorNames = []
# loop over the colors dictionary
for (i, (name, rgb)) in enumerate(colors.items()):
# update the L*a*b* array and the color names list
self.lab[i] = rgb
self.colorNames.append(name)
# convert the L*a*b* array from the RGB color space
# to L*a*b*
self.lab = cv2.cvtColor(self.lab, cv2.COLOR_RGB2LAB)
第2-5行导入我们所需的Python程序包,而第7行定义ColorLabeler
类。
然后我们进入第8行的构造函数。首先,我们需要初始化一个colors
字典(第11-14行),该字典指定颜色名称(字典的键)到RGB元组(字典的值)。
从那里,我们为NumPy数组分配内存以存储这些颜色,然后初始化颜色名称列表(第18和19行)。
下一步是遍历colors
字典,然后分别更新NumPy数组和colorNames
列表(第22-25行)。
最后,我们将NumPy“图像”从RGB颜色空间转换为L*a*b*颜色空间。
那么为什么我们使用L*a*b*颜色空间而不是RGB或HSV?
好吧,为了实际地标记和标记图像的区域包含某种颜色,我们将计算已知colors的数据集(即lab
数组)与特定图像区域的平均值之间的欧几里得距离。
使欧几里德距离最小的已知颜色将被选作颜色标识。
而且与HSV和RGB颜色空间不同,L*a*b*颜色之间的欧几里得距离具有实际的感知意义,因此在本文的其余部分中将使用它。
下一步是定义label
方法:
def label(self, image, c):
# construct a mask for the contour, then compute the
# average L*a*b* value for the masked region
mask = np.zeros(image.shape[:2], dtype="uint8")
cv2.drawContours(mask, [c], -1, 255, -1)
mask = cv2.erode(mask, None, iterations=2)
mean = cv2.mean(image, mask=mask)[:3]
# initialize the minimum distance found thus far
minDist = (np.inf, None)
# loop over the known L*a*b* color values
for (i, row) in enumerate(self.lab):
# compute the distance between the current L*a*b*
# color value and the mean of the image
d = dist.euclidean(row[0], mean)
# if the distance is smaller than the current distance,
# then update the bookkeeping variable
if d < minDist[0]:
minDist = (d, i)
# return the name of the color with the smallest distance
return self.colorNames[minDist[1]]
label方法需要两个参数:L*a*b*图像
,其中包含我们要为其计算颜色通道统计信息的形状,然后是c
,我们感兴趣的图像
的轮廓区域。
第34和35行为轮廓区域构造了一个mask,我们可以在下面看到一个示例:
注意如何将mask
的前景区域设置为白色,而背景设置为黑色。我们只会在图像的mask(白色)区域内执行计算。
第37行仅针对mask
区域计算图像的L*,a*和b *通道中每个通道的平均值。
最后,第43-51行处理遍历lab
矩阵的每一行,计算每种已知颜色与平均颜色之间的欧几里得距离,然后返回具有最小欧几里得距离的颜色的名称。
2.2 定义颜色标签和形状检测过程
现在我们已经定义了ColorLabeler
,让我们创建detect_color.py
驱动程序脚本。在此脚本中,我们将结合上周的ShapeDetector
类和今天的帖子中的ColorLabeler
。
让我们开始吧:
# import the necessary packages
from pyimagesearch.shapedetector import ShapeDetector
from pyimagesearch.colorlabeler import ColorLabeler
import argparse
import imutils
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to the input image")
args = vars(ap.parse_args())
第2-6行导入所需的Python软件包,请注意我们如何导入ShapeDetector
和ColorLabeler
。
然后,第9-12行解析我们的命令行参数。像本系列中的其他两篇文章一样,我们只需要一个参数即可:--image
,即我们要处理的图像在硬盘上路径。
接下来,我们可以加载图像并进行处理:
# load the image and resize it to a smaller factor so that
# the shapes can be approximated better
image = cv2.imread(args["image"])
resized = imutils.resize(image, width=300)
ratio = image.shape[0] / float(resized.shape[0])
# blur the resized image slightly, then convert it to both
# grayscale and the L*a*b* color spaces
blurred = cv2.GaussianBlur(resized, (5, 5), 0)
gray = cv2.cvtColor(blurred, cv2.COLOR_BGR2GRAY)
lab = cv2.cvtColor(blurred, cv2.COLOR_BGR2LAB)
thresh = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY)[1]
# find contours in the thresholded image
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# initialize the shape detector and color labeler
sd = ShapeDetector()
cl = ColorLabeler()
第16-18行从磁盘加载图像,然后创建其调整大小的版本resized
,并跟踪原始高度与调整后的高度的比率ratio
。我们调整图像大小,以便轮廓近似更精确地用于形状识别。此外,图像越小,要处理的数据越少,因此我们的代码将执行得更快。
第22-25行将高斯平滑应用于我们调整大小后的图像,转换为灰度和L*a*b*,最后进行阈值处理以显示图像中的形状:
在第29-30行,我们找到形状的轮廓,并根据我们的OpenCV版本获取适当的cnts
元组值。
现在我们准备检测图像中每个对象的形状和颜色:
# loop over the contours
for c in cnts:
# compute the center of the contour
M = cv2.moments(c)
cX = int((M["m10"] / M["m00"]) * ratio)
cY = int((M["m01"] / M["m00"]) * ratio)
# detect the shape of the contour and label the color
shape = sd.detect(c)
color = cl.label(lab, c)
# multiply the contour (x, y)-coordinates by the resize ratio,
# then draw the contours and the name of the shape and labeled
# color on the image
c = c.astype("float")
c *= ratio
c = c.astype("int")
text = "{} {}".format(color, shape)
cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
cv2.putText(image, text, (cX, cY),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
# show the output image
cv2.imshow("Image", image)
cv2.waitKey(0)
我们开始在第38行上遍历每个轮廓,而第40-42行计算形状的中心。
使用轮廓,我们可以检测物体的形状,然后在第45和46行确定其颜色。
最后,第51-57行处理当前形状的轮廓,然后在输出图像上绘制颜色和文本标签。
第60和61行将结果显示到我们的屏幕上。
2.3 颜色标签结果
执行以下命令:
$ python detect_color.py --image example_shapes.png
从上面的GIF中可以看出,每个对象在形状和颜色方面都已正确识别。
3 局限性
使用本文中介绍的方法标记颜色的主要缺点之一是,由于光照条件以及各种色调和饱和度,颜色很少看起来像纯红色,绿色,蓝色等。
您通常可以使用L*a*b*颜色空间和欧几里得距离来识别少量颜色,但是对于较大的调色板,此方法可能会返回错误的结果,具体取决于图像的复杂性。
因此,话虽如此,我们如何才能更可靠地标记图像中的颜色?
也许有一种方法可以“学习”现实世界中颜色的“外观”。
确实有。
这正是我将在以后的博客文章中讨论的内容。
4 总结
今天是我们关于形状检测和分析的三部分系列的最后一篇文章。
我们首先学习如何使用OpenCV计算轮廓中心。上周,我们学习了如何利用轮廓逼近来检测图像中的形状。最后,今天,我们将形状检测算法与颜色标记器相结合,用于标记形状的特定颜色名称。
虽然此方法适用于半控制的照明条件下的较小颜色集,但可能不适用于控制较少的环境中的较大调色板。正如我在这篇文章的“限制”部分所暗示的那样,实际上,我们有一种方法可以“学习”现实世界中“看起来”的颜色。我将在以后的博客文章中保留对这种方法的讨论。