""" 3D 모델 및 이미지 데이터 로더 GLB 파일과 이미지 파일을 로드하고 전처리하는 기능을 제공합니다. """ import numpy as np import trimesh import open3d as o3d from PIL import Image import cv2 from typing import Dict, List, Tuple, Optional import os class DataLoader: """3D 모델과 이미지 데이터를 로드하고 전처리하는 클래스""" def __init__(self): self.supported_3d_formats = ['.glb', '.gltf', '.obj', '.ply', '.stl'] self.supported_image_formats = ['.png', '.jpg', '.jpeg', '.bmp', '.tiff'] def load_glb_model(self, path: str) -> Dict: """ GLB 파일을 로드하고 메시 정보를 추출합니다. Args: path (str): GLB 파일 경로 Returns: Dict: 3D 모델 정보 (vertices, faces, textures, materials) """ try: # trimesh를 사용하여 GLB 파일 로드 scene = trimesh.load(path) # 메시 정보 추출 if hasattr(scene, 'geometry'): # Scene인 경우 첫 번째 메시 사용 mesh = list(scene.geometry.values())[0] else: # 단일 메시인 경우 mesh = scene model_info = { 'vertices': np.array(mesh.vertices), 'faces': np.array(mesh.faces), 'textures': None, 'materials': None, 'bounding_box': mesh.bounds, 'center': mesh.centroid, 'scale': mesh.scale, 'file_path': path } # 텍스처 정보가 있는 경우 if hasattr(mesh.visual, 'material'): model_info['materials'] = mesh.visual.material # UV 좌표가 있는 경우 if hasattr(mesh.visual, 'uv'): model_info['uv_coords'] = mesh.visual.uv return model_info except Exception as e: raise ValueError(f"GLB 파일 로드 실패: {path}, 오류: {str(e)}") def load_reference_image(self, path: str) -> np.ndarray: """ 참조 이미지를 로드하고 전처리합니다. Args: path (str): 이미지 파일 경로 Returns: np.ndarray: 전처리된 이미지 배열 (H, W, C) """ try: # PIL을 사용하여 이미지 로드 image = Image.open(path) # RGB로 변환 (RGBA인 경우) if image.mode == 'RGBA': image = image.convert('RGB') elif image.mode != 'RGB': image = image.convert('RGB') # numpy 배열로 변환 image_array = np.array(image) return image_array except Exception as e: raise ValueError(f"이미지 로드 실패: {path}, 오류: {str(e)}") def extract_mesh_info(self, model: Dict) -> Dict: """ 3D 모델에서 메시 정보를 추출합니다. Args: model (Dict): 3D 모델 정보 Returns: Dict: 추출된 메시 정보 """ vertices = model['vertices'] faces = model['faces'] # 메시 통계 계산 mesh_info = { 'num_vertices': len(vertices), 'num_faces': len(faces), 'bounding_box_size': np.max(vertices, axis=0) - np.min(vertices, axis=0), 'volume': self._calculate_mesh_volume(vertices, faces), 'surface_area': self._calculate_surface_area(vertices, faces), 'center_of_mass': np.mean(vertices, axis=0) } return mesh_info def create_ground_truth(self, image: np.ndarray) -> Dict: """ 이미지에서 Ground Truth 정보를 생성합니다. Args: image (np.ndarray): 입력 이미지 Returns: Dict: Ground Truth 정보 """ # 이미지 크기 정보 height, width = image.shape[:2] # 간단한 객체 감지를 위한 전처리 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) # 엣지 검출 edges = cv2.Canny(gray, 50, 150) # 윤곽선 찾기 contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 바운딩 박스 생성 bounding_boxes = [] for contour in contours: if cv2.contourArea(contour) > 100: # 최소 면적 필터 x, y, w, h = cv2.boundingRect(contour) bounding_boxes.append({ 'bbox': [x, y, x + w, y + h], 'area': w * h, 'confidence': 1.0 }) ground_truth = { 'image_size': (width, height), 'bounding_boxes': bounding_boxes, 'num_objects': len(bounding_boxes), 'image_histogram': np.histogram(gray, bins=256)[0] } return ground_truth def mesh_to_pointcloud(self, vertices: np.ndarray, num_points: int = 10000) -> np.ndarray: """ 메시를 점군으로 변환합니다. Args: vertices (np.ndarray): 메시 정점 num_points (int): 생성할 점의 개수 Returns: np.ndarray: 점군 데이터 """ if len(vertices) >= num_points: # 정점이 충분한 경우 랜덤 샘플링 indices = np.random.choice(len(vertices), num_points, replace=False) return vertices[indices] else: # 정점이 부족한 경우 중복 허용하여 샘플링 indices = np.random.choice(len(vertices), num_points, replace=True) return vertices[indices] def _calculate_mesh_volume(self, vertices: np.ndarray, faces: np.ndarray) -> float: """메시의 부피를 계산합니다.""" try: mesh = trimesh.Trimesh(vertices=vertices, faces=faces) return mesh.volume except: return 0.0 def _calculate_surface_area(self, vertices: np.ndarray, faces: np.ndarray) -> float: """메시의 표면적을 계산합니다.""" try: mesh = trimesh.Trimesh(vertices=vertices, faces=faces) return mesh.surface_area except: return 0.0 def validate_file(self, file_path: str, file_type: str = 'auto') -> bool: """ 파일 유효성을 검사합니다. Args: file_path (str): 파일 경로 file_type (str): 파일 타입 ('3d', 'image', 'auto') Returns: bool: 파일 유효성 """ if not os.path.exists(file_path): return False _, ext = os.path.splitext(file_path.lower()) if file_type == 'auto': return (ext in self.supported_3d_formats or ext in self.supported_image_formats) elif file_type == '3d': return ext in self.supported_3d_formats elif file_type == 'image': return ext in self.supported_image_formats return False