Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ src/Open3DConfig.h
util/pip_package/setup.py
open3d*.so
open3d*.pyd
__init__.pyc
*.pyc
__pycache__
._*
.DS_Store
Expand Down
122 changes: 25 additions & 97 deletions docs/tutorial/Advanced/color_map_optimization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,108 +5,39 @@ Color Map Optimization

Consider color mapping to the geometry reconstructed from depth cameras. As color and depth frames are not perfectly aligned, the texture mapping using color images is subject to results in blurred color map. Open3D provides color map optimization method proposed by [Zhou2014]_. Before begin, download fountain dataset from `here <https://drive.google.com/open?id=1eT45y8qw3TLED2YY9-K1Ot6dQuF9GDPJ>`_. The following script shows an example of color map optimization.

.. code-block:: python
.. literalinclude:: ../../../examples/Python/Advanced/color_map_optimization.py
:language: python
:lineno-start: 5
:lines: 5-
:linenos:

# examples/Python/Tutorial/Advanced/color_map_optimization.py

from open3d import *
from trajectory_io import *
import os, sys
sys.path.append("../Utility")
from common import *

path = "[set_this_path_to_fountain_dataset]"
debug_mode = False

if __name__ == "__main__":
set_verbosity_level(VerbosityLevel.Debug)

# Read RGBD images
rgbd_images = []
depth_image_path = get_file_list(
os.path.join(path, "depth/"), extension = ".png")
color_image_path = get_file_list(
os.path.join(path, "image/"), extension = ".jpg")
assert(len(depth_image_path) == len(color_image_path))
for i in range(len(depth_image_path)):
depth = read_image(os.path.join(depth_image_path[i]))
color = read_image(os.path.join(color_image_path[i]))
rgbd_image = create_rgbd_image_from_color_and_depth(color, depth,
convert_rgb_to_intensity = False)
if debug_mode:
pcd = create_point_cloud_from_rgbd_image(rgbd_image,
PinholeCameraIntrinsic.get_prime_sense_default())
draw_geometries([pcd])
rgbd_images.append(rgbd_image)

# Read camera pose and mesh
camera = read_pinhole_camera_trajectory(os.path.join(path, "scene/key.log"))
mesh = read_triangle_mesh(os.path.join(path, "scene", "integrated.ply"))

# Before full optimization, let's just visualize texture map
# with given geometry, RGBD images, and camera poses.
option = ColorMapOptmizationOption()
option.maximum_iteration = 0
color_map_optimization(mesh, rgbd_images, camera, option)
draw_geometries([mesh])
write_triangle_mesh(os.path.join(path, "scene",
"color_map_before_optimization.ply"), mesh)

# Optimize texture and save the mesh as texture_mapped.ply
# This is implementation of following paper
# Q.-Y. Zhou and V. Koltun,
# Color Map Optimization for 3D Reconstruction with Consumer Depth Cameras,
# SIGGRAPH 2014
option.maximum_iteration = 300
option.non_rigid_camera_coordinate = True
color_map_optimization(mesh, rgbd_images, camera, option)
draw_geometries([mesh])
write_triangle_mesh(os.path.join(path, "scene",
"color_map_after_optimization.ply"), mesh)

Input
````````````````````````

.. code-block:: python

# read RGBD images
rgbd_images = []
depth_image_path = get_file_list(
os.path.join(path, "depth/"), extension=".png")
color_image_path = get_file_list(
os.path.join(path, "image/"), extension=".jpg")
assert(len(depth_image_path) == len(color_image_path))
for i in range(len(depth_image_path)):
depth = read_image(os.path.join(depth_image_path[i]))
color = read_image(os.path.join(color_image_path[i]))
rgbd_image = create_rgbd_image_from_color_and_depth(color, depth,
convert_rgb_to_intensity=False)
if debug_mode:
pcd = create_point_cloud_from_rgbd_image(rgbd_image,
PinholeCameraIntrinsic.get_prime_sense_default())
draw_geometries([pcd])
rgbd_images.append(rgbd_image)
.. literalinclude:: ../../../examples/Python/Advanced/color_map_optimization.py
:language: python
:lineno-start: 19
:lines: 19-35
:linenos:

This script reads color and depth image pairs and makes ``rgbd_image``. Note that ``convert_rgb_to_intensity`` flag is ``False``. This is to preserve 8-bit color channels instead of using single channel float type image.

It is always good practice to visualize RGBD image before applying it to color map optimization. ``debug_mode`` switch is for visualizing RGBD image.

.. code-block:: python

# read camera pose and mesh
camera = read_pinhole_camera_trajectory(os.path.join(path, "scene/key.log"))
mesh = read_triangle_mesh(os.path.join(path, "scene", "integrated.ply"))
.. literalinclude:: ../../../examples/Python/Advanced/color_map_optimization.py
:language: python
:lineno-start: 37
:lines: 37-39
:linenos:

The script reads camera trajectory and mesh.

.. code-block:: python

option = ColorMapOptmizationOption()
option.maximum_iteration = 0
color_map_optimization(mesh, rgbd_images, camera, option)
draw_geometries([mesh])
write_triangle_mesh(os.path.join(path, "scene",
"color_map_before_optimization.ply"), mesh)
.. literalinclude:: ../../../examples/Python/Advanced/color_map_optimization.py
:language: python
:lineno-start: 43
:lines: 43-48
:linenos:

To visualize how the camera poses are not good for color mapping, this script intentionally set the iteration number as 0, which means no optimization. ``color_map_optimization`` paints a mesh using corresponding RGBD images and camera poses. Without optimization, the texture map is blurred.

Expand All @@ -121,14 +52,11 @@ Rigid Optimization

The next step is to optimize camera poses to get a sharp color map.

.. code-block:: python

option.maximum_iteration = 300
option.non_rigid_camera_coordinate = True
color_map_optimization(mesh, rgbd_images, camera, option)
draw_geometries([mesh])
write_triangle_mesh(os.path.join(path, "scene",
"color_map_after_optimization.ply"), mesh)
.. literalinclude:: ../../../examples/Python/Advanced/color_map_optimization.py
:language: python
:lineno-start: 55
:lines: 55-60
:linenos:

The script sets ``maximum_iteration = 300`` for actual iterations. The optimization displays the following energy profile.

Expand Down
155 changes: 25 additions & 130 deletions docs/tutorial/Advanced/colored_pointcloud_registration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,103 +5,33 @@ Colored point cloud registration

This tutorial demonstrates an ICP variant that uses both geometry and color for registration. It implements the algorithm of [Park2017]_. The color information locks the alignment along the tangent plane. Thus this algorithm is more accurate and more robust than prior point cloud registration algorithms, while the running speed is comparable to that of ICP registration. This tutorial uses notations from :ref:`icp_registration`.

.. code-block:: python

# examples/Python/Tutorial/Advanced/colored_pointcloud_registration.py

import numpy as np
import copy
from open3d import *


def draw_registration_result_original_color(source, target, transformation):
source_temp = copy.deepcopy(source)
source_temp.transform(transformation)
draw_geometries([source_temp, target])


if __name__ == "__main__":

print("1. Load two point clouds and show initial pose")
source = read_point_cloud("../../TestData/ColoredICP/frag_115.ply")
target = read_point_cloud("../../TestData/ColoredICP/frag_116.ply")

# draw initial alignment
current_transformation = np.identity(4)
draw_registration_result_original_color(
source, target, current_transformation)

# point to plane ICP
current_transformation = np.identity(4);
print("2. Point-to-plane ICP registration is applied on original point")
print(" clouds to refine the alignment. Distance threshold 0.02.")
result_icp = registration_icp(source, target, 0.02,
current_transformation, TransformationEstimationPointToPlane())
print(result_icp)
draw_registration_result_original_color(
source, target, result_icp.transformation)

# colored pointcloud registration
# This is implementation of following paper
# J. Park, Q.-Y. Zhou, V. Koltun,
# Colored Point Cloud Registration Revisited, ICCV 2017
voxel_radius = [ 0.04, 0.02, 0.01 ];
max_iter = [ 50, 30, 14 ];
current_transformation = np.identity(4)
print("3. Colored point cloud registration")
for scale in range(3):
iter = max_iter[scale]
radius = voxel_radius[scale]
print([iter,radius,scale])

print("3-1. Downsample with a voxel size %.2f" % radius)
source_down = voxel_down_sample(source, radius)
target_down = voxel_down_sample(target, radius)

print("3-2. Estimate normal.")
estimate_normals(source_down, KDTreeSearchParamHybrid(
radius = radius * 2, max_nn = 30))
estimate_normals(target_down, KDTreeSearchParamHybrid(
radius = radius * 2, max_nn = 30))

print("3-3. Applying colored point cloud registration")
result_icp = registration_colored_icp(source_down, target_down,
radius, current_transformation,
ICPConvergenceCriteria(relative_fitness = 1e-6,
relative_rmse = 1e-6, max_iteration = iter))
current_transformation = result_icp.transformation
print(result_icp)
draw_registration_result_original_color(
source, target, result_icp.transformation)

.. literalinclude:: ../../../examples/Python/Advanced/colored_pointcloud_registration.py
:language: python
:lineno-start: 5
:lines: 5-
:linenos:

.. _visualize_color_alignment:

Helper visualization function
``````````````````````````````````````

.. code-block:: python

def draw_registration_result_original_color(source, target, transformation):
source_temp = copy.deepcopy(source)
source_temp.transform(transformation)
draw_geometries([source_temp, target])
.. literalinclude:: ../../../examples/Python/Advanced/colored_pointcloud_registration.py
:language: python
:lineno-start: 12
:lines: 12-15
:linenos:

In order to demonstrate the alignment between colored point clouds, ``draw_registration_result_original_color`` renders point clouds with their original color.

Input
```````````````

.. code-block:: python

print("1. Load two point clouds and show initial pose")
source = read_point_cloud("../../TestData/ColoredICP/frag_115.ply")
target = read_point_cloud("../../TestData/ColoredICP/frag_116.ply")

# draw initial alignment
current_transformation = np.identity(4)
draw_registration_result_original_color(
source, target, current_transformation)
.. literalinclude:: ../../../examples/Python/Advanced/colored_pointcloud_registration.py
:language: python
:lineno-start: 20
:lines: 20-27
:linenos:

This script reads a source point cloud and a target point cloud from two files. An identity matrix is used as initialization.

Expand All @@ -117,17 +47,11 @@ This script reads a source point cloud and a target point cloud from two files.
Point-to-plane ICP
``````````````````````````````````````

.. code-block:: python

# point to plane ICP
current_transformation = np.identity(4);
print("2. Point-to-plane ICP registration is applied on original point")
print(" clouds to refine the alignment. Distance threshold 0.02.")
result_icp = registration_icp(source, target, 0.02,
current_transformation, TransformationEstimationPointToPlane())
print(result_icp)
draw_registration_result_original_color(
source, target, result_icp.transformation)
.. literalinclude:: ../../../examples/Python/Advanced/colored_pointcloud_registration.py
:language: python
:lineno-start: 29
:lines: 29-37
:linenos:

We first run :ref:`point_to_plane_icp` as a baseline approach. The visualization below shows misaligned green triangle textures. This is because geometric constraint does not prevent two planar surfaces from slipping.

Expand Down Expand Up @@ -163,40 +87,11 @@ where :math:`C_{\mathbf{p}}(\cdot)` is a precomputed function continuously defin

To further improve efficiency, [Park2017]_ proposes a multi-scale registration scheme. This has been implemented in the following script.

.. code-block:: python

# colored pointcloud registration
# This is implementation of following paper
# J. Park, Q.-Y. Zhou, V. Koltun,
# Colored Point Cloud Registration Revisited, ICCV 2017
voxel_radius = [ 0.04, 0.02, 0.01 ];
max_iter = [ 50, 30, 14 ];
current_transformation = np.identity(4)
print("3. Colored point cloud registration")
for scale in range(3):
iter = max_iter[scale]
radius = voxel_radius[scale]
print([iter,radius,scale])

print("3-1. Downsample with a voxel size %.2f" % radius)
source_down = voxel_down_sample(source, radius)
target_down = voxel_down_sample(target, radius)

print("3-2. Estimate normal.")
estimate_normals(source_down, KDTreeSearchParamHybrid(
radius = radius * 2, max_nn = 30))
estimate_normals(target_down, KDTreeSearchParamHybrid(
radius = radius * 2, max_nn = 30))

print("3-3. Applying colored point cloud registration")
result_icp = registration_colored_icp(source_down, target_down,
radius, current_transformation,
ICPConvergenceCriteria(relative_fitness = 1e-6,
relative_rmse = 1e-6, max_iteration = iter))
current_transformation = result_icp.transformation
print(result_icp)
draw_registration_result_original_color(
source, target, result_icp.transformation)
.. literalinclude:: ../../../examples/Python/Advanced/colored_pointcloud_registration.py
:language: python
:lineno-start: 39
:lines: 39-70
:linenos:

In total, 3 layers of multi-resolution point clouds are created with :ref:`voxel_downsampling`. Normals are computed with :ref:`vertex_normal_estimation`. The core registration function ``registration_colored_icp`` is called for each layer, from coarse to fine. ``lambda_geometric`` is an optional argument for ``registration_colored_icp`` that determines :math:`\lambda \in [0,1]` in the overall energy :math:`\lambda E_{G} + (1-\lambda) E_{C}`.

Expand Down
Loading