本博客內(nèi)容來源于網(wǎng)絡(luò)以及其他書籍,結(jié)合自己學(xué)習(xí)的心得進(jìn)行重編輯,因?yàn)榭戳撕芏辔恼虏槐阋灰粯?biāo)注引用,如圖片文字等侵權(quán),請(qǐng)告知?jiǎng)h除。
傳統(tǒng)2D計(jì)算機(jī)視覺學(xué)習(xí)筆記目錄------->傳送門
傳統(tǒng)3D計(jì)算機(jī)視覺學(xué)習(xí)筆記目錄------->傳送門
前言
在閱讀本篇文章之前,建議首先要了解知道什么是相機(jī)的針孔模型,我在之前的文章小孔相機(jī)參數(shù)學(xué)習(xí)筆記中有詳細(xì)的解釋,或者說不是建議,而是必須要知道,不然也不知道這篇文章在干什么。本片文章主要來講張正友視覺標(biāo)定法,在講張正友之前,我們先說一下什么是相機(jī)標(biāo)定。
所謂相機(jī)標(biāo)定,就是通過實(shí)驗(yàn)等方法將相機(jī)模型中的未知數(shù)給解算出來。例如小孔模型我們一般需要節(jié)算出內(nèi)參矩陣 fx,fy,cx,cy 以及畸變參數(shù)。
為什么要標(biāo)定出這些參數(shù)呢?一個(gè)是因?yàn)槊總€(gè)鏡頭的畸變程度各不相同,通過相機(jī)標(biāo)定可以校正這種鏡頭畸變矯正畸變,生成矯正后的圖像;另一個(gè)是根據(jù)獲得的圖像重構(gòu)三維場(chǎng)景,在之前文章我們有寫過PNP算法,就是通過相機(jī)的內(nèi)參以及畸變解算出目標(biāo)在相機(jī)下的位置,以及雙目3D相機(jī)也需要相機(jī)模型參數(shù)進(jìn)行計(jì)算等。
有很多標(biāo)定方法,比如傳統(tǒng)相機(jī)標(biāo)定法、主動(dòng)視覺相機(jī)標(biāo)定方法、相機(jī)自標(biāo)定法等,但是現(xiàn)在用的最多的就是相機(jī)的自標(biāo)定法,而相機(jī)的自標(biāo)定法的基礎(chǔ)就是張正友標(biāo)定法的解算流程。下面我們一起來看一下張正友標(biāo)定法。

張正友標(biāo)定法簡(jiǎn)介
首先一個(gè)問題,張正友是誰?
張正友博士。世界著名的計(jì)算機(jī)視覺和多媒體技術(shù)的專家,ACM Fellow,IEEE Fellow。剛剛從微軟研究院視覺技術(shù)組離職,加入騰訊AI Lab擔(dān)任負(fù)責(zé)人。他在立體視覺、三維重建、運(yùn)動(dòng)分析、圖像配準(zhǔn)、攝像機(jī)標(biāo)定等方面都有開創(chuàng)性的貢獻(xiàn)。
那么張正友標(biāo)定法又是什么?
“張正友標(biāo)定法”是張正友博士在1999年發(fā)表在國際頂級(jí)會(huì)議ICCV上的論文《Flexible Camera Calibration By Viewing a Plane From Unknown Orientations》中,提出的一種利用平面棋盤格進(jìn)行相機(jī)標(biāo)定的實(shí)用方法。該方法既克服了攝影標(biāo)定法需要的高精度三維標(biāo)定物的缺點(diǎn),又解決了之前自標(biāo)定法魯棒性差的難題。
標(biāo)定過程僅需使用一個(gè)打印出來的棋盤格,并從不同方向拍攝幾組圖片即可,任何人都可以自己制作標(biāo)定圖案,不僅實(shí)用靈活方便,而且精度很高,魯棒性好。因此很快被全世界廣泛采用,極大的促進(jìn)了三維計(jì)算機(jī)視覺從實(shí)驗(yàn)室走向真實(shí)世界的進(jìn)程。
世界正需要這樣的發(fā)明,張正友標(biāo)定法也是張正友博士的成名之作。
下面我們看一下,張正友標(biāo)定法的具體流程,主要是數(shù)學(xué)計(jì)算較多,要細(xì)心慢慢看。
張正友標(biāo)定法計(jì)算流程
首先我們看一下張正友標(biāo)定法使用OpenCV的計(jì)算流程:
- 準(zhǔn)備標(biāo)定圖片,原理上三張就夠,一般在多個(gè)角度采集20張左右。
- 提取標(biāo)定板的關(guān)鍵點(diǎn),并計(jì)算出標(biāo)定板上關(guān)鍵點(diǎn)的實(shí)際相對(duì)位置,一般將標(biāo)定板當(dāng)做XY平面,Z為0,標(biāo)定板第一個(gè)點(diǎn)為坐標(biāo)原點(diǎn)。
- 相機(jī)標(biāo)定,通過張正友標(biāo)定法計(jì)算出內(nèi)參外參以及畸變。
- 對(duì)標(biāo)定結(jié)果進(jìn)行評(píng)價(jià),一般通過重投影的誤差進(jìn)行評(píng)價(jià)。
- 查看標(biāo)定效果,利用標(biāo)定結(jié)果對(duì)棋盤圖進(jìn)行矯正
從上邊的過程可以看出,我們其實(shí)只有第三步是真正的解算過程,我們現(xiàn)在來看一下大致的方法。
首先用于標(biāo)定的棋盤格是三維場(chǎng)景中的一個(gè)平面Π,其在成像平面的像是另一個(gè)平面??,知道了兩個(gè)平面的對(duì)應(yīng)點(diǎn)的坐標(biāo),就可以求解得到兩個(gè)平面的單應(yīng)矩陣??。其中,標(biāo)定的棋盤格是特制的,其角點(diǎn)的坐標(biāo)是已知的;圖像中的角點(diǎn),可以通過角點(diǎn)提取算法得到,這樣就可以得到棋盤平面Π和圖像平面??的單應(yīng)矩陣??,即:


是不是通過對(duì)應(yīng)的點(diǎn)對(duì)解得??后,則可以通過上面的等式得到相機(jī)的內(nèi)參數(shù)??,以及外參旋轉(zhuǎn)矩陣??和平移向量??。
至于怎么解出來,我們接著看。
設(shè)棋盤格所在的平面為世界坐標(biāo)系中??=0的平面,這樣棋盤格的任一角點(diǎn)??的世界坐標(biāo)為(??,??,0),根據(jù)小孔相機(jī)模型:





那么對(duì)于一幅棋盤標(biāo)定版的圖像(一個(gè)單應(yīng)矩陣)可以獲得兩個(gè)對(duì)內(nèi)參數(shù)的約束等式。
我們令:






其中,??是一個(gè)2??×6的矩陣,??是一個(gè)6維向量,所以
- 當(dāng)??≥3,可以得到??的唯一解;
- 當(dāng)??=2,則可以假設(shè)扭曲參數(shù)??=0作為額外的約束條件
- 當(dāng)??=1,則值能計(jì)算兩個(gè)相機(jī)的內(nèi)參數(shù)


其中fx = ??(1/??),fy = ??(1/??) 。
為了進(jìn)一步增加標(biāo)定結(jié)果的可靠性,可以使用最大似然估計(jì)來優(yōu)化上面估計(jì)得到的結(jié)果。
假設(shè)同一相機(jī)從??個(gè)不同的角度的得到了??幅標(biāo)定板的圖像,每幅圖像上有??個(gè)像點(diǎn)。??????表示第??幅圖像上第??個(gè)像點(diǎn)對(duì)應(yīng)的標(biāo)定板上的三維點(diǎn),則:



問題變成了一個(gè)非線性優(yōu)化問題,利用上面得到的解作為初始值,迭代得到最優(yōu)解。這個(gè)過程就是在減少重投影誤差的過程。
至此,通過張正友標(biāo)定法,我們獲得了相機(jī)的內(nèi)參以及外參,但是畸變沒有獲得。張正友標(biāo)定法只關(guān)注了影響較大的徑向畸變。畸變的解算有點(diǎn)類似內(nèi)參解算,暫時(shí)先不列舉,腦袋有點(diǎn)炸了。

OpenCV 張正友標(biāo)定流程展示[代碼]
#include <iostream>
#include <opencv2/opencv.hpp>
#include <boost/filesystem.hpp>
std::vector<std::string> get_all_image_file(std::string image_folder_path){
boost::filesystem::path dirpath = image_folder_path;
boost::filesystem::directory_iterator end;
std::vector<std::string> files;
for (boost::filesystem::directory_iterator iter(dirpath); iter != end; iter++)
{
boost::filesystem::path p = *iter;
files.push_back(dirpath.string()+ "/"+ p.leaf().string());
}
std::sort(files.begin(),files.end());
return files;
}
std::vector<cv::Mat> get_all_iamge(std::string image_folder_path)
{
std::vector<cv::Mat> images;
std::vector<std::string> image_files_path = get_all_image_file(image_folder_path);
for(int i=0; i< image_files_path.size() ;i++){
cv::Mat image;
image = cv::imread(image_files_path[i]);
images.push_back(image);
}
return images;
}
int find_chessboard(cv::Mat image, std::vector<cv::Point2f> &image_points, cv::Size board_size)
{
if (0 == findChessboardCorners(image,board_size,image_points))
{
std::cout<<"can not find chessboard corners!\n";
return 0;
}
else
{
cv::Mat view_gray;
cv::cvtColor(image,view_gray,cv::COLOR_RGB2GRAY);
cv::find4QuadCornerSubpix(view_gray,image_points,cv::Size(11,11)); //對(duì)粗提取的角點(diǎn)進(jìn)行亞像素精確化
// int nChessBoardFlags = cv::CALIB_CB_EXHAUSTIVE | cv::CALIB_CB_ACCURACY;
// bool bFindResult = findChessboardCornersSB( view_gray,board_size,image_points,nChessBoardFlags );
// Opencv4 識(shí)別棋盤格方法,比opencv3有較大提升
}
return 1;
}
int init_chessboard_3dpoints(cv::Size board_size, std::vector<cv::Point3f> &points, float point_size)
{
cv::Size2f square_size = cv::Size2f(point_size,point_size);
for (int i=0;i<board_size.height;i++){
for (int j=0;j<board_size.width;j++){
cv::Point3f realPoint;
realPoint.x = j*square_size.width;
realPoint.y = i*square_size.height;
realPoint.z = 0;
points.push_back(realPoint);
}
}
return 0;
}
void calib_monocular(std::vector<cv::Mat> images){
cv::Size image_szie;
cv::Size board_size(4,11);
std::vector<cv::Mat> images_tvecs_mat;
std::vector<cv::Mat> images_rvecs_mat;
image_szie.width = images[0].cols;
image_szie.height = images[0].rows;
std::vector<std::vector<cv::Point2f> > images_points;
// 識(shí)別所有圖片的棋盤格
for(int i=0;i<images.size();i++){
std::vector<cv::Point2f> image_points;
if(find_chessboard(images[i],image_points,board_size)>0){
images_points.push_back(image_points);
}
}
std::vector<cv::Point3f> image_points_in3d;
// 計(jì)算棋盤格角點(diǎn)在棋盤格坐標(biāo)系中的位置
init_chessboard_3dpoints(board_size,image_points_in3d,0.045); // 0.045為棋盤格一個(gè)格子的大小
std::vector<std::vector<cv::Point3f> > images_points_in3d;
// 生成所有識(shí)別出的標(biāo)定板對(duì)應(yīng)在各自棋盤格坐標(biāo)系中的位置
for(int i=0;i<images_points.size();i++){
images_points_in3d.push_back(image_points_in3d);
}
cv::Mat intrinsic,distortion;
// 使用張正友標(biāo)定法計(jì)算內(nèi)參畸變以及外參
cv::calibrateCamera(images_points_in3d,images_points,image_szie,
intrinsic,distortion,
images_rvecs_mat,images_tvecs_mat);
}
int main(int argc, char *argv[])
{
std::string image_file_path = argv[1];
std::vector<cv::Mat> images = get_all_iamge(image_file_path);
calib_monocular(images);
return 0;
}

總結(jié)
張正友標(biāo)定法的思路并不是很難,主要是解算的數(shù)學(xué)原理較復(fù)雜,需要有比較打的耐心看下去,我現(xiàn)在也只能看懂,讓自己完全推導(dǎo)一遍還是挺難的。張正友標(biāo)定法更重要的是將標(biāo)定這項(xiàng)工作簡(jiǎn)潔化,不在需要精密高額的設(shè)備,而只需要通過打印標(biāo)定板就可以獲得比較好的效果。
在實(shí)際的標(biāo)定項(xiàng)目中,還是需要注意很多的事情,以下是我在標(biāo)定時(shí)用的一些小trick或者一些注意點(diǎn):
- 比如某個(gè)點(diǎn)識(shí)別錯(cuò)了,要通過重投影誤差將其剔除,然后重新計(jì)算標(biāo)定結(jié)果。
- 增加圖片的數(shù)目,標(biāo)定板在圖片中的各個(gè)角落都要有著各個(gè)角度的分布。
- 對(duì)于畸變不大的圖片,opencv 中圓形標(biāo)定板的效果要比棋盤格的效果要好,opencv4 棋盤格識(shí)別精度有較大提升,但還是建議用圓形。
重要的事情說三遍:
如果我的文章對(duì)您有所幫助,那就點(diǎn)贊加個(gè)關(guān)注唄 ( * ^ __ ^ * )
如果我的文章對(duì)您有所幫助,那就點(diǎn)贊加個(gè)關(guān)注唄 ( * ^ __ ^ * )
如果我的文章對(duì)您有所幫助,那就點(diǎn)贊加個(gè)關(guān)注唄 ( * ^ __ ^ * )
傳統(tǒng)2D計(jì)算機(jī)視覺學(xué)習(xí)筆記目錄------->傳送門
傳統(tǒng)3D計(jì)算機(jī)視覺學(xué)習(xí)筆記目錄------->傳送門
任何人或團(tuán)體、機(jī)構(gòu)全部轉(zhuǎn)載或者部分轉(zhuǎn)載、摘錄,請(qǐng)保留本博客鏈接或標(biāo)注來源。博客地址:開飛機(jī)的喬巴
作者簡(jiǎn)介:開飛機(jī)的喬巴(WeChat:zhangzheng-thu),現(xiàn)主要從事機(jī)器人抓取視覺系統(tǒng)以及三維重建等3D視覺相關(guān)方面,另外對(duì)slam以及深度學(xué)習(xí)技術(shù)也頗感興趣,歡迎加我微信或留言交流相關(guān)工作。