在本節中,咱們將學習算法
一些針孔相機會給圖像帶來明顯的失真。兩種主要的變形是徑向變形和切向變形。徑向變形會致使直線出現彎曲。距圖像中心越遠,徑向畸變越大。例如,下面顯示一個圖像,其中棋盤的兩個邊緣用紅線標記。可是,您會看到棋盤的邊框不是直線,而且與紅線不匹配。全部預期的直線都凸出。有關更多詳細信息,請訪問「失真(光學)」。數組
徑向變形能夠表示成以下:app
一樣,因爲攝像鏡頭未徹底平行於成像平面對齊,所以會發生切向畸變。所以,圖像中的某些區域看起來可能比預期的要近。切向畸變的量能夠表示爲:ide
簡而言之,咱們須要找到五個參數,稱爲失真係數,公式以下:函數
除此以外,咱們還須要其餘一些信息,例如相機的內在和外在參數。內部參數特定於攝像機。它們包括諸如焦距(fx,fy)(f_x,f_y)(fx,fy)和光學中心(cx,cy)(c_x,c_y)(cx,cy)之類的信息。焦距和光學中心可用於建立相機矩陣,該相機矩陣可用於消除因爲特定相機鏡頭而引發的畸變。相機矩陣對於特定相機而言是惟一的,所以一旦計算出,就能夠在同一相機拍攝的其餘圖像上重複使用。它表示爲3x3矩陣:學習
外在參數對應於旋轉和平移矢量,其將3D點的座標平移爲座標系。測試
對於立體聲應用,首先須要糾正這些失真。要找到這些參數,咱們必須提供一些定義良好的圖案的示例圖像(例如國際象棋棋盤)。咱們找到一些已經知道其相對位置的特定點(例如棋盤上的四角)。咱們知道現實世界空間中這些點的座標,也知道圖像中的座標,所以咱們能夠求解失真係數。爲了得到更好的結果,咱們至少須要10個測試模式。優化
如上所述,相機校準至少須要10個測試圖案。OpenCV附帶了一些國際象棋棋盤的圖像(請參見samples / data / left01.jpg – left14.jpg),所以咱們將利用這些圖像。考慮棋盤的圖像。相機校準所需的重要輸入數據是3D現實世界點集以及圖像中這些點的相應2D座標。能夠從圖像中輕鬆找到2D圖像點。(這些圖像點是國際象棋棋盤中兩個黑色正方形相互接觸的位置)3d
真實世界中的3D點如何處理?這些圖像是從靜態相機拍攝的,而國際象棋棋盤放置在不一樣的位置和方向。所以,咱們須要知道(X,Y,Z)(X,Y,Z)(X,Y,Z)值。可是爲簡單起見,咱們能夠說棋盤在XY平面上保持靜止(所以Z始終爲0),而且照相機也相應地移動了。這種考慮有助於咱們僅找到X,Y值。如今對於X,Y值,咱們能夠簡單地將點傳遞爲(0,0),(1,0),(2,0),…,這表示點的位置。在這種狀況下,咱們獲得的結果將是棋盤正方形的大小比例。可是,若是咱們知道正方形大小(例如30毫米),則能夠將值傳遞爲(0,0),(30,0),(60,0),…。所以,咱們獲得的結果以毫米爲單位。(在這種狀況下,咱們不知道正方形的大小,由於咱們沒有拍攝那些圖像,所以咱們以正方形的大小進行傳遞)。日誌
3D點稱爲對象點,而2D圖像點稱爲圖像點。
所以,要在國際象棋棋盤中查找圖案,咱們可使用函數cv.findChessboardCorners()。咱們還須要傳遞所需的圖案,例如8x8網格,5x5網格等。在此示例中,咱們使用7x6網格。(一般,棋盤有8x8的正方形和7x7的內部角)。它返回角點和retval,若是得到圖案,則爲True。這些角將按順序放置(從左到右,從上到下)
此功能可能沒法在全部圖像中找到所需的圖案。所以,一個不錯的選擇是編寫代碼,使它啓動相機並檢查每幀所需的圖案。得到圖案後,找到角並將其存儲在列表中。另外,在閱讀下一幀以前請提供一些時間間隔,以便咱們能夠在不一樣方向上調整棋盤。繼續此過程,直到得到所需數量的良好圖案爲止。即便在此處提供的示例中,咱們也不肯定給出的14張圖像中有多少張是好的。
所以,咱們必須閱讀全部圖像並僅拍攝好圖像。
除了棋盤,咱們還可使用圓形網格。
在這種狀況下,咱們必須使用函數cv.findCirclesGrid()來找到模式。
較少的圖像足以使用圓形網格執行相機校準。
一旦找到拐角,就可使用cv.cornerSubPix()來提升其精度。咱們還可使用cv.drawChessboardCorners()繪製圖案。全部這些步驟都包含在如下代碼中:
import numpy as np import cv2 as cv import glob # 終止條件 criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001) # 準備對象點, 如 (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) # 用於存儲全部圖像的對象點和圖像點的數組。 objpoints = [] # 真實世界中的3d點 imgpoints = [] # 圖像中的2d點 images = glob.glob('*.jpg') for fname in images: img = cv.imread(fname) gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 找到棋盤角落 ret, corners = cv.findChessboardCorners(gray, (7,6), None) # 若是找到,添加對象點,圖像點(細化以後) if ret == True: objpoints.append(objp) corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) imgpoints.append(corners) # 繪製並顯示拐角 cv.drawChessboardCorners(img, (7,6), corners2, ret) cv.imshow('img', img) cv.waitKey(500) cv.destroyAllWindows()
一張上面畫有圖案的圖像以下所示:
如今咱們有了目標點和圖像點,如今能夠進行校準了。咱們可使用函數cv.calibrateCamera()返回相機矩陣,失真係數,旋轉和平移矢量等。
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
如今,咱們能夠拍攝圖像並對其進行扭曲。OpenCV提供了兩種方法來執行此操做。可是,首先,咱們可使用cv.getOptimalNewCameraMatrix()基於自由縮放參數來優化相機矩陣。若是縮放參數alpha = 0,則返回具備最少不須要像素的未失真圖像。所以,它甚至可能會刪除圖像角落的一些像素。若是alpha = 1,則全部像素都保留有一些額外的黑色圖像。此函數還返回可用於裁剪結果的圖像ROI。
所以,咱們拍攝一張新圖像(在本例中爲left12.jpg。這是本章的第一張圖像)
img = cv.imread('left12.jpg') h, w = img.shape[:2] newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
這是最簡單的方法。只需調用該函數並使用上面得到的ROI裁剪結果便可。
# undistort dst = cv.undistort(img, mtx, dist, None, newcameramtx) # 剪裁圖像 x, y, w, h = roi dst = dst[y:y+h, x:x+w] cv.imwrite('calibresult.png', dst)
該方式有點困難。首先,找到從扭曲圖像到未扭曲圖像的映射函數。而後使用重映射功能。
# undistort mapx, mapy = cv.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), 5) dst = cv.remap(img, mapx, mapy, cv.INTER_LINEAR) # 裁剪圖像 x, y, w, h = roi dst = dst[y:y+h, x:x+w] cv.imwrite('calibresult.png', dst)
儘管如此,兩種方法都給出相同的結果。看到下面的結果:
您能夠看到全部邊緣都是筆直的。
如今,您可使用NumPy中的寫入功能(np.savez,np.savetxt等)存儲相機矩陣和失真係數,以備未來使用。
重投影偏差能夠很好地估計找到的參數的精確程度。重投影偏差越接近零,咱們發現的參數越準確。給定固有,失真,旋轉和平移矩陣,咱們必須首先使用cv.projectPoints()將對象點轉換爲圖像點。而後,咱們能夠計算出經過變換獲得的絕對值和拐角發現算法之間的絕對值範數。爲了找到平均偏差,咱們計算爲全部校準圖像計算的偏差的算術平均值。
mean_error = 0 for i in xrange(len(objpoints)): imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist) error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)/len(imgpoints2) mean_error += error print( "total error: {}".format(mean_error/len(objpoints)) )
歡迎關注公衆號:算法工程師的學習日誌