Skip to content

Commit

Permalink
feat: standardized depth image and distance image terminology
Browse files Browse the repository at this point in the history
  • Loading branch information
yxlao authored Oct 13, 2024
1 parent cb61fa2 commit 4a64aac
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 73 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ clear and easy-to-use APIs.
# Back-project depth image to 3D points.
points = ct.project.im_depth_to_points(im_depth, K, T)

# Ray cast a triangle mesh to depth image.
im_depth = ct.raycast.mesh_to_depths(mesh, Ks, Ts, height, width)
# Ray cast a triangle mesh to depth image given the camera parameters.
im_depth = ct.raycast.mesh_to_im_depth(mesh, K, T, height, width)

# And more...
```
Expand Down
20 changes: 0 additions & 20 deletions camtools/colormap.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,3 @@ def normalize(array, vmin=0.0, vmax=1.0, clip=False):
array = (array - amin) / (amax - amin) * (vmax - vmin) + vmin

return array


def main():
"""
Test create color map image.
"""
height = 200
width = 1600

colors = query(np.linspace(0, 1, num=width))
im = np.zeros((height, width, 3), dtype=np.float32)
for i in range(width):
im[:, i : i + 1, :] = colors[i]

im_path = "colormap.png"
io.imwrite(im_path, im)


if __name__ == "__main__":
main()
110 changes: 110 additions & 0 deletions camtools/convert.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import cv2
import numpy as np
import open3d as o3d
from jaxtyping import Float
from typing import Optional

from . import sanity
from . import convert
Expand Down Expand Up @@ -505,3 +508,110 @@ def spherical_to_T_towards_origin(radius, theta, phi):
T = pose_to_T(pose)

return T


def mesh_to_lineset(
mesh: o3d.geometry.TriangleMesh,
color: Optional[Float[np.ndarray, "3"]] = None,
) -> o3d.geometry.LineSet:
"""
Convert Open3D mesh to Open3D lineset.
"""
if not isinstance(mesh, o3d.geometry.TriangleMesh):
raise ValueError(f"Expected Open3D mesh, but got {type(mesh)}.")

vertices = np.asarray(mesh.vertices)
triangles = np.asarray(mesh.triangles)

edges = set()
for triangle in triangles:
edges.add(tuple(sorted([triangle[0], triangle[1]])))
edges.add(tuple(sorted([triangle[1], triangle[2]])))
edges.add(tuple(sorted([triangle[2], triangle[0]])))

edges = np.array(list(edges))

lineset = o3d.geometry.LineSet()
lineset.points = o3d.utility.Vector3dVector(vertices)
lineset.lines = o3d.utility.Vector2iVector(edges)

if color is not None:
if len(color) != 3:
raise ValueError(f"Expected color of shape (3,), but got {color.shape}.")
lineset.paint_uniform_color(color)

return lineset


def im_distance_to_im_depth(
im_distance: Float[np.ndarray, "h w"],
K: Float[np.ndarray, "3 3"],
) -> Float[np.ndarray, "h w"]:
"""
Convert distance image to depth image.
Args:
im_distance: Distance image (H, W), float.
K: Camera intrinsic matrix (3, 3).
Returns:
Depth image (H, W), float.
"""
if not im_distance.ndim == 2:
raise ValueError(
f"Expected im_distance of shape (H, W), but got {im_distance.shape}."
)
sanity.assert_K(K)
height, width = im_distance.shape
fx, fy = K[0, 0], K[1, 1]
cx, cy = K[0, 2], K[1, 2]
dtype = im_distance.dtype

u = np.arange(width)
v = np.arange(height)
u_grid, v_grid = np.meshgrid(u, v)

u_norm = (u_grid - cx) / fx
v_norm = (v_grid - cy) / fy
norm_square = u_norm**2 + v_norm**2
im_depth = im_distance / np.sqrt(norm_square + 1)
im_depth = im_depth.astype(dtype)

return im_depth


def im_depth_to_im_distance(
im_depth: Float[np.ndarray, "h w"],
K: Float[np.ndarray, "3 3"],
) -> Float[np.ndarray, "h w"]:
"""
Convert depth image to distance image.
Args:
im_depth: Depth image (H, W), float.
K: Camera intrinsic matrix (3, 3).
Returns:
Distance image (H, W), float.
"""
if not im_depth.ndim == 2:
raise ValueError(
f"Expected im_depth of shape (H, W), but got {im_depth.shape}."
)
sanity.assert_K(K)
height, width = im_depth.shape
fx, fy = K[0, 0], K[1, 1]
cx, cy = K[0, 2], K[1, 2]
dtype = im_depth.dtype

u = np.arange(width)
v = np.arange(height)
u_grid, v_grid = np.meshgrid(u, v)

u_norm = (u_grid - cx) / fx
v_norm = (v_grid - cy) / fy
norm_square = u_norm**2 + v_norm**2
im_distance = im_depth * np.sqrt(norm_square + 1)
im_distance = im_distance.astype(dtype)

return im_distance
31 changes: 21 additions & 10 deletions camtools/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

import cv2
import numpy as np
from jaxtyping import Float
from typing import Union, Tuple, Optional

from . import convert, image, sanity


def point_cloud_to_pixel(points, K, T):
def points_to_pixels(
points: Float[np.ndarray, "n 3"],
K: Float[np.ndarray, "3 3"],
T: Float[np.ndarray, "4 4"],
) -> Float[np.ndarray, "N 2"]:
"""
Project points in world coordinates to pixel coordinates.
Expand Down Expand Up @@ -52,22 +58,27 @@ def point_cloud_to_pixel(points, K, T):
return pixels


def depth_to_point_cloud(
im_depth: np.ndarray,
K: np.ndarray,
T: np.ndarray,
im_color: np.ndarray = None,
def im_depth_to_point_cloud(
im_depth: Float[np.ndarray, "h w"],
K: Float[np.ndarray, "3 3"],
T: Float[np.ndarray, "4 4"],
im_color: Optional[Float[np.ndarray, "h w 3"]] = None,
to_image: bool = False,
ignore_invalid: bool = True,
scale_factor: float = 1.0,
):
) -> Union[
Float[np.ndarray, "n 3"],
Float[np.ndarray, "h w 3"],
Tuple[Float[np.ndarray, "n 3"], Float[np.ndarray, "n 3"]],
Tuple[Float[np.ndarray, "h w 3"], Float[np.ndarray, "h w 3"]],
]:
"""
Convert a depth image to a point cloud, optionally including color information.
Can return either a sparse (N, 3) point cloud or a dense one with the image
shape (H, W, 3).
Args:
im_depth: Depth image (H, W), float32, in world scale.
im_depth: Depth image (H, W), float32 or float64, in world scale.
K: Intrinsics matrix (3, 3).
T: Extrinsics matrix (4, 4).
im_color: Color image (H, W, 3), float32/float64, range [0, 1].
Expand Down Expand Up @@ -95,8 +106,8 @@ def depth_to_point_cloud(
sanity.assert_T(T)
if not isinstance(im_depth, np.ndarray):
raise TypeError("im_depth must be a numpy array")
if im_depth.dtype != np.float32:
raise TypeError("im_depth must be of type float32")
if im_depth.dtype not in [np.float32, np.float64]:
raise TypeError("im_depth must be of type float32 or float64")
if im_depth.ndim != 2:
raise ValueError("im_depth must be a 2D array")
if im_color is not None:
Expand Down
Loading

0 comments on commit 4a64aac

Please sign in to comment.