【乾貨】OpenCV測量圖像中物體的尺寸大小

首先,您需要知道類似於比例的概念稱爲每度量比的像素pixels per metric ratio)。

近似含義是每個單位指標中包含的像素數。例如,圖表上的1釐米包含100張圖像。

實際上,相當於引用對象的作用,例如已知地圖上的引用材質,我們可以使用此引用對象將其轉換爲地圖上其他對象的大小。

引用對象需要具有兩個重要屬性:

  • 屬性#1:我們應該以可測量的單位(例如毫米,英寸等)知道該對象的尺寸(在寬度或高度方面)。
  • 屬性#2:我們應該能夠在圖像中輕鬆找到這個參考對象,或者根據對象的 位置(例如參考對象總是放在圖像的左上角),或者通過外觀(如是獨特的顏色或形狀,獨特且與圖像中的所有其他物體不同)。在任何一種情況下,我們的參考應該 以某種方式唯一可識別。

我們應該能夠輕鬆地在圖像中找到參考對象,無論是基於對象的位置(例如,參考對象總是放置在圖像的左上角)還是通過外觀(例如,獨特的顏色或形狀,與圖像不同)其他物品)。在任何一種情況下,我們的參考應該以某種方式唯一可識別。

在下面的示例中,我們將使用美國硬幣作爲參考對象。在所有示例中,確保它始終是圖像中最左側的對象。

我們將使用美國四分之一作爲參考對象,並確保它始終作爲圖像中最左側的對象放置,使我們可以通過基於其位置對輪廓進行排序來輕鬆提取它。

通過保證四分之一是最左邊的對象,我們可以從左到右對對象輪廓進行排序,抓住四分之一(它將始終是排序列表中的第一個輪廓),並使用它來定義 pixel_per_metric,我們定義爲:

pixels_per_metric = object_width / know_width

美國四分之一的 已知寬度爲0.955英寸。現在,假設我們的 object_width(以像素爲單位)計算爲150像素寬(基於其關聯的邊界框)。

因此 pixels_per_metric是:

pixels_per_metric = 150px / 0.955in = 157px

因此暗示在我們的圖像中每0.955英寸大約有157個像素。使用此比率,我們可以計算圖像中對象的大小。

用計算機視覺測量物體的大小

現在我們瞭解“每度量像素數”比率,我們可以實現用於測量圖像中對象大小的Python驅動程序腳本。

打開一個新文件,將其命名爲 object_size 。py ,並插入以下代碼:

# import the necessary packages

from scipy.spatial import distance as dist

from imutils import perspective

from imutils import contours

import numpy as np

import argparse

import imutils

import cv2

def midpoint(ptA, ptB):

return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)

# construct the argument parse and parse the arguments

ap = argparse.ArgumentParser()

ap.add_argument("-i", "--image", required=True,

help="path to the input image")

ap.add_argument("-w", "--width", type=float, required=True,

help="width of the left-most object in the image (in inches)")

args = vars(ap.parse_args())

第2-8行導入我們所需的Python包。我們將在此示例中大量使用imutils包,因此如果您沒有安裝它,請確保在繼續之前安裝它:

$ pip install imutils

請確保您擁有最新版本

pip install --upgrade imutils

第10行和第11行定義了一個稱爲中點的輔助方法 ,顧名思義,它用於計算兩組(x,y)座標之間 的中點。

然後我們在第14-19行解析命令行參數 。我們需要兩個參數, - image ,它是包含我們想要測量的對象的輸入圖像的路徑,以及 - width ,它是我們的參考對象的寬度(以英寸爲單位),被認爲是最左邊的我們的形象 。

我們現在可以加載我們的圖像並預處理它:

# load the image, convert it to grayscale, and blur it slightly

image = cv2.imread(args["image"])

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

gray = cv2.GaussianBlur(gray, (7, 7), 0)

# perform edge detection, then perform a dilation + erosion to

# close gaps in between object edges

edged = cv2.Canny(gray, 50, 100)

edged = cv2.dilate(edged, None, iterations=1)

edged = cv2.erode(edged, None, iterations=1)

# find contours in the edge map

cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,

cv2.CHAIN_APPROX_SIMPLE)

cnts = imutils.grab_contours(cnts)

# sort the contours from left-to-right and initialize the

# 'pixels per metric' calibration variable

(cnts, _) = contours.sort_contours(cnts)

pixelsPerMetric = None

第22-24行從磁盤加載我們的圖像,將其轉換爲灰度,然後使用高斯濾波器對其進行平滑處理。然後,我們進行邊緣檢測以及擴張+侵蝕,以封閉邊緣圖中邊緣之間的任何間隙(第28-30行)。

第33-35行找到與我們的邊緣圖中的對象相對應的輪廓(即輪廓)。

然後在第39行從左到右(允許我們提取參考對象)對這些輪廓進行排序 。我們還在第40行初始化 pixelPerMetric 值 。

下一步是檢查每個輪廓:

# loop over the contours individually

for c in cnts:

# if the contour is not sufficiently large, ignore it

if cv2.contourArea(c) < 100:

continue

# compute the rotated bounding box of the contour

orig = image.copy()

box = cv2.minAreaRect(c)

box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)

box = np.array(box, dtype="int")

# order the points in the contour such that they appear

# in top-left, top-right, bottom-right, and bottom-left

# order, then draw the outline of the rotated bounding

# box

box = perspective.order_points(box)

cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 2)

# loop over the original points and draw them

for (x, y) in box:

cv2.circle(orig, (int(x), int(y)), 5, (0, 0, 255), -1)

在 第43行,我們開始在每個輪廓上循環。如果輪廓不夠大,我們丟棄該區域,假設它是邊緣檢測過程遺留的噪聲(第45和46行)。

如果輪廓區域足夠大,我們計算第50-52行上圖像的旋轉邊界框 ,特別注意使用 cv2 。cv 。BoxPoints 功能適用於OpenCV 2.4和 cv2 。openCV 3的boxPoints方法。

然後,我們將旋轉的邊界框 座標排列 在左上角,右上角,右下角和左下角,如上週的博文 (第58行)所述。

最後, 第59-63行以綠色繪製對象 的輪廓,然後以小的紅色圓圈繪製邊界框矩形的頂點。

現在我們已經訂購了邊界框,我們可以計算出一系列中點:

# unpack the ordered bounding box, then compute the midpoint

# between the top-left and top-right coordinates, followed by

# the midpoint between bottom-left and bottom-right coordinates

(tl, tr, br, bl) = box

(tltrX, tltrY) = midpoint(tl, tr)

(blbrX, blbrY) = midpoint(bl, br)

# compute the midpoint between the top-left and top-right points,

# followed by the midpoint between the top-righ and bottom-right

(tlblX, tlblY) = midpoint(tl, bl)

(trbrX, trbrY) = midpoint(tr, br)

# draw the midpoints on the image

cv2.circle(orig, (int(tltrX), int(tltrY)), 5, (255, 0, 0), -1)

cv2.circle(orig, (int(blbrX), int(blbrY)), 5, (255, 0, 0), -1)

cv2.circle(orig, (int(tlblX), int(tlblY)), 5, (255, 0, 0), -1)

cv2.circle(orig, (int(trbrX), int(trbrY)), 5, (255, 0, 0), -1)

# draw lines between the midpoints

cv2.line(orig, (int(tltrX), int(tltrY)), (int(blbrX), int(blbrY)),

(255, 0, 255), 2)

cv2.line(orig, (int(tlblX), int(tlblY)), (int(trbrX), int(trbrY)),

(255, 0, 255), 2)

第68-70行打開我們訂購的邊界框,然後計算左上角和右上角之間的中點,然後是右下角之間的中點。

我們還將分別計算左上角+左下角和右上角+右下角之間的中點(第74和75行)。

第78-81行在我們的圖像上繪製 藍色中點 ,然後用紫色線連接中點 。

接下來,我們需要 通過調查我們的引用對象來初始化 pixelPerMetric變量:

# compute the Euclidean distance between the midpoints

dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))

dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))

# if the pixels per metric has not been initialized, then

# compute it as the ratio of pixels to supplied metric

# (in this case, inches)

if pixelsPerMetric is None:

pixelsPerMetric = dB / args["width"]

首先,我們計算我們的中點集之間的歐幾里德距離(第90和91行)。該 DA 變量將包含 高度距離(以像素爲單位),而 分貝 將保存我們的 寬度的距離。

然後,我們就檢查 96號線,看看我們的 pixelsPerMetric 變量已經初始化,如果沒有,我們把 分貝 由我們提供 - 寬度 ,從而使我們每英寸我們的(近似)的像素。

現在我們 已經定義了 pixelPerMetric變量,我們可以測量圖像中對象的大小:

# compute the size of the object

dimA = dA / pixelsPerMetric

dimB = dB / pixelsPerMetric

# draw the object sizes on the image

cv2.putText(orig, "{:.1f}in".format(dimA),

(int(tltrX - 15), int(tltrY - 10)), cv2.FONT_HERSHEY_SIMPLEX,

0.65, (255, 255, 255), 2)

cv2.putText(orig, "{:.1f}in".format(dimB),

(int(trbrX + 10), int(trbrY)), cv2.FONT_HERSHEY_SIMPLEX,

0.65, (255, 255, 255), 2)

# show the output image

cv2.imshow("Image", orig)

cv2.waitKey(0)

第100行和第101行通過將相應的歐幾里德距離除以pixelsPerMetric 值來計算對象的尺寸(以英寸爲單位) (有關此比率的工作原理的詳細信息,請參閱上面的 “每公制像素數”部分)。

第104-109行在圖像上繪製對象的尺寸 ,而 第112和113行顯示輸出結果。

物體尺寸測量結果

測試我們的 object_size 。py 腳本,只需發出以下命令:

$ python object_size.py --image images/example_01.png --width 0.955

輸出應如下所示:

圖2:使用OpenCV,Python和計算機視覺+圖像處理技術測量圖像中對象的大小。

如您所見,我們已成功計算出圖像中每個對象的大小 - 我們的名片正確報告爲 3.5in x 2in。同樣,我們的鎳被準確地描述爲 0.8英寸x 0.8英寸。

然而,並非我們所有的結果都是 完美的。

據報道,Game Boy墨盒的尺寸略有不同(即使尺寸相同)。兩個季度的高度也 減去了0.1英寸。

那麼爲什麼呢?爲什麼物體測量不是100%準確?

原因有兩方面:

首先,我匆匆用iPhone拍了這張照片。角度肯定 不是物體上“俯視”(如鳥瞰圖)的完美90度角。如果沒有完美的90度視圖(或儘可能接近它),對象的尺寸可能會出現扭曲。

其次,我沒有使用相機的內在和外在參數來校準我的iPhone。在不確定這些參數的情況下,照片可能容易發生徑向和切向鏡頭失真。執行額外的校準步驟來查找這些參數可以“扭曲”我們的圖像並導致更好的對象大小近似(但我將討論失真校正作爲未來博客文章的主題)。

同時,在拍攝物體照片時儘量獲得儘可能接近90度的視角 - 這有助於提高物體尺寸估計的準確性。

那就是說,讓我們看一下測量物體尺寸的第二個例子,這次測量藥丸的尺寸:

在美國,所有20,000多種處方藥中有近50%是圓形和/或白色,因此如果我們可以根據它們的測量值來過濾藥丸,我們就有更好的機會準確識別藥物。

最後,我們有一個最後的例子,這次使用 3.5英寸x 2英寸名片來測量兩個乙烯基EP和一個信封的大小:

  •  

$ python object_size.py --image images/example_03.png --width 3.5

同樣,結果不是很完美,但這是由於(1)視角和(2)透鏡畸變,如上所述。

總結

在本文中,我們學習瞭如何通過使用python和OpenCV來測量圖片中的物體的大小。

我們需要確定pixels per metric比率(單位尺寸像素數),即在給定的度量(如英寸、毫米、米等)下,像素的數量。

爲了計算這個比率,我們需要一個參考物體,它需要兩點重要的性質:

1、參考物體需要有含測量單位(英寸、毫米等等)的尺寸

2、無論從物體的位置還是形狀,參考物體都需要容易被找到。

加入上面的性質都能滿足,你可以使用參考物體計算pixels per metric比率,並根據這個計算圖片中物體的大小。

相關資源關注微信公衆號:“圖像算法”或者微信搜索imalg_cn 獲取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章