Python 和 OpenCV 測量相機(jī)到目標(biāo)的距離

英文:Adrian Rosebrock 編譯: 伯樂在線 - G.K.
http://python.jobbole.com/84378/
幾天前,一個叫 Cameron 的 PyImageSearch 讀者發(fā)來郵件詢問攝像頭測距的方法。他花了一些時間研究,但是沒有找到解決辦法。
我很能體會 Cameron 的感受。幾年前我做過一個分析棒球離手飛向本壘的運(yùn)動的小項目。
我通過使用運(yùn)動分析和基于軌跡的跟蹤方法來確定或者估計小球在視頻幀中的位置。并且因為棒球的大小是已知的,所以我也能估計出其到本壘的距離。
那是個有趣的項目,雖然系統(tǒng)的精度沒有達(dá)到我的預(yù)期?!羟蜻\(yùn)動太快所造成的“運(yùn)動模糊”讓達(dá)到高精度變得十分困難。
我的項目完全算是一個個例,但是通常來說,在計算機(jī)視覺或者圖形處理領(lǐng)域計算從相機(jī)到目標(biāo)的距離實際上是一個非常容易的問題。你可以找到一個像三角形相似這樣簡單粗暴的方法,或者你也可以用上相機(jī)模型的內(nèi)參這樣更復(fù)雜一點(但是更精確)的方法。
在這篇博客,我將會告訴大家我和 Cameron 是如果解決這個計算相機(jī)到已知物體或目標(biāo)的距離。
千萬要看——你一定不想錯過。
OpenCV 和 Python 版本: 這個例子可以在 Python 2.7/Python 3.4+ 和 OpenCV 2.4.X上運(yùn)行。
用相似三角形計算物體或者目標(biāo)到相機(jī)的距離
我們將使用相似三角形來計算相機(jī)到一個已知的物體或者目標(biāo)的距離。
相似三角形就是這么一回事:假設(shè)我們有一個寬度為 W 的目標(biāo)或者物體。然后我們將這個目標(biāo)放在距離我們的相機(jī)為 D 的位置。我們用相機(jī)對物體進(jìn)行拍照并且測量物體的像素寬度 P 。這樣我們就得出了相機(jī)焦距的公式:
F = (P x D) / W
舉個例子,假設(shè)我在離相機(jī)距離 D = 24 英寸的地方放一張標(biāo)準(zhǔn)的 8.5 x 11 英寸的 A4 紙(橫著放;W = 11)并且拍下一張照片。我測量出照片中 A4 紙的像素寬度為 P = 249 像素。
因此我的焦距 F 是:
F = (248px x 24in) / 11in = 543.45
當(dāng)我繼續(xù)將我的相機(jī)移動靠近或者離遠(yuǎn)物體或者目標(biāo)時,我可以用相似三角形來計算出物體離相機(jī)的距離:
D’ = (W x F) / P
為了更具體,我們再舉個例子,假設(shè)我將相機(jī)移到距離目標(biāo) 3 英尺(或者說 36 英寸)的地方并且拍下上述的 A4 紙。通過自動的圖形處理我可以獲得圖片中 A4 紙的像素距離為 170 像素。將這個代入公式得:
D’ = (11in x 543.45) / 170 = 35 英寸
或者約 36 英寸,合 3 英尺。
注意:當(dāng)我給這次例子拍照時,我的卷尺有一點松,因此結(jié)果造成了大約 1 英寸的誤差。還有我也是很快速地拍下了照片并且沒有完全對齊卷尺上的腳標(biāo),這也會對最終結(jié)果的 1 英寸誤差產(chǎn)生影響。綜上所述,相似三角形的方法還是合理的,你也可以用這個方法很簡單地計算出物體或者目標(biāo)距離你的相機(jī)的距離。
現(xiàn)在理解了?
太棒了。接下來讓我們用一些代碼來看看如何用 Python、OpenCV、圖像處理和計算機(jī)視覺技術(shù)來獲得相機(jī)到物體或者目標(biāo)的距離。
用Python和OpenCV來測量相機(jī)到目標(biāo)的距離
繼續(xù),我們開始這個項目。打開一個文件,命名為distance_to_camera.py,然后就可以開工了。
# import the necessary packages
import numpy as np
import cv2
def find_marker(image):
# convert the image to grayscale, blur it, and detect edges
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 35, 125)
# find the contours in the edged image and keep the largest one;
# we'll assume that this is our piece of paper in the image
(cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
c = max(cnts, key = cv2.contourArea)
# compute the bounding box of the of the paper region and return it
return cv2.minAreaRect(c)
第一件要做的事情就是導(dǎo)入必要的包。我們將用 NumPy 來進(jìn)行數(shù)值計算和 cv2 來綁定 OpenCV 。
在那之后我們定義 find_marker 函數(shù)。這個函數(shù)接收一個 image 參數(shù),并且這意味著我們將用它來找出將要計算距離的物體。
在這個例子中我們使用標(biāo)準(zhǔn)的 8.5 x 11 英寸的 A4 紙作為我們的目標(biāo)。
目前我們的第一個任務(wù)是找出圖像中的這張紙。
我們先將圖像轉(zhuǎn)成灰度圖,用高斯模糊除去明顯的噪點,并且在第 7-9 行 使用邊緣檢測。
完成這幾步后,我們的圖像應(yīng)該長這樣:
如你所見,我們的目標(biāo)(A4 紙)的邊緣已經(jīng)很清晰了?,F(xiàn)在我們只要找出這張紙的輪廓(比如:外形)。
我們用 13 行 的 cv2.findContours 函數(shù)找到目標(biāo),并且在 14 行 計算出面積最大的輪廓。
我們假設(shè)面積最大的輪廓是我們的那張 A4 紙。這個假設(shè)在我們的這個例子是成立的,但是實際上在圖像中找出目標(biāo)是和是與應(yīng)用場景高度相關(guān)的。
在我們的例子中,簡單的邊緣檢測和計算最大的輪廓是可行的。我們可以通過使用輪廓近似法使系統(tǒng)更具魯棒性,排除不包含有4個頂點的輪廓(因為 A4 紙是矩形有四個頂點),然后計算面積最大的四點輪廓。
注意:更多這樣的方法見這篇文章,講述了如何做一個簡單粗暴的手機(jī)掃描儀。
其他找到圖像中目標(biāo)可選的方法是利用顏色特征(目標(biāo)的顏色和背景有著明顯的不同)。你還可以使用關(guān)鍵點檢測,局部不變性描述子,和關(guān)鍵點匹配來尋找目標(biāo)。但是這些方法以及超出了這篇文章的范疇,并且具有高度定制化的特性。
不管怎樣,我們現(xiàn)在獲得了目標(biāo)的輪廓,并且在第 17 行 返回包含 (x, y) 坐標(biāo)和像素高度和寬度信息的邊界框給調(diào)用函數(shù)。
讓我們也快速定義一個用上述的相似三角形法計算距離的函數(shù):
def distance_to_camera(knownWidth, focalLength, perWidth):
# compute and return the distance from the maker to the camera
return (knownWidth * focalLength) / perWidth
這個函數(shù)傳入目標(biāo)的 knownWidth ,計算好的 focalLength ,和目標(biāo)在圖像中的像素距離,并且使用上面推導(dǎo)的相似三角形公式來計算到物體的距離。
繼續(xù)讀下列代碼來看看我們是如何利用這些函數(shù)的:
#import the necessary packages
import numpy as np
import cv2
def find_marker(image):
# convert the image to grayscale, blur it, and detect edges
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 35, 125)
# find the contours in the edged image and keep the largest one;
# we'll assume that this is our piece of paper in the image
(cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
c = max(cnts, key = cv2.contourArea)
# compute the bounding box of the of the paper region and return it
return cv2.minAreaRect(c)
def distance_to_camera(knownWidth, focalLength, perWidth):
# compute and return the distance from the maker to the camera
return (knownWidth * focalLength) / perWidth
# initialize the known distance from the camera to the object, which
# in this case is 24 inches
KNOWN_DISTANCE = 24.0
# initialize the known object width, which in this case, the piece of
# paper is 11 inches wide
KNOWN_WIDTH = 11.0
# initialize the list of images that we'll be using
IMAGE_PATHS = ["images/2ft.png", "images/3ft.png", "images/4ft.png"]
# load the furst image that contains an object that is KNOWN TO BE 2 feet
# from our camera, then find the paper marker in the image, and initialize
# the focal length
image = cv2.imread(IMAGE_PATHS[0])
marker = find_marker(image)
focalLength = (marker[1][0] * KNOWN_DISTANCE) / KNOWN_WIDTH
找到圖像中目標(biāo)的距離的第一步是標(biāo)定和計算焦距。我們需要知道以下參數(shù):
相機(jī)到物體的距離
這個物體的寬度(單位英尺或米)。注意:也可以用高度,這個例子中我們使用寬度。
這里不得不提示一下我們所做的并不是實質(zhì)意義上的攝像機(jī)標(biāo)定。真正的攝像機(jī)標(biāo)定包括攝像機(jī)的內(nèi)參,你可以從這里獲得更多相關(guān)知識。
在第 25 行 我們初始化了已知的 KNOWN_DISTANCE ,從相機(jī)到物體的距離為 24 英寸。在第 29 行 我們初始了物體的寬度 KNOWN_WIDTH 為 11 英寸(一張橫著放的標(biāo)準(zhǔn) A4 紙)。
然后我們在第 32 行 定義要用到的圖片的路徑。
下一步比較重要:是一個簡單的標(biāo)定。
第 37 行 從硬盤讀取第一張圖,——我們將用這張圖來作為標(biāo)定圖片。
圖片加載以后,在第 38 行 計算圖中 A4 紙的輪廓信息,在第 39 行 使用三角形相似法計算出 focalLength。
由于我們已經(jīng)“標(biāo)定”了我們的系統(tǒng)并且獲得了 focalLength ,我們可以很容易地計算出相機(jī)離接下來圖片中目標(biāo)的距離。
讓我們看看這個是這么做的:
41 # loop over the images
42 for imagePath in IMAGE_PATHS:
# load the image, find the marker in the image, then compute the
# distance to the marker from the camera
image = cv2.imread(imagePath)
46 marker = find_marker(image)
47 inches = distance_to_camera(KNOWN_WIDTH, focalLength, marker[1][0])
# draw a bounding box around the image and display it
box = np.int0(cv2.cv.BoxPoints(marker))
cv2.drawContours(image, [box], -1, (0, 255, 0), 2)
cv2.putText(image, "%.2fft" % (inches / 12),
(image.shape[1] - 200, image.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX,
2.0, (0, 255, 0), 3)
cv2.imshow("image", image)
cv2.waitKey(0)
在第 42 行 開始遍歷所有的圖片路徑。
然后,在第 45 行 我們將列表中所有的圖片從硬盤讀取下來。在第 46 行 提取目標(biāo)輪廓,并且在第 47 行 計算攝像機(jī)到物體的距離。
在第 50-56 行,我們簡單地畫出目標(biāo)的邊框并且顯示出距離。
結(jié)果
來看看我們的腳本運(yùn)作,打開一個終端,導(dǎo)航到你的代碼目錄,執(zhí)行以下命令:
$ python distance_to_camera.py
如果一切正常你將會看到 2ft.png 的結(jié)果,這張圖是用來“標(biāo)定”我們的系統(tǒng)并且計算初始的 focalLength:
從上面的圖片我們可以看到我們的焦距被正確地計算出來并且按照代碼中的變量 KNOWN_DISTANCE 和 KNOWN_WIDTH,A4 紙的距離是 2 英尺。
現(xiàn)在我們有了焦距,我們可以在接下來的圖片中計算出目標(biāo)的距離:

上上面的例子,我們的相機(jī)大概離目標(biāo)有 3 英尺遠(yuǎn)。
讓我們退后一步:

再次需要注意的是,我在拍這個例子的時候動作很快并且卷尺并沒有繃緊。而且,我也沒有確保我的相機(jī)是百分之百地對準(zhǔn)目標(biāo)底部,因此,這些例子總會有大概 1 英寸的誤差。
以上是我要說的,這篇文章描述的三角形相似法仍然可以用,并且能夠讓你測量出圖像上的物體或目標(biāo)到你相機(jī)的距離。
總結(jié)
在這篇博客我們學(xué)習(xí)了如何計算一個圖像上的已知物體到相機(jī)的距離。
為了完成這個任務(wù)我們利用了三角形相似法,并且需要知道兩個重要的參數(shù):
1、 目標(biāo)的實際寬度(或高度),單位可以是英寸或者米。
2、 標(biāo)定過程 1 中相機(jī)到目標(biāo)的距離。
計算機(jī)視覺和圖像處理算法可以被用來自動檢測圖像中物體的像素寬度或高度并且完成相似三角形的計算,得出一個焦距。
然后在接下來的圖片中,我們只要提取出目標(biāo)輪廓就可以利用得到的焦距測量出目標(biāo)到相機(jī)的距離。
End 
聲明:部分內(nèi)容來源于網(wǎng)絡(luò),僅供讀者學(xué)術(shù)交流之目的。文章版權(quán)歸原作者所有。如有不妥,請聯(lián)系刪除。
往期推薦
