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計算輪廓中心。上週,我們學習瞭如何利用輪廓逼近來檢測圖像中的形狀。最後,今天,我們將形狀檢測算法與顏色標記器相結合,用於標記形狀的特定顏色名稱。
雖然此方法適用於半控制的照明條件下的較小顏色集,但可能不適用於控制較少的環境中的較大調色板。正如我在這篇文章的“限制”部分所暗示的那樣,實際上,我們有一種方法可以“學習”現實世界中“看起來”的顏色。我將在以後的博客文章中保留對這種方法的討論。