1、相機標定基本理論html
一、相機成像系統介紹python
圖中總共有4個座標系:git
圖中所示的座標轉換關係:程序員
{World世界座標系:Ow }---Extrinsics [R|t]---{Camera座標系:Oc}---Intrinsics A---{成像座標系:Oi }---Liner Convert---{圖像座標系:Op }算法
這樣就完成了世界座標系中的點到圖像座標系的映射關係!app
二、相機內參介紹ide
一、標準的針孔模型(針孔模型實際上沒有焦距f,這裏只是爲了方便):函數
在實際計算過程當中,爲了計算方便,咱們將像平面翻轉平移到針孔前,從而獲得一種數學上更爲簡單的等價形式(方便類似三角形的計算)工具
圖1.標定全過程解析性能
這裏的Q點其實是O點對面的X點的位置,q實際爲x的位置
注意:因爲世界座標系的選定不是惟一的,所以爲了方便起見,咱們在Opencv使用的過程當中,Ow世界座標系選取的位置爲標定板平面及其法線組成的三維空間座標系,所以,世界座標系中的全部的標定板上的點的Zw=0,因此在下面的python-Opencv程序中,咱們角點的objpoints的取值Zw都爲0,如旁邊的小圖所示:紅色爲Zw、藍色爲Yw、綠色爲Xw
以Oc點爲原點創建攝像機座標系。點Q(x,y,z)爲攝像機座標系空間中的一點,該點被光線投影到圖像平面Oi上的q(x',y',z')點,因爲圖像平面的位置再相機座標系的焦點的位置,因此這裏z‘=f,q點的座標爲(x',y',f)。
圖像平面與光軸z軸垂直,和投影中心距離爲f (f是相機的焦距)。按照三角比例關係能夠得出:
Formulation-001
注意這裏的Zc實際上的值爲s,下面的過程咱們將會帶入s數值
以上將座標爲(x,y,z)的Q點映射到投影平面上座標爲(x',y')的q點的過程稱做投影變換
上述Q點到q點的變換關係用3*3的矩陣可表示爲:q = A1Q ,其中
Formulation-002
s*A1稱爲攝像機的內參數矩陣,單位均爲物理尺寸(Units:mm)
經過上面,能夠把相機座標系轉換到像圖像座標系的物理單位,即 {Camera座標系:Oc}--- Intrinsics A1--- {成像座標系:Oi }經過下面,能夠把像平面座標系物理單位到像素單位,即{成像座標系:Oi }---Liner Convert---{圖像座標系:Op }
以圖像平面的左上角或左下角爲原點創建座標系Op。假設像平面座標系原點位於圖像左下角,水平向右爲u軸,垂直向上爲v軸,均以像素爲單位
以圖像平面與光軸的交點Oi 爲原點創建座標系,水平向右爲x軸,垂直向上爲y軸。原點Oi 通常位於圖像中心處,Oi 在以像素爲單位的圖像座標系中的座標爲(u0, v0)
像平面座標系和像素座標系雖然在同一個平面上,可是原點並非同一個。
設每一個像素的物理尺寸大小爲 dx * dy (mm) ( 因爲單個像素點投影在圖像平面上是矩形而不是正方形,所以可能dx != dy),
圖像平面上某點在成像平面座標系中的座標爲(x', y'),在像素座標系中的座標爲(u, v),則兩者知足以下關係:[即(x', y')→(u, v)]
Formulation-004
用齊次座標與矩陣形式表示爲:
Formulation-005
將等式兩邊都乘以點Q(x,y,zc)座標中的Zc(也就是s)可得:
Formulation-006
將攝像機座標系中的Formulation-003 式代入上式可得:
Formulation-007
則右邊第一個矩陣和第二個矩陣的乘積亦爲攝像機的內參數矩陣A(單位爲像素),相乘後可得:
Formulation-008
和 Formulation-003 式相比,此內參數矩陣中f/dx, f/dy, u0, v0 的單位均爲像素。令內參數矩陣爲A,則上式可寫成:
Formulation-009
三.相機內參A(與棋盤所在空間的3D幾何無關)
在計算機視覺中,攝像機內參數矩陣
Formulation-010
其中 f 爲攝像機的焦距,單位通常是mm;dx,dy 爲像元尺寸;u0,v0 爲圖像中心。
fx = f/dx, fy = f/dy,分別稱爲x軸和y軸上的歸一化焦距.
爲更好的理解f、dx、dy、fx、fy、u0、v0,舉個實例:
現以NiKon D700相機爲例進行求解其內參數矩陣:
焦距 f = 35mm 最高分辨率:4256×2832 傳感器尺寸:36.0×23.9 mm
根據以上定義能夠有:
u0= 4256/2 = 2128 v0= 2832/2 = 1416 dx = 36.0/4256 dy = 23.9/2832
fx = f/dx = 4137.8 fy = f/dy = 4147.3
分辨率能夠從顯示分辨率與圖像分辨率兩個方向來分類:
三、相機外參介紹
旋轉向量R=[Rx,Ry,Rz](大小爲1×3的矢量或旋轉矩陣3×3)和平移向量 t=(Tx,Ty,Tz)
旋轉向量:旋轉向量是旋轉矩陣緊湊的變現形式,旋轉向量爲1×3的行矢量。
Formulation-011
r就是旋轉向量,旋轉向量的方向是旋轉軸 ,旋轉向量的模爲圍繞旋轉軸旋轉的角度,如圖中的e向量,即旋轉向量表現形式。
經過上面的公式,咱們就能夠求解出旋轉矩陣R。一樣的已知旋轉矩陣,咱們也能夠經過下面的公式求解獲得旋轉向量:
Fmulation-012
根據前面的 圖1.相機標定全過程解析 中的變換方式,標定過程當中的外參Extrinsics = [R|t],以下所示爲[R|t]:
Formulation-013
則從世界座標系變換到Q點的方式以下:
Formulation-014
即簡寫爲以下形式:
Formulation-015
四、畸變參數介紹
採用理想針孔模型,因爲經過針孔的光線少,攝像機曝光太慢,在實際使用中均採用透鏡,可使圖像生成迅速,但代價是引入了畸變。有兩種畸變對投影圖像影響較大: 徑向畸變和切向畸變。
一、徑向畸變
對某些透鏡,光線在遠離透鏡中心的地方比靠近中心的地方更加彎曲,產生「筒形」或「魚眼」現象,稱爲徑向畸變。
通常來說,成像儀中心的徑向畸變爲0,越向邊緣移動,畸變越嚴重。不過徑向畸變能夠經過下面的泰勒級數展開式來校訂:
xcorrected = x(1+k1r2+k2r4+k3r6)
ycorrected = y(1+k1r2+k2r4+k3r6)
這裏(x, y)是畸變點在成像儀上的原始位置,r爲該點距離成像儀中心的距離,(xcorrected ,ycorrected )是校訂後的新位置。
對於通常的攝像機校訂,一般使用泰勒級數中的前兩項k1和k2就夠了;對畸變很大的攝像機,好比魚眼透鏡,可使用第三徑向畸變項k3
無畸變 桶形畸變k1>0 枕形畸變k1<0
二、切向畸變
當成像儀被粘貼在攝像機的時候,會存在必定的偏差,使得圖像平面和透鏡不徹底平行,從而產生切向畸變。也就是說,若是一個矩形被投影到成像儀上時,可能會變成一個梯形。切向畸變能夠經過以下公式來校訂:
xcorrected = x + [ 2p1y + p2 (r2 + 2x2) ]
ycorrected = y + [ 2p2x + p1 (r2 + 2y2) ]
這裏(x, y)是畸變點在成像儀上的原始位置,r爲該點距離成像儀中心的距離,(xcorrected ,ycorrected )是校訂後的新位置。
三、參數表示
x、y、xcorrected 、ycorrected 、r參數的表示以下圖所示:
畸變係數的做用在於計算矯正以後的實際的成像平面位置,將獲得的矯正公式帶入到上述的計算公式Formulation-015式中便可獲得矯正後的圖像。
2、相機標定方法實踐
一、採樣標準pattern模板圖像若干
1 import cv2 2 import glob 3 import time 4 import threading 5 import numpy as np 6 7 cap = cv2.VideoCapture(0) 8 while not cap.isOpened(): # 檢查攝像頭是否打開成功 9 time.sleep(100) 10 print('Camera is Initialize...') 11 12 width = int(cap.get(3)) # 讀取攝像頭分辨率參數 13 height = int(cap.get(4)) 14 15 frame = np.zeros((width,height,3),dtype=np.uint8) # 建立圖像模板 16 ret, frame = cap.read() 17 18 Key_val = 0 # 保存鍵值 19 20 def Keybo_Moni(): # 按鍵測試函數 21 count = 0 22 while True: 23 global Key_val, frame, process_flag, cap 24 if Key_val == ord('r'): 25 Key_val= 0 26 cv2.imwrite('ResPic' + str(count) + '.jpg', frame) # 保存圖像 27 count += 1 28 print('Get new pic %d' % count) 29 30 try: 31 32 Keybo_Moni_Thread = threading.Thread(target=Keybo_Moni, name='Keyboard-Thread') # 建立鍵盤監控線程 33 Keybo_Moni_Thread.start() # 啓動鍵盤監測線程 34 except: 35 print('Error:uqnable to start the thread!') 36 37 while True: 38 ret, frame = cap.read() # 讀取視頻幀 39 cv2.imshow('Video_Show', frame) # 顯示圖像 40 Key_val = cv2.waitKey(1) # 獲取鍵值,不加此句,沒法運行程序!(Ref:https://www.cnblogs.com/kissfu/p/3608016.html) 41 if Key_val == ord('q'): 42 cv2.destroyAllWindows() 43 cap.release() 44 print('Pic Sample Finished!') 45 break 46
1)系統實際拍攝場景 2)硬件設備Logitech_Camera
3)捕獲素材以下所示(部分圖像)
二、使用MATLAB工具箱進行相機標定
1)使用流程:
step1:打開Matlab 2015軟件,上方欄目有一個應用程序,在應用程序下有一個工具箱,Camera Calibrator.
step2:打開工具箱,單擊左上角的Add Images,選中捕獲的全部的ResPic?.jpg,打開以後須要選擇方塊大小,取d=30mm.
step3:點擊工具箱中間的綠色按鈕Calibrate,單擊開始咱們的內參以及外參的計算和仿真,以後控制檯輸出Camera參數.
step4:咱們能夠選擇處處MATLAB Script來分析Camera的標定流程.
2)仿真結果:
1 cameraParams = 2 cameraParameters (具備屬性): 3 Camera Intrinsics 4 IntrinsicMatrix: [3x3 double] part1:內參矩陣 5 FocalLength: [725.6633 724.1423] 焦距F 6 PrincipalPoint: [336.0815 233.9824] 主點P 7 Skew: 0 8 Lens Distortion 鏡頭畸變 9 RadialDistortion: [0.0391 -0.3128] 徑向畸變參數(k1,k2) xcorrected = x(1+k1r2+k2r4+k3r6) ycorrected = y(1+k1r2+k2r4+k3r6) 10 TangentialDistortion: [0 0] 切向畸變參數(p1,p2) xcorrected = x+[2p1y+p2(r2+2x2)] ycorrected = y+[2p2x+p1(r2+2y2)] 11 Camera Extrinsics 相機外參 12 RotationMatrices: [3x3x11 double] part2:世界外參旋轉矩陣 13 TranslationVectors: [11x3 double] part3:世界外參平移矩陣 14 Accuracy of Estimation 計算精度 15 MeanReprojectionError: 0.1804 16 ReprojectionErrors: [48x2x11 double] 17 ReprojectedPoints: [48x2x11 double] 18 Calibration Settings 19 NumPatterns: 11 20 WorldPoints: [48x2 double] 21 WorldUnits: 'mm' 22 EstimateSkew: 0 23 NumRadialDistortionCoefficients: 2 24 EstimateTangentialDistortion: 0
對上述腳本進行分析以下所示:
圖中已經用紅色標註出來了Lens的焦距f=725mm,主點分別爲P=336mm和P`=234mm
part1:內參矩陣
圖像參數 實際圖像
從上圖中的參數可知
a)相機CCD的點陣總個數爲640*480(units:Dots),人工計算U0以及V0以下:(U0,V0)=(width/2,height/2)=(320,240)
b)注意這裏的分辨率dpi是指顯示器的dpi,和相機的像素尺寸無關,有關ppi和dpi的區別參考線面的連接。
part2:世界外參旋轉矩陣
1 val(:,:,1) = -0.0005 -0.9542 0.2991 0.9816 -0.0575 -0.1819 0.1908 0.2935 0.9367 2 val(:,:,2) = -0.0406 -0.9795 0.1971 0.9472 0.0250 0.3195 -0.3179 0.1997 0.9269 3 val(:,:,3) = 0.0217 -0.9162 0.4002 0.9898 -0.0368 -0.1378 0.1410 0.3991 0.9060 4 val(:,:,4) = 0.5801 -0.7895 0.2002 0.7831 0.4730 -0.4039 0.2242 0.3911 0.8926 val(:,:,5) = 0.9954 -0.0478 -0.0836 0.0074 0.9032 -0.4291 0.0960 0.4265 0.8994 5 val(:,:,6) = 0.4233 0.7063 -0.5674 -0.7637 0.6151 0.1959 0.4874 0.3504 0.7998 val(:,:,7) = -0.0189 -0.9922 -0.1236 0.9970 -0.0095 -0.0762 0.0745 -0.1247 0.9894 6 val(:,:,8) = 0.8786 -0.0926 -0.4685 -0.0071 0.9784 -0.2068 0.4775 0.1851 0.8589 7 val(:,:,9) = 0.0320 -0.9789 0.2020 0.7774 -0.1027 -0.6206 0.6282 0.1769 0.7577 8 val(:,:,10) = 0.0167 -0.9229 0.3846 0.9836 -0.0541 -0.1723 0.1799 0.3811 0.9069 9 val(:,:,11) = -0.5271 -0.7808 0.3356 0.8481 -0.4583 0.2658 -0.0537 0.4247 0.9038
外參旋轉矩陣的來源在原理中已經介紹,每個軸都須要旋轉x,y,z旋轉矩陣相乘以後就獲得瞭如上所示的旋轉矩陣參數!3*3的矩陣,最後任然爲3*3.
part3:世界外參平移矩陣
外參平移矩陣主要用來對準棋盤圖像的中心,T3=[dx,dy,dz]
MATLAB導出的腳本代碼:Camera_Calibrator.m
1 % Auto-generated by cameraCalibrator app on 13-Mar-2019 2 %------------------------------------------------------- 3 4 5 % Define images to process 6 imageFileNames = {'D:\PythonDevelopment\Opencv_Test\data\ResPic0.jpg',... 7 'D:\PythonDevelopment\Opencv_Test\data\ResPic1.jpg',... 8 'D:\PythonDevelopment\Opencv_Test\data\ResPic2.jpg',... 9 'D:\PythonDevelopment\Opencv_Test\data\ResPic3.jpg',... 10 'D:\PythonDevelopment\Opencv_Test\data\ResPic4.jpg',... 11 'D:\PythonDevelopment\Opencv_Test\data\ResPic5.jpg',... 12 'D:\PythonDevelopment\Opencv_Test\data\ResPic6.jpg',... 13 'D:\PythonDevelopment\Opencv_Test\data\ResPic7.jpg',... 14 'D:\PythonDevelopment\Opencv_Test\data\ResPic8.jpg',... 15 'D:\PythonDevelopment\Opencv_Test\data\ResPic9.jpg',... 16 'D:\PythonDevelopment\Opencv_Test\data\ResPic10.jpg',... 17 }; 18 19 % Detect checkerboards in images 20 [imagePoints, boardSize, imagesUsed] = detectCheckerboardPoints(imageFileNames); 21 imageFileNames = imageFileNames(imagesUsed); 22 23 % Generate world coordinates of the corners of the squares 24 squareSize = 30; % in units of 'mm' 25 worldPoints = generateCheckerboardPoints(boardSize, squareSize); 26 27 % Calibrate the camera 28 [cameraParams, imagesUsed, estimationErrors] = estimateCameraParameters(imagePoints, worldPoints, ... 29 'EstimateSkew', false, 'EstimateTangentialDistortion', false, ... 30 'NumRadialDistortionCoefficients', 2, 'WorldUnits', 'mm', ... 31 'InitialIntrinsicMatrix', [], 'InitialRadialDistortion', []); 32 33 % View reprojection errors 34 h1=figure; showReprojectionErrors(cameraParams, 'BarGraph'); 35 36 % Visualize pattern locations 37 h2=figure; showExtrinsics(cameraParams, 'CameraCentric'); 38 39 % Display parameter estimation errors 40 displayErrors(estimationErrors, cameraParams); 41 42 % For example, you can use the calibration data to remove effects of lens distortion. 43 originalImage = imread(imageFileNames{1}); 44 undistortedImage = undistortImage(originalImage, cameraParams); 45 46 % See additional examples of how to use the calibration data. At the prompt type: 47 % showdemo('MeasuringPlanarObjectsExample') 48 % showdemo('StructureFromMotionExample')
三、採用Python及Opencv進行相機標定
注意標定板的方塊大小Opencv是已知的30mm,根據Opencv官網給出的標定模板,打印在A4的紙上。
代碼以下:
1 import cv2 2 import glob 3 import time 4 import threading 5 import numpy as np 6 7 print('Starting Capture...') 8 cap = cv2.VideoCapture(0) 9 while not cap.isOpened(): # 檢查攝像頭是否打開成功 10 time.sleep(100) 11 print('Camera is Initialize...') 12 13 width = int(cap.get(3)) # 讀取攝像頭分辨率參數 14 height = int(cap.get(4)) 15 16 frame = np.zeros((width,height,3),dtype=np.uint8) # 建立圖像模板 17 ret, frame = cap.read() 18 19 Key_val = 0 # 保存鍵值 20 21 def Keybo_Moni(): # 按鍵測試函數 22 count = 0 23 while True: 24 global Key_val, frame, process_flag, cap 25 if Key_val == ord('r'): 26 Key_val= 0 27 cv2.imwrite('ResPic' + str(count) + '.jpg', frame) # 保存圖像 28 count += 1 29 print('Get new pic %d' % count) 30 31 try: 32 Keybo_Moni_Thread = threading.Thread(target=Keybo_Moni, name='Keyboard-Thread') # 建立鍵盤監控線程 33 Keybo_Moni_Thread.start() # 啓動鍵盤監測線程 34 except: 35 print('Error:uqnable to start the thread!') 36 37 while True: 38 ret, frame = cap.read() # 讀取視頻幀 39 cv2.imshow('Video_Show', frame) # 顯示圖像 40 Key_val = cv2.waitKey(1) # 獲取鍵值,不加此句,沒法運行程序!(Ref:https://www.cnblogs.com/kissfu/p/3608016.html) 41 if Key_val == ord('q'): 42 cv2.destroyAllWindows() 43 cap.release() 44 print('Pic Sample Finished!') 45 break 46 47 print('Starting CalibrateCamera...') 48 49 # 標定算法開始 50 51 # 設置尋找亞像素角點的參數,採用的中止準則是最大循環次數30和最大偏差容限0.001 52 criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) 53 # 獲取標定板角點的位置 54 ojbp = np.zeros((6*7,3),np.float32) 55 # reshape(-1,N)轉換矩陣的爲N列的矩陣,-1表示能夠表明任意行 56 # 好比有10個元素: 57 # reshape(-1,1)==>10*1的矩陣 58 # reshape(-1,2)===>5*2的矩陣 59 # T 表示將矩陣當中的全部的元素所有順序反轉一遍 60 # 將世界座標系建在標定板上,全部點的Z座標所有爲0,因此只須要賦值x和y 61 ojbp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2) 62 63 objpoints = [] # 存儲世界座標系中的3D點(實際上Zw在標定板上的值爲0) 64 imgpoints = [] # 存儲圖像座標系中的2D點 65 66 images = glob.glob('*.jpg') 67 for fname in images: 68 img = cv2.imread(fname) 69 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 70 cv2.imshow('gray',gray) 71 cv2.waitKey(300) 72 # Find the chess board corners 73 ret, corners = cv2.findChessboardCorners(gray, (7,6), flags=3) 74 # If found, add object points, image points(after refining them) 75 if ret == True: 76 if fname.find('ResPic') != -1: 77 time_name = str(int(time.time())) 78 cv2.imwrite(time_name+'.jpg',img) 79 os.remove(fname) 80 objpoints.append(ojbp) 81 corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria) # 亞像素級焦點檢測,基於提取的角點,進一步提升精確度 82 imgpoints.append(corners2) 83 # Draw and display the corners 84 img = cv2.drawChessboardCorners(img,(7,6),corners2,ret) 85 cv2.imshow('img',img) 86 cv2.waitKey(500) 87 else: 88 os.remove(fname) 89 90 cv2.destroyAllWindows() 91 92 gray = cv2.cvtColor(cv2.imread(images[0]),cv2.COLOR_RGB2GRAY) 93 ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None) 94 95 print('retval:\n',ret) 96 print('Camera-Intrinsics:\n',mtx) 97 print('Camera-Distortion:\n',dist) 98 print('Camera-Extrinsics-R:') 99 N = 1 100 for r in rvecs: 101 print('r'+str(N)+':',r[0],r[1],r[2]) 102 N += 1 103 print('Camera-Extrinsics-t:') 104 N = 1 105 for t in tvecs: 106 print('t'+str(N)+':',t[0],t[1],t[2]) 107 N += 1 108 # Test The Result 109 110 # The Picture need to Correct 111 img = cv2.imread('./Calibsource/test2.jpg') 112 cv2.imshow('img',img) 113 cv2.waitKey(800) 114 cv2.destroyAllWindows() 115 116 h, w = img.shape[:2] 117 newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h)) 118 mothed = 'undistort' 119 print('Starting Correct Photo...') 120 if mothed != 'remapping': 121 # undistort 122 dst = cv2.undistort(img, mtx, dist, None, newcameramtx) 123 x,y,w,h = roi 124 print('ROI\'Param:', roi) 125 dst1 = dst[y:y+h, x:x+w] 126 cv2.imwrite('./Calibresult/calibresult.jpg',dst1) 127 else: 128 # undistort 129 mapx,mapy = cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5) 130 dst = cv2.remap(img,mapx,mapy,cv2.INTER_CUBIC) 131 # crop the image 132 x,y,w,h = roi 133 print('ROI\'Param:', roi) 134 dst1 = dst[y:y+h, x:x+w] 135 cv2.imwrite('./Calibresult/calibresult.jpg',dst1) 136 137 # gesture_Draw 138 print('Starting Gesture_Draw...') 139 140 # 繪製簡單的座標系,調用此函數記得將下面的座標修改成axis_axis 141 axis_axis = np.float32([[3,0,0], [0,3,0], [0,0,-3]]).reshape(-1,3) 142 # 繪製簡單的座標系,調用此函數記得將下面的座標修改成axis_axis 143 def draw_axis(img, corners, imgpts): 144 corner = tuple(corners[0].ravel()) 145 img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5) 146 img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5) 147 img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5) 148 return img 149 # 繪製立體方塊在Pattern上,調用此函數記得將下面的座標修改成axis_cube 150 axis_cube = np.float32([[0,0,0], [0,3,0], [3,3,0], [3,0,0],[0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3]]) 151 # 繪製立體方塊在Pattern上,調用此函數記得將下面的座標修改成axis_cube 152 def draw_cube(img, corners, imgpts): 153 imgpts = np.int32(imgpts).reshape(-1,2) 154 # draw ground floor in green 155 img = cv2.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3) 156 # draw pillars in blue color 157 for i,j in zip(range(4),range(4,8)): 158 img = cv2.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3) 159 # draw top layer in red color 160 img = cv2.drawContours(img, [imgpts[4:]],-1,(0,0,255),3) 161 return img 162 163 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) 164 objp = np.zeros((6*7,3), np.float32) 165 objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2) 166 167 images = glob.glob('*.jpg') 168 if len(images) == 0: 169 printf('No Test Picture can be loading!') 170 exit() 171 img = cv2.imread(images[0]) 172 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 173 ret, corners = cv2.findChessboardCorners(gray, (7,6),None) 174 if ret == True: 175 corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria) 176 # Find the rotation and translation vectors. 177 ret, rvecs, tvecs, inliers = cv2.solvePnPRansac(objp, corners2, mtx, dist) 178 # project 3D points to image plane 179 imgpts, jac = cv2.projectPoints(axis_cube, rvecs, tvecs, mtx, dist) 180 img = draw_cube(img,corners2,imgpts) 181 cv2.imshow('img',img) 182 cv2.waitKey(800) 183 cv2.imwrite('./Calibresult/gesture.png', img) 184 cv2.destroyAllWindows() 185 print('Gesture_Draw Finised') 186 try: 187 sys.exit(0) 188 except: 189 print('Program is dead.') 190 finally: 191 print('Clean-Up')
代碼分析:
criteria:參數用來設置Opencv迭代計算的最小容許偏差。
findChessboardCorners:函數用來找到對應的角點,(7,6)表示找7*6個角點在標定圖上。
cornerSubPix:函數在找到的角點基礎上更精確的尋找亞像素的角點位置並更新.(11,11)表示尋找的窗口大小。
drawChessboardCorners:函數在找到的角點模板上標出角點位置。
calibrateCamera:函數用來計算當前模型的內參和外參,相機畸變等信息。
getOptimalNewCameraMatrix:函數使用得到的相機的畸變參數對相機內參進行修正獲得新得內參和截取圖像的ROI窗口。
undistort:函數計算新的圖像結果,結果與以前就算的ROI區域獲得矯正後的圖像。
標定函數API介紹:
1 retval, corners =cv.findChessboardCorners(image, patternSize[, corners[, flags]]) 2 3 參數: 4 image:棋盤圖像,8位灰度或彩色圖像 5 patternSize:棋盤的尺寸(注意應爲內角點個數,內角點是和其餘格子連着的點,邊邊上的不是!不是否是不是有幾個方格!好比說「田」字只有一個內角點!) 6 corners:存放角點的位置 7 flags:迭代的準則 8 9 返回值: 10 retval:是否檢測出角點 11 corners:角點的位置
1 corners = cv.cornerSubPix(image, corners, winSize, zeroZone, criteria) 2 3 參數: 4 image:輸入圖像,8位或者float型 5 corners:角點初始座標。 6 winsize:搜索窗口爲2*winsize+1 7 zerozone:死區,不計算區域,避免自相關矩陣的奇異性,沒有死區,參數爲(-1,-1) 8 criteria:求角點的迭代終止條件 9 10 返回值: 11 corner:角點位置
1 image = cv.drawChessboardCorners(image, patternSize, corners, patternWasFound) 2 3 patternWasFound:標誌位,檢測是否全部board都被檢測到,若爲是,則將角點連線,不然不連線
1 retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv.calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs[, rvecs[, tvecs[, flags[,criteria]]]]) 2 3 參數: 4 objectPoints:世界座標系裏的位置 5 imagePoints: 像素座標 6 imageSize:爲圖像的像素尺寸大小 7 flags:標定採用的算法 8 criteria:迭代終止條件設定 9 10 返回值: 11 cameraMatrix:3*3矩陣,相機內參數矩陣 12 disCoeffs:畸變矩陣 13 rvecs:旋轉向量 14 tvecs:位移向量
1 retval,validPixROI=cv.getOptimalNewCameraMatrix(cameraMatrix,distCoeffs,imageSize, alpha[, newImgSize[, centerPrincipalPoint]]) 2 3 這個函數是爲了微調參數,使圖像不會在矯正過程當中丟失不少像素,致使被矯正的太小。 4 5 參數: 6 imageSize:原始圖像尺寸 7 newImageSize:校訂後圖像尺寸 8 alpha:取0或1。0返回剪裁後圖像,1返回有黑色區域的圖像,ROI是一個矩形框,圈出最優的裁切範圍 參考:https://www.cnblogs.com/riddick/p/6711263.html 9 centerPrincipalPoint:是否做用於中心。默認爲opencv本身根據圖像選擇位置 10 11 返回值: 12 validPixROI:截圖過程當中的ROI區域參數
f.最終結果 cv2.undistort
1 dst = cv.undistort(src, cameraMatrix, distCoeffs[, dst[, newCameraMatrix]]) 2 3 參數: 4 scr:待矯正圖片 5 cameraMatrix:相機內參 6 distCoeffs:畸變參數 7 newCameraMatrix:矯正後的內參 8 9 返回值: 10 dst:輸出圖像
實驗結果:
1 retval: 2 0.2208040557518684 3 Camera-Intrinsics: 4 [[616.76321725 0. 327.79663413] 5 [ 0. 615.46127457 226.00002854] 6 [ 0. 0. 1. ]] 7 Camera-Distortion: 8 [[ 0.10478425 -0.30005988 -0.00268881 0.00525918 0.05811324]] 9 Camera-Extrinsics-R: 10 r1: [0.26699373] [0.36829143] [1.59609539] 11 r2: [-0.08216888] [0.02643058] [1.5739028] 12 r3: [0.6560576] [-0.11198011] [-2.9833505] 13 r4: [0.05691746] [0.11485781] [-1.44155599] 14 r5: [0.16876199] [0.54717179] [1.55050292] 15 r6: [-0.08410575] [0.36611198] [1.91347658] 16 r7: [0.41366115] [-0.25637666] [1.36897102] 17 r8: [0.24991709] [-0.12493294] [2.03319431] 18 Camera-Extrinsics-t: 19 t1: [1.58486804] [-3.82773375] [14.50375578] 20 t2: [1.78441286] [-3.92299521] [15.32974111] 21 t3: [2.25776368] [2.77561687] [15.41762194] 22 t4: [-3.76555275] [2.14859951] [15.0817749] 23 t5: [5.30132186] [-1.28340352] [16.12856882] 24 t6: [6.78619912] [0.42631112] [16.23319328] 25 t7: [2.17053829] [-2.92354049] [11.15674799] 26 t8: [4.3559293] [-0.99258867] [13.06667609]
參數中第二部分爲攝像頭內參:A = [[f/dx 0 u0], [0 f/dy v0], [0 0 1]]
第三部分爲攝像頭畸變參數:dist = [k1,k2,p1,p2[k3,[k4,k5,k6]]] 依次返回4個或5個或8個參數
第三部分爲攝像頭外參R:R = [[Vec1_x,Vec1_y,Vec1_z], [Vec2_x,Vec2_y,Vec3_z], ..., [Vecn_x,Vecn_y,Vecn_z]]
第四部分爲攝像頭外參T(平移參數):t = [[T1_x,T1_y,T1_z], [T2_x,T2_y,T2_z], ..., [Tn_x,Tn_y,Tn_z]]
測試使用的圖像必須由原來的相機來採集圖片,同時圖像的大小必須相同,這樣才能適應當前的相機內參&外參,採集代碼以下:
1 import cv2 2 import glob 3 import threading 4 import numpy as np 5 6 print('Starting Capture...') 7 cap = cv2.VideoCapture(0) 8 while not cap.isOpened(): # 檢查攝像頭是否打開成功 9 time.sleep(100) 10 print('Camera is Initialize...') 11 12 width = int(cap.get(3)) # 讀取攝像頭分辨率參數 13 height = int(cap.get(4)) 14 15 frame = np.zeros((width,height,3),dtype=np.uint8) # 建立圖像模板 16 ret, frame = cap.read() 17 18 Key_val = 0 # 保存鍵值 19 20 def Keybo_Moni(): # 按鍵測試函數 21 count = 0 22 while True: 23 global Key_val, frame, process_flag, cap 24 if Key_val == ord('r'): 25 Key_val= 0 26 cv2.imwrite('test' + str(count) + '.jpg', frame) # 保存圖像 27 count += 1 28 print('Get new pic %d' % count) 29 30 try: 31 Keybo_Moni_Thread = threading.Thread(target=Keybo_Moni, name='Keyboard-Thread') # 建立鍵盤監控線程 32 Keybo_Moni_Thread.start() # 啓動鍵盤監測線程 33 except: 34 print('Error:uqnable to start the thread!') 35 36 while True: 37 ret, frame = cap.read() # 讀取視頻幀 38 cv2.imshow('Video_Show', frame) # 顯示圖像 39 Key_val = cv2.waitKey(1) # 獲取鍵值,不加此句,沒法運行程序!(Ref:https://www.cnblogs.com/kissfu/p/3608016.html) 40 if Key_val == ord('q'): 41 cv2.destroyAllWindows() 42 cap.release() 43 print('Pic Sample Finished!') 44 break
原圖像(size=640*480) convert 矯正圖像(size=620*461)
3、其餘擴展內容
一、姿式估計
相關內容,完成上面的攝像機內參的相關內容以後,咱們接下來在標定板上繪製咱們的世界座標系
在上述Python代碼的最下放添加以下所示的代碼,請注意,要選擇可以提取角點的標定圖像,因爲某些採集的標定圖像不能很好的返回角點,程序將會跳過第33行,咱們將沒法獲得對應的效果,建議直接取以前標定過程當中刪選出來的圖片做爲姿式估計的圖片源.
1 print('Starting Gesture_Draw...') 2 3 # 繪製簡單的座標系,調用此函數記得將下面的座標修改成axis_axis 4 axis_axis = np.float32([[3,0,0], [0,3,0], [0,0,-3]]).reshape(-1,3) 5 # 繪製簡單的座標系,調用此函數記得將下面的座標修改成axis_axis 6 def draw_axis(img, corners, imgpts): 7 corner = tuple(corners[0].ravel()) 8 img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5) 9 img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5) 10 img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5) 11 return img 12 # 繪製立體方塊在Pattern上,調用此函數記得將下面的座標修改成axis_cube 13 axis_cube = np.float32([[0,0,0], [0,3,0], [3,3,0], [3,0,0],[0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3]]) 14 # 繪製立體方塊在Pattern上,調用此函數記得將下面的座標修改成axis_cube 15 def draw_cube(img, corners, imgpts): 16 imgpts = np.int32(imgpts).reshape(-1,2) 17 # draw ground floor in green 18 img = cv2.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3) 19 # draw pillars in blue color 20 for i,j in zip(range(4),range(4,8)): 21 img = cv2.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3) 22 # draw top layer in red color 23 img = cv2.drawContours(img, [imgpts[4:]],-1,(0,0,255),3) 24 return img 25 26 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) 27 objp = np.zeros((6*7,3), np.float32) 28 objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2) 29 30 img = cv2.imread('C:/Users/miao.ma/Desktop/Software/test.jpg') 31 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 32 ret, corners = cv2.findChessboardCorners(gray, (7,6),None) 33 if ret == True: 34 corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria) 35 # Find the rotation and translation vectors. 36 ret, rvecs, tvecs, inliers = cv2.solvePnPRansac(objp, corners2, mtx, dist) 37 # project 3D points to image plane 38 imgpts, jac = cv2.projectPoints(axis_cube, rvecs, tvecs, mtx, dist) 39 img = draw_cube(img,corners2,imgpts) 40 cv2.imshow('img',img) 41 cv2.waitKey(800) 42 cv2.imwrite('C:/Users/miao.ma/Documents/Python Scripts/Calibresult/gesture.png', img) 43 cv2.destroyAllWindows() 44 print('Gesture_Draw Finised')
兩種繪製的結果以下圖所示:
二、單目測距
待補充~歡迎評論,你們有什麼好的建議和意見均可以一塊兒來討論一下,完善一下相機標定的相關內容,爲後面的雙目相機的標定作好準備!
4、參考文獻&參看連接
相機標定Reference01:http://www.javashuo.com/article/p-xeugwosn-cg.html
相機標定Reference02:http://www.javashuo.com/article/p-owssmyfs-c.html
相機標定Reference03:http://www.vision.caltech.edu/bouguetj/calib_doc/htmls/example.html
PPI(Pixle per inch) DPI(Dots per inch)之間的區別Reference04:http://www.javashuo.com/article/p-ctvyyjqb-t.html
相關空間座標,相機座標,圖像座標解釋參看Reference05:http://www.javashuo.com/article/p-divaddew-hs.html
三維空間旋轉,三維旋轉矩陣的向量表示方法參考Resference06:https://blog.csdn.net/wu_wenhuan/article/details/52588921
cv2中的計算內參外參函數calibrateCamera實現原理Reference07:https://blog.csdn.net/u010368556/article/details/79434240
彈幕測距參考Reference08:http://www.javashuo.com/article/p-rqnuqfyn-nm.html
轉載參考Reference09:http://www.javashuo.com/article/p-xeugwosn-cg.html
相關文件參考個人博客園文件:https://files.cnblogs.com/files/uestc-mm/Camera_calibration.7z
若是您以爲看完有所收穫,歡迎掃一掃,能夠資助一分,幾分money,不在意多少(我也是跟網上的大神們學的),不想掙錢娶媳婦的程序員不是好程序員,謝謝