前言
在我的上一篇笔记中记录了有关投影图像恢复仿射特性,去除投影畸变的方法,主要是通过找平行线的交点来完成的。这里将使用另外一种方法来找vanishing point,进而确定infinite line。这种方法更加适合类似例子中的这种比较整齐有规律的图像。
原理
对于一维空间(一条直线上)的射影几何,点的齐次坐标为(x1,x2),点的投影变换公式为:
投影变换矩阵H有三个自由度,每对点映射可以提供一个约束条件,即一个方程,因此只要找到三对点映射,建立三元一次方程组,就能求解出H,进而使用上面的投影变换公式就可以得到原直线上的无穷远点(1,0)对应投影图像上的一维齐次坐标。然后将一维齐次坐标变换为图像平面上的二维齐次坐标(x1,x2,x3)即可,接下来的方法原理和上一篇笔记就完全一样了。
思路
主要是怎样找到一条直线上的三对点映射,如下图所示:
a’、b’、c’是投影图像中共线的三点,对应原平面上的a、b、c三点,不难看出,在原平面上:
ab:bc = 1:1
然后通过图中的坐标,我们可以求出:
a'b':b'c' = 1.104:1
在原平面中,令a、b、c三点的一维齐次坐标分别为
(0,1),(1,1),(2,1)。类似的,在投影平面中,令a’、b’、c’三点的一维齐次坐标分别为*(0,1),(1.104,1),(2.104,1)*。于是,我们得到了三对点映射,正如原理中所说,我们可以通过这三对点映射求出投影变换矩阵H,因为点取得比较特殊,该三元一次方程组求解比较简单,我们解出了:
H = ((1.161,0),(0.052,1))
通过这个矩阵H,我们使用一维空间点的投影变换公式求出原平面中直线abc上的无穷远点(1,0)对应投影平面上直线a’b’c’的一维齐次坐标为(22.4,1),我们利用相似三角形(其他方法也行)求出该点二维齐次坐标为(1340,-544,1)。同理,我们利用直线def也能求出另外一点(-522,-391,1),通过这两个点即可求出连线的齐次坐标为(0.00019,0.0023,1)。然后就可以使用上一篇笔记中的方法校正图像了。
代码
同样是先使用鼠标获取图像上点的坐标:
void on_mouse(int event, int x, int y, int flags, void* ustc)
{
CvFont font;
cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 0.5, 0.5, 0, 1, CV_AA);
if (event == CV_EVENT_LBUTTONDOWN)
{
CvPoint pt = cvPoint(x, y);
char temp[16];
sprintf(temp, "(%d,%d)", pt.x, pt.y);
cvPutText(src, temp, pt, &font, cvScalar(255, 255, 255, 0));
cvCircle(src, pt, 2, cvScalar(255, 0, 0, 0), CV_FILLED, CV_AA, 0);
cvShowImage("src", src);
}
}
void GetMouse(IplImage *img)
{
src = img;
cvNamedWindow("src", 1);
cvSetMouseCallback("src", on_mouse, 0);
cvShowImage("src", src);
waitKey(0);
}
下面是校正相关的函数:
void RectifyByCrossRatio(Point2d points_3d_6[6], Mat src)
{
vector<Point3f> vanishing_points;
for (int i = 0; i < 2; i++)
{
//1、测量ad:bc
float length_ratio = MeasureLengthRatio(points_3d_6[i * 3], points_3d_6[i * 3 + 1], points_3d_6[i * 3 + 2]);
//2、定义abc直线上的齐次坐标,变换前和变换后
Point2f point_a = { 0, 1 };
Point2f point_b = { 1, 1 };
Point2f point_c = { 2, 1 };
Point2f point_a1 = { 0, 1 };
Point2f point_b1 = { length_ratio, 1 };
Point2f point_c1 = { length_ratio + 1, 1 };
vector<Point2f> points;
vector<Point2f> points1;
points.push_back(point_a);
points.push_back(point_b);
points.push_back(point_c);
points1.push_back(point_a1);
points1.push_back(point_b1);
points1.push_back(point_c1);
//计算变换矩阵H(2×2) H =((a,b),(c,1)),使用三组点映射
float H[2][2] = { {1, 0},
{0, 1} };
ComputeMappingMatrix(H, points, points1);
//计算vanishing point的直线坐标
Point2f point1_line = { H[0][0] * 1, H[1][0] * 1 };
//对该坐标归一化
point1_line.x = point1_line.x / point1_line.y;
point1_line.y = 1;
float vanishing_ratio = point1_line.x;
//利用相似三角形,计算vanishing point的齐次坐标
float x0 = points_3d_6[i * 3].x;
float y0 = points_3d_6[i * 3].y;
float x1 = points_3d_6[i * 3 + 1].x;
float y1 = points_3d_6[i * 3 + 1].y;
float x_vanishing = 0;
float x = (x1 - x0) * vanishing_ratio / length_ratio + x0;
float y = (y1 - y0) * vanishing_ratio / length_ratio + y0;
Point3f vanishing_point = { x, y, 1 };
vanishing_points.push_back(vanishing_point);
}
vector<vector<float>> vanishing_line;
GetLineFromPoints(vanishing_points[0], vanishing_points[1], vanishing_line);
Mat image = Mat::zeros(src.rows, src.cols, CV_8UC1);
GetRectifingImage(vanishing_line[0], src, image);
}
//返回ad:bc的长度比
float MeasureLengthRatio(Point2d a, Point2d b, Point2d c)
{
//首先计算ab和bc的长度
float length_ab = sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
float length_bc = sqrt((b.x - c.x) * (b.x - c.x) + (b.y - c.y) * (b.y - c.y));
float ratio = 0;
if (length_bc != 0)
ratio = length_ab / length_bc;
else
cout << "error!" << endl;
return ratio;
}
//利用三对点映射计算变换矩阵(2×2),这里用了更复杂的方法,其实第一个方程就能解出b = 0,
//进而转换完二元一次方程组
void ComputeMappingMatrix(float H[2][2], vector<Point2f> points, vector<Point2f> points1)
{
//获得三元一次方程组的系数矩阵 (3×4)
float coefficient_matrix[3][4] = { 0 };
for (int i = 0; i < 3; i++)
{
coefficient_matrix[i][0] = points[i].x;
coefficient_matrix[i][1] = 1;
coefficient_matrix[i][2] = -points[i].x * points1[i].x;
coefficient_matrix[i][3] = points1[i].x;
}
float delta[3][3] = { 0 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
delta[i][j] = coefficient_matrix[i][j];
}
}
float delta_x[3][3] = { 0 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (j == 0)
delta_x[i][j] = coefficient_matrix[i][3];
else
delta_x[i][j] = coefficient_matrix[i][j];
}
}
float delta_y[3][3] = { 0 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (j == 1)
delta_y[i][j] = coefficient_matrix[i][3];
else
delta_y[i][j] = coefficient_matrix[i][j];
}
}
float delta_z[3][3] = { 0 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (j == 2)
delta_z[i][j] = coefficient_matrix[i][3];
else
delta_z[i][j] = coefficient_matrix[i][j];
}
}
//计算4个delta矩阵的行列式
float det_delta = ComputeMatrixDet(delta);
float det_delta_x = ComputeMatrixDet(delta_x);
float det_delta_y = ComputeMatrixDet(delta_y);
float det_delta_z = ComputeMatrixDet(delta_z);
//求解三元一次方程组
float a = det_delta_x / det_delta;
float b = det_delta_y / det_delta;
float c = det_delta_z / det_delta;
H[0][0] = a;
H[0][1] = b;
H[1][0] = c;
H[1][1] = 1;
}
//计算3×3矩阵的行列式
float ComputeMatrixDet(float matrix[3][3])
{
float a1 = matrix[0][0];
float a2 = matrix[1][0];
float a3 = matrix[2][0];
float b1 = matrix[0][1];
float b2 = matrix[1][1];
float b3 = matrix[2][1];
float c1 = matrix[0][2];
float c2 = matrix[1][2];
float c3 = matrix[2][2];
return a1 * b2 * c3 + a2 * b3 * c1 + a3 * b1 * c2 - a3 * b2 * c1 - a2 * b1 * c3 - a1 * b3 * c2;
}