程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

python利用opencv進行相機標定獲取參數,並根據畸變參數修正圖像附有全部代碼(流暢無痛版)

編輯:Python

python利用opencv進行相機標定獲取參數,並根據畸變參數修正圖像附有全部代碼

一、前言

今天的低價單孔攝像機(照相機)會給圖像帶來很多畸變。畸變主要有兩
種:徑向畸變和切想畸變。如下圖所示,用紅色直線將棋盤的兩個邊標注出來,
但是你會發現棋盤的邊界並不和紅線重合。所有我們認為應該是直線的也都凸
出來了。

在 3D 相關應用中,必須要先校正這些畸變。為了找到這些糾正參數,我們必
須要提供一些包含明顯圖案模式的樣本圖片(比如說棋盤)。我們可以在上面找
到一些特殊點(如棋盤的四個角點)。我們起到這些特殊點在圖片中的位置以及
它們的真是位置。有了這些信息,我們就可以使用數學方法求解畸變系數。這
就是整個故事的摘要了。為了得到更好的結果,我們至少需要 10 個這樣的圖
案模式。

二、獲取待標定的攝像頭拍攝帶棋盤圖的圖片

1、運行生成棋盤圖的程序:

import cv2
import numpy as np
# 定義棋盤格的尺寸
size = 140
# 定義標定板尺寸
boardx = size * 10
boardy = size * 10
canvas = np.zeros((boardy, boardx, 1), np.uint8) # 創建畫布
for i in range(0, boardx):
for j in range(0, boardy):
if (int(i/size) + int(j/size)) % 2 != 0: # 判定是否為奇數格
canvas[j, i] = 255
cv2.imwrite("./chessboard.png", canvas)

生成結果如下:

2、打印圖片並張貼至平板上

將棋盤圖用A4紙打印,並將將A4紙貼到一個很平的板子上固定好
例子如下:
有錢的大佬,可以直接買標定板。
注意,如果是打印的棋盤格一定要貼平,不然標定不准確

3、拍攝畸變圖像

使用相機從不同角度,不同位置拍攝(15-20)張標定圖。類似這樣的:

python調用opencv相機拍照代碼(例):


import cv2
camera = cv2.VideoCapture(0)
i = 0
ret, img = camera.read()
print('輸入j,下載當前圖片')
print('輸入q,終止程序')
while ret:
cv2.imshow('img', img)
ret, img = camera.read()
if cv2.waitKey(1) & 0xFF == ord('j'): # 按j保存一張圖片
i += 1
firename = str('./img' + str(i) + '.jpg')
cv2.imwrite(firename, img)
print('寫入:', firename)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.release()
cv2.destroyAllWindows()

按j拍攝圖片,將會按照順序批量保存,按q退出程序。

三、相機標定程序流程及相關原理解釋

注:該部分只起解釋作用並無實際操作,正式操作可以跳過直接執行第四步

1、利用opencv尋找棋盤

為了找到棋盤的圖案,我們要使用函數 cv2.findChessboardCorners()。
我們還需要傳入圖案的類型,比如說 8x8 的格子或 5x5 的格子等。在本例中
我們使用的9×6 的格子。(通常情況下棋盤都是 8x8 或者 7x7)。它會返
回角點,如果得到圖像的話返回值類型(Retval)就會是 True。這些角點會
按順序排列(從左到右,從上到下)

這個函數可能不會找出所有圖像中應有的圖案。所以一個好的方法是編
寫代碼,啟動攝像機並在每一幀中檢查是否有應有的圖案。在我們獲得圖案之後我們要找到角點並把它們保存成一個列表。在讀取下一幀圖像之前要設置一定的間隔,這樣我們就有足夠的時間調整棋盤的方向。繼續這個過程直到我們得到足夠多好的圖案。就算是我們舉得這個例子,在所有的14 幅圖像中也不知道有幾幅是好的。所以我們要讀取每一張圖像從其中找到好的能用的。

除 了 使 用 棋 盤 之 外, 我 們 還 可 以 使 用 環 形 格 子, 但 是 要 使 用 函 數
cv2.findCirclesGrid() 來找圖案。據說使用環形格子只需要很少的圖像 就可以了。

在找到這些角點之後我們可以使用函數 cv2.cornerSubPix() 增加准確
度。我們使用函數 cv2.drawChessboardCorners() 繪制圖案。所有的這
些步驟都被包含在下面的代碼中了:

2、標定

在得到了這些對象點和圖像點之後,我們已經准備好來做攝像機標定了。
我們要使用的函數是 cv2.calibrateCamera()。它會返回攝像機矩陣,畸
變系數,旋轉和變換向量等。

3、畸變矯正

現在我們找到我們想要的東西了,我們可以找到一幅圖像來對他進行校正
了。OpenCV 提供了兩種方法,我們都學習一下。不過在那之前我們可以使用
從函數 cv2.getOptimalNewCameraMatrix() 得到的自由縮放系數對攝
像機矩陣進行優化。如果縮放系數 alpha = 0,返回的非畸變圖像會帶有最少量
的不想要的像素。它甚至有可能在圖像角點去除一些像素。如果 alpha = 1,所
有的像素都會被返回,還有一些黑圖像。它還會返回一個 ROI 圖像,我們可以
用來對結果進行裁剪。

函數:cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))中參數1是個坑,

這裡我們使用cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),0,(w,h))參數設置為0

4 、畸變到非畸變
下面代碼中

  • dst1圖像使用的是 cv2.undistort() 這是最簡單的方法。只需使用這個函數和上邊得到的 ROI 對結果進行裁剪

  • dst2圖像使用的是remapping 這應該屬於“曲線救國”了。首先我們要找到從畸變圖像到非畸變圖像的映射方程。再使用重映射方程。(代碼中有詳細用法)

兩種效果可以自行對比看看

糾正前後對比:

5、反向投影誤差

我們可以利用反向投影誤差對我們找到的參數的准確性進行估計。得到的
結果越接近 0 越好。有了內部參數,畸變參數和旋轉變換矩陣,我們就可以使
用 cv2.projectPoints() 將對象點轉換到圖像點。然後就可以計算變換得到
圖像與角點檢測算法的絕對差了。然後我們計算所有標定圖像的誤差平均值。

(但是本文不需要,所以沒有將其寫入)

四、相機標定程序

目的:獲取相機修正畸變後的內參

1、配置環境

a、安裝opencv-python

pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple/

b、安裝glob

pip install glob2 -i https://pypi.tuna.tsinghua.edu.cn/simple/

2、運行程序獲取內參

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 = 9 # 10 - 1 
h = 9 # 10 - 1
# 世界坐標系中的棋盤格點,例如(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)
objp = objp*18.1 # 18.1 mm
# 儲存棋盤格角點的世界坐標和圖像坐標對
objpoints = [] # 在世界坐標系中的三維點
imgpoints = [] # 在圖像平面的二維點
#加載pic文件夾下所有的jpg圖像
images = glob.glob('./*.jpg') # 拍攝的十幾張棋盤圖片所在目錄
i=0
for fname in images:
img = cv2.imread(fname)
# 獲取畫面中心點
#獲取圖像的長寬
h1, w1 = img.shape[0], img.shape[1]
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
u, v = img.shape[:2]
# 找到棋盤格角點
ret, corners = cv2.findChessboardCorners(gray, (w,h),None)
# 如果找到足夠點對,將其存儲起來
if ret == True:
print("i:", i)
i = i+1
# 在原角點的基礎上尋找亞像素角點
cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
#追加進入世界三維點和平面二維點中
objpoints.append(objp)
imgpoints.append(corners)
# 將角點在圖像上顯示
cv2.drawChessboardCorners(img, (w,h), corners, ret)
cv2.namedWindow('findCorners', cv2.WINDOW_NORMAL)
cv2.resizeWindow('findCorners', 640, 480)
cv2.imshow('findCorners',img)
cv2.waitKey(200)
cv2.destroyAllWindows()
#%% 標定
print('正在計算')
#標定
ret, mtx, dist, rvecs, tvecs = \
cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
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 ) # 平移向量 # 外參數
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (u, v), 0, (u, v))
print('newcameramtx外參',newcameramtx)
#打開攝像機
camera=cv2.VideoCapture(0)
while True:
(grabbed,frame)=camera.read()
h1, w1 = frame.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (u, v), 0, (u, v))
# 糾正畸變
dst1 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
#dst2 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
mapx,mapy=cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w1,h1),5)
dst2=cv2.remap(frame,mapx,mapy,cv2.INTER_LINEAR)
# 裁剪圖像,輸出糾正畸變以後的圖片
x, y, w1, h1 = roi
dst1 = dst1[y:y + h1, x:x + w1]
#cv2.imshow('frame',dst2)
#cv2.imshow('dst1',dst1)
cv2.imshow('dst2', dst2)
if cv2.waitKey(1) & 0xFF == ord('q'): # 按q保存一張圖片
cv2.imwrite("../u4/frame.jpg", dst1)
break
camera.release()
cv2.destroyAllWindows()

代碼放到圖片相同的文件夾直接運行即可
運行結果如下:

五、根據上一步獲取的內參修正相機


上一個程序可以運行得到畸變內參,將mtx值保存在k,
將dist保存在d

注:復制的時候,數組內部需要手動加一下逗號

1、視頻程序

import cv2 as cv
import numpy as np
def undistort(frame):
k=np.array( [[408.96873567 , 0. ,329.01126845],
[ 0. , 409.20308599 ,244.73617469],
[ 0. , 0. , 1. ]])
d=np.array([-0.33880708 , 0.16416173 ,-0.00039069 ,-0.00056267 ,-0.056967 ])
h,w=frame.shape[:2]
mapx,mapy=cv.initUndistortRectifyMap(k,d,None,k,(w,h),5)
return cv.remap(frame,mapx,mapy,cv.INTER_LINEAR)
cap=cv.VideoCapture(0)# 換成要打開的攝像頭編號
ret,frame=cap.read()
while ret:
cv.imshow('later',frame)
cv.imshow('img',undistort(frame))
ret,frame=cap.read()
if cv.waitKey(1)&0xff==27:
break
cap.release()
cv.destroyAllWindows()

2、糾正結果

效果對比
糾正前後:


可以看到,畸變被糾正的差不多了。
畸變這個程序運行一次即可,之後的話,在攝像頭每次獲取圖像的時候都加上上面那個即可


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved