1.设置vtkCamera内参
已知相机内参为:fx、fy、cx、cy
,图像尺寸为:width、height
。
相机相对于世界坐标系的外参矩阵为extrinsicMatrix4x4
,即相对于世界坐标系的旋转平移。如果不存在外参,则该矩阵为单位矩阵。
首先保证renderWindow
的尺寸也设置为width、height
。
// 将相机相对世界坐标系的外参赋值给相机
camera->SetModelTransformMatrix(extrinsicMatrix4x4);
// 由于SetModelTransformMatrix已经将规定了相机与世界坐标系的关系,此时相机设置Position的参照系为相机自己的参照系,此时相机应当处于原点
camera->SetPosition(0, 0, 0);
// 按照深度相机定义,相机应当看向Z+方向
camera->SetFocalPoint(0, 0, 1);
// 相机坐标系x沿u方向,y沿v方向,因此y是向下的
camera->SetViewUp(0,-1,0);
// 设置深度范围,按照相机默认参数即可,米单位
camera->SetClippingRange(depth_min, depth_max);
// 将主点转化为归一化的图像坐标系
double wcx = -2*(cx - double(width)/2) / width;
double wcy = 2*(cy - double(height)/2) / height;
camera->SetWindowCenter(wcx, wcy);
// 将焦距转化为视场角
double view_angle = (2.0 * std::atan2( height/2.0, fy)) * 180.d / PI;
camera->SetViewAngle( view_angle );
2.拍摄彩色图和深度图
2.1.拍摄彩色图
使用vtkWindowToImageFilter
和vtkJPEGWriter
即可将renderWindow
中的内容保存为图片。主要代码如下:
m_renderWindow->Render();
vtkSmartPointer<vtkWindowToImageFilter> wti = vtkSmartPointer<vtkWindowToImageFilter>::New();
wti->SetInput(m_renderWindow);
vtkSmartPointer<vtkJPEGWriter> jpegWriter = vtkSmartPointer<vtkJPEGWriter>::New();
jpegWriter->SetFileName(filename);
jpegWriter->SetInputConnection(wti->GetOutputPort());
jpegWriter->Write();
m_renderWindow->Finalize();
测试拍摄的图片如下:
2.2.拍摄深度图
第一节中已经将相机的内参赋值给vtkCamera
,则使用ZBuffer
和DisplayToWorld
转化,完成深度图的采集。主要代码如下:
// 使用OpenCV的CV_16UC1保存深度图
cv::Mat depthImage(cv::Size(width, height), CV_16UC1);
// 获取到renderWindow的ZBuffer(ZBuffer是0~1的深度值,范围在vtkCamera.SetClippingRange中规定)
float * data = m_renderWindow->GetZbufferData(0, 0, width- 1, height - 1);
double wPos[4];
// 用于将世界坐标系转化为相机坐标系下深度值,外参矩阵的第三行。
double m20 = extrinsicMatrix4x4(2, 0);
double m21 = extrinsicMatrix4x4(2, 1);
double m22 = extrinsicMatrix4x4(2, 2);
double m23 = extrinsicMatrix4x4(2, 3);
for (int y = 0; y < height; y++)
{
uint16_t * dData = depthImage.ptr<uint16_t>(y);
for (int x = 0; x < width; x++)
{
// 获取ZBuffer对应数值
float depth = data[y*width+ x];
// 为1时表示无穷远,无深度
if (depth == 1)
{
dData[x] = 0;
}
else
{
// 将zBuffer转化为世界坐标系点坐标
vtkInteractorObserver::ComputeDisplayToWorld(m_render, x, y, depth, wPos);
// 将世界坐标系点坐标,转化为相机坐标系下Z,即深度
float z = wPos[0] * m20 + wPos[1] * m21 + wPos[2] * m22 + m23;
// 将米单位转化为毫米单位uint16_t进行存储
dData[x] = (uint16_t)(z * 1000);
}
}
}
delete data;
// 由于vtkRenderWindow的x、y方向与相机的不一致,需要进行flip。
cv::flip(depthImage, depthImage, 0);
// 保存深度图
cv::imwrite(filename, depthImage);
保存的深度图如下所示(与上述彩色图对应):
3.验证
使用相机内参,将采集到的深度图换算为相机坐标系下三维点云,如下所示:
使用外参进行旋转平移,得到如下结果:
可以看到换算到世界坐标系下的三维点云与之前拍摄的模型完全重合。
以上完成了使用VTK模拟深度相机拍摄RGB-D图像的全过程。