03.06 OpenCV-Python 進行相機校準

目標

在本節中,我們將學習

  • 由相機引起的失真類型,
  • 如何找到相機的固有和非固有特性
  • 如何根據這些特性使圖像不失真

基礎

一些針孔相機會給圖像帶來明顯的失真。兩種主要的變形是徑向變形和切向變形。徑向變形會導致直線出現彎曲。

距圖像中心越遠,徑向畸變越大。例如,下面顯示一個圖像,其中棋盤的兩個邊緣用紅線標記。但是,您會看到棋盤的邊框不是直線,並且與紅線不匹配。所有預期的直線都凸出。有關更多詳細信息,請訪問“失真(光學)”。

OpenCV-Python 進行相機校準

徑向變形可以表示成如下:

OpenCV-Python 進行相機校準

同樣,由於攝像鏡頭未完全平行於成像平面對齊,因此會發生切向畸變。因此,圖像中的某些區域看起來可能比預期的要近。切向畸變的量可以表示為:

OpenCV-Python 進行相機校準

簡而言之,我們需要找到五個參數,稱為失真係數,公式如下:

OpenCV-Python 進行相機校準

除此之外,我們還需要其他一些信息,例如相機的內在和外在參數。內部參數特定於攝像機。它們包括諸如焦距(f_x,f_y)和光學中心(c_x,c_y)之類的信息。焦距和光學中心可用於創建相機矩陣,該相機矩陣可用於消除由於特定相機鏡頭而引起的畸變。相機矩陣對於特定相機而言是唯一的,因此一旦計算出,就可以在同一相機拍攝的其他圖像上重複使用。它表示為3x3矩陣:

OpenCV-Python 進行相機校準

外在參數對應於旋轉和平移矢量,其將3D點的座標平移為座標系。

對於立體聲應用,首先需要糾正這些失真。要找到這些參數,我們必須提供一些定義良好的圖案的示例圖像(例如國際象棋棋盤)。我們找到一些已經知道其相對位置的特定點(例如棋盤上的四角)。我們知道現實世界空間中這些點的座標,也知道圖像中的座標,因此我們可以求解失真係數。為了獲得更好的結果,我們至少需要10個測試模式。

代碼

如上所述,相機校準至少需要10個測試圖案。OpenCV附帶了一些國際象棋棋盤的圖像(請參見samples / data / left01.jpg – left14.jpg),因此我們將利用這些圖像。考慮棋盤的圖像。相機校準所需的重要輸入數據是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),…。因此,我們得到的結果以毫米為單位。(在這種情況下,我們不知道正方形的大小,因為我們沒有拍攝那些圖像,因此我們以正方形的大小進行傳遞)。

3D點稱為對象點,而2D圖像點稱為圖像點

開始

因此,要在國際象棋棋盤中查找圖案,我們可以使用函數cv.findChessboardCorners()。我們還需要傳遞所需的圖案,例如8x8網格,5x5網格等。在此示例中,我們使用7x6網格。(通常,棋盤有8x8的正方形和7x7的內部角)。它返回角點和retval,如果獲得圖案,則為True。這些角將按順序放置(從左到右,從上到下)

另外

此功能可能無法在所有圖像中找到所需的圖案。因此,一個不錯的選擇是編寫代碼,使它啟動相機並檢查每幀所需的圖案。獲得圖案後,找到角並將其存儲在列表中。另外,在閱讀下一幀之前請提供一些時間間隔,以便我們可以在不同方向上調整棋盤。繼續此過程,直到獲得所需數量的良好圖案為止。即使在此處提供的示例中,我們也不確定給出的14張圖像中有多少張是好的。

因此,我們必須

閱讀所有圖像並僅拍攝好圖像。除了棋盤,我們還可以使用圓形網格。在這種情況下,我們必須使用函數cv.findCirclesGrid()來找到模式。較少的圖像足以使用圓形網格執行相機校準。

一旦找到拐角,就可以使用cv.cornerSubPix()來提高其精度。我們還可以使用cv.drawChessboardCorners()繪製圖案。所有這些步驟都包含在以下代碼中:

<code>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()/<code>

一張上面畫有圖案的圖像如下所示:

OpenCV-Python 進行相機校準

校準

現在我們有了目標點和圖像點,現在可以進行校準了。我們可以使用函數cv.calibrateCamera()返回相機矩陣,失真係數,旋轉和平移矢量等。

<code>ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) /<code>

不失真

現在,我們可以拍攝圖像並對其進行扭曲。OpenCV提供了兩種方法來執行此操作。但是,首先,我們可以使用cv.getOptimalNewCameraMatrix()基於自由縮放參數來優化相機矩陣。如果縮放參數alpha = 0,則返回具有最少不需要像素的未失真圖像。因此,它甚至可能會刪除圖像角落的一些像素。如果alpha = 1,則所有像素都保留有一些額外的黑色圖像。此函數還返回可用於裁剪結果的圖像ROI。

因此,我們拍攝一張新圖像(在本例中為left12.jpg。這是本章的第一張圖像)

<code>img = cv.imread('left12.jpg')
h,  w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))/<code>

1. 使用cv.undistort()

這是最簡單的方法。只需調用該函數並使用上面獲得的ROI裁剪結果即可。

<code> # 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)/<code>

2. 使用remapping

該方式有點困難。首先,找到從扭曲圖像到未扭曲圖像的映射函數。然後使用重映射功能。

<code># 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)/<code>

儘管如此,兩種方法都給出相同的結果。看到下面的結果:

OpenCV-Python 進行相機校準

您可以看到所有邊緣都是筆直的。現在,您可以使用NumPy中的寫入功能(np.savez,np.savetxt等)存儲相機矩陣和失真係數,以備將來使用。

重投影誤差

重投影誤差可以很好地估計找到的參數的精確程度。重投影誤差越接近零,我們發現的參數越準確。給定固有,失真,旋轉和平移矩陣,我們必須首先使用cv.projectPoints()將對象點轉換為圖像點。然後,我們可以計算出通過變換得到的絕對值和拐角發現算法之間的絕對值範數。為了找到平均誤差,我們計算為所有校準圖像計算的誤差的算術平均值。

<code>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)) )/<code>

附加資源

練習

  1. 嘗試使用圓形網格進行相機校準。


分享到:


相關文章: