前一段时间忙着秋招和修改论文意见反稿,就没有接着做关于Azure Kinect DK相关的探索总结,现在有时间就慢慢补起来。上一篇是利用PP-Humanseg模型分割出color图和深度图中的人像,这一篇紧接着上一篇的工作,从人像分割后的图像结果获得人体的点云数据(也可以直接先生成点云再作点云的分割,等后续探索)。
关于这部分的原理很多博客和文章都已经有详细的叙述,这里就作一个简单的记录。
首先,我们需知道相机成像原理中的一些映射过程:
上图中有四个坐标系分别为世界坐标系( X w X_w Xw, Y w Y_w Yw, Z w Z_w Zw),相机坐标系( X c X_c Xc, X c X_c Xc, X c X_c Xc),像素坐标系( u u u, v v v)和图像物理坐标系( x x x, y y y)。
图像中任意一个像素点m在世界坐标系坐标为( x w x_w xw, y w y_w yw, z w z_w zw),在摄像机坐标系坐标为( x c x_c xc, y c y_c yc, z c z_c zc),在像素坐标系坐标为( u m u_m um, v m v_m vm),在图像物理坐标系坐标为( x m x_m xm, y m y_m ym)。
图像物理坐标系的原点在图像坐标系中的原点为( u 0 u_0 u0, v 0 v_0 v0),图像上每个点在 x x x, y y y轴方向上的物理尺寸是 d x d_x dx, d y d_y dy。则图像中任意一个像素点m在( u u u, v v v)坐标系中满足如下关系:
[ u m v m 1 ] = [ 1 d x 0 u 0 0 1 d y v 0 0 0 1 ] [ x m y m 1 ] \begin{bmatrix} u_m \\ v_m \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{1}{d_x}&0&u_0 \\ 0&\frac{1}{d_y} &v_0 \\ 0&0&1 \end{bmatrix} \begin{bmatrix} x_m \\ y_m \\ 1 \end{bmatrix} ⎣ ⎡umvm1⎦ ⎤=⎣ ⎡dx1000dy10u0v01⎦ ⎤⎣ ⎡xmym1⎦ ⎤
根据刚体变换的过程,世界坐标系中的一点到相机坐标系中的点,可以由一个旋转矩阵R和平移矩阵T来描述:
[ x c y c z c 1 ] = [ R T 0 3 T 1 ] [ x w y w z w 1 ] \begin{bmatrix} x_c \\ y_c \\ z_c \\ 1 \end{bmatrix} = \begin{bmatrix} R & T \\ 0_3^T&1\end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ z_w \\ 1 \end{bmatrix} ⎣ ⎡xcyczc1⎦ ⎤=[R03TT1]⎣ ⎡xwywzw1⎦ ⎤
又因为:
x m = f x c z c , y m = f y c z c , − − − > z c [ x m y m 1 ] = [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ x c y c z c 1 ] x_m = f\frac{x_c}{z_c}, \qquad y_m = f\frac{y_c}{z_c}, \qquad ---> \quad z_c \begin{bmatrix} x_m \\ y_m \\ 1 \end{bmatrix} = \begin{bmatrix} f&0&0&0 \\ 0&f&0&0 \\ 0&0&1&0 \end{bmatrix} \begin{bmatrix} x_c \\ y_c \\ z_c \\ 1 \end{bmatrix} xm=fzcxc,ym=fzcyc,−−−>zc⎣ ⎡xmym1⎦ ⎤=⎣ ⎡f000f0001000⎦ ⎤⎣ ⎡xcyczc1⎦ ⎤
由上述描述的三个矩阵等式变换可得:
z c [ u m v m 1 ] = [ 1 d x 0 u 0 0 1 d y v 0 0 0 1 ] [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ R T 0 3 T 1 ] [ x w y w z w 1 ] z_c \begin{bmatrix} u_m \\ v_m \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{1}{d_x}&0&u_0 \\ 0&\frac{1}{d_y} &v_0 \\ 0&0&1 \end{bmatrix} \begin{bmatrix} f&0&0&0 \\ 0&f&0&0 \\ 0&0&1&0 \end{bmatrix} \begin{bmatrix} R & T \\ 0_3^T&1\end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ z_w \\ 1 \end{bmatrix} zc⎣ ⎡umvm1⎦ ⎤=⎣ ⎡dx1000dy10u0v01⎦ ⎤⎣ ⎡f000f0001000⎦ ⎤[R03TT1]⎣ ⎡xwywzw1⎦ ⎤
= [ f d x 0 u 0 0 0 f d y v 0 0 0 0 1 0 ] [ R T 0 3 T 1 ] [ x w y w z w 1 ] = \begin{bmatrix} \frac{f}{d_x}&0&u_0&0 \\ 0&\frac{f}{d_y} &v_0&0 \\ 0&0&1&0 \end{bmatrix} \begin{bmatrix} R & T \\ 0_3^T&1\end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ z_w \\ 1 \end{bmatrix} =⎣ ⎡dxf000dyf0u0v01000⎦ ⎤[R03TT1]⎣ ⎡xwywzw1⎦ ⎤
其中等式右边的第一个矩阵是相机标定的内参矩阵,第二个矩阵是相机的外参矩阵。
对于单个相机来说,由于世界坐标原点和相机原点重合,也就无旋转和平移,所以有:
z c [ u m v m 1 ] = [ f d x 0 u 0 0 0 f d y v 0 0 0 0 1 0 ] [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] [ x w y w z w 1 ] z_c \begin{bmatrix} u_m \\ v_m \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{f}{d_x}&0&u_0&0 \\ 0&\frac{f}{d_y} &v_0&0 \\ 0&0&1&0 \end{bmatrix} \begin{bmatrix} 1&0&0&0 \\ 0&1&0&0 \\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix} \begin{bmatrix} x_w \\ y_w \\ z_w \\ 1 \end{bmatrix} zc⎣ ⎡umvm1⎦ ⎤=⎣ ⎡dxf000dyf0u0v01000⎦ ⎤⎣ ⎡1000010000100001⎦ ⎤⎣ ⎡xwywzw1⎦ ⎤
从以上的矩阵变换可以得到像素点到世界坐标点的变换,即:
z w = z c x w = z c ⋅ ( u m − u 0 ) ⋅ d x / f y w = z c ⋅ ( v m − v 0 ) ⋅ d y / f z_w=z_c \qquad x_w = z_c \cdot (u_m -u_0) \cdot dx / f \qquad y_w = z_c \cdot (v_m -v_0) \cdot dy / f zw=zcxw=zc⋅(um−u0)⋅dx/fyw=zc⋅(vm−v0)⋅dy/f
pcl::PointCloud<pcl::PointXYZRGB>::Ptr ImageToPointcloud(cv::Mat& color, cv::Mat& depth){
pcl::PointCloud<pcl::PointXYZRGB>::Ptr pointcloud( new pcl::PointCloud<pcl::PointXYZRGB>() );
for (int v = 0; v < depth.rows; v++){
for (int u = 0; u < depth.cols; u++){
unsigned int d = depth.ptr<unsigned short>(v)[u];
pcl::PointXYZRGB point;
point.z = double(d) / _depthScale;
point.x = (u - _cx) * point.z / _fx; // _cx, _cy是摄像头光学中心
point.y = (v - _cy) * point.z / _fy; // _fx, _fy是摄像头焦距
point.b = color.data[v*color.step+u*color.channels()];
point.g = color.data[v*color.step+u*color.channels() + 1];
point.r = color.data[v*color.step+u*color.channels() + 2];
pointcloud->points.push_back(point);
}
}
pointcloud->height = 1;
pointcloud->width = pointcloud->points.size();
pointcloud->is_dense = false;
return pointcloud;
}
关于从深度图像生成点云,就是坐标系点的转换,只要明白其中的原理,代码还是很好写的,后续还会进行相机标定和点云拼接等操作。