相机标定opencv鸟瞰视角透视变换python
Bird's eye view perspective transformation from camera calibration opencv python
我正在尝试从相机内在、外在矩阵和畸变系数中获取鸟瞰图透视变换。
我尝试使用 this 问题的答案。
使用的图片是示例图片left02.jpg来自opencv官方github repo
我校准了相机并找到了内在矩阵、外在矩阵和畸变系数。
我取消了图像失真并找到了姿势。检查参数是否正确。
我用来求透视变换矩阵的方程是(参考上面link):
Hr = K * R.inv() * K.inv()
其中 R 是旋转矩阵(来自 cv2.Rodrigues()),K 是从 cv2.getoptimalnewcameramatrix()
获得的
[ 1 0 | ]
Ht = [ 0 1 | -K*C/Cz ]
[ 0 0 | ]
其中 C=-R.inv()*T
其中 T 是来自 cv2.solvePnP()
的平移向量
Cz 是 C 向量的第三个分量
所需的转换是:H = Ht * Hr
我用来构造上述等式的代码是:
K = newcameramtx # from cv2.getoptimalnewcameramatrix()
ret,rvec,tvec = cv2.solvePnP(world_points,corners2,K,dist)
R,_ = cv2.Rodrigues(rvec)
_,R_inv = cv2.invert(R)
_,K_inv = cv2.invert(K)
Hr = np.matmul(K,np.matmul(R_inv,K_inv))
C = np.matmul(-R_inv,tvec)
Cz = C[2]
temp_vector = np.matmul(-K,C/Cz)
Ht = np.identity(3)
for i,val in enumerate(temp_vector):
Ht[i][2] = val
homography = np.matmul(Ht,Hr)
warped_img =cv2.warpPerspective(img,homography,(img.shape[1],img.shape[0]))
# where img is the above undistored image with visualized pose
生成的扭曲图像不正确。
如果我使用以下代码从单应性中删除翻译
homography = Hr.copy()
warped_img =cv2.warpPerspective(img,homography,(img.shape[1],img.shape[0]))
我得到以下图像
我认为上图显示我的旋转部分是正确的,但我的翻译是错误的。
由于平移矩阵 (Ht) 是增广矩阵,我不确定我对上述矩阵的构造是否正确。
我特别想从相机标定中搞清楚鸟瞰视角的变换。
那么,我该如何修正上述等式,以便获得棋盘图像的完美鸟瞰图
谁能解释一下上述 Ht 和 Hr 的方程式是如何推导出来的?我对线性代数了解不多,所以这些方程式对我来说不是很明显。
更新:
homography = np.matmul(Ht,Hr)
warped_img =cv2.warpPerspective(img,homography,(img.shape[1],img.shape[0]),flags=cv2.WARP_INVERSE_MAP)
cv2.WARP_INVERSE_MAP flag 给了我不同的结果
还是不是我要的结果!
教程中解释了您想实现的目标:Demo 3: Homography from the camera displacement。
你有当前的相机姿势(旋转+平移),你可以计算出允许从鸟瞰图查看棋盘的所需相机姿势。
由于棋盘框与相机框不同(相机框见here),允许鸟瞰的相机姿势所需的旋转是:
只需按照教程进行操作,您应该会得到类似于以下内容的单应矩阵:
H:
[0.935, -0.337, 40.383;
-0.116, 0.729, 64.381;
0.000408, -0.001299, 1]
与warpPerspective
:
另一个例子:
由于棋盘是平的(对于一般场景,单应性仅对纯旋转相机运动有效),您还可以玩一下翻译:
编辑:教程中的代码
#include <opencv2/opencv.hpp>
#include <opencv2/aruco.hpp>
using namespace std;
using namespace cv;
namespace
{
enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };
void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners, Pattern patternType = CHESSBOARD)
{
corners.resize(0);
switch (patternType)
{
case CHESSBOARD:
case CIRCLES_GRID:
//! [compute-chessboard-object-points]
for( int i = 0; i < boardSize.height; i++ )
for( int j = 0; j < boardSize.width; j++ )
//To try to center the chessboard frame, we substract the image size
corners.push_back(Point3f(float((j-boardSize.width/2)*squareSize),
float((i-boardSize.height/2)*squareSize), 0));
//! [compute-chessboard-object-points]
break;
case ASYMMETRIC_CIRCLES_GRID:
for( int i = 0; i < boardSize.height; i++ )
for( int j = 0; j < boardSize.width; j++ )
corners.push_back(Point3f(float((2*j + i % 2)*squareSize),
float(i*squareSize), 0));
break;
default:
CV_Error(Error::StsBadArg, "Unknown pattern type\n");
}
}
void computeC2MC1(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,
Mat &R_1to2, Mat &tvec_1to2)
{
//c2Mc1 = c2Mo * oMc1 = c2Mo * c1Mo.inv()
R_1to2 = R2 * R1.t();
tvec_1to2 = R2 * (-R1.t()*tvec1) + tvec2;
}
} //namespace
int main()
{
Mat img = imread("left02.jpg");
Mat img_corners = img.clone(), img_pose = img.clone(), img_bird_eye_view = img.clone();
vector<Point2f> corners;
Size patternSize(9,6);
bool found = findChessboardCorners(img, patternSize, corners);
drawChessboardCorners(img_corners, patternSize, corners, found);
imshow("Chessboard corners detection", img_corners);
vector<Point3f> objectPoints;
float squareSize = 2.5e-2;
calcChessboardCorners(patternSize, squareSize, objectPoints);
FileStorage fs("left_intrinsics.yml", FileStorage::READ);
Mat cameraMatrix, distCoeffs;
fs["camera_matrix"] >> cameraMatrix;
fs["distortion_coefficients"] >> distCoeffs;
Mat rvec, tvec;
solvePnP(objectPoints, corners, cameraMatrix, distCoeffs, rvec, tvec);
aruco::drawAxis(img_pose, cameraMatrix, distCoeffs, rvec, tvec, 2*squareSize);
imshow("Pose", img_pose);
Mat R_desired = (Mat_<double>(3,3) <<
0, 1, 0,
-1, 0, 0,
0, 0, 1);
Mat R;
Rodrigues(rvec, R);
Mat normal = (Mat_<double>(3,1) << 0, 0, 1);
Mat normal1 = R*normal;
Mat origin(3, 1, CV_64F, Scalar(0));
Mat origin1 = R*origin + tvec;
double d_inv1 = 1.0 / normal1.dot(origin1);
Mat R_1to2, tvec_1to2;
Mat tvec_desired = tvec.clone();
computeC2MC1(R, tvec, R_desired, tvec_desired, R_1to2, tvec_1to2);
Mat H = R_1to2 + d_inv1 * tvec_1to2*normal1.t();
H = cameraMatrix * H * cameraMatrix.inv();
H = H/H.at<double>(2,2);
std::cout << "H:\n" << H << std::endl;
warpPerspective(img_pose, img_bird_eye_view, H, img.size());
Mat compare;
hconcat(img_pose, img_bird_eye_view, compare);
imshow("Bird eye view", compare);
waitKey();
return 0;
}
我正在尝试从相机内在、外在矩阵和畸变系数中获取鸟瞰图透视变换。
我尝试使用 this 问题的答案。
使用的图片是示例图片left02.jpg来自opencv官方github repo
我校准了相机并找到了内在矩阵、外在矩阵和畸变系数。
我取消了图像失真并找到了姿势。检查参数是否正确。
我用来求透视变换矩阵的方程是(参考上面link):
Hr = K * R.inv() * K.inv()
其中 R 是旋转矩阵(来自 cv2.Rodrigues()),K 是从 cv2.getoptimalnewcameramatrix()
[ 1 0 | ]
Ht = [ 0 1 | -K*C/Cz ]
[ 0 0 | ]
其中 C=-R.inv()*T
其中 T 是来自 cv2.solvePnP()
的平移向量
Cz 是 C 向量的第三个分量
所需的转换是:H = Ht * Hr
我用来构造上述等式的代码是:
K = newcameramtx # from cv2.getoptimalnewcameramatrix()
ret,rvec,tvec = cv2.solvePnP(world_points,corners2,K,dist)
R,_ = cv2.Rodrigues(rvec)
_,R_inv = cv2.invert(R)
_,K_inv = cv2.invert(K)
Hr = np.matmul(K,np.matmul(R_inv,K_inv))
C = np.matmul(-R_inv,tvec)
Cz = C[2]
temp_vector = np.matmul(-K,C/Cz)
Ht = np.identity(3)
for i,val in enumerate(temp_vector):
Ht[i][2] = val
homography = np.matmul(Ht,Hr)
warped_img =cv2.warpPerspective(img,homography,(img.shape[1],img.shape[0]))
# where img is the above undistored image with visualized pose
生成的扭曲图像不正确。
如果我使用以下代码从单应性中删除翻译
homography = Hr.copy()
warped_img =cv2.warpPerspective(img,homography,(img.shape[1],img.shape[0]))
我得到以下图像
我认为上图显示我的旋转部分是正确的,但我的翻译是错误的。
由于平移矩阵 (Ht) 是增广矩阵,我不确定我对上述矩阵的构造是否正确。
我特别想从相机标定中搞清楚鸟瞰视角的变换。
那么,我该如何修正上述等式,以便获得棋盘图像的完美鸟瞰图
谁能解释一下上述 Ht 和 Hr 的方程式是如何推导出来的?我对线性代数了解不多,所以这些方程式对我来说不是很明显。
更新:
homography = np.matmul(Ht,Hr)
warped_img =cv2.warpPerspective(img,homography,(img.shape[1],img.shape[0]),flags=cv2.WARP_INVERSE_MAP)
cv2.WARP_INVERSE_MAP flag 给了我不同的结果
还是不是我要的结果!
教程中解释了您想实现的目标:Demo 3: Homography from the camera displacement。
你有当前的相机姿势(旋转+平移),你可以计算出允许从鸟瞰图查看棋盘的所需相机姿势。
由于棋盘框与相机框不同(相机框见here),允许鸟瞰的相机姿势所需的旋转是:
只需按照教程进行操作,您应该会得到类似于以下内容的单应矩阵:
H:
[0.935, -0.337, 40.383;
-0.116, 0.729, 64.381;
0.000408, -0.001299, 1]
与warpPerspective
:
另一个例子:
由于棋盘是平的(对于一般场景,单应性仅对纯旋转相机运动有效),您还可以玩一下翻译:
编辑:教程中的代码
#include <opencv2/opencv.hpp>
#include <opencv2/aruco.hpp>
using namespace std;
using namespace cv;
namespace
{
enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };
void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners, Pattern patternType = CHESSBOARD)
{
corners.resize(0);
switch (patternType)
{
case CHESSBOARD:
case CIRCLES_GRID:
//! [compute-chessboard-object-points]
for( int i = 0; i < boardSize.height; i++ )
for( int j = 0; j < boardSize.width; j++ )
//To try to center the chessboard frame, we substract the image size
corners.push_back(Point3f(float((j-boardSize.width/2)*squareSize),
float((i-boardSize.height/2)*squareSize), 0));
//! [compute-chessboard-object-points]
break;
case ASYMMETRIC_CIRCLES_GRID:
for( int i = 0; i < boardSize.height; i++ )
for( int j = 0; j < boardSize.width; j++ )
corners.push_back(Point3f(float((2*j + i % 2)*squareSize),
float(i*squareSize), 0));
break;
default:
CV_Error(Error::StsBadArg, "Unknown pattern type\n");
}
}
void computeC2MC1(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,
Mat &R_1to2, Mat &tvec_1to2)
{
//c2Mc1 = c2Mo * oMc1 = c2Mo * c1Mo.inv()
R_1to2 = R2 * R1.t();
tvec_1to2 = R2 * (-R1.t()*tvec1) + tvec2;
}
} //namespace
int main()
{
Mat img = imread("left02.jpg");
Mat img_corners = img.clone(), img_pose = img.clone(), img_bird_eye_view = img.clone();
vector<Point2f> corners;
Size patternSize(9,6);
bool found = findChessboardCorners(img, patternSize, corners);
drawChessboardCorners(img_corners, patternSize, corners, found);
imshow("Chessboard corners detection", img_corners);
vector<Point3f> objectPoints;
float squareSize = 2.5e-2;
calcChessboardCorners(patternSize, squareSize, objectPoints);
FileStorage fs("left_intrinsics.yml", FileStorage::READ);
Mat cameraMatrix, distCoeffs;
fs["camera_matrix"] >> cameraMatrix;
fs["distortion_coefficients"] >> distCoeffs;
Mat rvec, tvec;
solvePnP(objectPoints, corners, cameraMatrix, distCoeffs, rvec, tvec);
aruco::drawAxis(img_pose, cameraMatrix, distCoeffs, rvec, tvec, 2*squareSize);
imshow("Pose", img_pose);
Mat R_desired = (Mat_<double>(3,3) <<
0, 1, 0,
-1, 0, 0,
0, 0, 1);
Mat R;
Rodrigues(rvec, R);
Mat normal = (Mat_<double>(3,1) << 0, 0, 1);
Mat normal1 = R*normal;
Mat origin(3, 1, CV_64F, Scalar(0));
Mat origin1 = R*origin + tvec;
double d_inv1 = 1.0 / normal1.dot(origin1);
Mat R_1to2, tvec_1to2;
Mat tvec_desired = tvec.clone();
computeC2MC1(R, tvec, R_desired, tvec_desired, R_1to2, tvec_1to2);
Mat H = R_1to2 + d_inv1 * tvec_1to2*normal1.t();
H = cameraMatrix * H * cameraMatrix.inv();
H = H/H.at<double>(2,2);
std::cout << "H:\n" << H << std::endl;
warpPerspective(img_pose, img_bird_eye_view, H, img.size());
Mat compare;
hconcat(img_pose, img_bird_eye_view, compare);
imshow("Bird eye view", compare);
waitKey();
return 0;
}