Pythonを使ったOBJファイルの入出力

Pythonを使ったOBJファイルの入出力に関するTipsです.

CG技術の実装と数理の実装で使ったOBJファイルの入出力のPythonコードをまとめます.このプロジェクトでは,OBJファイルデータを読み取り,メッシュに変更を加えて別のOBJファイルデータとして出力するということをやっていました.出力ファイルは,MeshLab等のソフトウェアで確認することができます.

OBJモデルデータの読み込み

以下,Pythonを使ってOBJモデルを読み込むサンプルコードです.

loadOBJ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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モデルを出力するサンプルコードです.

saveOBJ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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型に変更して各種処理を行っています.例えば,以下の例では,頂点座標にノイズを加えて保存しています.

numpy vertices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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のサイズが対応していれば,特に変換しなくてもそのまま使えます.