一、相機標定的原理
1.1 相機如何成像:
相機成像系統中,共包含四個坐標系:世界坐標系、相機坐標系、圖像坐標系、像素坐標系。
相機成像的原理:


1.1.1 世界坐標系:
世界坐標系(world coordinate),也稱為測量坐標系,是一個三維直角坐標系,以其為基准可以描述相機和待測物體的空間位置。世界坐標系的位置可以根據實際情況自由確定。
1.1.2 相機坐標系:
相機坐標系(camera coordinate),也是一個三維直角坐標系,原點位於鏡頭光心處,x、y軸分別與相面的兩邊平行,z軸為鏡頭光軸,與像平面垂直
1.1.3 像素坐標系、圖像坐標系:
像素坐標系(pixel coordinate)
如上圖,像素坐標系是一個二維直角坐標系,反映了相機CCD/CMOS芯片中像素的排列情況。原點位於圖像的左上角,軸、軸分別於像面的兩邊平行。像素坐標系中坐標軸的單位是像素(整數)。
像素坐標系不利於坐標變換,因此需要建立圖像坐標系,其坐標軸的單位通常為毫米(mm),原點是相機光軸與相面的交點(稱為主點),即圖像的中心點,軸、軸分別與軸、軸平行。故兩個坐標系實際是平移關系,即可以通過平移就可得到。
1.2 相機標定的目的:
求出相機的內、外參數,以及畸變參數。
標定相機后通常是想做兩件事:一個是由於每個鏡頭的畸變程度各不相同,通過相機標定可以校正這種鏡頭畸變矯正畸變,生成矯正后的圖像;另一個是根據獲得的圖像重構三維場景。
1.2 相機標定的總體原理:
攝像機標定(Camera calibraTIon)簡單來說就是從世界坐標系換到圖像坐標系的過程,也就是求最終的投影矩陣的過程。
1.3 步驟:
1.3.1 剛體變換(從世界坐標系到相機坐標系)
這一步是三維點到三維點的轉換,包括相機外參;
剛體變換只改變物體的空間位置(平移)和朝向(旋轉),而不改變其形狀,可用兩個變量來描述:旋轉矩陣R和平移向量t:

其中為33的旋轉矩陣,為31的平移矢量,為相機坐標系的齊次坐標,為世界坐標系的齊次坐標。
齊次坐標下可寫為:

旋轉矩陣R是正交矩陣,可通過羅德里格斯(Rodrigues)變換轉換為只有三個獨立變量的旋轉向量:

因此,剛體變換可用6個參數來描述,這6個參數就稱為相機的外參(Extrinsic),相機外參決定了空間點從世界坐標系轉換到相機坐標系的變換,也可以說外參描述了相機在世界坐標系中的位置和朝向。
1.3.2 透視投影(從相機坐標系到圖像坐標系)
我們可以將透鏡的成像簡單地抽象成下圖所示:

針孔成像原理
如圖中,空間任意一點與其圖像點之間的關系,p與相機光心 的連線為op,與像面的交點即為空間點在圖像平面上的投影。 該過程為透視投影,由上圖的矩陣表示。
其中,Zc為比例因子(Zc不為0),為有效焦距(光心到圖像平面的距離),是空間點在相機坐標系中的齊次坐標,是像點在圖像坐標系中的齊次坐標。
1.3.3 畸變矯正
理想的針孔成像模型確定的坐標變換關系均為線性的,而實際上,現實中使用的相機由於鏡頭中鏡片因為光線的通過產生的不規則的折射,鏡頭畸變(lens distortion)總是存在的,即根據理想針孔成像模型計算出來的像點坐標與實際坐標存在偏差。畸變的引入使得成像模型中的幾何變換關系變為非線性,增加了模型的復雜度,但更接近真實情形。畸變導致的成像失真可分為徑向失真和切向失真兩類:

畸變類型很多,總體上可分為徑向畸變和切向畸變兩類,徑向畸變的形成原因是鏡頭制造工藝不完美,使得鏡頭形狀存在缺陷,包括枕形畸變和桶形畸變等,可以用如下表達式來描述:

切向畸變又分為薄透鏡畸變和離心畸變等,薄透鏡畸變則是因為透鏡存在一定的細微傾斜造成的;離心畸變的形成原因是鏡頭是由多個透鏡組合而成的,而各個透鏡的光軸不在同一條中心線上。切向畸變可以用如下數學表達式來描述:

在引入鏡頭的畸變后,成像點從理想圖像坐標系到真實圖像坐標系的變換關系可以表示為:

實際計算過程中,如果考慮太多高階的畸變參數,會導致標定求解的不穩定。
1.3.4 數字化圖像
光線通過相機鏡頭后最終成像在感光陣列(CCD或CMOS)上,然后感光陣列將光信號轉化為電信號,最后形成完整的圖像。我們用dx和dy分別表示感光陣列的每個點在x和y方向上物理尺寸,即一個像素是多少毫米,這兩個值一般比較接近,但由於制造工藝的精度問題,會有一定誤差,同樣的,感光陣列的法向和相機光軸也不是完全重合,即可以看作成像平面與光軸不垂直。

我們用仿射變換來描述這個過程,如上圖,O點是圖像中心點,對應圖像坐標(u0,v0),Xd - Yd是真實圖像坐標系,U-V是數字化圖像坐標系,有:

齊次坐標下有:

上式中的變換矩陣即為相機的內參數矩陣 K,其描述了相機坐標系中點到二維圖像上點的變換過程。
綜上所述,在不考慮鏡頭畸變的情況下,相機的整個成像過程可表示為:

1.3.5 相機參數標定
參數標定即通過一定方法求得上述成像模型中的各個未知量(5個內參、6個外參以及畸變參數)。
這里主要介紹張正友平面標定法,其操作相對簡單,且精度較高,實際應用中很常用。
整個標定過程包括如下步驟:
1、打印一張棋盤格,把它貼在一個平面上,作為標定物。
2、通過調整標定物或攝像機的方向,為標定物拍攝一些不同方向的照片。
3、從照片中提取棋盤格角點。
4、估算理想無畸變的情況下,五個內參和六個外參。
5、應用最小二乘法估算實際存在徑向畸變下的畸變系數。
6、極大似然法,優化估計,提升估計精度。
二、相機標定的步驟
1)、准備一個張正友標定法的棋盤格,棋盤格大小已知,用相機對其進行不同角度的拍攝,得到一組圖像;
2)、對圖像中的特征點如標定板角點進行檢測,得到標定板角點的像素坐標值,根據已知的棋盤格大小和世界坐標系原點,計算得到標定板角點的物理坐標值;
3)、求解內參矩陣與外參矩陣。
根據物理坐標值和像素坐標值的關系,求出 H 矩陣,進而構造 V 矩陣,求解 B 矩陣,利用B矩陣求解相機內參矩陣 A ,最后求解每張圖片對應的相機外參矩陣 :

4)、求解畸變參數。
構造 D 矩陣,計算徑向畸變參數;
5)、利用L-M(Levenberg-Marquardt)算法對上述參數進行優化
三、實現
3.1 數據准備
手機型號:iphone7

3.2 代碼(Python+OpenCV)
import cv2
import numpy as np
import glob
# 找棋盤格角點
# 閾值
# 設置尋找亞像素角點的參數,采用的停止准則是最大循環次數30和最大誤差容限0.001
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 棋盤格模板規格
w = 6 # 內角點個數,內角點是和其他格子連着的點
h = 4
# 獲取標定板角點的位置
# 世界坐標系中的棋盤格點,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐標,記為二維矩陣
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
# 儲存棋盤格角點的世界坐標和圖像坐標對
objpoints = [] # 在世界坐標系中的三維點
imgpoints = [] # 在圖像平面的二維點
# 讀取照片
images = glob.glob('picture/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 找到棋盤格角點
# 棋盤圖像(8位灰度或彩色圖像) 棋盤尺寸 存放角點的位置
ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
# 如果找到足夠點對,將其存儲起來
if ret == True:
# 角點精確檢測
# 參數:輸入圖像 角點初始坐標 搜索窗口為2*winsize+1 死區 求角點的迭代終止條件
cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners)
# 將角點在圖像上顯示
cv2.drawChessboardCorners(img, (w, h), corners, ret)
cv2.imshow('findCorners', img)
cv2.waitKey(1000)
cv2.destroyAllWindows()
# 標定、去畸變
# 輸入:世界坐標系里的位置 像素坐標 圖像的像素尺寸大小 3*3矩陣,相機內參數矩陣 畸變矩陣
# 輸出:標定結果 相機的內參數矩陣 畸變系數 旋轉矩陣 平移向量
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# mtx:內參數矩陣
# dist:畸變系數
# rvecs:旋轉向量 (外參數)
# tvecs :平移向量 (外參數)
print("標定結果 ret:", ret)
print("內參矩陣 mtx:\n", mtx) # 內參數矩陣
print("畸變系數 dist:\n", dist) # 畸變系數 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print("旋轉向量(外參) rvecs:\n", rvecs) # 旋轉向量 # 外參數
print("平移向量(外參) tvecs:\n", tvecs) # 平移向量 # 外參數
# 去畸變
img2 = cv2.imread('picture/4_d.jpg')
h, w = img2.shape[:2]
# 我們已經得到了相機內參和畸變系數,在將圖像去畸變之前,
# 我們還可以使用cv.getOptimalNewCameraMatrix()優化內參數和畸變系數,
# 通過設定自由自由比例因子alpha。當alpha設為0的時候,
# 將會返回一個剪裁過的將去畸變后不想要的像素去掉的內參數和畸變系數;
# 當alpha設為1的時候,將會返回一個包含額外黑色像素點的內參數和畸變系數,並返回一個ROI用於將其剪裁掉
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 0, (w, h)) # 自由比例參數
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 根據前面ROI區域裁剪圖片
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.jpg', dst)
# 反投影誤差
# 通過反投影誤差,我們可以來評估結果的好壞。越接近0,說明結果越理想。
# 通過之前計算的內參數矩陣、畸變系數、旋轉矩陣和平移向量,使用cv2.projectPoints()計算三維點到二維圖像的投影,
# 然后計算反投影得到的點與圖像上檢測到的點的誤差,最后計算一個對於所有標定圖像的平均誤差,這個值就是反投影誤差。
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print(("total error: "), total_error / len(objpoints))
四、運行結果
對標定結果進行評價的方法是通過得到的攝像機內外參數,對空間的三維點進行重新投影計算,得到空間三維點在圖像上新的投影點的坐標,計算投影坐標和亞像素角點坐標之間的偏差,偏差越小,標定結果越好。
標定樣例:

參數:

誤差接近與0,說明結果還不錯。
去畸變前:

去畸變后:

