cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

Open3DのPythonチュートリアルをC++に移植(その5) customized_visualizer.py

Open3Dは、2018年3月現在v0.1です。頻繁にアップデートがあると思われますので、この記事はすぐ風化する予定です。

customized_visualizerは、キー入力、アニメーション、画面キャプチャなどのサンプルを提供しています。
これと類似のテストがすでにc++で用意されていますので、基本的にはこのソースは見る必要がありません。(精神衛生上、取り残しがあると気持ち悪いから、移植してみたまでです。一部手抜きをしています。)

元のpythonソースはこちら。
Open3D/customized_visualization.py at 50a31d8daaf3218b767ddbacc0db4e4f125f13d6 · IntelVCL/Open3D · GitHub

C++の参考ソースはこちらとこちら。
Open3D/TestVisualizer.cpp at 50a31d8daaf3218b767ddbacc0db4e4f125f13d6 · IntelVCL/Open3D · GitHub
Open3D/TestDepthCapture.cpp at 50a31d8daaf3218b767ddbacc0db4e4f125f13d6 · IntelVCL/Open3D · GitHub

TestCustomizedVisualizer.cpp

// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include <iostream>
#include <memory>
#include <thread>

#include <Core/Core.h>
#include <IO/IO.h>
#include <Visualization/Visualization.h>

#include <string>
#include <iomanip>
#include <sstream>

using namespace three;

bool CustomDrawGeometry(const std::vector<std::shared_ptr<const PointCloud>> &pcd_ptrs)
{
	const std::string &window_name = "Open3D";
	int width = 640;
	int height = 480;
	int left = 50;
	int top = 50;

	// The following code achieves the same effect as:
	// bool DrawGeometries({pcd})
	Visualizer visualizer;
	if(visualizer.CreateWindow(window_name, width, height, left, top) ==
			false) {
		std::cout << "[DrawGeometries] Failed creating OpenGL window." << std::endl;
		return false;
	}
	for(const auto &pcd_ptr : pcd_ptrs) {
		if(visualizer.AddGeometry(pcd_ptr) == false) {
			std::cout << "[DrawGeometries] Failed addomg geometry." << std::endl;
			std::cout << "[DrawGeometries] Possibly due to bad geometry or wrong geometry type." << std::endl;
			return false;
		}
	}
	visualizer.Run();
	visualizer.DestroyWindow();
	return true;
}

bool CustomDrawGeometryWithRotation(const std::vector<std::shared_ptr<const PointCloud>> &pcd_ptrs)
{
	int width = 640;
	int height = 480;

	std::vector<std::shared_ptr<const Geometry>> geom_ptrs;
	geom_ptrs.assign(pcd_ptrs.begin(), pcd_ptrs.end()); //

	return DrawGeometriesWithAnimationCallback(
				{geom_ptrs},
				[&](Visualizer *vis) {
					vis->GetViewControl().Rotate(10, 0);
					std::this_thread::sleep_for(std::chrono::milliseconds(30));
					return false;
				}, "Spin", width, height);
}

bool CustomDrawGeometryLoadOption(const std::vector<std::shared_ptr<const PointCloud>> &pcd_ptrs)
{
        const std::string &window_name = "Open3D";
        int width = 640;
        int height = 480;
        int left = 50;
        int top = 50;

	Visualizer visualizer;
        if(visualizer.CreateWindow(window_name, width, height, left, top) ==
                        false) {
                std::cout << "[DrawGeometries] Failed creating OpenGL window." << std::endl;
                return false;
        }
	for(const auto &pcd_ptr : pcd_ptrs) {
                if(visualizer.AddGeometry(pcd_ptr) == false) {
                        std::cout << "[DrawGeometries] Failed addomg geometry." << std::endl;
                        std::cout << "[DrawGeometries] Possibly due to bad geometry or wrong geomeetry type." << std::endl;
                        return false;
                }
        }
	RenderOption ro;
	ReadIJsonConvertible("../../lib/TestData/renderoption.json", ro);
	visualizer.GetRenderOption() = ro;
	visualizer.Run();
	visualizer.DestroyWindow();
	return true;
}

class CustomVisualizerWithKeyCallback : public Visualizer
{
protected:
	void KeyPressCallback(GLFWwindow *window,
			int key, int scannode, int action, int mods) override {
		if(action == GLFW_RELEASE){
			return;
		}
		if(key == GLFW_KEY_K){
			Eigen::Vector3d background_color = Eigen::Vector3d::Zero();
			this->GetRenderOption().background_color_ = background_color;
		}
		if(key == GLFW_KEY_R){
			RenderOption ro;
			ReadIJsonConvertible("../../lib/TestData/renderoption.json", ro);
			this->GetRenderOption() = ro;
		}
		if(key == GLFW_KEY_COMMA){
			std::string depth_filename = "depth.png";
			bool do_render = true;
			double depth_scale = 1000.0;
			std::string camera_filename = "camera.json";

			this->CaptureDepthImage(depth_filename, do_render, depth_scale);
			PinholeCameraTrajectory camera;
			camera.extrinsic_.resize(1);
			this->view_control_ptr_->ConvertToPinholeCameraParameters(
					camera.intrinsic_, camera.extrinsic_[0]);
			WriteIJsonConvertible(camera_filename, camera);

			//auto depth = this->CaptureDepthFloatBuffer();
			//DrawGeometries({depth}, "Depth", depth->width_, depth->height_); // Segmentation falt
			// maybe, a best solution is to use cv::imshow()
		}
		if(key == GLFW_KEY_PERIOD){
			std::string filename = "image.jpg";
			bool do_render =true;
			this->CaptureScreenImage(filename, do_render);

			//auto image = this->CaptureScreenFloatBuffer();
			//DrawGeometries({image}, "Image", image->width_, image->height_);
		}
		UpdateRender();
	}
};

bool CustomDrawGeometryWithKeyCallback(const std::vector<std::shared_ptr<const PointCloud>> &pcd_ptrs)
{
        const std::string &window_name = "Open3D";
        int width = 640;
        int height = 480;
        int left = 50;
        int top = 50;

        // The following code achieves the same effect as:
        // bool DrawGeometries({pcd})
        CustomVisualizerWithKeyCallback visualizer;
        if(visualizer.CreateWindow(window_name, width, height, left, top) ==
                        false) {
                std::cout << "[DrawGeometries] Failed creating OpenGL window." << std::endl;
                return false;
        }
        for(const auto &pcd_ptr : pcd_ptrs) {
                if(visualizer.AddGeometry(pcd_ptr) == false) {
                        std::cout << "[DrawGeometries] Failed addomg geometry." << std::endl;
                        std::cout << "[DrawGeometries] Possibly due to bad geometry or wrong geometry type." << std::endl;
                        return false;
                }
        }
        visualizer.Run();
        visualizer.DestroyWindow();
        return true;
}

bool CustomDrawGeometryWithCameraTrajectory(const std::vector<std::shared_ptr<const PointCloud>> &pcd_ptrs)
{
	PinholeCameraTrajectory trajectory;
	ReadPinholeCameraTrajectory("../../lib/TestData/camera_trajectory.json", trajectory);

	std::vector<std::shared_ptr<const Geometry>> geom_ptrs;
        geom_ptrs.assign(pcd_ptrs.begin(), pcd_ptrs.end()); //
	return DrawGeometriesWithAnimationCallback({geom_ptrs},
			[&](Visualizer *vis) {
				static int index = -1;
				static PinholeCameraTrajectory trajectory;
				if(index == -1){ // overhead
					ReadPinholeCameraTrajectory("../../lib/TestData/camera_trajectory.json", trajectory);
				}
				
				if(index >= 0){
					std::cout << "Capture image " << std::setfill('0') << std::setw(5) << std::to_string(index) << std::endl;
					std::string dpath, cpath; 
					std::stringstream ss1, ss2;
					ss1 << "TempData/depth" << std::setfill('0') << std::setw(5) << std::to_string(index) << ".png";
					ss1 >> dpath;
					ss2 << "TempData/image" << std::setfill('0') << std::setw(5) << std::to_string(index) << ".jpg";
					ss2 >> cpath;

					// auto depth = vis->CaptureDepthFloatBuffer();
                                        // auto image = vis->CaptureScreenFloatBuffer();
					// WriteImage(dpath, *CreateImageFromFloatImage<uint16_t>(*depth));
					// int quality = 90;
					// WriteImage(cpath, *CreateImageFromFloatImage<uint16_t>(*image), quality);
					// above is not work due to mendoukusai.

					vis->CaptureDepthImage(dpath, false);
					vis->CaptureScreenImage(cpath, false);
				}
				index++;
				if(index < trajectory.extrinsic_.size()){
                                        // ViewControlWithCustomAnimation &
					auto ctr = vis->GetViewControl();
					ctr.ConvertFromPinholeCameraParameters(trajectory.intrinsic_, trajectory.extrinsic_[index]);
				}
				else {
					vis->RegisterAnimationCallback(nullptr);
				}
				return true;
			}, "Capture Animation", trajectory.intrinsic_.width_, trajectory.intrinsic_.height_);
}

int main(int argc, char *argv[])
{
	SetVerbosityLevel(VerbosityLevel::VerboseAlways);

	std::shared_ptr<PointCloud> pcd;
	pcd = CreatePointCloudFromFile("../../lib/TestData/fragment.ply");

	std::cout << "1. Customized visualization to mimic DrawGeometry" << std::endl;
	CustomDrawGeometry({pcd});

	std::cout << "2. Customized visualization with a rotating view" << std::endl;
	CustomDrawGeometryWithRotation({pcd});

	std::cout << "3. Customized visualization showing normal rendering" << std::endl;
	CustomDrawGeometryLoadOption({pcd});

	std::cout << "4. Customized visualization with key press callbacks" << std::endl;
	std::cout << "   Press 'K' to change background color to black" << std::endl;
	std::cout << "   Press 'R' to load a customized render option, showing normals" << std::endl;
	std::cout << "   Press ',' to capture the depth buffer and show it" << std::endl;
	std::cout << "   Press '.' to capture the screen and show it" << std::endl;
	CustomDrawGeometryWithKeyCallback({pcd});

	std::cout << "5. Customized visualization playing a camera trajectory" << std::endl;
	CustomDrawGeometryWithCameraTrajectory({pcd});

	return 1;
}

1. DrawGeometriesの代わりにVisualizerを自前で作って単に表示
f:id:cvl-robot:20180305193030p:plain
2. コールバックを使って、アニメーション表示
f:id:cvl-robot:20180305193044p:plain
3. jsonファイルに保存された画面設定を読み込んで表示
f:id:cvl-robot:20180305193216p:plain
4. キー入力イベントコールバックを使って、インタラクティブに表示の変更、と、画面キャプチャ
f:id:cvl-robot:20180305193056p:plain
5. アニメーションをしながらコールバックして、アニメーションを画像として保存。ただし、実画面サイズとカメラ内部パラメータの幅、高さが一致していないと上手く動かない。
f:id:cvl-robot:20180305193109p:plain