相机矫正_实战 | 我用位姿解算实现单目相机测距
在項目過程中,總遇到需要單目視覺給出目標測距信息的情況,其實單目相機本不適合測距,即使能給出,精度也有限,只能在有限制的條件下或者對精度要求很不高的情況下進行應用。該文結合SLAM方法,通過3D-2D解算相機位姿的方式給出一種另類的單目測距方法,行之有效。
1
相機模型
要實現單目測距,那么相機參數是單目測距所必不可少的。相機參數有內參和外參之分:- 相機內參:是與相機自身特性相關的參數,比如相機的焦距、像素大小等;
- 相機外參:是在世界坐標系中的參數,比如相機的位置、旋轉方向等。
『world』——>『camera』
從世界坐標系到相機坐標系的,為剛體變換,反應了物體與相機的相對運動關系。
R為正交旋轉矩陣,T為平移矩陣。共有6個自由度,三個軸的旋轉角度(R)以及平移矩陣(T),這6個參數稱為相機的外參(Extrinsic)
『camera』——>『image』
若將成像平面移動到,相機光心與物體之間
則從相機坐標系到圖像坐標系的對應關系如下式所示:
從相機坐標系到圖像坐標系的投影只和相機的焦距f有關,只有一個自由度f。
『image』——>『pixel』
令dx、dy分別表示感光sensor 上每個點在象平面x和y方向上的物理尺寸,其中:
從圖像平面到像素平面的變換有 4個自由度。
『world』——>『pixel』
2
相機畸變
圖像的畸變主要有兩種:徑向畸變和切向畸變。
- 徑向畸變
- 切向畸變
3
相機標定
相機標定在OpenCV中已經做的很成熟了,只需要調用封裝好的API就可以。接下來簡要地說明一下流程:1.完成標定板圖像的采集2.角點檢測
利用findChessboardCorners()函數檢測標定板角點,并利用find4QuadCornerSubpix()函數完成亞像素級校準1、角點檢測函數bool findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE ); // image:傳入拍攝的棋盤圖Mat圖像,必須是8位的灰度或者彩色圖像// patternSize:每個棋盤圖上內角點的行列數,一般情況下,行列數不要相同,便于后續標定程序識別標定板的方向;// corners:用于存儲檢測到的內角點圖像坐標位置,一般用元素是Point2f的向量來表示:vector image_points_buf;// flage:用于定義棋盤圖上內角點查找的不同處理方式,有默認值。2、提取亞像素角點信息專門用來獲取棋盤圖上內角點的精確位置,降低相機標定偏差,還可以使用cornerSubPix函數bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);// img:輸入的Mat矩陣,最好是8位灰度圖像,檢測效率更高// corners:初始的角點坐標向量,同時作為亞像素坐標位置的輸出vector iamgePointsBuf;// region_size:角點搜索窗口的尺寸3.參數標定
利用calibrateCamera()函數進行相機標定,得到內參矩陣和畸變系數3、相機標定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:世界坐標系中的三維點,三維坐標點的向量的向量vector> object_points// imagePoints:每一個內角點對應的圖像坐標點,vector> image_points_seq形式// imageSize:圖像的像素尺寸大小(列數=cols,行數=rows)(寬度=width,高度=height)// cameraMatrix:相機的3*3內參矩陣,Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));// distCoeffs:1*5畸變矩陣,Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))// rvecs:旋轉向量,輸入一個Mat類型的vector,即vectorrvecs;// tvecs:位移向量,和rvecs一樣,應該為vector tvecs;// flags:標定時所采用的算法// criteria:最優迭代終止條件設定4
單目測距
核心思想:通過SLAM中3D-2D相機位姿估計(PnP)來實現單目測距
PnP(Perspective-n-Point)描述了當知道n個3D空間點及其投影位置時,如何估計相機的位姿。對應到SLAM問題上,在初始化完成后,前一幀圖像的特征點都已經被三角化,即已經知道了這些點的3D位置。那么新的幀到來后,通過圖像匹配就可以得到與那些3D點相對應的2D點,再根據這些3D-2D的對應關系,利用PnP算法解出當前幀的相機位姿。
PnP問題有多種求解方法,包括P3P、直接線性變換(DLT)、EPnP(Efficient PnP)、UPnP等等,而且它們在OpenCV中都有提供。
問題是:我們在實際應用中,無法知道相機拍到的物體的3D空間點坐標?!
要解決這個問題,重點在于活用上面的思想,如果沒有目標的3D空間點坐標,可以造一個出來,我們最后要的是相對距離,真實的世界坐標并不是一定需要的:當單目視覺檢測到前方物體時,該物體已經在圖像上成像并且有bouding_box。設該物體的實際尺寸已知(提前測量),則以該物體的左上角為坐標系原點,建立虛擬世界坐標系。這樣就有了目標的3D空間坐標,同時還有圖像上的目標檢測框,2D像素坐標也有了,剩下的工作就是調用下面的函數,直接獲取相機的位姿,并提取平移矩陣T的兩個分量,再經過簡單操作就可以獲得目標在相機坐標系下的水平和垂直方向的距離。void solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int flags = CV_ITERATIVE)Parameters:objectPoints?-?世界坐標系下的控制點的坐標,vector的數據類型在這里可以使用imagePoints - 在圖像坐標系下對應的控制點的坐標。vector在這里可以使用cameraMatrix - 相機的內參矩陣distCoeffs - 相機的畸變系數以上兩個參數通過相機標定可以得到。相機的內參數的標定參見:http://www.cnblogs.com/star91/p/6012425.htmlrvec - 輸出的旋轉向量。使坐標點從世界坐標系旋轉到相機坐標系tvec - 輸出的平移向量。使坐標點從世界坐標系平移到相機坐標系flags - 默認使用CV_ITERATIV迭代法下面給出一個求解單目測距的可調用類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
如果喜歡請關注我們
分圖片與文字來源于網絡,如有侵權請聯系刪除。
總結
以上是生活随笔為你收集整理的相机矫正_实战 | 我用位姿解算实现单目相机测距的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 添加白名单_上网行为管理如何添加网站白名
- 下一篇: 设置finder窗口大小_五个Finde