1 四個坐標系
要想深入搞清楚相機的內參和外參含義, 首先得清楚以下4個坐標系的定義:
- 世界坐標系: 名字看著很唬人, 其實沒什么大不了的, 這個就是你自己定義的某一個坐標系。 比如, 你把房間的某一個點定為原點, 并且定義好方向, 這就是世界坐標系。 一般是個三維坐標系, 單位是m
- 相機坐標系: 以相機光心為原點, 一般我們把z軸指向相機前方,x向右,y向下,是個三維坐標系, 單位是m
- 成像平面坐標系: 這個是定義在物理成像平面的坐標系,方向定義與相機坐標系一致(沒有z方向), 原點是光心在物理成像平面上的投影,是個二維坐標系, 單位是m。 注意, 我們一般都會忽略這個坐標系, 因為它是個中間過渡狀態, 很少直接使用它。
- 像素坐標系: 這個應該是最為常用, 也最為大家熟悉的坐標系, 計算機視覺任務的輸出的坐標表達都是在這個坐標系。 它是個二維坐標系, 原點通常在圖像的左上角, x向右,y向下, 單位是像素, 沒有具體的尺度。
2 內參和外參
除了世界坐標系, 后面三個坐標系只跟相機本身有關。 相機內參表達的就是這三個坐標之間的轉換關系, 而相機外參表達的是相機與世界坐標系之間的轉換關系。
成像的過程實質上是幾個坐標系的轉換。首先空間中的一點由世界坐標系轉換到相機坐標系 ,然后再將其投影到物理成像平面 ( 成像平面坐標系 ) ,最后再將成像平面上的數據轉換像素坐標系 。
從世界坐標到像素坐標總共有3步轉換, 前面2個合在一起就是相機內參, 最后一個是相機外參。
上面矩陣中的參數很好理解, 也都有明確的物理含義: a 和b 表示從成像平面坐標轉到像素坐標時, 分別在x和y軸上的縮放系數, u 0 u_0 u0?和 v 0 v_0 v0?是原點的平移量。 r r r是扭曲因子, 一般為0。 f f f是相機的焦距。 R和T是旋轉和平移矩陣。
把前面2個合一起, 就得到了如下更為常見的內參矩陣:
K = [ f x 0 c x 0 f y c y 0 0 1 ] \mathbf{K} = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} K= ?fx?00?0fy?0?cx?cy?1? ?
下面的講解中, 我們直接用這種形式,跳過成像平面坐標系。
注意, 實際上內參包含2部分: 內參矩陣K和畸變系數D, 上面只講到了內參矩陣的原理, 沒有講畸變系數。 這里簡單列一下畸變系數, 不做詳細介紹。
D = [k1 k2 p1 p2 k3]
其中 k1、k2、k3 是徑向畸變系數(radial distortion coefficients) p1、p2 是切向畸變系數(tangential distortion coefficients)。
3 常用的坐標轉換
假設有某一個點, 在世界坐標下的坐標為 P w = ( X w , Y w , Z w ) P_w = (X_w, Y_w, Z_w) Pw?=(Xw?,Yw?,Zw?), 在相機坐標系下的坐標為 P = ( X c , Y c , Z c ) P = (X_c, Y_c, Z_c) P=(Xc?,Yc?,Zc?), 在像素坐標系下的坐標為 P u v = ( u , v ) P_{uv} = (u, v) Puv?=(u,v)。
-
世界坐標轉到相機坐標:
P = R P w + t P = RP_w + t P=RPw?+t -
相機坐標轉到像素坐標:
P u v = 1 Z c K P P_{uv} =\frac 1{Z_c} KP Puv?=Zc?1?KP
展開就是:
[ u v 1 ] = 1 Z c K [ X c Y c Z c ] = 1 Z c [ f x 0 c x 0 f y c y 0 0 1 ] [ X c Y c Z c ] \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \frac 1{Z_c} \mathbf{K} \begin{bmatrix} X_c \\ Y_c \\ Z_c \\ \end{bmatrix} = \frac 1{Z_c} \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} X_c \\ Y_c \\ Z_c \\ \end{bmatrix} ?uv1? ?=Zc?1?K ?Xc?Yc?Zc?? ?=Zc?1? ?fx?00?0fy?0?cx?cy?1? ? ?Xc?Yc?Zc?? ? -
像素坐標轉到像素坐標
這個轉換是最為常用的, 因為我們通常得到的都是像素坐標系下的信息, 如目標檢測、分割的結果就是像素坐標, 得到像素坐標后 ,可以轉為相機坐標, 進一步再轉為世界坐標。另外, 在點云目標檢測中, 通常要把原始的彩色圖像和深度信息轉換為點云, 就需要用到這個轉換。
P = Z c K ? 1 P u v P = Z_cK^{-1}P_{uv} P=Zc?K?1Puv?
展開就是:
X c = u ? c x f x ? Z c X_c = \frac {u-c_x}{f_x}*Z_c Xc?=fx?u?cx???Zc?
Y c = v ? c y f y ? Z c Y_c = \frac {v-c_y}{f_y}*Z_c Yc?=fy?v?cy???Zc?
注意上面有個尺度因子 Z c Z_c Zc?, 從相機坐標系的三維坐標投影到像素平面的二維坐標, 實際上丟失了z方向也就是深度信息。 所以如果不知道深度, 從像素坐標就無法恢復出準確的相機坐標。深度通常可以通過深度相機直接測量得到, 或通過深度估計算法預測。
4 內外參標定方法
4.1 內參標定方法
內參標定通常使用張正友標定法, 也就是常見的棋盤格標定。
代碼可參考https://github.com/leo038/hand_eye_calibrate/blob/main/hand_eye_calibrate.py 中的camera_calibrate
函數。
4.2 外參標定方法
外參標定的核心是:已知多個點分別在相機坐標系下的坐標和在世界坐標系下的坐標, 求它們之間的映射關系。
常用求解PnP 的方法,即已知多個點, 在像素坐標系的二維坐標, 和在世界坐標系的三維坐標,并且已知內參, 求解旋轉平移矩陣。
cv2 中提供了外參標定方法, cv2.solvePnP
只需提供對應的點對即可。
完整代碼實現如下:
import cv2
import numpy as np# 定義已知的3D空間點和對應的2D圖像點
objectPoints = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32)
imagePoints = np.array([[10, 20], [30, 50], [20, 70], [50, 40]], dtype=np.float32)# 定義相機的內參矩陣和畸變系數
cameraMatrix = np.array([[1000, 0, 320], [0, 1000, 240], [0, 0, 1]], dtype=np.float32)
distCoeffs = np.zeros((4, 1), dtype=np.float32)# 使用solvePnP函數求解相機的位姿
ret, rvec, tvec = cv2.solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs, flags=cv2.SOLVEPNP_EPNP)if ret:print("Rotation vector:\n", rvec)print("Translation vector:\n", tvec)
else:print("Failed to solve PnP.")res, _ = cv2.Rodrigues(rvec)
print(f"旋轉矩陣: {res}")