目次
(1) アルゴリズムのプロセスは主に 5 つの部分で構成されます。
1. チェス盤のキャリブレーション ボードでコーナー ポイントを見つけます。
【1】校正関連の紹介
(1) 校正の目的
カメラキャリブレーションを行う前に、カメラキャリブレーションの目的を理解する必要があります. 簡単に言うと、カメラキャリブレーションは主に、カメラの下にあるキャリブレーションプレートのさまざまな位置に応じてカメラの内部および外部パラメータを解決することです。内部パラメータはカメラの固有のプロパティです。つまり、キャリブレーション プレートをどのように配置しても、カメラは固定されず、カメラの内部パラメータは影響を受けません。ただし、内部パラメータの精度はキャリブレーションの回数に関係します。一般に、キャリブレーション プレートをより包括的に配置するほど、カメラの内部パラメータがより正確になり、誤差が小さくなります。カメラの外部パラメータの場合、カメラ座標系に対するワールド座標系の空間変換 (回転行列と平行移動行列) になります。
(2) 内部パラメータの紹介
: 単位ミリメートル内の X 方向のピクセル数。
: 単位ミリメートル内の Y 方向のピクセル数。
, : 画像の中心の x 座標と y 座標を表します。
内部パラメータ行列 (3 x 3) は次のとおりです。
実際のアプリケーションではテレセントリック レンズが使用されますが、被写界深度の範囲内では画像の視野が一定であるため、テレセントリック レンズは歪みがなく、内部パラメータも確実です。カメラのパラメータに従って設定されます。ただし、通常のカメラの場合は、魚眼レンズと同様に、カメラが固定されているかどうかに関係なく、使用中に内部パラメータを補正する必要があります。
(3) 歪率
歪み係数には主に 5 つのパラメータがあります。k1、k2、k3 は半径方向の歪み係数、p1、p2 は接線方向の歪み係数です。放射状歪みは、カメラ座標系を画像の物理座標系に変換する過程で発生します。接線方向の歪みはカメラの製造工程で発生します。これは、感光体の平面がレンズと平行ではないために発生します。放射状の歪み、つまりレンズのさまざまな領域の焦点距離の違いによって生じる歪みは、下の図に示すように、糸巻型歪みと樽型歪みに分けられ、レンズの端に近づくほど、歪みがより顕著になります。ねじれ。変形は図のようになります。
(4) カメラ外部パラメータ
カメラの外部パラメータは、カメラ座標系におけるワールド座標系の記述です。R は各軸の回転行列の積である回転パラメータです。ここで、各軸の回転パラメータ ( ϕ 、 ω 、 θ )、平行移動パラメータ ( Tx 、 Ty 、 Tz )、行列は (4) x 4 均質行列)は次のようになります。
【2】アルゴリズムの流れと関連演算子の紹介
(1) アルゴリズムのプロセスは主に 5 つの部分で構成されます。
1. 校正プレートのコーナーポイントを抽出します。
2. サブピクセルのコーナーポイント精度。
3. コーナーポイントを視覚化します。
4. カメラのキャリブレーション。
5. 誤差計算(再投影誤差)。
(2) 関連事業者の紹介
1. チェス盤のキャリブレーション ボードでコーナー ポイントを見つけます。
bool findChessboardCorners(
InputArray image,
Size patternSize,
OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE
);
最初のパラメータは受信イメージで、イメージ タイプは 8UC1 および 8UC3 です。
2 番目のパラメーターは、チェス盤キャリブレーション ボード内の内側のコーナー ポイントの数 (1 行に何個、1 列に何個) です。たとえば、
Size size(9,6)
//一行九个内角点,一列6个内角点
3 番目のパラメーターは、検索されるコーナーポイントのピクセル座標を記録するために使用されます。
データ型は、画像のコーナーポイント座標を格納するvector<point2f>で、複数のキャリブレーションプレートのコーナーポイントの格納が定義されています。
ベクトル<ベクトル<point2f>>
4 番目のパラメータ:
CALIB_CB_ADAPTIVE_THRESH = 1、適応閾値法によってキャリブレーション プレートを検索します;
CALIB_CB_NORMALIZE_IMAGE = 2、このフラグが設定されている場合、閾値を適用する前に CV::equalizeHist() を使用して画像を正規化します; CALIB_CB_FILTER_QUADS = 4、キャリブレーション プレートの画像が歪みの場合、誤った四辺形を防ぐために四辺形アプリケーションが制限されます。CALIB_CB_FAST_CHECK
= 8 の場合、画像内にコーナー ポイントがあるかどうかを確認するために画像がすぐにスキャンされます。コーナー ポイントがない場合、画像は直接スキップされます。
この演算子の戻り値は、コーナーポイントが正常に見つかったかどうかです。
(円形グリッドのキャリブレーション プレートの検索はこれに似ているため、まだ紹介しません。)
2. サブピクセルコーナー精度
主に、2 つの演算子 cv::cornerSubPix と find4QuadCornerSubpix には同じパラメータがあります。
bool cv::find4QuadCornerSubpix ( InputArray img,
InputOutputArray corners,
Size region_size
)
img
: 入力 Mat 行列、できれば 8 ビット グレースケール画像の方が検出効率が高くなります; corners
: 初期コーナー点座標ベクトルはサブピクセル座標位置の出力としても使用されるため、通常は浮動小数点データである必要がありますPointf2f/Point2d
ベクトルで表される要素を使用します。region_size
: コーナーポイント検索ウィンドウのサイズ、座標を最適化するときに考慮される近傍範囲。
3. コーナーポイントを視覚化する
コーナーポイントの視覚化オペレーターはdrawChessboardCornersです。
void cv::drawChessboardCorners ( InputOutputArray image,
Size patternSize,
InputArray corners,
bool patternWasFound
)
image: コーナーポイントを描画する必要があるターゲットイメージ。8 ビットカラーイメージである必要があります。
patternSize: 各キャリブレーション ボード上の内側のコーナー ポイントの行数と列数。
コーナー: 検出されたコーナーポイント座標の配列。
patternWasFound: コーナー パターンを描画するためのフラグ。完全なキャリブレーション プレートが見つかったかどうかを示すために使用されます。
patternWasFound=true の場合、各内側の角の点を順番に接続します。
patternWasFound=false の場合、角の位置は (赤い) 丸でマークされます。
4.カメラのキャリブレーション
calibrateCamera はカメラ キャリブレーションのオペレーターであり、具体的な機能は次のとおりです。
double calibrateCamera(
InputArrayOfArrays objectPoints, //输入:目标点位 的集合的集合
InputArrayOfArrays imagePoints, //输入:图像点位 的集合的集合
Size imageSize, //输入:图像尺寸
InputOutputArray cameraMatrix, //输出:相机内参矩阵(fx,fy,cx,cy)
InputOutputArray distCoeffs, //输出:畸变矫正多项式(k1,k2,p1,p2,k3...)
OutputArrayOfArrays rvecs, //输出:旋转矩阵 的集合
OutputArrayOfArrays tvecs, //输出:平移矩阵 的集合
int flags = 0, //输入:对标定过程方法进行更精细的控制
TermCriteria criteria = //输入:迭代参数
TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON));
最初のパラメーター objectPoints は、ワールド座標系の 3 次元の点です。使用する場合は、3 次元座標点ベクトルのベクトル、つまり、vector<vector<Point3f>> object_points を入力する必要があります。チェス盤上の単一の黒と白のマトリクスのサイズに基づいて、各内側コーナー点のワールド座標を計算 (初期化) する必要があります。
2 番目のパラメーター imagePoints は、各内側のコーナー点に対応する画像座標点です。objectPoints と同様に、vector<vector<Point2f>> image_points_seq の形式の変数を入力する必要があります。
3 番目のパラメータ imageSize は画像のピクセル サイズで、カメラの内部パラメータと歪みマトリックスを計算するときにこのパラメータを使用する必要があります。
4番目のパラメータcameraMatrixは、カメラの内部パラメータ行列です。Mat CameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0)); のように、Mat CameraMatrix を入力するだけです。
5 番目のパラメーター distCoeffs は歪み行列です。マットを入力します distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0));
6 番目のパラメーター rvecs は回転ベクトルです。Mat タイプのベクトル、つまり、vector<Mat>rvecs を入力する必要があります。
7 番目のパラメータ tvcs は変位ベクトルです。rvecs と同様に、vector<Mat> tvcs でなければなりません。
8 番目のパラメーター flags は、キャリブレーション中に使用されるアルゴリズムです。次のパラメータがあります。
CV_CALIB_USE_INTRINSIC_GUESS: このパラメーターを使用する場合、cameraMatrix マトリックスに fx、fy、u0、v0 の推定値が存在する必要があります。それ以外の場合、(u0, v0) 画像の中心点が初期化され、最小二乗法を使用して fx、fy が推定されます。
CV_CALIB_FIX_PRINCIPAL_POINT: 最適化中に光軸点が固定されます。CV_CALIB_USE_INTRINSIC_GUESS パラメータが設定されている場合、光軸点は中心または入力値のままになります。
CV_CALIB_FIX_ASPECT_RATIO: fx/fy の比率を固定し、最適化計算の可変変数として fy のみを使用します。CV_CALIB_USE_INTRINSIC_GUESS が設定されていない場合、fx と fy は無視されます。計算には fx/fy 比率のみが使用されます。
CV_CALIB_ZERO_TANGENT_DIST: 接線方向の歪みパラメータ (p1、p2) をゼロに設定します。
CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6: 対応する放射状の歪みは、最適化中に変更されません。
CV_CALIB_RATIONAL_MODEL: 3 つの歪みパラメータ k4、k5、k6 を計算します。設定しない場合は、他の 5 つの歪みパラメータのみが計算されます。
9 番目のパラメーター基準は、最適な反復終了条件の設定です。
この関数を使用してキャリブレーション操作を実行する前に、チェス盤上の各内側のコーナー ポイントの空間座標系の位置座標を初期化する必要があります。キャリブレーションの結果は、カメラの内部参照行列 CameraMatrix を生成することです。歪み係数 distCoeffs と各画像は、それぞれ独自の平行移動ベクトルと回転ベクトルを生成します。
戻り値: 再投影の二乗平均平方根誤差
5. 誤差計算
再投影誤差 projectPoints のキャリブレーション結果を評価する方法は、取得したカメラの内部および外部パラメータを通じて空間内の 3 次元点を再投影し、3 つの新しい投影点の座標を取得します。画像上の空間内の次元点を測定し、投影座標とサブピクセルを計算します コーナーポイント座標間の偏差 偏差が小さいほど、キャリブレーション結果は良好です。
void projectPoints( InputArray objectPoints,
InputArray rvec, InputArray tvec,
InputArray cameraMatrix, InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian=noArray(),
double aspectRatio=0 );
最初のパラメータ objectPoints は、カメラ座標系の 3D 点座標です。
2 番目のパラメータ rvec は回転ベクトルで、各画像には独自の選択ベクトルがあります。
3 番目のパラメーター tvec は変位ベクトルであり、各画像には独自の平行移動ベクトルがあります。
4 番目のパラメーター CameraMatrix は、取得したカメラの内部パラメーター行列です。
5 番目のパラメーター distCoeffs は、カメラの歪み行列です。
6 番目のパラメーター imagePoints は、各内側の角の点に対応する画像上の座標点です。
7 番目のパラメーター jacobian はヤコビアンです。
8 番目のパラメータ「アスペクト比」は、カメラ センサーの感光ユニットに関連するオプションのパラメータです。これがゼロ以外に設定されている場合、関数はデフォルトで感光ユニットの dx/dy が固定され、それに応じてヤコビ行列が調整されます。 ;
【3】完成コード
#include<opencv2\opencv.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<iostream>
#include<fstream>
#include<io.h>
#include<string>
using namespace cv;
using namespace std;
const char* path = "E:\\乔大花进度\\11-24\\相机标定\\calibration2\\*.jpg";//标定板存放地址
string pic_path = "E:\\乔大花进度\\11-24\\相机标定\\calibration2\\";
int main(int argc, char** argv)
{
//【1】 从文件夹中读取标定图片
intptr_t handle; //用于文件查找的句柄
struct _finddata_t fileinfo; //定义文件存储的结构体
handle = _findfirst(path, &fileinfo); //用于第一次查找,handle记录查找结果,未找到返回-1;
if (handle==-1)
{
return -1; //未成功读取图片结束程序
}
if (fileinfo.attrib!= _A_SUBDIR)
{
printf("%s\n",fileinfo.name); //如果不是子文件夹则输出名字
}
ofstream outdata; //定义输出流
outdata.open(pic_path + "list.txt",ios::trunc);//打开存放标定板名字的txt文件,并且清空其内容
outdata << fileinfo.name << endl;
outdata.close();
while (!_findnext(handle,&fileinfo))//继续在文件夹下搜索标定板jpg格式文件,未找到返回-1,找到返回0;
{
ofstream outdata; //定义输出流
if (fileinfo.attrib != _A_SUBDIR)//
printf("%s\n", fileinfo.name);
outdata.open(pic_path + "list.txt", ios::app);
outdata << fileinfo.name << endl;
}
outdata.close();//关闭输出流
_findclose(handle);//关闭查找句柄
//定义vector用于存放图片名字;
vector<string> pic_name;
ifstream indata;
indata.open(pic_path + "list.txt", ios::in);
if (!indata)//未打开成功返回0
{
cout << "读取txt文件失败" << endl;
return -1;
}
while (!indata.eof())
{
string str;
getline(indata,str);
if (str.length()>2)
{
pic_name.push_back(str);
}
}
cout << "标定板照片的数量为" << pic_name.size() << endl;
//【2】 依次读取每张照片,提取角点
cout << "开始提取角点……" << endl;
int image_nums = 0; //图片数量
Size image_size; //图片尺寸
int points_per_row = 9; //每行的内点数
int points_per_col = 6; //每列的内点数
Size corner_size = Size(points_per_row, points_per_col); //标定板每行每列角点个数,共12*12个角点
vector<Point2f> points_per_image; //缓存每幅图检测到的角点
vector<vector<Point2f>> points_all_images; //用一个二维数组保存检测到的所有角点
string image_file_name; //声明一个文件名的字符串
for (int i = 0; i < pic_name.size(); i++)
{
image_nums++;
//读入图片
Mat image_raw = imread(pic_path + pic_name[i]);
if (image_nums == 1)
{
// cout<<"channels = "<<image_raw.channels()<<endl;
// cout<<image_raw.type()<<endl; //CV_8UC3
image_size.width = image_raw.cols; //图像的宽对应着列数
image_size.height = image_raw.rows; //图像的高对应着行数
cout << "image_size.width = " << image_size.width << endl;
cout << "image_size.height = " << image_size.height << endl;
}
//角点检测
Mat image_gray;
cvtColor(image_raw, image_gray,COLOR_BGR2GRAY); //将BGR图转化为灰度图
//step1 提取角点,并且返回布尔值
bool success = findChessboardCorners(image_gray, corner_size, points_per_image, CALIB_CB_FAST_CHECK);
if (!success)
{
cout << pic_name[i]<<":图片未找到角点" << endl;
continue;
}
else
{
//亚像素精确化(两种方法)
//step2 亚像素角点
find4QuadCornerSubpix(image_gray, points_per_image, Size(5, 5));
// cornerSubPix(image_gray,points_per_image,Size(5,5));
points_all_images.push_back(points_per_image); //保存亚像素角点
//step3 角点可视化,//在图中画出角点位置
drawChessboardCorners(image_raw, corner_size, points_per_image, success); //将角点连线
imshow("Camera calibration", image_raw);
waitKey(0); //等待按键输入
cout << "照片的序号为:" << image_nums << endl;
}
}
//destroyAllWindows();
int nums = points_all_images.size();
cout << "处理标定照片的数量为:" << nums << endl;
cout << "开始进行相机标定........." << endl;
//【3】将标定板中二维像素点位转为三维空间点
//开始相机标定
Size2f block_size(0.023, 0.023); //每个小方格实际大小, 只会影响最后求解的平移向量t(标定板中每个小方块的实际大小)
Mat camera_K(3, 3, CV_32FC1, Scalar::all(0)); //内参矩阵3*3
Mat distCoeffs(1, 5, CV_32FC1, Scalar::all(0)); //畸变矩阵1*5
vector<cv::Mat> rotationMat; //旋转矩阵
vector<cv::Mat> translationMat; //平移矩阵
//初始化角点三维坐标,从左到右,从上到下!!!
vector<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(Point3f(block_size.width * j, block_size.height * i, 0));
}
}
//生成与图片数量相同的标定板照片向量,也就是一张拍摄照片和一张标准标定板照片对应
vector<vector<Point3f>> points3D_all_images(nums, points3D_per_image); //保存所有图像角点的三维坐标, z=0
int point_counts = corner_size.area(); //每张图片上角点个数
cout << "标定板的角点数量为:" << point_counts << endl;
for (int i = 0; i < points3D_per_image.size(); i++)
{
cout << points3D_per_image[i] << endl;
}
//【4】开始进行标定
calibrateCamera(points3D_all_images, points_all_images, image_size, camera_K, distCoeffs, rotationMat, translationMat, 0);
//【5】对标定结果进行评价,计算标定误差
ofstream fout;
//将标定出来的误差存到该文件下;
fout.open(pic_path+"calibration.txt", ios::app);
double total_err = 0.0; //所有图像平均误差总和
double err = 0.0; //每幅图像的平均误差
vector<cv::Point2f> points_reproject; //重投影点
cout << "\n\t每幅图像的标定误差:\n";
fout << "每幅图像的标定误差:\n";
for (int i = 0; i < nums; i++)
{
vector<cv::Point3f> points3D_per_image = points3D_all_images[i];//标准的标定板图像
//通过之前标定得到的相机内外参,对三维点进行重投影
cv::projectPoints(points3D_per_image, rotationMat[i], translationMat[i], camera_K, distCoeffs, points_reproject);
//计算两者之间的误差,转为双通道,一个通道存储x,一个通道存储y
vector<cv::Point2f> detect_points = points_all_images[i]; //提取到的图像角点
cv::Mat detect_points_Mat = cv::Mat(1, detect_points.size(), CV_32FC2); //变为1*144的矩阵,2通道保存提取角点的像素坐标
cv::Mat points_reproject_Mat = cv::Mat(1, points_reproject.size(), CV_32FC2); //2通道保存投影角点的像素坐标
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 / nums << "像素" << endl;
fout << "总体平均误差为: " << total_err / nums << "像素" << endl;
cout << "评价完成!" << endl;
//将标定结果写入txt文件
Mat rotate_Mat = Mat(3, 3, CV_32FC1, cv::Scalar::all(0)); //保存旋转矩阵
cout << "\n相机内参数矩阵:" << endl;
cout << camera_K << endl << endl;
fout << "\n相机内参数矩阵:" << endl;
fout << camera_K << endl << endl;
cout << "畸变系数:\n";
cout << distCoeffs << endl << endl << endl;
fout << "畸变系数:\n";
fout << distCoeffs << endl << endl << endl;
for (int i = 0; i < nums; i++)
{
cv::Rodrigues(rotationMat[i], rotate_Mat); //将旋转向量通过罗德里格斯公式转换为旋转矩阵
fout << "第" << i + 1 << "幅图像的旋转矩阵为:" << endl;
fout << rotate_Mat << endl;
fout << "第" << i + 1 << "幅图像的平移向量为:" << endl;
fout << translationMat[i] << endl
<< endl;
}
fout << endl;
fout.close();
system("pause");
return 0;
}
実行中のコードは次のように表示されます。