๐Ÿ“– Fundamentals/3D vision & Graphics

[3D Vision] Marching Cubes: 3D ๋ณผ๋ฅจ ๋ฐ์ดํ„ฐ๋ฅผ Mesh๋กœ ๋ฐ”๊พธ๋Š” ๋ฐฉ๋ฒ•

๋ญ…์ฆค 2025. 3. 24. 16:54
๋ฐ˜์‘ํ˜•

3D ์Šค์บ”, CT ์ด๋ฏธ์ง€, Neural Radiance Field(NeRF), Signed Distance Function(SDF) ๋“ฑ์—์„œ ์‚ฌ์šฉ๋˜๋Š” 3D ๋ฐ์ดํ„ฐ๋Š” ๋Œ€๋ถ€๋ถ„ density field ํ˜น์€ scalar field๋กœ ํ‘œํ˜„๋œ๋‹ค. ์ด๋Ÿฌํ•œ volumetric data๋Š” ๊ฐ 3์ฐจ์› ์ขŒํ‘œ์— ์–ด๋–ค ๊ฐ’(์˜ˆ: ๋ฐ€๋„, ๊ฑฐ๋ฆฌ ๋“ฑ)์ด ํ• ๋‹น๋œ ํ˜•ํƒœ์ผ ๋ฟ์ด๋ฉฐ, ๊ฒ‰๋ณด๊ธฐ์—๋Š” ๋‹จ์ˆœํ•œ ์ˆซ์ž๋“ค์˜ ์ง‘ํ•ฉ์— ๋ถˆ๊ณผํ•˜๋‹ค.

 

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๊ฐ€ 3D ๋ฐ์ดํ„ฐ๋ฅผ ์ง๊ด€์ ์œผ๋กœ ์ดํ•ดํ•˜๊ณ  ์‹œ๊ฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, ์ด ๊ฐ’๋“ค์˜ ๋ถ„ํฌ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์–ด๋””๊ฐ€ ๋ฌผ์ฒด์ด๊ณ  ์–ด๋””๊ฐ€ ๋ฐฐ๊ฒฝ์ธ์ง€, ์ฆ‰ surface๊ฐ€ ์–ด๋””์— ์กด์žฌํ•˜๋Š”์ง€๋ฅผ ์•Œ์•„์•ผ ํ•œ๋‹ค. ์ด์ฒ˜๋Ÿผ ์—ฐ์†์ ์ธ ๊ฐ’์˜ ํ•„๋“œ์—์„œ ์˜๋ฏธ ์žˆ๋Š” 3D mesh์„ ์ถ”์ถœํ•˜๋Š” ์ž‘์—…์ด ํ•„์š”ํ•˜๋ฉฐ, ์ด๋•Œ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋Œ€ํ‘œ์ ์ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ๋ฐ”๋กœ Marching Cubes์ด๋‹ค.

 


๐Ÿ“Œ Marching Cubes๋ž€?

Marching Cubes๋Š” 1987๋…„์— ๋ฐœํ‘œ๋œ ๊ณ ์ „์ ์ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ, 3์ฐจ์› scalar field์—์„œ isosurface๋ฅผ ์ถ”์ถœํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์Šค์นผ๋ผ ํ•„๋“œ๋ž€, 3D ๊ณต๊ฐ„์˜ ๊ฐ ์œ„์น˜์— ์‹ค์ˆ˜ ๊ฐ’(์˜ˆ: ๋ฐ€๋„, ๊ฑฐ๋ฆฌ ๋“ฑ)์ด ์ •์˜๋œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ๋Œ€ํ‘œ์ ์ธ ์˜ˆ๋กœ๋Š” CT, MRI ๊ฐ™์€ ์˜๋ฃŒ ์˜์ƒ, 3D ์Šค์บ”, NeRF๋‚˜ SDF์—์„œ ์ƒ์„ฑ๋˜๋Š” density field ๋“ฑ์ด ์žˆ๋‹ค.

 

Marching Cubes๋Š” ์ด๋Ÿฌํ•œ ์Šค์นผ๋ผ ํ•„๋“œ์—์„œ ํŠน์ • ๊ฐ’(์ž„๊ณ„๊ฐ’ ๋˜๋Š” isovalue)์„ ๊ธฐ์ค€์œผ๋กœ "ํ‘œ๋ฉด"์ด ๋  ์œ„์น˜๋ฅผ ์ฐพ์•„๋‚ด์–ด, ์ด๋ฅผ mesh ํ˜•ํƒœ๋กœ ๊ทผ์‚ฌํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ์ถ”์ถœ๋œ mesh๋Š” ์‹œ๊ฐํ™”๋‚˜ 3D ๋ชจ๋ธ๋ง, ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋“ฑ ๋‹ค์–‘ํ•œ ์‘์šฉ์— ํ™œ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ“Œ ํ•ต์‹ฌ ์•„์ด๋””์–ด

 

Marching Cubes๋Š” 3์ฐจ์› ๊ณต๊ฐ„์„ ์ž‘์€ ๊ฒฉ์ž ๋‹จ์œ„๋กœ ๋‚˜๋ˆˆ ๋’ค, ๊ฐ ์ž‘์€ ์ •์œก๋ฉด์ฒด(cube) ์•ˆ์—์„œ ํ‘œ๋ฉด์ด ํ†ต๊ณผํ•  ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.

  • ๊ฐ cube๋Š” 8๊ฐœ์˜ ๊ผญ์ง“์ ์„ ๊ฐ€์ง€๋ฉฐ, ๊ฐ ๊ผญ์ง“์ ์—๋Š” ์Šค์นผ๋ผ ๊ฐ’์ด ์กด์žฌํ•œ๋‹ค.
  • ์ด ๊ฐ’๋“ค์ด ๊ธฐ์ค€์ด ๋˜๋Š” isovalue๋ณด๋‹ค ํฐ์ง€ ์ž‘์€์ง€๋ฅผ ํŒ๋‹จํ•˜์—ฌ, ํ‘œ๋ฉด์ด ๊ทธ cube ๋‚ด๋ถ€๋ฅผ ํ†ต๊ณผํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
  • ์ด 8๊ฐœ์˜ ๊ผญ์ง“์ ์€ 2๊ฐœ์˜ ์ƒํƒœ(inside ๋˜๋Š” outside)๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, cube๋งˆ๋‹ค ์ด 256๊ฐ€์ง€ ์กฐํ•ฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์ด 256๊ฐ€์ง€ ์กฐํ•ฉ์— ๋Œ€ํ•ด ์‚ฌ์ „์— ์ •์˜๋œ mesh ํŒจํ„ด ํ…Œ์ด๋ธ”(look-up table)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ‘œ๋ฉด์„ ๊ทผ์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ‘œ๋ฉด์„ ๊ตฌ์„ฑํ•˜๋Š” vertex๋“ค์€ cube์˜ edge ์œ„์—์„œ interpolation์„ ํ†ตํ•ด ์œ„์น˜๊ฐ€ ๊ณ„์‚ฐ๋œ๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•ด, "์ด ๋ถ€๋ถ„์ด ๋ฌผ์ฒด ๋‚ด๋ถ€์ธ์ง€ ์™ธ๋ถ€์ธ์ง€"๋ฅผ ํŒŒ์•…ํ•˜๊ณ , ๊ทธ ๊ฒฝ๊ณ„๋ฉด์„ ๋”ฐ๋ผ ์‚ผ๊ฐํ˜•์œผ๋กœ ์ด์–ด์ฃผ๋Š” ๋ฐฉ์‹์ด๋ผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ density volume์„ ํ๋ธŒ๋กœ ๋‚˜๋ˆ„์–ด 3D mesh๋ฅผ ๊ทผ์‚ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์–ผ๋งˆ๋‚˜ ์ด˜์ด˜ํ•˜๊ฒŒ ํ๋ธŒ๋ฅผ ๋‚˜๋ˆ„๋А๋ƒ์— ๋”ฐ๋ผ ์–ผ๋งˆ๋‚˜ ์ •๋ฐ€ํ•œ mesh๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋Š”์ง€๊ฐ€ ๋‚˜๋‰˜๊ฒŒ ๋œ๋‹ค. ๋‹น์—ฐํžˆ voxel์ด ๋” ์ž‘๊ณ  ๋งŽ์„์ˆ˜๋ก ๋” ์„ธ๋ฐ€ํ•œ mesh๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

 

 

marching cubes ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ 2D ๋„๋ฉ”์ธ์—์„œ ์‹œ๊ฐํ™”ํ•ด๋ณด๋ฉด ์œ„์™€ ๊ฐ™๋‹ค.

 

๐Ÿ“Œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ณผ์ • ์ •๋ฆฌ

  1. 3D ๋ณผ๋ฅจ ๋ฐ์ดํ„ฐ๋ฅผ ๊ท ์ผํ•œ ๊ฒฉ์ž(grid)๋กœ ๋‚˜๋ˆ”
  2. ๊ฐ ๊ฒฉ์ž ์…€(cube)์— ๋Œ€ํ•ด 8๊ฐœ์˜ ๊ผญ์ง“์  ๊ฐ’์„ ํ™•์ธ
  3. ๊ฐ ๊ผญ์ง“์ ์ด isovalue๋ณด๋‹ค ํฐ์ง€ ์ž‘์€์ง€๋ฅผ ์ด์ง„ ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜
    • ์˜ˆ: density > threshold → 1, ์•„๋‹ˆ๋ฉด 0
  4. ์ด์ง„ ๊ฐ’์„ ์กฐํ•ฉํ•˜์—ฌ 8๋น„ํŠธ ์ฝ”๋“œ (0~255)๋ฅผ ๋งŒ๋“ฆ
    • ์ด ๊ฐ’์€ ํ•ด๋‹น cube์˜ ํ‘œ๋ฉด ํ˜•ํƒœ๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ํ‚ค๊ฐ€ ๋จ
  5. ์‚ฌ์ „ ์ •์˜๋œ look-up table์„ ์ฐธ์กฐํ•˜์—ฌ ์‚ผ๊ฐํ˜• ๊ตฌ์„ฑ ๋ฐฉ์‹์„ ๊ฒฐ์ •
  6. cube์˜ edge ์œ„์—์„œ ํ‘œ๋ฉด ์ •์  ์œ„์น˜๋ฅผ ๋ณด๊ฐ„ํ•˜์—ฌ ๊ณ„์‚ฐ
  7. ์‚ผ๊ฐํ˜•๋“ค์„ ์ „์ฒด ๊ณต๊ฐ„์— ๋Œ€ํ•ด ์ด์–ด๋ถ™์—ฌ ํ•˜๋‚˜์˜ ์—ฐ์†๋œ mesh๋ฅผ ๊ตฌ์„ฑ

 

์ด๋Ÿฌํ•œ ๊ณผ์ •์„ 3์ฐจ์› ๊ณต๊ฐ„์˜ ๋ชจ๋“  ๊ฒฉ์ž์— ๋ฐ˜๋ณต ์ ์šฉํ•˜๋ฉด, ์ฃผ์–ด์ง„ ๋ฐ€๋„ ํ•„๋“œ๋กœ๋ถ€ํ„ฐ ์—ฐ์†์ ์ธ 3D ํ‘œ๋ฉด์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ์‹ค์ œ๋กœ ์‹œ๊ฐํ™”ํ•˜๊ฑฐ๋‚˜, .obj, .ply ๋“ฑ์œผ๋กœ ์ €์žฅํ•ด ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿ“Œ ์˜ˆ์ œ

import numpy as np
import matplotlib.pyplot as plt
from skimage import measure
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import trimesh

# Step 1: NeRF-style volumetric density (Gaussian blob)
def generate_density(shape=(64, 64, 64), center=(32, 32, 32), radius=15):
    Z, Y, X = np.indices(shape)
    dist = np.sqrt((X - center[0])**2 + (Y - center[1])**2 + (Z - center[2])**2)
    volume = np.exp(-(dist**2) / (2 * radius**2))  # Gaussian density
    return volume

volume = generate_density()

# Step 2: Threshold for isosurface
iso_level = 0.1

# Step 3: Marching Cubes mesh extraction
verts, faces, normals, values = measure.marching_cubes(volume, level=iso_level)

# Step 4a: Voxel-based visualization (scatter plot of high-density points)
fig = plt.figure(figsize=(14, 6))

ax1 = fig.add_subplot(121, projection='3d')
threshold = 0.1
points = np.argwhere(volume > threshold)
ax1.scatter(points[:, 0], points[:, 1], points[:, 2], c='red', s=1, alpha=0.3)
ax1.set_title('Volumetric Density (Voxel View)')
ax1.set_xlim(0, 64)
ax1.set_ylim(0, 64)
ax1.set_zlim(0, 64)

# Step 4b: Mesh visualization using Marching Cubes
ax2 = fig.add_subplot(122, projection='3d')
mesh = Poly3DCollection(verts[faces], alpha=0.6)
mesh.set_facecolor([0.3, 0.7, 1])
ax2.add_collection3d(mesh)
ax2.set_title('Marching Cubes Mesh')
ax2.set_xlim(0, 64)
ax2.set_ylim(0, 64)
ax2.set_zlim(0, 64)

plt.tight_layout()
plt.show()

mesh = trimesh.Trimesh(vertices=verts, faces=faces)
mesh.export('output_mesh.obj') 
mesh.export('output_mesh.ply')

 

 

 

์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์˜ˆ์‹œ density volume์œผ๋กœ ๋ถ€ํ„ฐ marching cubes ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ 3D ๊ตฌ์ฒด mesh๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ์‹œ๊ฐํ™”ํ•˜์—ฌ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ obj ๋˜๋Š” ply ํฌ๋งท์œผ๋กœ ์ €์žฅํ•˜์—ฌ ํ™•์ธํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์€ ์ž๋ฃŒ

๋ฐ˜์‘ํ˜•