學(xué)習(xí)記錄。
事實上很早就接觸過視覺定位這東西,,但是到現(xiàn)在才返回頭學(xué)習(xí)一下相機的標(biāo)定,,真是可恥啊,!我把想法和過程記錄一下,。
相機成像
相機的成像原理——小孔成像
然而,,在實際由于設(shè)計工藝問題、相機安裝環(huán)境或物體擺放位置等影響,,會照成成像與實際圖像不一樣的現(xiàn)象,。
由于設(shè)計工藝照成的影響是無法改變的事實,所以這將是相機的內(nèi)參,;
由環(huán)境或安裝方式照成的影響是可以改變的,,這就是相機的外參。
在https://blog.csdn.net/aoulun/article/details/78768570中詳細介紹了相機成像原理,,相機內(nèi),、外參數(shù)是什么。這里為了保證記錄的完整型,,把成像平面的像素座標(biāo)與實際物體的世界座標(biāo)公式寫下來,。
1.紅框就是相機外參,R為旋轉(zhuǎn)矩陣,,T為平移向量,;如果相機鏡頭和物體平面平行(室內(nèi)定位中,有一種基于視覺的室內(nèi)定位,,定位方式就是在移動的小車上安裝單目相機,,在屋頂安裝各種可識別的標(biāo)簽,相機的光軸一直與屋頂是垂直的),,在這種情況下,旋轉(zhuǎn)矩陣可以看作是單位向量及R=E,,而平移向量T=0,。
2.藍框就是相機的內(nèi)參,相機的內(nèi)參從出廠后就被固定了,。
f:相機的焦距
(u0,v0):像平面的投影中心點
dx,dy:就是單位像素對應(yīng)實際距離
Zc:可以認為相機鏡頭到成像物體的垂直距離
對這部分我就不贅述了,,在麻呱智能 的文章中介紹的特別詳細,我就不班門弄斧了,。
生成棋盤標(biāo)定圖
創(chuàng)建自定義的棋盤標(biāo)定圖,,這個沒啥要說的,就是調(diào)用了opencv的畫矩形框的函數(shù),,代碼如下:
#生成想要的標(biāo)定圖,,大小自定義
import cv2
import sys
#讀入一張空白圖片,該圖片最好和你想要標(biāo)定的相機分辨率一致
image = cv2.imread('C:\\Users\\wlx\\Documents\\py_study\\camera calibration\\white.jpg')
#設(shè)置圖片上黑白方格
dpi = 96 #dpi自己電腦上一英寸顯示的像素個數(shù)
cm_to_inch = 0.3937 #1cm = 0.3937inch
square_length = 1.5 #黑白方格邊長1.5cm
x_nums = 10 #x方向畫10個方格
y_nums = 8 #y方向畫8個方格
square_pixel = int(square_length * cm_to_inch * dpi) #方格邊長的像素
#為了把方格圖像放在紙張的中間,設(shè)定起始座標(biāo)
x0 = 40
y0 = 16
#畫方格
def DrawSquare():
flag = -1 #顏色轉(zhuǎn)變標(biāo)志
#一行一行的畫
for i in range(y_nums):
#每畫一行先換一次顏色
flag = 0 - flag
for j in range(x_nums):
if flag > 0:
color = [0,0,0] #黑色方格
else:
color = [255,255,255]
#調(diào)用opencv中的畫方框函數(shù)
cv2.rectangle(image,(x0 + j*square_pixel,y0 + i*square_pixel),
(x0 + j*square_pixel+square_pixel,y0 + i*square_pixel+square_pixel),color,-1)
flag = 0 - flag
#保存圖像
cv2.imwrite('chess_map.jpg',image)
#判斷本程序是獨立運行還是被調(diào)用
if __name__ == '__main__':
DrawSquare()
上面的代碼可以生成自己想要的棋盤標(biāo)定圖,,修改x_nums和y_nums參數(shù)的值,,就能獲得任意數(shù)量的黑白格子。實現(xiàn)原理就是在圖像的某一點開始,,先畫一個黑色方格(或者白色),畫完后將起點座標(biāo)和終點座標(biāo)都向右移動方格邊長的距離,,然后改變顏色再畫一個方格,,依次類推,畫完一行后,,就轉(zhuǎn)戰(zhàn)到第二行,,直到全部完成。
注意:在圖像上座標(biāo)是這樣的
cv2.rectangle(img,pt1,pt2,color,thickness=None,line_type=None,shift=None)
img:圖像,,要畫圖的圖像
pt1:方格的起點座標(biāo)(x0,y0)
pt2:方格的終點座標(biāo)(x1,y1)
color:方框的顏色
thickness:方框線的寬度(像素),當(dāng)值為負數(shù)時,,填充方框
line_type:方框線的樣式
采集標(biāo)定圖
我采用離線標(biāo)定,先把不同角度的標(biāo)定圖采集保存下來,,然后再開始標(biāo)定,。下面是采集的代碼
import cv2
import os
#標(biāo)定圖像保存路徑
photo_path = "C:\\Users\\wlx\\Documents\\py_study\\camera calibration\\image"
#創(chuàng)建路徑
def CreateFolder(path):
#去除首位空格
del_path_space = path.strip()
#去除尾部'\'
del_path_tail = del_path_space.rstrip('\\')
#判讀輸入路徑是否已存在
isexists = os.path.exists(del_path_tail)
if not isexists:
os.makedirs(del_path_tail)
return True
else:
return False
#獲取不同角度的標(biāo)定圖像
def gain_photo(photo_path):
# 檢查輸入路徑是否存在——不存在就創(chuàng)建
CreateFolder(photo_path)
#開啟攝像頭
video = cv2.VideoCapture(0)
#顯示窗口名稱
photo_window = 'calibration'
#保存的標(biāo)定圖像名稱以數(shù)量命名
photo_num = 0
while video.isOpened():
ok,frame = video.read() #讀一幀的圖像
if not ok:
break
else:
cv2.imshow(photo_window,frame)
key = cv2.waitKey(10)
#按鍵盤‘A’保存圖像
if key & 0xFF == ord('a'):
photo_num += 1
photo_name = photo_path + '\\' + str(photo_num) + '.jpg'
cv2.imwrite(photo_name,frame)
print('create photo is :',photo_name)
#按鍵盤‘Q’中斷采集
if key & 0xFF == ord('q'):
break
if __name__ == '__main__':
gain_photo(photo_path)
video = cv2.VideoCapture(0)中的0代表的是我的USB相機在我電腦上的驅(qū)動位置
if __name__ == '__main__':就是判斷這些代碼是不是要當(dāng)作單獨的代碼執(zhí)行,如果是就執(zhí)行if中的內(nèi)容,。
標(biāo)定
我先上代碼吧
import cv2
import sys
import numpy as np
import glob
#標(biāo)定圖像保存路徑
photo_path = "C:\\Users\\wlx\\Documents\\py_study\\camera calibration\\image"
#標(biāo)定圖像
def calibration_photo(photo_path):
#設(shè)置要標(biāo)定的角點個數(shù)
x_nums = 9 #x方向上的角點個數(shù)
y_nums = 7
# 設(shè)置(生成)標(biāo)定圖在世界座標(biāo)中的座標(biāo)
world_point = np.zeros((x_nums * y_nums,3),np.float32) #生成x_nums*y_nums個座標(biāo),,每個座標(biāo)包含x,y,z三個元素
world_point[:,:2] = np.mgrid[:x_nums,:y_nums].T.reshape(-1, 2) #mgrid[]生成包含兩個二維矩陣的矩陣,每個矩陣都有x_nums列,y_nums行
#.T矩陣的轉(zhuǎn)置
#reshape()重新規(guī)劃矩陣,,但不改變矩陣元素
#保存角點座標(biāo)
world_position = []
image_position = []
#設(shè)置角點查找限制
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,30,0.001)
#獲取所有標(biāo)定圖
images = glob.glob(photo_path+'\\*.jpg')
#print(images)
for image_path in images:
image = cv2.imread(image_path)
gray = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
#查找角點
ok,corners = cv2.findChessboardCorners(gray,(y_nums,x_nums),None)
if ok:
#把每一幅圖像的世界座標(biāo)放到world_position中
world_position.append(world_point)
#獲取更精確的角點位置
exact_corners = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
#把獲取的角點座標(biāo)放到image_position中
image_position.append(exact_corners)
#可視化角點
# image = cv2.drawChessboardCorners(image,(y_nums,x_nums),exact_corners,ok)
# cv2.imshow('image_corner',image)
# cv2.waitKey(5000)
#計算內(nèi)外參數(shù)
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(world_position, image_position, gray.shape[::-1], None,None)
print(mtx, dist)
#計算偏差
mean_error = 0
for i in range(len(world_position)):
image_position2, _ = cv2.projectPoints(world_position[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(image_position[i], image_position2, cv2.NORM_L2) / len(image_position2)
mean_error += error
print("total error: ", mean_error / len(image_position))
if __name__ == '__main__':
calibration_photo(photo_path)
使用opencv標(biāo)定這些圖像,,步驟大致就是:
1、設(shè)置想標(biāo)定角點的個數(shù)
2,、創(chuàng)建對應(yīng)角點個數(shù)的世界座標(biāo)
3,、將采集到的標(biāo)定圖讀入緩存
4、灰度處理
5,、使用findChessboardCorners(img,patternSize,corners,flags=None)函數(shù),,查找圖像中的內(nèi)點
- image:輸入的棋盤圖像,必須是8位的灰度或者彩色圖像
- patternSize:棋盤中每行每列的角點個數(shù)
- corners:檢測到的角點
- flags:各種操作標(biāo)準(zhǔn)
6,、使用cornerSubPix(image,corners,winSize,zeroZone,criteria)函數(shù),,精確查找圖像上的角點
-
image:輸入圖像,,,必須是8位的灰度或者彩色圖像
-
corners:輸入角點的初始化座標(biāo),,也存儲精確的輸出角點座標(biāo)
-
winSize:搜索窗口的一半尺度,如winSize=(5,5),,則使用(2x5+1, 2x5+1)=(11,11)的搜索窗
-
zeroZone:死區(qū)的一半尺寸,,死區(qū)為不對搜索區(qū)做求和運算的區(qū)域,當(dāng)值為(-1,-1)時,,表示沒有死區(qū)
-
criteria:搜索角點停止的標(biāo)志
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,30,0.001)就是一個標(biāo)志,,TERM_CRITERIA_EPS 代表誤差也就是精度,TERM_CRITERIA_MAX_ITER代表迭代次數(shù),,它兩之和就是指兩個因素同時作用,,這里當(dāng)?shù)螖?shù)超過30或誤差大于0.001都會停止運算。
7、將圖像的世界座標(biāo)保存到數(shù)組world_position中,,將找到的角點座標(biāo)保存到數(shù)組image_position中
8,、使用cv2.calibrateCamera(world_position, image_position, gray.shape[::-1], None,None)計算內(nèi)外參數(shù)
9、計算一下準(zhǔn)確度,,也就是通過你算出來的內(nèi)外參數(shù),,逆運算出角點座標(biāo),然后將這個座標(biāo)和識別出來的角點座標(biāo)進行誤差運算,,得到偏差值,。
最后,可以將得到的內(nèi)外參數(shù)保存到一個文件中,,以后在用相機采集圖像時,,就可以用內(nèi)外參數(shù)去矯正圖像了,這里我沒做圖像的矯正,,過幾天做了再也上來,。
另外,用來標(biāo)定的圖像不要太少,,我做了實驗隨著標(biāo)定圖像的增加,,最后計算出的偏差會減小,;而且拍攝標(biāo)定圖像時,,角度要合理,相機固定位置不要發(fā)生變化,。
下面是我實驗,,就是為了驗證代碼是否能運行,所以沒有打印標(biāo)定圖紙,,而是直接用攝像頭拍攝電腦顯示器上的圖像,,攝像頭也沒固定牢靠,所以結(jié)果不是很好,。拍攝了20張圖像
相機內(nèi)參:
[[1.24956824e+03 0.00000000e+00 2.16230694e+02]
[0.00000000e+00 2.92154313e+03 3.87901459e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
相機外參:
[[-2.80875093e+02 8.92572104e+03 -2.80196846e+00 8.90336346e+00 -1.46134126e+05]]
偏差值:
error: 6.968280883027417
|