原理
下圖表示了小孔成像模型(圖片及公式參考opencv官方資料)
這個圖里涉及4個坐標(biāo)系:
- 世界坐標(biāo)系:其坐標(biāo)原點可視情況而定谚咬,可以表示空間的物體萝快,單位為長度單位川蒙,比如mm,用矩陣表示蹈矮;
- 相機坐標(biāo)系:以攝像機光心為原點(在針孔模型中也就是針孔為中心)砰逻,z軸與光軸重合,也就是z軸指向相機的前方(與成像平面垂直)泛鸟,x軸與y軸的正方向與世界坐標(biāo)系平行蝠咆,單位為長度單位,比如mm北滥,用矩陣表示刚操;
- 圖像物理坐標(biāo)系(也叫成像平面坐標(biāo)系):用物理長度單位表示像素的位置,坐標(biāo)原點為攝像機光軸與圖像物理坐標(biāo)系的交點位置再芋。坐標(biāo)系為圖上o-xy菊霜,單位為長度單位,比如mm济赎,用矩陣表示鉴逞。
- 像素坐標(biāo)系:坐標(biāo)原點在左上角,以像素為單位司训,有明顯的范圍限制构捡,即用于表示全畫面的像素長和像素長寬,矩陣表示壳猜。
以下公式描述了叭喜、、和之間的轉(zhuǎn)換關(guān)系蓖谢。
以上公式中捂蕴,和表示1個像素有多少長度,即用傳感器的尺寸除以像素數(shù)量闪幽,比如2928.384umx2205.216um的傳感的分辨率為2592x1944啥辨,每個像素的大小即約1.12um。
表示焦距盯腌,在上圖中根據(jù)相似三角形溉知,P點和p點具有以下關(guān)系:
即 ,可見:越大腕够,和越大级乍,越大,和越小帚湘。
和表示中心點在像素坐標(biāo)系中的位置玫荣。
要求像素坐標(biāo)系中某像素點對應(yīng)在世界坐標(biāo)系中的位置,需要知道相機的內(nèi)參大诸、外參捅厂,相機的內(nèi)參可以通過標(biāo)定獲得贯卦,外參可以人為設(shè)定。
第一步焙贷,將像素坐標(biāo)變換到相機坐標(biāo)系:
兩邊乘以K的逆后推導(dǎo)出:
第二步撵割,從相機坐標(biāo)系變換到世界坐標(biāo)系:
將方程乘以,可以推導(dǎo)出:
代碼
通過輸入相機的內(nèi)參辙芍,旋轉(zhuǎn)向量啡彬,平移向量和像素坐標(biāo),可以通過以下函數(shù)求出對應(yīng)的世界坐標(biāo)點故硅。
以下代碼中需求注意要對平移向量取轉(zhuǎn)置庶灿,將1x3矩陣變?yōu)?x1矩陣后,才能實現(xiàn)3x3矩陣和3x1矩陣的乘法運算契吉。
void cameraToWorld(InputArray cameraMatrix, InputArray rV, InputArray tV, vector<Point2f> imgPoints, vector<Point3f> &worldPoints)
{
Mat invK64, invK;
invK64 = cameraMatrix.getMat().inv();
invK64.convertTo(invK, CV_32F);
Mat r, t, rMat;
rV.getMat().convertTo(r, CV_32F);
tV.getMat().convertTo(t, CV_32F);
Rodrigues(r, rMat);
//計算 invR * T
Mat invR = rMat.inv();
//cout << "invR\n" << invR << endl;
//cout << "t\n" << t << t.t() << endl;
Mat transPlaneToCam;
if(t.size() == Size(1, 3)){
transPlaneToCam = invR * t;//t.t();
}
else if(t.size() == Size(3, 1)){
transPlaneToCam = invR * t.t();
}
else{
return;
}
//cout << "transPlaneToCam\n" << transPlaneToCam << endl;
int npoints = (int)imgPoints.size();
//cout << "npoints\n" << npoints << endl;
for (int j = 0; j < npoints; ++j){
Mat coords(3, 1, CV_32F);
Point3f pt;
coords.at<float>(0, 0) = imgPoints[j].x;
coords.at<float>(1, 0) = imgPoints[j].y;
coords.at<float>(2, 0) = 1.0f;
//[x,y,z] = invK * [u,v,1]
Mat worldPtCam = invK * coords;
//cout << "worldPtCam:" << worldPtCam << endl;
//[x,y,1] * invR
Mat worldPtPlane = invR * worldPtCam;
//cout << "worldPtPlane:" << worldPtPlane << endl;
//zc
float scale = transPlaneToCam.at<float>(2) / worldPtPlane.at<float>(2);
//cout << "scale:" << scale << endl;
Mat scale_worldPtPlane(3, 1, CV_32F);
//scale_worldPtPlane.at<float>(0, 0) = worldPtPlane.at<float>(0, 0) * scale;
//zc * [x,y,1] * invR
scale_worldPtPlane = scale * worldPtPlane;
//cout << "scale_worldPtPlane:" << scale_worldPtPlane << endl;
//[X,Y,Z]=zc*[x,y,1]*invR - invR*T
Mat worldPtPlaneReproject = scale_worldPtPlane - transPlaneToCam;
//cout << "worldPtPlaneReproject:" << worldPtPlaneReproject << endl;
pt.x = worldPtPlaneReproject.at<float>(0);
pt.y = worldPtPlaneReproject.at<float>(1);
//pt.z = worldPtPlaneReproject.at<float>(2);
pt.z = 1.0f;
worldPoints.push_back(pt);
}
}
def cameraToWorld(self, cameraMatrix, r, t, imgPoints):
invK = np.asmatrix(cameraMatrix).I
rMat = np.zeros((3, 3), dtype=np.float64)
cv2.Rodrigues(r, rMat)
#print('rMat=', rMat)
#計算 invR * T
invR = np.asmatrix(rMat).I #3*3
#print('invR=', invR)
transPlaneToCam = np.dot(invR , np.asmatrix(t)) #3*3 dot 3*1 = 3*1
#print('transPlaneToCam=', transPlaneToCam)
worldpt = []
coords = np.zeros((3, 1), dtype=np.float64)
for imgpt in imgPoints:
coords[0][0] = imgpt[0][0]
coords[1][0] = imgpt[0][1]
coords[2][0] = 1.0
worldPtCam = np.dot(invK , coords) #3*3 dot 3*1 = 3*1
#print('worldPtCam=', worldPtCam)
#[x,y,1] * invR
worldPtPlane = np.dot(invR , worldPtCam) #3*3 dot 3*1 = 3*1
#print('worldPtPlane=', worldPtPlane)
#zc
scale = transPlaneToCam[2][0] / worldPtPlane[2][0]
#print("scale: ", scale)
#zc * [x,y,1] * invR
scale_worldPtPlane = np.multiply(scale , worldPtPlane)
#print("scale_worldPtPlane: ", scale_worldPtPlane)
#[X,Y,Z]=zc*[x,y,1]*invR - invR*T
worldPtPlaneReproject = np.asmatrix(scale_worldPtPlane) - np.asmatrix(transPlaneToCam) #3*1 dot 1*3 = 3*3
#print("worldPtPlaneReproject: ", worldPtPlaneReproject)
pt = np.zeros((3, 1), dtype=np.float64)
pt[0][0] = worldPtPlaneReproject[0][0]
pt[1][0] = worldPtPlaneReproject[1][0]
pt[2][0] = 0
worldpt.append(pt.T.tolist())
#print('worldpt:',worldpt)
return worldpt
驗證
先使用projectPoints生成像素點:
Vec3f eulerAngles;//歐拉角
vector<Vec3d> translation_vectors;/* 每幅圖像的平移向量 */
Mat rotationMatrix = eulerAnglesToRotationMatrix(eulerAngles);
*pR_matrix = rotationMatrix;
cvRodrigues2(pR_matrix, pnew_vec, 0); //從旋轉(zhuǎn)矩陣求旋轉(zhuǎn)向量
Mat mat_tmp(pnew_vec->rows, pnew_vec->cols, pnew_vec->type, pnew_vec->data.fl);
cv::Mat distortion_coeffs1 = cv::Mat(1, 5, CV_32FC1, cv::Scalar::all(0)); /* 攝像機的5個畸變系數(shù):k1,k2,p1,p2,k3 */
projectPoints(tempPointSet, mat_tmp, translation_vectors[i], intrinsic_matrix, distortion_coeffs1, image_points2);
使用以下歐拉角:
[0, 0, 0]//歐拉角度跳仿,表示平面和相機的角度
旋轉(zhuǎn)向量:[0, 0, 0]
對應(yīng)的平移向量,表示空間坐標(biāo)原點相對在相平面原點偏移x=134mm捐晶,y=132mm菲语,z=200mm。
原始:[134.0870803179094, 132.7580766544178, 200.3789038923399]
生成空間坐標(biāo)點:
Size board_size = Size(11,8);
Size square_size = Size(30, 30);
vector<Point3f> tempPointSet;
for (int j = 0; j<board_size.height; j++)
{
for (int i = 0; i<board_size.width; i++)
{
/* 假設(shè)定標(biāo)板放在世界坐標(biāo)系中z=0的平面上 */
Point3f tempPoint;
tempPoint.x = i*square_size.height;
tempPoint.y = j*square_size.width;
tempPoint.z = 0;
tempPointSet.push_back(tempPoint);
}
}
經(jīng)projectPoints計算后對應(yīng)的像素空間點是:
projectPoints(tempPointSet, mat_tmp, translation_vectors[i], intrinsic_matrix, distortion_coeffs1, image_points2);
cout << "原始空間點:\n" << image_points2 << endl;
[1194.8174, 1074.1355;
1285.1735, 1074.1355;
1375.5295, 1074.1355;
1465.8856, 1074.1355;
1556.2417, 1074.1355;
1646.5978, 1074.1355;
1736.9539, 1074.1355;
1827.3099, 1074.1355;
1917.666, 1074.1355;
2008.0221, 1074.1355;
2098.3782, 1074.1355;
1194.8174, 1164.5713;
1285.1735, 1164.5713;
1375.5295, 1164.5713;
1465.8856, 1164.5713;
1556.2417, 1164.5713;
1646.5978, 1164.5713;
1736.9539, 1164.5713;
1827.3099, 1164.5713;
1917.666, 1164.5713;
2008.0221, 1164.5713;
2098.3782, 1164.5713;
1194.8174, 1255.0072;
1285.1735, 1255.0072;
1375.5295, 1255.0072;
1465.8856, 1255.0072;
1556.2417, 1255.0072;
1646.5978, 1255.0072;
1736.9539, 1255.0072;
1827.3099, 1255.0072;
1917.666, 1255.0072;
2008.0221, 1255.0072;
2098.3782, 1255.0072;
1194.8174, 1345.443;
1285.1735, 1345.443;
1375.5295, 1345.443;
1465.8856, 1345.443;
1556.2417, 1345.443;
1646.5978, 1345.443;
1736.9539, 1345.443;
1827.3099, 1345.443;
1917.666, 1345.443;
2008.0221, 1345.443;
2098.3782, 1345.443;
1194.8174, 1435.8789;
1285.1735, 1435.8789;
1375.5295, 1435.8789;
1465.8856, 1435.8789;
1556.2417, 1435.8789;
1646.5978, 1435.8789;
1736.9539, 1435.8789;
1827.3099, 1435.8789;
1917.666, 1435.8789;
2008.0221, 1435.8789;
2098.3782, 1435.8789;
1194.8174, 1526.3147;
1285.1735, 1526.3147;
1375.5295, 1526.3147;
1465.8856, 1526.3147;
1556.2417, 1526.3147;
1646.5978, 1526.3147;
1736.9539, 1526.3147;
1827.3099, 1526.3147;
1917.666, 1526.3147;
2008.0221, 1526.3147;
2098.3782, 1526.3147;
1194.8174, 1616.7506;
1285.1735, 1616.7506;
1375.5295, 1616.7506;
1465.8856, 1616.7506;
1556.2417, 1616.7506;
1646.5978, 1616.7506;
1736.9539, 1616.7506;
1827.3099, 1616.7506;
1917.666, 1616.7506;
2008.0221, 1616.7506;
2098.3782, 1616.7506;
1194.8174, 1707.1864;
1285.1735, 1707.1864;
1375.5295, 1707.1864;
1465.8856, 1707.1864;
1556.2417, 1707.1864;
1646.5978, 1707.1864;
1736.9539, 1707.1864;
1827.3099, 1707.1864;
1917.666, 1707.1864;
2008.0221, 1707.1864;
2098.3782, 1707.1864]
經(jīng)函數(shù)求出的空間坐標(biāo)點是:
vector<Point3f> worldPoint;
cameraToWorld(intrinsic_matrix, mat_tmp, translation_vec_tmp, image_points2, worldPoint);
cout << "計算空間點:\n" << worldPoint << endl;
[0, 0, 1;
30, 0, 1;
60, 0, 1;
90.000015, 0, 1;
120.00002, 0, 1;
149.99995, 0, 1;
179.99998, 0, 1;
209.99998, 0, 1;
239.99998, 0, 1;
270, 0, 1;
300, 0, 1;
0, 29.999985, 1;
30, 29.999985, 1;
60, 29.999985, 1;
90.000015, 29.999985, 1;
120.00002, 29.999985, 1;
149.99995, 29.999985, 1;
179.99998, 29.999985, 1;
209.99998, 29.999985, 1;
239.99998, 29.999985, 1;
270, 29.999985, 1;
300, 29.999985, 1;
0, 60.000015, 1;
30, 60.000015, 1;
60, 60.000015, 1;
90.000015, 60.000015, 1;
120.00002, 60.000015, 1;
149.99995, 60.000015, 1;
179.99998, 60.000015, 1;
209.99998, 60.000015, 1;
239.99998, 60.000015, 1;
270, 60.000015, 1;
300, 60.000015, 1;
0, 89.999969, 1;
30, 89.999969, 1;
60, 89.999969, 1;
90.000015, 89.999969, 1;
120.00002, 89.999969, 1;
149.99995, 89.999969, 1;
179.99998, 89.999969, 1;
209.99998, 89.999969, 1;
239.99998, 89.999969, 1;
270, 89.999969, 1;
300, 89.999969, 1;
0, 120.00002, 1;
30, 120.00002, 1;
60, 120.00002, 1;
90.000015, 120.00002, 1;
120.00002, 120.00002, 1;
149.99995, 120.00002, 1;
179.99998, 120.00002, 1;
209.99998, 120.00002, 1;
239.99998, 120.00002, 1;
270, 120.00002, 1;
300, 120.00002, 1;
0, 149.99998, 1;
30, 149.99998, 1;
60, 149.99998, 1;
90.000015, 149.99998, 1;
120.00002, 149.99998, 1;
149.99995, 149.99998, 1;
179.99998, 149.99998, 1;
209.99998, 149.99998, 1;
239.99998, 149.99998, 1;
270, 149.99998, 1;
300, 149.99998, 1;
0, 179.99998, 1;
30, 179.99998, 1;
60, 179.99998, 1;
90.000015, 179.99998, 1;
120.00002, 179.99998, 1;
149.99995, 179.99998, 1;
179.99998, 179.99998, 1;
209.99998, 179.99998, 1;
239.99998, 179.99998, 1;
270, 179.99998, 1;
300, 179.99998, 1;
0, 209.99998, 1;
30, 209.99998, 1;
60, 209.99998, 1;
90.000015, 209.99998, 1;
120.00002, 209.99998, 1;
149.99995, 209.99998, 1;
179.99998, 209.99998, 1;
209.99998, 209.99998, 1;
239.99998, 209.99998, 1;
270, 209.99998, 1;
300, 209.99998, 1]
可以對比按11*8格和30mm/格所生成空間坐標(biāo)點結(jié)果惑灵,基本一致山上。