相机矫正_实战 | 我用位姿解算实现单目相机测距
在項(xiàng)目過程中,總遇到需要單目視覺給出目標(biāo)測距信息的情況,其實(shí)單目相機(jī)本不適合測距,即使能給出,精度也有限,只能在有限制的條件下或者對(duì)精度要求很不高的情況下進(jìn)行應(yīng)用。該文結(jié)合SLAM方法,通過3D-2D解算相機(jī)位姿的方式給出一種另類的單目測距方法,行之有效。
1
相機(jī)模型
要實(shí)現(xiàn)單目測距,那么相機(jī)參數(shù)是單目測距所必不可少的。相機(jī)參數(shù)有內(nèi)參和外參之分:- 相機(jī)內(nèi)參:是與相機(jī)自身特性相關(guān)的參數(shù),比如相機(jī)的焦距、像素大小等;
- 相機(jī)外參:是在世界坐標(biāo)系中的參數(shù),比如相機(jī)的位置、旋轉(zhuǎn)方向等。
『world』——>『camera』
從世界坐標(biāo)系到相機(jī)坐標(biāo)系的,為剛體變換,反應(yīng)了物體與相機(jī)的相對(duì)運(yùn)動(dòng)關(guān)系。
R為正交旋轉(zhuǎn)矩陣,T為平移矩陣。共有6個(gè)自由度,三個(gè)軸的旋轉(zhuǎn)角度(R)以及平移矩陣(T),這6個(gè)參數(shù)稱為相機(jī)的外參(Extrinsic)
『camera』——>『image』
若將成像平面移動(dòng)到,相機(jī)光心與物體之間
則從相機(jī)坐標(biāo)系到圖像坐標(biāo)系的對(duì)應(yīng)關(guān)系如下式所示:
從相機(jī)坐標(biāo)系到圖像坐標(biāo)系的投影只和相機(jī)的焦距f有關(guān),只有一個(gè)自由度f。
『image』——>『pixel』
令dx、dy分別表示感光sensor 上每個(gè)點(diǎn)在象平面x和y方向上的物理尺寸,其中:
從圖像平面到像素平面的變換有 4個(gè)自由度。
『world』——>『pixel』
2
相機(jī)畸變
圖像的畸變主要有兩種:徑向畸變和切向畸變。
- 徑向畸變
- 切向畸變
3
相機(jī)標(biāo)定
相機(jī)標(biāo)定在OpenCV中已經(jīng)做的很成熟了,只需要調(diào)用封裝好的API就可以。接下來簡要地說明一下流程:1.完成標(biāo)定板圖像的采集2.角點(diǎn)檢測
利用findChessboardCorners()函數(shù)檢測標(biāo)定板角點(diǎn),并利用find4QuadCornerSubpix()函數(shù)完成亞像素級(jí)校準(zhǔn)1、角點(diǎn)檢測函數(shù)bool findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE ); // image:傳入拍攝的棋盤圖Mat圖像,必須是8位的灰度或者彩色圖像// patternSize:每個(gè)棋盤圖上內(nèi)角點(diǎn)的行列數(shù),一般情況下,行列數(shù)不要相同,便于后續(xù)標(biāo)定程序識(shí)別標(biāo)定板的方向;// corners:用于存儲(chǔ)檢測到的內(nèi)角點(diǎn)圖像坐標(biāo)位置,一般用元素是Point2f的向量來表示:vector image_points_buf;// flage:用于定義棋盤圖上內(nèi)角點(diǎn)查找的不同處理方式,有默認(rèn)值。2、提取亞像素角點(diǎn)信息專門用來獲取棋盤圖上內(nèi)角點(diǎn)的精確位置,降低相機(jī)標(biāo)定偏差,還可以使用cornerSubPix函數(shù)bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);// img:輸入的Mat矩陣,最好是8位灰度圖像,檢測效率更高// corners:初始的角點(diǎn)坐標(biāo)向量,同時(shí)作為亞像素坐標(biāo)位置的輸出vector iamgePointsBuf;// region_size:角點(diǎn)搜索窗口的尺寸3.參數(shù)標(biāo)定
利用calibrateCamera()函數(shù)進(jìn)行相機(jī)標(biāo)定,得到內(nèi)參矩陣和畸變系數(shù)3、相機(jī)標(biāo)定double calibrateCamera( InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, Size imageSize, CV_OUT InputOutputArray cameraMatrix, CV_OUT InputOutputArray distCoeffs, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags=0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) ); // objectPoints:世界坐標(biāo)系中的三維點(diǎn),三維坐標(biāo)點(diǎn)的向量的向量vector> object_points// imagePoints:每一個(gè)內(nèi)角點(diǎn)對(duì)應(yīng)的圖像坐標(biāo)點(diǎn),vector> image_points_seq形式// imageSize:圖像的像素尺寸大小(列數(shù)=cols,行數(shù)=rows)(寬度=width,高度=height)// cameraMatrix:相機(jī)的3*3內(nèi)參矩陣,Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));// distCoeffs:1*5畸變矩陣,Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))// rvecs:旋轉(zhuǎn)向量,輸入一個(gè)Mat類型的vector,即vectorrvecs;// tvecs:位移向量,和rvecs一樣,應(yīng)該為vector tvecs;// flags:標(biāo)定時(shí)所采用的算法// criteria:最優(yōu)迭代終止條件設(shè)定4
單目測距
核心思想:通過SLAM中3D-2D相機(jī)位姿估計(jì)(PnP)來實(shí)現(xiàn)單目測距
PnP(Perspective-n-Point)描述了當(dāng)知道n個(gè)3D空間點(diǎn)及其投影位置時(shí),如何估計(jì)相機(jī)的位姿。對(duì)應(yīng)到SLAM問題上,在初始化完成后,前一幀圖像的特征點(diǎn)都已經(jīng)被三角化,即已經(jīng)知道了這些點(diǎn)的3D位置。那么新的幀到來后,通過圖像匹配就可以得到與那些3D點(diǎn)相對(duì)應(yīng)的2D點(diǎn),再根據(jù)這些3D-2D的對(duì)應(yīng)關(guān)系,利用PnP算法解出當(dāng)前幀的相機(jī)位姿。
PnP問題有多種求解方法,包括P3P、直接線性變換(DLT)、EPnP(Efficient PnP)、UPnP等等,而且它們在OpenCV中都有提供。
問題是:我們在實(shí)際應(yīng)用中,無法知道相機(jī)拍到的物體的3D空間點(diǎn)坐標(biāo)?!
要解決這個(gè)問題,重點(diǎn)在于活用上面的思想,如果沒有目標(biāo)的3D空間點(diǎn)坐標(biāo),可以造一個(gè)出來,我們最后要的是相對(duì)距離,真實(shí)的世界坐標(biāo)并不是一定需要的:當(dāng)單目視覺檢測到前方物體時(shí),該物體已經(jīng)在圖像上成像并且有bouding_box。設(shè)該物體的實(shí)際尺寸已知(提前測量),則以該物體的左上角為坐標(biāo)系原點(diǎn),建立虛擬世界坐標(biāo)系。這樣就有了目標(biāo)的3D空間坐標(biāo),同時(shí)還有圖像上的目標(biāo)檢測框,2D像素坐標(biāo)也有了,剩下的工作就是調(diào)用下面的函數(shù),直接獲取相機(jī)的位姿,并提取平移矩陣T的兩個(gè)分量,再經(jīng)過簡單操作就可以獲得目標(biāo)在相機(jī)坐標(biāo)系下的水平和垂直方向的距離。void solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int flags = CV_ITERATIVE)Parameters:objectPoints?-?世界坐標(biāo)系下的控制點(diǎn)的坐標(biāo),vector的數(shù)據(jù)類型在這里可以使用imagePoints - 在圖像坐標(biāo)系下對(duì)應(yīng)的控制點(diǎn)的坐標(biāo)。vector在這里可以使用cameraMatrix - 相機(jī)的內(nèi)參矩陣distCoeffs - 相機(jī)的畸變系數(shù)以上兩個(gè)參數(shù)通過相機(jī)標(biāo)定可以得到。相機(jī)的內(nèi)參數(shù)的標(biāo)定參見:http://www.cnblogs.com/star91/p/6012425.htmlrvec - 輸出的旋轉(zhuǎn)向量。使坐標(biāo)點(diǎn)從世界坐標(biāo)系旋轉(zhuǎn)到相機(jī)坐標(biāo)系tvec - 輸出的平移向量。使坐標(biāo)點(diǎn)從世界坐標(biāo)系平移到相機(jī)坐標(biāo)系flags - 默認(rèn)使用CV_ITERATIV迭代法下面給出一個(gè)求解單目測距的可調(diào)用類PnPDistance()的簡單測試代碼樣例,如下所示:
# -*-coding:utf-8-*-import?cv2import numpy as npclass PnPDistance():????def?__init__(self,?args):????????self.category?=?args.classes_names self.cam = np.array([1.5880436204354560e+03, 0., 960., 0., 1.5880436204354560e+03,????????????????????????600.,?0.,?0.,?1.],?dtype=np.float64).reshape((3,?3)) self.distortion = np.array([-1.8580303062080919e-01, 5.7927645928450899e-01, 5.5271164249844681e-03, -1.2684978794253729e-04, -5.6884229185639223e-01], dtype=np.float64).reshape((1, 5)) self.obj_true_size = [[180, 180], [250, 250], [170, 45], [100, 150], [250, 180], [100, 150], [2000, 1000], [170, 45], [200, 100], [110, 48], [110, 48], [110, 48], [110, 48], [110, 48], [40, 105], [40, 105], [40, 105], [80, 30], [80, 30], [80, 30], [80, 30], [80, 30], [80, 30], [80, 30], [80, 30], [80, 30], [70, 35], [45, 45]] def get_distance(self, obj_dict): """ :param bbox: x,y,w,h :param category: category :return: """ # boxes = [] # categories = [] out_dist = [] out_dist_size = [] for keys, items in obj_dict.items(): left, top, right, bottom = items['bbox'] bbox = [left, top, right-left, bottom-top] category = self.category.index(items['label'].split(' ')[0]) # boxes.append(bbox) # categories.append(category) obj_pw, obj_ph = self.obj_true_size[category] if obj_pw < obj_ph: obj_pw = obj_ph else: obj_ph = obj_pw obj_p = np.array([(0, 0, 0), (obj_pw, 0, 0), (obj_pw, obj_ph, 0), (0, obj_ph, 0)], dtype=np.float64).reshape((1, 4, 3)) img_rect_w, img_rect_h = bbox[2], bbox[3] if img_rect_w < img_rect_h: img_rect_w = img_rect_h else: img_rect_h = img_rect_w img_points = np.array([(bbox[0], bbox[1]), (bbox[0] + img_rect_w, bbox[1]), (bbox[0] + img_rect_w, bbox[1] + img_rect_h), (bbox[0] + img_rect_w, bbox[1])], dtype=np.float64).reshape((1, 4, 2)) ret_val, r_vec, t_vec = cv2.solvePnP(obj_p, img_points, self.cam, self.distortion, useExtrinsicGuess=False, flags=cv2.SOLVEPNP_AP3P) out_dist.append([round(t_vec[0][0] / 100.0, 1), round(t_vec[2][0] / 100.0, 1)]) out_dist_size.append(self.obj_true_size[category]) return out_dist, out_dist_size參考鏈接:
https://www.pianshen.com/article/5864313789/
https://blog.csdn.net/u011144848/article/details/90605108
如果喜歡請關(guān)注我們
分圖片與文字來源于網(wǎng)絡(luò),如有侵權(quán)請聯(lián)系刪除。
總結(jié)
以上是生活随笔為你收集整理的相机矫正_实战 | 我用位姿解算实现单目相机测距的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 添加白名单_上网行为管理如何添加网站白名
- 下一篇: 设置finder窗口大小_五个Finde