1. 內(nèi)參與畸變
理論部分可以參考其他博客或者視覺slam十四講
相機(jī)標(biāo)定主要是為了獲得相機(jī)的內(nèi)參矩陣K和畸變參數(shù)
內(nèi)參矩陣K
畸變系數(shù):徑向畸變(k1,k2,k3), 切向畸變(p1,p2)徑向畸變公式
切向畸變公式
張正友標(biāo)定方法能夠提供一個(gè)比較好的初始解,用于后序的最優(yōu)化.
這里用棋盤格進(jìn)行標(biāo)定,如果能夠處理圓的偏心誤差問題,用圓形圖案標(biāo)定板可能效果更好.
至少三張圖片,一般用10-20張圖片為最佳,要保證相機(jī)視野內(nèi)各個(gè)角度,各個(gè)位置,各個(gè)方向都有圖像.盡量多角度多位置.
最好用買的標(biāo)定板,效果好,平.最好是背光板,能夠保證足夠的亮度和均勻度.文章來源:http://www.zghlxwxcb.cn/news/detail-468988.html
2. 用OpenCV標(biāo)定相機(jī)程序
1,提取角點(diǎn)
2,亞像素角點(diǎn)
3,可視化提取角點(diǎn)(非必須)
4,標(biāo)定
5,誤差計(jì)算(重投影誤差)文章來源地址http://www.zghlxwxcb.cn/news/detail-468988.html
#include <iostream>
#include <fstream>
#include <string>
#include <opencv2/opencv.hpp>
using namespace std;
int main(int argc, char **argv)
{
string dir = "/home/wfq/MyProjects/cal_images/"; //標(biāo)定圖片所在文件夾
ifstream fin(dir + "file_images.txt"); //讀取標(biāo)定圖片的路徑,與cpp程序在同一路徑下
if (!fin) //檢測是否讀取到文件
{
cerr << "沒有找到文件" << endl;
return -1;
}
ofstream fout(dir + "calibration_result.txt"); //輸出結(jié)果保存在此文本文件下
//依次讀取每一幅圖片,從中提取角點(diǎn)
cout << "開始提取角點(diǎn)……" << endl;
int image_nums = 0; //圖片數(shù)量
cv::Size image_size; //圖片尺寸
int points_per_row = 10; //每行的內(nèi)點(diǎn)數(shù)
int points_per_col = 7; //每列的內(nèi)點(diǎn)數(shù)
cv::Size corner_size = cv::Size(points_per_row, points_per_col); //標(biāo)定板每行每列角點(diǎn)個(gè)數(shù),共10*7個(gè)角點(diǎn)
vector<cv::Point2f> points_per_image; //緩存每幅圖檢測到的角點(diǎn)
vector<vector<cv::Point2f>> points_all_images; //用一個(gè)二維數(shù)組保存檢測到的所有角點(diǎn)
string image_file_name; //聲明一個(gè)文件名的字符串
while (getline(fin, image_file_name)) //逐行讀取,將行讀入字符串
{
image_nums++;
//讀入圖片
cv::Mat image_raw = cv::imread(dir + image_file_name);
if (image_nums == 1)
{
// cout<<"channels = "<<image_raw.channels()<<endl;
// cout<<image_raw.type()<<endl; //CV_8UC3
image_size.width = image_raw.cols; //圖像的寬對應(yīng)著列數(shù)
image_size.height = image_raw.rows; //圖像的高對應(yīng)著行數(shù)
cout << "image_size.width = " << image_size.width << endl;
cout << "image_size.height = " << image_size.height << endl;
}
//角點(diǎn)檢測
cv::Mat image_gray; //存儲灰度圖的矩陣
cv::cvtColor(image_raw, image_gray, CV_BGR2GRAY); //將BGR圖轉(zhuǎn)化為灰度圖
// cout<<"image_gray.type() = "<<image_gray.type()<<endl; //CV_8UC1
//step1 提取角點(diǎn)
bool success = cv::findChessboardCorners(image_gray, corner_size, points_per_image);
if (!success)
{
cout << "can not find the corners " << endl;
exit(1);
}
else
{
//亞像素精確化(兩種方法)
//step2 亞像素角點(diǎn)
cv::find4QuadCornerSubpix(image_gray, points_per_image, cv::Size(5, 5));
// cornerSubPix(image_gray,points_per_image,Size(5,5));
points_all_images.push_back(points_per_image); //保存亞像素角點(diǎn)
//在圖中畫出角點(diǎn)位置
//step3 角點(diǎn)可視化
cv::drawChessboardCorners(image_raw, corner_size, points_per_image, success); //將角點(diǎn)連線
cv::imshow("Camera calibration", image_raw);
cv::waitKey(0); //等待按鍵輸入
}
}
cv::destroyAllWindows();
//輸出圖像數(shù)目
int image_sum_nums = points_all_images.size();
cout << "image_sum_nums = " << image_sum_nums << endl;
//開始相機(jī)標(biāo)定
cv::Size block_size(21, 21); //每個(gè)小方格實(shí)際大小, 只會影響最后求解的平移向量t
cv::Mat camera_K(3, 3, CV_32FC1, cv::Scalar::all(0)); //內(nèi)參矩陣3*3
cv::Mat distCoeffs(1, 5, CV_32FC1, cv::Scalar::all(0)); //畸變矩陣1*5
vector<cv::Mat> rotationMat; //旋轉(zhuǎn)矩陣
vector<cv::Mat> translationMat; //平移矩陣
//初始化角點(diǎn)三維坐標(biāo),從左到右,從上到下!!!
vector<cv::Point3f> points3D_per_image;
for (int i = 0; i < corner_size.height; i++)
{
for (int j = 0; j < corner_size.width; j++)
{
points3D_per_image.push_back(cv::Point3f(block_size.width * j, block_size.height * i, 0));
}
}
vector<vector<cv::Point3f>> points3D_all_images(image_nums,points3D_per_image); //保存所有圖像角點(diǎn)的三維坐標(biāo), z=0
int point_counts = corner_size.area(); //每張圖片上角點(diǎn)個(gè)數(shù)
//!標(biāo)定
/**
* points3D_all_images: 真實(shí)三維坐標(biāo)
* points_all_images: 提取的角點(diǎn)
* image_size: 圖像尺寸
* camera_K : 內(nèi)參矩陣K
* distCoeffs: 畸變參數(shù)
* rotationMat: 每個(gè)圖片的旋轉(zhuǎn)向量
* translationMat: 每個(gè)圖片的平移向量
* */
//step4 標(biāo)定
cv::calibrateCamera(points3D_all_images, points_all_images, image_size, camera_K, distCoeffs, rotationMat, translationMat, 0);
//step5 對標(biāo)定結(jié)果進(jìn)行評價(jià)
double total_err = 0.0; //所有圖像平均誤差總和
double err = 0.0; //每幅圖像的平均誤差
vector<cv::Point2f> points_reproject; //重投影點(diǎn)
cout << "\n\t每幅圖像的標(biāo)定誤差:\n";
fout << "每幅圖像的標(biāo)定誤差:\n";
for (int i = 0; i < image_nums; i++)
{
vector<cv::Point3f> points3D_per_image = points3D_all_images[i];
//通過之前標(biāo)定得到的相機(jī)內(nèi)外參,對三維點(diǎn)進(jìn)行重投影
cv::projectPoints(points3D_per_image, rotationMat[i], translationMat[i], camera_K, distCoeffs, points_reproject);
//計(jì)算兩者之間的誤差
vector<cv::Point2f> detect_points = points_all_images[i]; //提取到的圖像角點(diǎn)
cv::Mat detect_points_Mat = cv::Mat(1, detect_points.size(), CV_32FC2); //變?yōu)?*70的矩陣,2通道保存提取角點(diǎn)的像素坐標(biāo)
cv::Mat points_reproject_Mat = cv::Mat(1, points_reproject.size(), CV_32FC2); //2通道保存投影角點(diǎn)的像素坐標(biāo)
for (int j = 0; j < detect_points.size(); j++)
{
detect_points_Mat.at<cv::Vec2f>(0, j) = cv::Vec2f(detect_points[j].x, detect_points[j].y);
points_reproject_Mat.at<cv::Vec2f>(0, j) = cv::Vec2f(points_reproject[j].x, points_reproject[j].y);
}
err = cv::norm(points_reproject_Mat, detect_points_Mat, cv::NormTypes::NORM_L2);
total_err += err /= point_counts;
cout << "第" << i + 1 << "幅圖像的平均誤差為: " << err << "像素" << endl;
fout << "第" << i + 1 << "幅圖像的平均誤差為: " << err << "像素" << endl;
}
cout << "總體平均誤差為: " << total_err / image_nums << "像素" << endl;
fout << "總體平均誤差為: " << total_err / image_nums << "像素" << endl;
cout << "評價(jià)完成!" << endl;
//將標(biāo)定結(jié)果寫入txt文件
cv::Mat rotate_Mat = cv::Mat(3, 3, CV_32FC1, cv::Scalar::all(0)); //保存旋轉(zhuǎn)矩陣
cout << "\n相機(jī)內(nèi)參數(shù)矩陣:" << endl;
cout << camera_K << endl<< endl;
fout << "\n相機(jī)內(nèi)參數(shù)矩陣:" << endl;
fout << camera_K << endl<< endl;
cout << "畸變系數(shù):\n";
cout << distCoeffs << endl<< endl<< endl;
fout << "畸變系數(shù):\n";
fout << distCoeffs << endl<< endl<< endl;
for (int i = 0; i < image_nums; i++)
{
cv::Rodrigues(rotationMat[i], rotate_Mat); //將旋轉(zhuǎn)向量通過羅德里格斯公式轉(zhuǎn)換為旋轉(zhuǎn)矩陣
fout << "第" << i + 1 << "幅圖像的旋轉(zhuǎn)矩陣為:" << endl;
fout << rotate_Mat << endl;
fout << "第" << i + 1 << "幅圖像的平移向量為:" << endl;
fout << translationMat[i] << endl
<< endl;
}
fout << endl;
fout.close();
return 0;
}
3.畫棋盤標(biāo)定板
//函數(shù)聲明,默認(rèn)每行11個(gè)block, 沒列8個(gè)block, block大小為75個(gè)像素. 也就是10*7個(gè)內(nèi)點(diǎn)
void drawChessBoard(int blocks_per_row=11, int blocks_per_col=8, int block_size = 75);
// 11 8 75
void drawChessBoard(int blocks_per_row, int blocks_per_col, int block_size)
{
//blocks_per_row=11 //每行11個(gè)格子,也就是10個(gè)點(diǎn)
//blocks_per_col=8 //每列8個(gè)格子,也就是7個(gè)點(diǎn)
//block_size=75 //每個(gè)格子的像素大小
cv::Size board_size = cv::Size(block_size * blocks_per_row, block_size * blocks_per_col);
cv::Mat chessboard = cv::Mat(board_size, CV_8UC1);
unsigned char color = 0;
for (int i = 0; i < blocks_per_row; i++)
{
color = ~color;
for (int j = 0; j < blocks_per_col; j++)
{
chessboard(cv::Rect(i * block_size, j * block_size, block_size, block_size)).setTo(color);
color = ~color;
}
}
cv::Mat chess_board = cv::Mat(board_size.height + 100, board_size.width + 100, CV_8UC1, cv::Scalar::all(256)); //上下左右留出50個(gè)像素空白
chessboard.copyTo(chess_board.rowRange(50, 50 + board_size.height).colRange(50, 50 + board_size.width));
cv::imshow("chess_board", chess_board);
cv::imwrite("chess_board.png", chess_board);
cv::waitKey(-1);
cv::destroyAllWindows();
}
4.OpenCV拍照
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main(int argc, char **argv)
{
cv::namedWindow("Camera",cv::WINDOW_AUTOSIZE);
cv::VideoCapture cap;
cap.open(0);
if(!cap.isOpened())
{
cout<<"camera open failed!\n";
return -1;
}
cv::Mat image;
int id=1;
char symbol;
while(id<=6)
{
cap>>image;
if(image.empty())
break;
cout<<"y or n"<<endl;
cin>>symbol;
if(symbol=='y')
{
cv::imwrite(to_string(id)+".png",image);
cout<<"第"<<id<<"張圖片"<<endl;
id++;
}
}
return 0;
}
到了這里,關(guān)于用OpenCV進(jìn)行相機(jī)標(biāo)定(張正友標(biāo)定,有代碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!