Spaces:
Running
Running
| import io | |
| import numpy as np | |
| from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。 | |
| from PIL import ImageDraw as PIL_ImageDraw # 上記と同じ命名規則にする。 | |
| from scipy.spatial import Delaunay | |
| import cv2 | |
| import os | |
| import struct | |
| import matplotlib.pyplot as plt | |
| import triangle | |
| import uuid | |
| from gltflib import ( | |
| GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType, | |
| BufferTarget, ComponentType, GLBResource, FileResource, PBRMetallicRoughness) | |
| # Common configuration information | |
| # Parts that are independent of the binary section's offset and size can be predefined. | |
| # Asset | |
| asset=Asset() | |
| # Image | |
| images=[ | |
| Image(mimeType='image/jpeg', bufferView=4), | |
| ] | |
| # Sampler | |
| samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング | |
| # Texture | |
| textures = [ | |
| Texture(name='Main',sampler=0,source=0), | |
| ] | |
| # Material | |
| materials = [ | |
| Material( | |
| pbrMetallicRoughness=PBRMetallicRoughness( | |
| baseColorTexture=TextureInfo(index=0), | |
| metallicFactor=0, | |
| roughnessFactor=1 | |
| ), | |
| name='Material0', | |
| alphaMode='OPAQUE', | |
| doubleSided=True | |
| ), | |
| ] | |
| # Mesh | |
| meshes = [ | |
| Mesh(name='Main', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), | |
| indices=3, material=0, mode=4)]), | |
| ] | |
| # Scene | |
| scene = 0 | |
| scenes = [Scene(name='Scene', nodes=[0])] | |
| def create_manhole_model(original_img_bytearray): | |
| # Image | |
| img = PIL_Image.open(original_img_bytearray).convert('RGB') | |
| # Add edge color space. | |
| # (edge color is decided at 'Decide edge color' section) | |
| temp_img = PIL_Image.new('RGB', (img.size[0] + 3, img.size[1] + 3), 'black') | |
| temp_img.paste(img, (3, 3)) | |
| img = temp_img.copy() | |
| # Get coordinates of manhole | |
| ret, mask = cv2.threshold(cv2.cvtColor(np.array(img), cv2.COLOR_RGB2GRAY), 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY_INV) | |
| edges = cv2.Canny(mask , 50, 150, apertureSize=3) | |
| circle = cv2.HoughCircles(edges, | |
| cv2.HOUGH_GRADIENT, 1, int(max(img.size)*2), | |
| param1=50, param2=30, minRadius=0, maxRadius=0)[0][0] | |
| coordinate_list = [] | |
| num_polygons = 360 | |
| for i in range(num_polygons): | |
| coordinate_list.append(( | |
| int(circle[0] + np.cos((i / num_polygons) * (2 * np.pi)) * circle[2]), | |
| int(circle[1] + np.sin((i / num_polygons) * (2 * np.pi)) * circle[2]) | |
| )) | |
| coordinate_list = [coordinate_list] | |
| # Decide edge color | |
| coordinate_colors = list(map(lambda x: img.getpixel(x), coordinate_list[0])) | |
| edge_color = tuple(np.mean(np.array(coordinate_colors), axis=0).astype(np.uint8)) | |
| draw = PIL_ImageDraw.Draw(img) | |
| draw.point(tuple((i, j) for i in range(3) for j in range(3)), fill=edge_color) | |
| # image binary data | |
| img_ext = "JPEG" | |
| img_bytearray = io.BytesIO() | |
| img.save(img_bytearray, format=img_ext, quality=95) | |
| img_bytearray = img_bytearray.getvalue() | |
| img_bytelen = len(img_bytearray) | |
| # calculate scale of 3d model | |
| scale_factor = np.power(img.size[0] * img.size[1], 0.5) | |
| scale = (img.size[0] / scale_factor, img.size[1] / scale_factor, 0.4) | |
| # Thickness of manhole | |
| thickness = 0.2 # manhole | |
| # Vertex data(POSITION) | |
| front_vertex_list = [] | |
| back_vertex_list = [] | |
| for coordinates in coordinate_list: | |
| front_vertices = [] | |
| back_vertices = [] | |
| # Be aware that the Y-axis direction is inverted between images and GLB files | |
| for coordinate in coordinates: | |
| front_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), thickness)) | |
| back_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), -thickness)) | |
| front_vertex_list.append(front_vertices) | |
| back_vertex_list.append(back_vertices) | |
| vertices = front_vertex_list[0] + back_vertex_list[0] | |
| vertex_bytearray = bytearray() | |
| # Set vertices with 'Front+Back' and 'Edge' | |
| for i in range(2): # first for 'Front+Back', second for 'Edge' | |
| for vertex in vertices: | |
| for value in vertex: | |
| vertex_bytearray.extend(struct.pack('f', value)) | |
| vertex_bytelen = len(vertex_bytearray) | |
| mins = [min([vertex[i] for vertex in vertices]) for i in range(3)] | |
| maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)] | |
| # Normal data(NORMAL) | |
| normals = [( 0.0, 0.0, 1.0)] * len(front_vertex_list[0]) + [( 0.0, 0.0, -1.0)] * len(back_vertex_list[0]) | |
| normal_bytearray = bytearray() | |
| # Consider not only 'Front+Back' but also 'Edge' | |
| for i in range(2): # first for 'Front+Back', second for 'Edge' | |
| for normal in normals: | |
| for value in normal: | |
| normal_bytearray.extend(struct.pack('f', value)) | |
| normal_bytelen = len(normal_bytearray) | |
| # Texture coordinates(TEXCOORD_0) | |
| texcoord_0s = [ | |
| ((vertex[0] + 1.0) / 2.0, 1.0 - ((vertex[1] + 1.0) / 2.0) ) for vertex in vertices | |
| ] | |
| texcoord_0_bytearray = bytearray() | |
| # 'Front+Back' | |
| for texcoord_0 in texcoord_0s: | |
| for value in texcoord_0: | |
| texcoord_0_bytearray.extend(struct.pack('f', value)) | |
| # 'Edge' | |
| for texcoord_0 in texcoord_0s: | |
| for value in texcoord_0: | |
| texcoord_0_bytearray.extend(struct.pack('f', 0.0)) | |
| texcoord_0_bytelen = len(texcoord_0_bytearray) | |
| # Vertex indices | |
| polygon = { | |
| 'vertices': np.array(front_vertex_list[0])[:, :2], | |
| 'segments': np.array([( i, (i + 1) % (len(front_vertex_list[0])) ) for i in range(len(front_vertex_list[0]))]) | |
| } | |
| triangulate_result = triangle.triangulate(polygon, 'p') | |
| vertex_indices = [] | |
| # Front | |
| vertex_indices.extend(list(np.array(triangulate_result['triangles']).flatten())) | |
| # Back | |
| temp_list = list((np.array(triangulate_result['triangles'])+len(front_vertex_list[0])).flatten()) | |
| # Swap vertex indices to get Back vertex indices from Front vertex indices | |
| def swap_elements(lst): | |
| if len(lst) < 2: | |
| return lst | |
| for i in range(len(lst) - 1): | |
| if i % 3 == 1: | |
| lst[i], lst[i + 1] = lst[i + 1], lst[i] | |
| return lst | |
| temp_list = swap_elements(temp_list) | |
| vertex_indices.extend(temp_list) # Back | |
| # Edge | |
| vertex_indices.extend(list(np.array([[len(vertices) + i, | |
| len(vertices) + (i + 1) % len(front_vertex_list[0]), | |
| len(vertices) + len(front_vertex_list[0]) + i] | |
| for i in range(len(front_vertex_list[0]))]).flatten())) | |
| vertex_indices.extend(list(np.array([[len(vertices) + len(front_vertex_list[0]) + (i + 1) % len(front_vertex_list[0]), | |
| len(vertices) + len(front_vertex_list[0]) + i, | |
| len(vertices) + (i + 1) % len(front_vertex_list[0])] | |
| for i in range(len(front_vertex_list[0]))]).flatten())) | |
| vertex_index_bytearray = bytearray() | |
| for value in vertex_indices: | |
| vertex_index_bytearray.extend(struct.pack('H', value)) | |
| vertex_index_bytelen = len(vertex_index_bytearray) | |
| # Concatenate binar data | |
| bytearray_list = [ | |
| vertex_bytearray, | |
| normal_bytearray, | |
| texcoord_0_bytearray, | |
| vertex_index_bytearray, | |
| img_bytearray, | |
| ] | |
| bytelen_list = [ | |
| vertex_bytelen, | |
| normal_bytelen, | |
| texcoord_0_bytelen, | |
| vertex_index_bytelen, | |
| img_bytelen, | |
| ] | |
| bytelen_cumsum_list = list(np.cumsum(bytelen_list)) | |
| bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list)) | |
| all_bytearray = bytearray() | |
| for temp_bytearray in bytearray_list: | |
| all_bytearray.extend(temp_bytearray) | |
| offset_list = [0] + bytelen_cumsum_list # First offset is 0 | |
| offset_list.pop() # Delete the last element | |
| # Resource | |
| resources = [GLBResource(data=all_bytearray)] | |
| # Buffer | |
| buffers = [Buffer(byteLength=len(all_bytearray))] | |
| # BufferView | |
| bufferViews = [ | |
| BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value), | |
| BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value), | |
| BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value), | |
| BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value), | |
| BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None), | |
| ] | |
| # Accessor | |
| accessors = [ | |
| Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices*2), type=AccessorType.VEC3.value, max=maxs, min=mins), | |
| Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals*2), type=AccessorType.VEC3.value, max=None, min=None), | |
| Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s*2), type=AccessorType.VEC2.value, max=None, min=None), | |
| Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None) | |
| ] | |
| # Node | |
| nodes = [ | |
| Node(mesh=0,rotation=None, scale=scale), | |
| ] | |
| model = GLTFModel( | |
| asset=asset, | |
| buffers=buffers, | |
| bufferViews=bufferViews, | |
| accessors=accessors, | |
| images=images, | |
| samplers=samplers, | |
| textures=textures, | |
| materials=materials, | |
| meshes=meshes, | |
| nodes=nodes, | |
| scene=scene, | |
| scenes=scenes, | |
| ) | |
| gltf = GLTF(model=model, resources=resources) | |
| tmp_filename = uuid.uuid4().hex | |
| model_path = f'../tmp/{tmp_filename}.glb' | |
| gltf.export(model_path) | |
| return model_path |