Pythonを使ったOBJファイルの入出力
Pythonを使ったOBJファイルの入出力に関するTipsです.
CG技術の実装と数理の実装で使ったOBJファイルの入出力のPythonコードをまとめます.このプロジェクトでは,OBJファイルデータを読み取り,メッシュに変更を加えて別のOBJファイルデータとして出力するということをやっていました.出力ファイルは,MeshLab等のソフトウェアで確認することができます.
OBJモデルデータの読み込み
以下,Pythonを使ってOBJモデルを読み込むサンプルコードです.
def loadOBJ(fliePath):
numVertices = 0
numUVs = 0
numNormals = 0
numFaces = 0
vertices = []
uvs = []
normals = []
vertexColors = []
faceVertIDs = []
uvIDs = []
normalIDs = []
for line in open(fliePath, "r"):
vals = line.split()
if len(vals) == 0:
continue
if vals[0] == "v":
v = map(float, vals[1:4])
vertices.append(v)
if len(vals) == 7:
vc = map(float, vals[4:7])
vertexColors.append(vc)
numVertices += 1
if vals[0] == "vt":
vt = map(float, vals[1:3])
uvs.append(vt)
numUVs += 1
if vals[0] == "vn":
vn = map(float, vals[1:4])
normals.append(vn)
numNormals += 1
if vals[0] == "f":
fvID = []
uvID = []
nvID = []
for f in vals[1:]:
w = f.split("/")
if numVertices > 0:
fvID.append(int(w[0])-1)
if numUVs > 0:
uvID.append(int(w[1])-1)
if numNormals > 0:
nvID.append(int(w[2])-1)
faceVertIDs.append(fvID)
uvIDs.append(uvID)
normalIDs.append(nvID)
numFaces += 1
print "numVertices: ", numVertices
print "numUVs: ", numUVs
print "numNormals: ", numNormals
print "numFaces: ", numFaces
return vertices, uvs, normals, faceVertIDs, uvIDs, normalIDs, vertexColors
取得できるデータ
- 頂点座標データ: vertices (numVertices x 3)
- テクスチャ座標データ:uv (numUVs x 3)
- 法線データ: normals (numNormals x 3)
- 面を構成する頂点のID: faceVertIDs (numFaces x 3 or 4)
- 面頂点と対応したテクスチャ座標ID: uvIDs (numFaces x 3 or 4)
- 面を構成する法線のID: normalIDs (numFaces x 3 or 4)
- 頂点カラーデータ: vertexColors (numVertices x 3)
OBJモデルデータの書き込み
以下,Pythonを使ってOBJモデルを出力するサンプルコードです.
def saveOBJ(filePath, vertices, uvs, normals, faceVertIDs, uvIDs, normalIDs, vertexColors):
f_out = open(filePath, 'w')
f_out.write("####\n")
f_out.write("#\n")
f_out.write("# Vertices: %s\n" %(len(vertices)))
f_out.write("# Faces: %s\n" %(len( faceVertIDs)))
f_out.write("#\n")
f_out.write("####\n")
for vi, v in enumerate( vertices ):
vStr = "v %s %s %s" %(v[0], v[1], v[2])
if len( vertexColors) > 0:
color = vertexColors[vi]
vStr += " %s %s %s" %(color[0], color[1], color[2])
vStr += "\n"
f_out.write(vStr)
f_out.write("# %s vertices\n\n" %(len(vertices)))
for uv in uvs:
uvStr = "vt %s %s\n" %(uv[0], uv[1])
f_out.write(uvStr)
f_out.write("# %s uvs\n\n" %(len(uvs)))
for n in normals:
nStr = "vn %s %s %s\n" %(n[0], n[1], n[2])
f_out.write(nStr)
f_out.write("# %s normals\n\n" %(len(normals)))
for fi, fvID in enumerate( faceVertIDs ):
fStr = "f"
for fvi, fvIDi in enumerate( fvID ):
fStr += " %s" %( fvIDi + 1 )
if len(uvIDs) > 0:
fStr += "/%s" %( uvIDs[fi][fvi] + 1 )
if len(normalIDs) > 0:
fStr += "/%s" %( normalIDs[fi][fvi] + 1 )
fStr += "\n"
f_out.write(fStr)
f_out.write("# %s faces\n\n" %( len( faceVertIDs)) )
f_out.write("# End of File\n")
f_out.close()
出力できるデータ
読み込みで取得できるデータと揃えてあります. - 頂点座標データ: vertices (numVertices x 3) - テクスチャ座標データ:uv (numUVs x 3) - 法線データ: normals (numNormals x 3) - 面を構成する頂点のID: faceVertIDs (numFaces x 3 or 4) - 面頂点と対応したテクスチャ座標ID: uvIDs (numFaces x 3 or 4) - 面を構成する法線のID: normalIDs (numFaces x 3 or 4) - 頂点カラーデータ: vertexColors (numVertices x 3)
OBJモデルデータの変更
実際に処理を行う時は,数値計算ライブラリnumpyを利用し,頂点座標データをnumpy.array型に変更して各種処理を行っています.例えば,以下の例では,頂点座標にノイズを加えて保存しています.
import numpy as np
import random
def noise(P, sigma = 0.02):
xRange = np.max(P[:,0]) - np.min(P[:,0])
yRange = np.max(P[:,1]) - np.min(P[:,1])
zRange = np.max(P[:,2]) - np.min(P[:,2])
noiseScale = 0.5 * np.linalg.norm( np.array([xRange, yRange, zRange]) )
P_new = np.array( P )
numVertices = len(P)
for i in range(numVertices):
P_new[i][0] += noiseScale * random.normalvariate(0,sigma)
P_new[i][1] += noiseScale * random.normalvariate(0,sigma)
P_new[i][2] += noiseScale * random.normalvariate(0,sigma)
return P_new
vertices, uvs, normals, faceVertIDs, uvIDs, normalIDs, vertexColors = loadOBJ('InputMesh.obj')
P = np.array(vertices)
P_noise = noise(P)
saveOBJ('NoiseMesh.obj', P_noise, uvs, normals, faceVertIDs, uvIDs, normalIDs, vertexColors)
先ほどのsaveOBJ関数は,np.arrayのサイズが対応していれば,特に変換しなくてもそのまま使えます.