3dmodelmaker / src /manhole_model.py
2Nike2
add manhole model creation
5d3564f
raw
history blame contribute delete
No virus
10.3 kB
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