基礎(chǔ)
今天的針孔攝像頭對(duì)圖像做了很多扭曲翻具,兩個(gè)主要的扭曲是徑向畸變和切向畸變悼嫉。
由于徑向畸變艇潭,直線會(huì)顯示成曲線,當(dāng)直線離圖像中心越遠(yuǎn)時(shí)越明顯戏蔑。比如下面顯示的這張圖蹋凝,棋盤(pán)的兩個(gè)用紅色標(biāo)出來(lái)的邊緣,你可以看到棋盤(pán)不是直線总棵,也不和紅線匹配鳍寂。所有的直線都凸了。
扭曲可以用下面的來(lái)解決:
類似的情龄,另一個(gè)切向畸變是因?yàn)槌上竦墓饩€不是完全平行的到達(dá)鏡像平面伐割。所以有些區(qū)域比期望的要看上去離的近候味。可以用下面的方式解決:
簡(jiǎn)單說(shuō)隔心,我們需要找到5個(gè)參數(shù),叫做畸變參數(shù):
除此之外尚胞,我們需要找到更多的信息硬霍,比如攝像頭的內(nèi)部和外部參數(shù),內(nèi)部參數(shù)是攝像頭特定的參數(shù)笼裳。包括焦距(fx, fy)唯卖。光學(xué)中心(cx, cy)。也叫攝像機(jī)矩陣躬柬。它只依賴攝像頭本身拜轨。一旦算出來(lái)就可以保存下來(lái)為以后使用,它應(yīng)該是一個(gè)3x3的矩陣:
外部參數(shù)對(duì)應(yīng)了旋轉(zhuǎn)和平移向量來(lái)反應(yīng)一個(gè)3維的點(diǎn)到2維的系統(tǒng)里允青。
對(duì)于立體的應(yīng)用橄碾,這些扭曲需要首先被矯正燕耿。要找到所有的這些參數(shù)颖变,我們得做的是提供一些有良好定義模式的樣例圖像(比如棋盤(pán))。我們找到特定的點(diǎn)(棋盤(pán)的四個(gè)角)饮焦,我們知道他們的真實(shí)世界的坐標(biāo)琼掠,我們知道他們?cè)趫D像里的坐標(biāo)拒垃。通過(guò)這些數(shù)據(jù),后臺(tái)就能解決一些數(shù)學(xué)問(wèn)題以得到畸變參數(shù)瓷蛙。
編碼
上面提到的悼瓮,我們需要10個(gè)測(cè)試模式來(lái)做攝像機(jī)矯正。重要的輸入數(shù)據(jù)是3D真實(shí)世界的點(diǎn)和他們對(duì)應(yīng)的2D圖像的點(diǎn)艰猬。2D圖像點(diǎn)好辦我們可以很容易的從圖像里的得到横堡。
3D真實(shí)世界的點(diǎn)呢?那些圖像是從靜態(tài)攝像機(jī)拍攝姥宝,棋盤(pán)放在另一個(gè)位置和方向翅萤。所以我們需要知道(X, Y, Z)的值腊满。但是為了簡(jiǎn)單套么,我們可以說(shuō)棋盤(pán)靜止在XY平面。(所以Z=0)且攝像機(jī)相應(yīng)的移動(dòng)碳蛋。這個(gè)考慮幫我們找到X胚泌,Y值,現(xiàn)在對(duì)于X肃弟,Y值玷室,我們可以簡(jiǎn)單的傳入點(diǎn)(0, 0), (1, 0)零蓉, (2, 0)穷缤,... 表示點(diǎn)的位置敌蜂。在這種情況下,我們得到的結(jié)果是棋盤(pán)的大小量度津肛。但是如果我們知道面積章喉,(比如30毫米),我們可以傳入值(0,0), (30,0), (60, 0)身坐,...,我們可以用mm來(lái)表示結(jié)果秸脱。
3D的點(diǎn)被叫做物體點(diǎn),而2D的圖像點(diǎn)被叫做圖像點(diǎn)部蛇。
設(shè)置
要找到棋盤(pán)的模式摊唇,我們用函數(shù)cv2.findChessboardCorners()。我們也需要傳我們要找的模式的類型涯鲁,比如8x8網(wǎng)格巷查,5x5網(wǎng)格等,在這個(gè)例子里撮竿,我們使用7x6網(wǎng)格(一般來(lái)說(shuō)棋盤(pán)都是8x8的方塊7x7的內(nèi)角)吮便,它返回角點(diǎn)。這些角點(diǎn)會(huì)按照從左到右幢踏,從上到下的順序放好髓需。
這個(gè)函數(shù)可能沒(méi)法在所有圖像里找到需要的模式,所以一個(gè)號(hào)的選擇是寫(xiě)代碼房蝉,啟動(dòng)攝像機(jī)僚匆,然后檢查每幀,找需要的模式搭幻,當(dāng)取得了模式咧擂,找到角點(diǎn),并存在列表里檀蹋。同時(shí)提供一些間隔松申,然后在讀下面的幀的時(shí)候我們可以調(diào)整我們的棋盤(pán)的方向。不斷進(jìn)行這個(gè)過(guò)程知道需要的好的模式都獲取到了俯逾。即使在這個(gè)例子里贸桶,我們也不知道多少是好的,所以我們讀入所有的圖像取里面好的桌肴。
除了棋盤(pán)皇筛,我們可以使用一些環(huán)形濾線。但是之后使用函數(shù)cv2.findCirclesGrid()來(lái)找模式坠七,據(jù)說(shuō)使用環(huán)形濾線的時(shí)候回用更少的圖像水醋。
當(dāng)我們找到了角點(diǎn)旗笔,我們用cv2.cornerSubPix()函數(shù)增加他們的準(zhǔn)確度.我們也可以用cv2.drawChessboardCorners()來(lái)畫(huà)出模式,所有這些步驟用下面的代碼:
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()
一個(gè)畫(huà)了模式的圖像:
標(biāo)定
所以現(xiàn)在我們有了物體點(diǎn)拄踪,和圖像點(diǎn)蝇恶,我們可以標(biāo)定了。我們使用函數(shù)cv2.calibrateCamera()惶桐。它返回?cái)z像機(jī)矩陣艘包,畸變參數(shù)。旋轉(zhuǎn)和平移向量等耀盗。
ret,mtx,dist,rvecs,tvecs=cv2.calibrateCamera(objpoints,imgpoints,gray.shape[::-1],None,None)
反畸變
我們得到了我們要的,現(xiàn)在我們可以拿個(gè)圖像把它反畸變了卦尊。OpenCV提供了兩個(gè)方法叛拷,我們都看看,但是在此之前岂却,我們可以打磨一下攝像機(jī)矩陣忿薇,用一個(gè)cv2.getOptimalNewCameraMatrix()。如果參數(shù)alpha = 0, 它返回含有最小不需要像素的非扭曲圖像躏哩,所以它可能移除一些圖像角點(diǎn)署浩。如果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()
這是個(gè)捷徑筋栋。只用調(diào)用函數(shù),使用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.使用重測(cè)圖
這是曲線救國(guó)正驻,首先找到從扭曲圖像到非扭曲圖像的映射函數(shù)弊攘。然后使用重測(cè)函數(shù)。
# 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)
兩個(gè)方法都返回同樣的結(jié)果姑曙〗蠼唬看下面:
你可以看到在結(jié)果里所有的邊都是直的
現(xiàn)在你可以把攝像機(jī)矩陣和畸變參數(shù)存下來(lái),使用Numpy的寫(xiě)函數(shù)(np.savez, np.savetxt等)伤靠,為以后使用
重投影差
重投影差給了找到的參數(shù)是否準(zhǔn)確的一個(gè)好的估計(jì)捣域。這個(gè)應(yīng)該越接近0越好。對(duì)于內(nèi)在的宴合,扭曲的焕梅,旋轉(zhuǎn)和平移矩陣,我們首先用cv2.projectPoints()轉(zhuǎn)換物體點(diǎn)到圖像點(diǎn)形纺,然后我們計(jì)算轉(zhuǎn)換和找角點(diǎn)算法之間的絕對(duì)范數(shù)丘侠。要找到平均差我們計(jì)算所有校對(duì)圖像的算術(shù)平均值。
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)