¿Qué es PLY?
PLY — Polygon File Format, también conocido como el Formato de Triángulo Stanford — es un formato de archivo 3D desarrollado en la Universidad de Stanford en los años 90, principalmente por Greg Turk durante el proyecto Stanford 3D Scanning Repository. Aunque puede almacenar mallas poligonales similares a OBJ o STL, la principal fortaleza de PLY es su flexibilidad: soporta propiedades arbitrarias por vértice y por cara, lo que lo convierte en el formato dominante para nubes de puntos 3D de escáneres LiDAR, equipos de fotogrametría y conjuntos de datos de investigación.
Los archivos PLY usan la extensión .ply y vienen en tres codificaciones:
- ASCII — Texto legible por humanos
- Binario little-endian — Binario compacto, estándar en sistemas x86/x64
- Binario big-endian — Binario compacto para arquitecturas big-endian
Estructura del Archivo PLY
ply
format ascii 1.0
comment Escaneado con Matterport Pro3
element vertex 4
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 2
property list uchar int vertex_indices
end_header
0.0 0.0 0.0 255 0 0
1.0 0.0 0.0 255 0 0
1.0 1.0 0.0 0 255 0
0.0 1.0 0.0 0 255 0
3 0 1 2
3 0 2 3
Propiedades Comunes de Vértice
| Propiedad | Tipo | Descripción |
|---|---|---|
x, y, z |
float | Coordenadas 3D |
nx, ny, nz |
float | Normales de superficie |
red, green, blue |
uchar | Color RGB (0-255) |
alpha |
uchar | Transparencia alfa |
intensity |
float | Intensidad en escala de grises (reflectancia LiDAR) |
confidence |
float | Puntuación de confianza/calidad por punto |
Nubes de Puntos vs Mallas en PLY
PLY se usa para dos tipos de datos distintos:
Nubes de puntos — Millones de puntos 3D no conectados con color e intensidad opcionales. Producidos por escáneres LiDAR (FARO, Leica, Velodyne), fotogrametría (RealityCapture, Metashape) y cámaras de profundidad. El elemento face está ausente.
Mallas poligonales — Malla conectada donde los elementos face enumeran los índices de vértices de cada polígono.
Trabajar con PLY en Python
plyfile: I/O PLY Binario/ASCII Preciso
pip install plyfile
import numpy as np
from plyfile import PlyData, PlyElement
def leer_nube_puntos(ruta: str) -> dict:
"""Carga una nube de puntos PLY y devuelve arrays de coordenadas y colores."""
ply = PlyData.read(ruta)
vertices = ply['vertex']
xyz = np.column_stack([
vertices['x'], vertices['y'], vertices['z']
]).astype(np.float32)
datos = {'xyz': xyz}
if 'red' in vertices.data.dtype.names:
rgb = np.column_stack([
vertices['red'], vertices['green'], vertices['blue']
]).astype(np.uint8)
datos['rgb'] = rgb
print(f"Cargados {len(xyz):,} puntos")
return datos
nube = leer_nube_puntos('escan.ply')
Escribir Archivos PLY
import numpy as np
from plyfile import PlyData, PlyElement
def escribir_nube_ply(puntos, colores, ruta: str, binario: bool = True) -> None:
"""Escribe una nube de puntos en formato PLY."""
n = len(puntos)
if colores is not None:
datos = np.array(
[(puntos[i,0], puntos[i,1], puntos[i,2],
colores[i,0], colores[i,1], colores[i,2])
for i in range(n)],
dtype=[('x','f4'),('y','f4'),('z','f4'),
('red','u1'),('green','u1'),('blue','u1')]
)
else:
datos = np.array(
[(puntos[i,0], puntos[i,1], puntos[i,2]) for i in range(n)],
dtype=[('x','f4'),('y','f4'),('z','f4')]
)
elemento = PlyElement.describe(datos, 'vertex')
PlyData([elemento], text=not binario, byte_order='<').write(ruta)
print(f"Guardados {n:,} puntos en {ruta}")
Open3D: Procesamiento de Alto Rendimiento
pip install open3d
import open3d as o3d
import numpy as np
def procesar_nube_puntos(ruta: str) -> dict:
"""Carga y procesa una nube de puntos PLY con Open3D."""
pcd = o3d.io.read_point_cloud(ruta)
print(f"Cargados {len(pcd.points):,} puntos")
# Estimar normales
if not pcd.has_normals():
pcd.estimate_normals(
search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)
)
# Muestreo voxel (reducir densidad)
pcd_reducida = pcd.voxel_down_sample(voxel_size=0.05)
print(f"Tras muestreo: {len(pcd_reducida.points):,} puntos")
# Eliminación de valores atípicos estadísticos
pcd_limpia, _ = pcd_reducida.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
# Reconstrucción de superficie (Poisson)
malla, densidades = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
pcd_limpia, depth=9
)
o3d.io.write_point_cloud('limpia.ply', pcd_limpia)
o3d.io.write_triangle_mesh('reconstruida.ply', malla)
o3d.io.write_triangle_mesh('reconstruida.obj', malla)
return {
'puntos_originales': len(pcd.points),
'puntos_limpios': len(pcd_limpia.points),
'triangulos_malla': len(malla.triangles),
}
resultados = procesar_nube_puntos('escan.ply')
PLY vs Otros Formatos de Nube de Puntos
| Formato | PLY | LAS/LAZ | E57 | XYZ/TXT |
|---|---|---|---|---|
| Uso principal | Investigación, renderizado | LiDAR, SIG | Escaneado 3D | Intercambio simple |
| Propiedades personalizadas | Sí | Limitadas | No | No |
| Color | Sí (RGB) | Sí (RGB + intensidad) | Sí | Opcional |
| Normales | Sí | Sin campo estándar | No | Opcional |
| Soporte de malla | Sí | No | No | No |
| Biblioteca Python | plyfile, open3d | laspy | pye57 | numpy.loadtxt |
| Amplio soporte | Sí (MeshLab, CloudCompare, Blender) | Sí (herramientas SIG) | Escáneres | Universal |
Conversiones relacionadas
Conversiones frecuentes del catálogo: