OpenCV-Python教程:46.攝像頭標定

基礎

今天的針孔攝像頭對圖像做了很多扭曲,兩個主要的扭曲是徑向畸變和切向畸變。

由于徑向畸變,直線會顯示成曲線,當直線離圖像中心越遠時越明顯。比如下面顯示的這張圖,棋盤的兩個用紅色標出來的邊緣,你可以看到棋盤不是直線,也不和紅線匹配。所有的直線都凸了。

扭曲可以用下面的來解決:

類似的,另一個切向畸變是因為成像的光線不是完全平行的到達鏡像平面。所以有些區域比期望的要看上去離的近。可以用下面的方式解決:

簡單說,我們需要找到5個參數,叫做畸變參數:

除此之外,我們需要找到更多的信息,比如攝像頭的內部和外部參數,內部參數是攝像頭特定的參數。包括焦距(fx, fy)。光學中心(cx, cy)。也叫攝像機矩陣。它只依賴攝像頭本身。一旦算出來就可以保存下來為以后使用,它應該是一個3x3的矩陣:

外部參數對應了旋轉和平移向量來反應一個3維的點到2維的系統里。

對于立體的應用,這些扭曲需要首先被矯正。要找到所有的這些參數,我們得做的是提供一些有良好定義模式的樣例圖像(比如棋盤)。我們找到特定的點(棋盤的四個角),我們知道他們的真實世界的坐標,我們知道他們在圖像里的坐標。通過這些數據,后臺就能解決一些數學問題以得到畸變參數。

編碼

上面提到的,我們需要10個測試模式來做攝像機矯正。重要的輸入數據是3D真實世界的點和他們對應的2D圖像的點。2D圖像點好辦我們可以很容易的從圖像里的得到。

3D真實世界的點呢?那些圖像是從靜態攝像機拍攝,棋盤放在另一個位置和方向。所以我們需要知道(X, Y, Z)的值。但是為了簡單,我們可以說棋盤靜止在XY平面。(所以Z=0)且攝像機相應的移動。這個考慮幫我們找到X,Y值,現在對于X,Y值,我們可以簡單的傳入點(0, 0), (1, 0), (2, 0),... 表示點的位置。在這種情況下,我們得到的結果是棋盤的大小量度。但是如果我們知道面積,(比如30毫米),我們可以傳入值(0,0), (30,0), (60, 0),...,我們可以用mm來表示結果。

3D的點被叫做物體點,而2D的圖像點被叫做圖像點。

設置

要找到棋盤的模式,我們用函數cv2.findChessboardCorners()。我們也需要傳我們要找的模式的類型,比如8x8網格,5x5網格等,在這個例子里,我們使用7x6網格(一般來說棋盤都是8x8的方塊7x7的內角),它返回角點。這些角點會按照從左到右,從上到下的順序放好。

這個函數可能沒法在所有圖像里找到需要的模式,所以一個號的選擇是寫代碼,啟動攝像機,然后檢查每幀,找需要的模式,當取得了模式,找到角點,并存在列表里。同時提供一些間隔,然后在讀下面的幀的時候我們可以調整我們的棋盤的方向。不斷進行這個過程知道需要的好的模式都獲取到了。即使在這個例子里,我們也不知道多少是好的,所以我們讀入所有的圖像取里面好的。

除了棋盤,我們可以使用一些環形濾線。但是之后使用函數cv2.findCirclesGrid()來找模式,據說使用環形濾線的時候回用更少的圖像。

當我們找到了角點,我們用cv2.cornerSubPix()函數增加他們的準確度.我們也可以用cv2.drawChessboardCorners()來畫出模式,所有這些步驟用下面的代碼:

import numpy as np
import cv2
import glob

# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)

# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.

images = glob.glob('*.jpg')

for fname in images:
? ? img = cv2.imread(fname)
? ? gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

? ? # Find the chess board corners
? ? ret, corners = cv2.findChessboardCorners(gray, (7,6),None)

? ? # If found, add object points, image points (after refining them)
? ? if ret == True:
? ? ? ? objpoints.append(objp)

? ? ? ? corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
? ? ? ? imgpoints.append(corners2)

? ? ? ? # Draw and display the corners
? ? ? ? img = cv2.drawChessboardCorners(img, (7,6), corners2,ret)
? ? ? ? cv2.imshow('img',img)
? ? ? ? cv2.waitKey(500)

cv2.destroyAllWindows()

一個畫了模式的圖像:


標定

所以現在我們有了物體點,和圖像點,我們可以標定了。我們使用函數cv2.calibrateCamera()。它返回攝像機矩陣,畸變參數。旋轉和平移向量等。

ret,mtx,dist,rvecs,tvecs=cv2.calibrateCamera(objpoints,imgpoints,gray.shape[::-1],None,None)

反畸變

我們得到了我們要的,現在我們可以拿個圖像把它反畸變了。OpenCV提供了兩個方法,我們都看看,但是在此之前,我們可以打磨一下攝像機矩陣,用一個cv2.getOptimalNewCameraMatrix()。如果參數alpha = 0, 它返回含有最小不需要像素的非扭曲圖像,所以它可能移除一些圖像角點。如果alpha = 1, 所有像素都返回。

img=cv2.imread('left12.jpg')
h,w=img.shape[:2]
newcameramtx,roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))

1. 使用cv2.undistort()

這是個捷徑。只用調用函數,使用ROI

# undistort
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png', dst)

2.使用重測圖

這是曲線救國,首先找到從扭曲圖像到非扭曲圖像的映射函數。然后使用重測函數。

# undistort
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx,(w,h), 5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)

# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png', dst)

兩個方法都返回同樣的結果。看下面:

你可以看到在結果里所有的邊都是直的

現在你可以把攝像機矩陣和畸變參數存下來,使用Numpy的寫函數(np.savez, np.savetxt等),為以后使用

重投影差

重投影差給了找到的參數是否準確的一個好的估計。這個應該越接近0越好。對于內在的,扭曲的,旋轉和平移矩陣,我們首先用cv2.projectPoints()轉換物體點到圖像點,然后我們計算轉換和找角點算法之間的絕對范數。要找到平均差我們計算所有校對圖像的算術平均值。

mean_error = 0
for i in xrange(len(objpoints)):
? ? imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
? ? error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2)/len(imgpoints2)
? ? tot_error += error
? ? print "total error: ", mean_error/len(objpoints)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容