点云基础知识储备(复习)
齐次坐标
一个点的齐次坐标可以表示为(x,y,1),这种表示方法具备以下特征
具有标量乘法不变性:点(x,y,z)与(kx, ky, kz)实际上表示的是同一个点
矩阵表示的便利性:在齐次坐标中,变换一个点可以通过乘以一个变换矩阵来完成
表示直线:直线ax+by+c = 0可以表示为齐次坐标中的点(a,b,0),这种表示方式允许我们将点和直线统一在一个框架内,并且可以轻松地转换为其他坐标系统。
齐次坐标表示欧式变换
齐次坐标可以非常方便的表示旋转和平移 ,如果使用齐次坐标来表达 a’ = R*a + t 的话可以写为:
[
R
t
0
1
]
[
a
1
]
=
T
[
a
1
]
\begin{bmatrix} R&t \\ 0 & 1 \end{bmatrix}\begin{bmatrix} a\\ 1 \end{bmatrix}=T\begin{bmatrix} a\\ 1 \end{bmatrix}
[R0t1][a1]=T[a1]
那么进行多次欧氏变换只需要连乘变换矩阵就行了。
旋转矩阵、旋转向量、四元组、欧拉角
旋转矩阵
将二维平面向量绕x轴逆时针旋转θ角,旋转矩阵表示为:
x` = rcos(α+θ) = rcosαcosθ - rsinαsinθ = xcosθ - ysinθ
y` = rsin(α+θ) = rsinαcosθ + rcosαsinθ = xsinθ + ycosθ
[ x ′ y ′ ] = [ c o s θ − s i n θ s i n θ c o s θ ] [ x y ] \begin{bmatrix} x'\\ y' \end{bmatrix} = \begin{bmatrix} cosθ&-sinθ \\ sinθ & cosθ \end{bmatrix}\begin{bmatrix} x\\ y \end{bmatrix} [x′y′]=[cosθsinθ−sinθcosθ][xy]
欧拉角
偏航角:绕z轴旋转, 得到偏航角
俯仰角:绕y轴旋转, 得到俯仰角
翻滚角:绕x轴旋转, 得到翻滚角
名字取得很生动,可以自行现象一架飞机在z轴上
欧拉角和旋转矩阵可以相互转换:
使用Eigen库将旋转矩阵、四元素、旋转向量相互转化
在Eigen库中,旋转向量、旋转矩阵和四元数的相互转换关系如下:
// 初始化旋转向量
double angle = M_PI/4; // 45°转换为弧度
Vector3d axis = Vector3d::UnitZ(); // Z轴
// 初始化旋转向量 (1)
Vector3d rotation_vector = angle * axis;
// 初始化旋转向量(2)
Eigen::AngleAxisd(const Scalar& angle, const Axis& axis);
// 初始化旋转矩阵
Matrix3d rotation_matrix;
// 初始化四元数
Quaterniond quaternion(rotation_matrix);
// 初始化欧拉角
Vector3d euler_angles = rotation_matrix.eulerAngles(2, 1, 0); // ZYX顺序
相机成像
相机坐标系下的点P(X, Y, Z)在相机成像平面上成的像为P’(X’, Y’, Z’) 。根据三角形相似原理:
z
f
=
X
X
′
=
Y
Y
′
→
X
′
=
f
X
Z
,
Y
′
=
f
Y
Z
(
1
)
\frac{z}{f} = \frac{X}{X'}= \frac{Y}{Y'} \to X'=f\frac{X}{Z}, Y'=f\frac{Y}{Z} (1)
fz=X′X=Y′Y→X′=fZX,Y′=fZY(1)
成像过程一般是以图像中心点为坐标系原点的,如下图所示。而我们做图像处理的时候习惯于从左上角为图像坐标系原点。
所以需要平移:
U = X’ + Cx (2)
V = Y’ + Cy
{ u = f x X Z + c x v = f y Y Z + c y \left\{\begin{matrix} u={f}_{x}\frac{X}{Z}+{c}_{x}\\ v={f}_{y}\frac{Y}{Z}+{c}_{y} \end{matrix}\right. {u=fxZX+cxv=fyZY+cy
使用齐次坐标表达为:
极几何与极约束
极几何与极约束在双目立体视觉测量,三位重建,运动估计,机器人导航与地图生成中都有一定作用,通过极线几何约束,可以将对应点匹配从整幅图像寻找压缩到在一条直线上寻找对应点,从而提高匹配效率。
上图表示的是一个运动的相机在两个不同位置的成像,C0, C1分别是两个位置中相机的光心,P是空间中的一个三维点,p0, p1分别是P点在不同成像平面上对应的像素点。
其中 两个相机光心的连线称为基线(C0C1),COC1与成像平面的交点称为极点,极平面与两像平面的交线,也就是极点与成像点的连线称为极线。
由平面关系得:
叉乘得出平面法向量,它与平面上的向量c0p0的点乘结果为0。
将上述式子中的向量全部转到c0坐标系,得p0·(t×Rp1)= 0,简化得
p
0
T
E
p
1
=
0
p0 ^T E p1 = 0
p0TEp1=0
E = [ t ] R E = [t]R E=[t]R
E是t的反对称矩阵乘旋转矩阵R,记为本质矩阵(Essential Matrix)
所有的向量可以通过构造得到一个对应的反对称矩阵。在三维空间中,向量的反对称矩阵能够将向量的叉乘转换为矩阵运算。
由于:点p在直线l上的充分必要条件是:
l
T
∗
P
′
=
0
l^T*P' = 0
lT∗P′=0
我们把Ep1看成是直线方程 ,那么由
p 0 T E p 1 = 0 p0 ^T E p1 = 0 p0TEp1=0
得:点p0在Ep1上,Ep1既是以C0为原点坐标系的极线。
单应矩阵
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
// 定义用户数据结构体,结构体包含原始图像im和用户点击的四个点(points)的位置
struct userdata {
Mat im
vector<Point2f> points;
};
// 当用户在广告牌图片上单击鼠标左键时,程序会在图像上画一个小圆圈,并将该点的坐标保存到points中。当点击四个点后,程序会停止记录点击。
void mouseHandler(int event, int x, int y, int flags, void* data_ptr) {
// 检测鼠标左键按下事件
if (event == EVENT_LBUTTONDOWN) {
// 定义了一个 userdata 类型的指针data,指向传入的 userdata 结构体实例
userdata* data = (userdata*)data_ptr;
circle(data->im, Point(x, y), 3, Scalar(0, 255, 255), 5, LINE_AA);
imshow("Image", data->im);
if (data->points.size() < 4) {
data->points.push_back(Point2f(x, y));
}
}
}
int main(int argc, char** argv) {
// Read in the logo and the image with the billboard
Mat im_logo = imread("cy.png"); // 标志图
Mat im_ad = imread("tm.jpg"); // 背景图
if (im_logo.empty() || im_ad.empty()) {
cout << "Could not open or find the images!" << endl;
return -1;
}
// Define the four corners of the logo in the logo image (source points)
Size logo_size = im_logo.size();
vector<Point2f> pts_logo = {
Point2f(0, 0),
Point2f(logo_size.width - 1, 0),
Point2f(logo_size.width - 1, logo_size.height - 1),
Point2f(0, logo_size.height - 1)
};
// Destination image
Mat im_temp = im_ad.clone();
userdata data;
data.im = im_temp;
// Show the image and set mouse callback
imshow("Image", im_temp);
cout << "Click on four corners of the billboard and then press ENTER" << endl;
// setMouseCallback函数将鼠标事件和mouseHandler回调函数绑定在一起。
setMouseCallback("Image", mouseHandler, &data);
waitKey(0);
// Check if we have exactly four points
if (data.points.size() != 4) {
cout << "You need to click exactly four points!" << endl;
return -1;
}
// Define the four corners of the billboard in the destination image (destination points)
vector<Point2f> pts_ad = data.points;
// Compute the perspective transform matrix
// 根据标志图像的四个角坐标pts_logo和广告牌四角的坐标pts_ad计算透视变换(单应性)矩阵H。
Mat H = findHomography(pts_logo, pts_ad);
// Warp the logo image to fit the billboard area in the destination image
Mat im_warped;
// 将标志图像根据透视变换矩阵H进行变换,使其匹配广告牌区域。
warpPerspective(im_logo, im_warped, H, im_ad.size());
// Create a mask for the warped logo
// 使用掩膜将广告牌图像和变换后的标志图像合并。
Mat mask = Mat::zeros(im_ad.size(), CV_8UC1);
// 这行代码的作用是将 pts_ad 中的 Point2f 类型的数据转换为 Point 类型,并存储到新的 mask_points 向量中。
vector<Point> mask_points(pts_ad.begin(), pts_ad.end());
// 这个函数会在图像上绘制一个指定的多边形,并填充该区域为指定的颜色。
fillConvexPoly(mask, mask_points, Scalar(255));
// Blend the warped logo with the destination image
Mat im_ad_masked;
// copyTo 函数在 ~mask 为非零值的区域复制 im_ad 到 im_ad_masked。因此,广告牌区域会被排除在外,只复制非广告牌区域的背景内容。
im_ad.copyTo(im_ad_masked, ~mask); // Copy the background
// 将 im_warped(即经过透视变换后的标志图像)复制到 im_ad_masked 的广告牌区域。
im_warped.copyTo(im_ad_masked, mask); // Overlay the warped logo
// Display the final result
imshow("Result", im_ad_masked);
waitKey(0);
return 0;
}