KinectFusion
KinectFusionとは、Kinectを用いてSLAM(Simultaneous Localization and Mapping):自己位置推定と地図作成を同時に行う技術である。
Kinect Developer Toolkit 1.8でも体験できるが、実際にソースコードをいじって、Kinectを動かした位置を得てみよう。
ソースコードのダウンロード
ソースコードは書籍「Kinect for Windows SDK 実践プログラミング」のサンプルプログラムからダウンロードする。書籍をぜひ買ってください。数少ないKinect v1 C++編の良書です。
- KinectFusion.cpp ・・・メイン
- KinectFusionHelper.cpp ・・・関数
- KinectFusionHelper.h ・・・関数の定義
自己位置の表示
メインのプログラムを見てみよう。
自己位置が格納されている変数はworldToCameraTransformである。
worldToCameraTransformは4×4の行列になっている。
- worldToCameraTransform.M41
- worldToCameraTransform.M42
- worldToCameraTransform.M43
がそれぞれ自己位置の(X,Y,Z)を表している。単位は[m]。
コマンドラインに表示する場合、3次元形状の再構成処理の下に、
std::cout << "T = " << worldToCameraTransform.M41 << " " << worldToCameraTransform.M42 << " " << worldToCameraTransform.M43 << std::endl;
を追加する。
自己位置の保存
毎フレームごとの自己位置を保持していくために、mainの最初に、
std::vector<Matrix4> worldToCameraTransformSeq;
を定義する。
3次元形状の再構成処理の下に、
worldToCameraTransformSeq.push_back(worldToCameraTransform);
を追加し、自己位置をプッシュバックしていく。
3次元再構築のエラーが多い場合、マップがリセットされるため、保持してきた自己位置もリセットしよう。
ResetReconstruction(pReconstruction, &worldToCameraTransform);
の下に、リサイズで初期化する。
worldToCameraTransformSeq.resize(0);
's'キーで再構築したマップ(mesh.ply)を保存できる。これと一緒に自己位置も点群として保存しよう。KinectFusion.cppの中、メッシュファイルの保存(Sキー)の中に、
std::cout << "Save Camera Pose" << std::endl;
fileName = "CameraPose.ply";
WriteAsciiPlyCameraPoseFile(&worldToCameraTransformSeq, fileName, true, true);
を追加して、CameraPose.plyファイルを作成する。
実際にファイルを生成する関数はWriteAsciiPlyCameraPoseFileである。
KinectFusionHelper.hの中にWriteAsciiPlyCameraPoseFileを定義する。
/// <summary>
/// Write ASCII .PLY file
/// See http://paulbourke.net/dataformats/ply/ for .PLY format
/// </summary>
/// <param name="worldToCameraTransformSeq">The Kinect Fusion Camera Transform Matrix.</param>
/// <param name="lpOleFileName">The full path and filename of the file to save.</param>
/// <param name="flipYZ">Flag to determine whether the Y and Z values are flipped on save.</param>
/// <param name="outputColor">Set this true to write out the camera pose color to the file when it has been captured.</param>
/// <returns>indicates success or failure</returns>
/*HRESULT WriteAsciiPlyCameraPoseFile(std::vector<Matrix4> *worldToCameraTransformSeq, LPOLESTR lpOleFileName, bool flipYZ, bool outputColor)*/
HRESULT WriteAsciiPlyCameraPoseFile(std::vector<Matrix4> *worldToCameraTransformSeq, char* lpOleFileName, bool flipYZ, bool outputColor);
KinectFusionHelper.cppの中にWriteAsciiPlyCameraPoseFileの中身を記述する。
/// <summary>
/// Write ASCII .PLY file
/// See http://paulbourke.net/dataformats/ply/ for .PLY format
/// </summary>
/// <param name="worldToCameraTransformSeq">The Kinect Fusion Camera Transform Matrix.</param>
/// <param name="lpOleFileName">The full path and filename of the file to save.</param>
/// <param name="flipYZ">Flag to determine whether the Y and Z values are flipped on save.</param>
/// <param name="outputColor">Set this true to write out the camera pose color to the file when it has been captured.</param>
/// <returns>indicates success or failure</returns>
/*HRESULT WriteAsciiPlyCameraPoseFile(std::vector<Matrix4> *worldToCameraTransformSeq, LPOLESTR lpOleFileName, bool flipYZ, bool outputColor)*/
HRESULT WriteAsciiPlyCameraPoseFile(std::vector<Matrix4> *worldToCameraTransformSeq, char* lpOleFileName, bool flipYZ, bool outputColor)
{
HRESULT hr = S_OK;
If (NULL == worldToCameraTransformSeq)
{
return E_INVALIDARG;
}
unsigned int numVertices = worldToCameraTransformSeq->size();
if (0 == numVertices)
{
return E_INVALIDARG;
}
// Open File
/*USES_CONVERSION;*/
char* pszFileName = NULL;
try
{
/*pszFileName = OLE2A(lpOleFileName);*/
pszFileName = lpOleFileName;
}
catch (...)
{
return E_INVALIDARG;
}
FILE *cameraPoseFile = NULL;
errno_t err = fopen_s(&cameraPoseFile, pszFileName, "wt");
// Could not open file for writing - return
if (0 != err || NULL == cameraPoseFile)
{
return E_ACCESSDENIED;
}
// Write the header line
std::string header = "ply\nformat ascii 1.0\ncomment file created by Microsoft Kinect Fusion\n";
fwrite(header.c_str(), sizeof(char), header.length(), cameraPoseFile);
const unsigned int bufSize = MAX_PATH * 3;
char outStr[bufSize];
int written = 0;
if (outputColor)
{
// Elements are: x,y,z, r,g,b
written = sprintf_s(outStr, bufSize, "element vertex %u\nproperty float x\nproperty float y\nproperty float z\nproperty uchar red\nproperty uchar green\nproperty uchar blue\n", numVertices);
fwrite(outStr, sizeof(char), written, cameraPoseFile);
}
else
{
// Elements are: x,y,z
written = sprintf_s(outStr, bufSize, "element vertex %u\nproperty float x\nproperty float y\nproperty float z\n", numVertices);
fwrite(outStr, sizeof(char), written, cameraPoseFile);
}
written = sprintf_s(outStr, bufSize, "end_header\n");
fwrite(outStr, sizeof(char), written, cameraPoseFile);
if (flipYZ)
{
if (outputColor)
{
//red color camera pose x -y -z
std::vector<Matrix4>::iterator begin = worldToCameraTransformSeq->begin();
std::vector<Matrix4>::iterator end = worldToCameraTransformSeq->end();
for (; begin != end; begin++){
written = sprintf_s(outStr, "%lf %lf %lf 255 0 0\n", begin->M41, -begin->M42, -begin->M43);
fwrite(outStr, sizeof(char), written, cameraPoseFile);
}
}
else
{
//camera pose
std::vector<Matrix4>::iterator begin = worldToCameraTransformSeq->begin();
std::vector<Matrix4>::iterator end = worldToCameraTransformSeq->end();
for (; begin != end; begin++){
written = sprintf_s(outStr, "%lf %lf %lf\n", begin->M41, -begin->M42, -begin->M43);
fwrite(outStr, sizeof(char), written, cameraPoseFile);
}
}
}
else
{
if (outputColor)
{
//read color camera pose
std::vector<Matrix4>::iterator begin = worldToCameraTransformSeq->begin();
std::vector<Matrix4>::iterator end = worldToCameraTransformSeq->end();
for (; begin != end; begin++){
written = sprintf_s(outStr, "%lf %lf %lf 255 0 0\n", begin->M41, begin->M42, begin->M43);
fwrite(outStr, sizeof(char), written, cameraPoseFile);
}
}
else
{
//camera pose
std::vector<Matrix4>::iterator begin = worldToCameraTransformSeq->begin();
std::vector<Matrix4>::iterator end = worldToCameraTransformSeq->end();
for (; begin != end; begin++){
written = sprintf_s(outStr, "%lf %lf %lf\n", begin->M41, begin->M42, begin->M43);
fwrite(outStr, sizeof(char), written, cameraPoseFile);
}
}
}
fflush(cameraPoseFile);
fclose(cameraPoseFile);
return hr;
}
できたソースコードはこちら。(Releseモードでしか実行できませんでした。)
実行して、's'キーで保存し、'esc'キーで終了。
MeshLabでmesh.plyとCameraPose.plyを同時に開き、見てみると、
こんな感じで、Kinectカメラが動いた位置が赤い点群で表示されているのがわかる。
※Kinect XBOX360で確認する際に、一部エラーが発生したので、以下の部分をコメントアウト&変更した。Releseモードでしか実行できませんでした。
// Near Modeの設定 XBOX360ではできない
//hResult = pSensor->NuiImageStreamSetImageFrameFlags(hDepthPlayerHandle, NUI_IMAGE_STREAM_FLAG_ENABLE_NEAR_MODE);
//if (FAILED(hResult)){
// std::cerr << "Error : NuiImageStreamSetImageFrameFlags" << std::endl;
// return -1;
//}
// 使用可能なメモリ量
UINT memorySize = reconstructionParameter.voxelCountX * reconstructionParameter.voxelCountY * reconstructionParameter.voxelCountZ * 4 / 1024;//だいたい4GB
//XBOX360でエラー
//hResult = NuiFusionGetDeviceInfo(NUI_FUSION_RECONSTRUCTION_PROCESSOR_TYPE_AMP/*NUI_FUSION_RECONSTRUCTION_PROCESSOR_TYPE_CPU*/, -1, nullptr, 0, nullptr, 0, &memorySize);
//if (FAILED(hResult)){
// std::cerr << "Error : NuiFusionGetDeviceInfo" << std::endl;
// return -1;
//}
Kinect v2によるKinectFusionはこちら