#!/usr/bin/env python
#
# abs2obj.py: convert a *.abs file from the Face Recognition Grand Challenge
# database (FRGC) to an *.obj file (wavefront).
# The output mesh is made of triangles defined counterclockwise when seen from
# the camera position.
#
#
# Copyright (C) 2010 Clement Creusot
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import sys
import string
import os.path
import getopt
import tempfile
from subprocess import *
def print_help():
print "Usage: "+os.path.basename(sys.argv[0])+" [OPTIONS] filein.abs[.gz]"
print " Options: -o OUTPUT_FILE"
print " Write the output mesh in OUTPUT_FILE"
print " -d FACTOR"
print " Downsample(average) the size of the 2D range image by FACTOR"
print " -r (dx)x(dy)"
print " Fix the size of output pixels (not compatible with -d)"
print " -c produce cross connection (8 instead of 6)"
print " -t D "
print " convert only the upper D millimeters of the mesh"
print " -p PERCENT minimum % of defined flags for the first line (when using -t)"
print " -T produce vt (texture) vertex "
print " -s '5x5,10' spike removal (mean) window and threshold (before reduction)"
print " -S '5x5,10' spike removal (erase) window and threshold (after reduction)"
sys.exit()
def print_error(*str):
print "ERROR: ",
for i in str:
print i,
print
sys.exit()
# Reading the parameters
try:
optlist, args = getopt.getopt(sys.argv[1:], 'ho:d:ct:p:s:S:r:T')
except getopt.GetoptError, err:
print str(err)
print_help()
if len(args) < 1:
print_help()
absfilename = args[0]
# By default the output is the abs filename followed by '.obj'
objfilename = absfilename.replace(".gz","")+".obj"
# By default we reduce the size image per 4
# There will be one vertex for every 16 pixels.
reductionFactor = 4
resolution = []
crossConnection = False
spikeRemoval = []
spikeRemoval2 = []
tmp = ""
topcrop = ""
minFlagRatio = ""
useTexture=False
# Uncompress if compressed
if absfilename.rfind(".gz") != -1:
code,tmp = tempfile.mkstemp(suffix=".abs")
call("gunzip -c "+absfilename+" > "+tmp,shell=True);
absfilename = tmp
if cmp(string.split(absfilename,".")[-1].lower(), "abs") != 0 :
print_error(absfilename,": The file is not an .abs file.")
if not os.path.exists(absfilename):
print_error(absfilename,": The file doesn't exist.")
# use options
for opt,value in optlist:
if opt in ("-h", "--help"):
print_help()
elif opt=='-o':
objfilename = value
elif opt=='-t':
topcrop = float(value)
elif opt=='-T':
useTexture = True
elif opt=='-p':
minFlagRatio = float(value)
elif opt=='-c':
crossConnection = True
elif opt=='-s':
try:
aux = string.split(value,",")
spikeRemoval = map(int,string.split(aux[0],"x"))
spikeRemoval.append(float(aux[1]))
if len(spikeRemoval) !=3:
raise NameError('missing parameter')
except :
print_error("spike removal parameter should be 'PxQ,distance'")
elif opt=='-S':
try:
aux = string.split(value,",")
spikeRemoval2 = map(int,string.split(aux[0],"x"))
spikeRemoval2.append(float(aux[1]))
if len(spikeRemoval2) !=3:
raise NameError('missing parameter')
except :
print_error("spike removal parameter should be 'PxQ,distance'")
elif opt=='-d':
try:
reductionFactor = int(value)
except ValueError:
print_error(value," is not an integer")
elif opt=='-r':
try:
resolution = map(float,string.split(value,"x"))
if len(resolution)!=2:
raise NameError('missing parameter')
except :
print_error(value," resolution should be 'axb'")
else:
print "Unhandled option"
print_help()
# start reading the ABS file
absfile = open(absfilename, "r")
try:
tab = string.split(absfile.readline())
rows = int(tab[0])
tab = string.split(absfile.readline())
columns= int(tab[0])
tab = string.split(absfile.readline()) # comment pixel
except ValueError:
print_error("Bad ABS header")
# Read all the flags 0 or 1
flags = map(int,string.split(absfile.readline()))
# Read all the x,y and z coordinates
X = map(float,string.split(absfile.readline()))
Y = map(float,string.split(absfile.readline()))
Z = map(float,string.split(absfile.readline()))
absfile.close()
if spikeRemoval != []:
removedNb = 0
p = (spikeRemoval[0]-1)/2
q = (spikeRemoval[1]-1)/2
for ii in range(rows-2*q):
i= ii+q
for jj in range(columns-2*p):
j = jj+p
validNb = 0
awayNb = 0
sumZ = 0.0
center = (ii+q)*columns+jj+p
if flags[center] != 1:
continue
for a in range(q*2+1): # local matrix
for b in range(p*2+1):
if a == q and b == p: # dont count center point
continue
offset = (ii+a)*columns+jj+b
if flags[offset] != 1:
continue
validNb+=1
sumZ += Z[offset]
if abs(Z[offset]-Z[center])>spikeRemoval[2]:
awayNb += 1
if awayNb > validNb/2.0:
Z[center] = sumZ/float(validNb)
removedNb += 1
#print removedNb
if topcrop != "":
# only keep the topcrop first mm on top of the mesh
threshold = - topcrop
if minFlagRatio == "":
for i,f in enumerate(flags):
if f == 1:
threshold += Y[i]
break
else:
for i in range(rows):
trueFlagNb = 0
ycurr = 0.0
for j in range(columns):
if flags[i*columns+j] == 1:
trueFlagNb += 1
ycurr = Y[i*columns+j]
if trueFlagNb/float(columns)*100 > minFlagRatio:
threshold += ycurr
break
else: #set flags to 0
for j in range(columns):
flags[i*columns+j] = 0
for i,y in enumerate(Y):
if y < threshold:
flags[i] = 0
#print threshold, topcrop
if resolution != []:
# compute the window of interest
ymin = 9999999.0
ymax = -9999999.0
xmin = 9999999.0
xmax = -9999999.0
yargmin = -1
yargmax = -1
xargmin = -1
xargmax = -1
for i in range(rows):#ymax
for j in range(columns):
offset = i*columns+j
if flags[offset] == 1:
if Y[offset] > ymax:
yargmax = i
ymax = Y[offset]
#if yargmax !=-1:
# break
for i in range(rows):#ymin
for j in range(columns):
offset = (rows-1-i)*columns+j
if flags[offset] == 1:
if Y[offset] < ymin:
yargmin = (rows-1-i)
ymin = Y[offset]
for i in range(rows):
for j in range(columns): #xmin
offset = i*columns+j
if flags[offset] == 1:
if X[offset] < xmin:
xargmin = j
xmin = X[offset]
#break
for j in range(columns): #xmax
offset = (i+1)*columns-1-j
if flags[offset] == 1:
if X[offset] > xmax:
xargmax = columns-1-j
xmax = X[offset]
#break
newRowNb = int((ymax-ymin)/resolution[0]) +2
newColNb = int((xmax-xmin)/resolution[1]) +2
# pre allocate for conveniance
pointsCoord = []
flagSum = []
pointId = []
for i in range(newRowNb):
row = []
pointsCoord.append(row)
flagRow = []
flagSum.append(flagRow)
pointIdRow = []
pointId.append(pointIdRow)
for j in range(newColNb):
pts = [0.0,0.0,0.0,0.0,0.0]
row.append(pts)
flagRow.append(0)
pointIdRow.append(-1)
if crossConnection : # an other grid (n-1)x(n-1) with an offset of 0.5,0.5
pointsCoordCross = []
flagSumCross = []
pointIdCross = []
for i in range(newRowNb-1):
row = []
pointsCoordCross.append(row)
flagRow = []
flagSumCross.append(flagRow)
pointIdRow = []
pointIdCross.append(pointIdRow)
for j in range(newColNb-1):
pts = [0.0,0.0,0.0,0.0,0.0]
row.append(pts)
flagRow.append(0)
pointIdRow.append(-1)
# Sum the values for each new pixel
for i in range(rows):
for j in range(columns):
offset = i*columns+j
if flags[offset] != 0:
newI = int((Y[offset]-ymin)/resolution[0])
newJ = int((X[offset]-xmin)/resolution[1])
pointsCoord[newI][newJ][0] += X[offset]
pointsCoord[newI][newJ][1] += Y[offset]
pointsCoord[newI][newJ][2] += Z[offset]
pointsCoord[newI][newJ][3] += j/float(columns)
pointsCoord[newI][newJ][4] += (rows-1-i)/float(rows)
# how many active pixels of the source belong to the new pixel
flagSum[newI][newJ] += 1
if crossConnection :
if ((Y[offset]-ymin)>resolution[0]/2.0
and (X[offset]-xmin)>resolution[1]/2.0
and (ymax - Y[offset])>resolution[0]/2.0
and (xmax - X[offset])>resolution[1]/2.0):
newI = int((Y[offset]-ymin-resolution[0]/2.0)/resolution[0])
newJ = int((X[offset]-xmin-resolution[1]/2.0)/resolution[1])
pointsCoordCross[newI][newJ][0] += X[offset]
pointsCoordCross[newI][newJ][1] += Y[offset]
pointsCoordCross[newI][newJ][2] += Z[offset]
pointsCoordCross[newI][newJ][3] += j/float(columns)
pointsCoordCross[newI][newJ][4] += (rows-1-i)/float(rows)
# how many active pixels of the source belong to the new pixel
flagSumCross[newI][newJ] += 1
else:
# Compute the reduction
newRowNb = (rows-1)/reductionFactor +1
newColNb = (columns-1)/reductionFactor +1
# pre allocate for conveniance
pointsCoord = []
flagSum = []
pointId = []
for i in range(newRowNb):
row = []
pointsCoord.append(row)
flagRow = []
flagSum.append(flagRow)
pointIdRow = []
pointId.append(pointIdRow)
for j in range(newColNb):
pts = [0.0,0.0,0.0,0.0,0.0]#x,y,z,u,v
row.append(pts)
flagRow.append(0)
pointIdRow.append(-1)
if crossConnection : # an other grid (n-1)x(n-1) with an offset of 0.5,0.5
pointsCoordCross = []
flagSumCross = []
pointIdCross = []
for i in range(newRowNb-1):
row = []
pointsCoordCross.append(row)
flagRow = []
flagSumCross.append(flagRow)
pointIdRow = []
pointIdCross.append(pointIdRow)
for j in range(newColNb-1):
pts = [0.0,0.0,0.0,0.0,0.0]
row.append(pts)
flagRow.append(0)
pointIdRow.append(-1)
# Sum the values for each new pixel
for i in range(rows):
newI = i/reductionFactor
for j in range(columns):
if flags[i*columns+j] != 0:
newJ = j/reductionFactor
pointsCoord[newI][newJ][0] += X[i*columns+j]
pointsCoord[newI][newJ][1] += Y[i*columns+j]
pointsCoord[newI][newJ][2] += Z[i*columns+j]
pointsCoord[newI][newJ][3] += j/float(columns)
pointsCoord[newI][newJ][4] += (rows-1-i)/float(rows)
# how many active pixels of the source belong to the new pixel
flagSum[newI][newJ] += 1
if crossConnection :
offset = int(reductionFactor/2)
for i in range(rows-reductionFactor):
newI = i/reductionFactor
for j in range(columns-reductionFactor):
id = (i+offset)*columns+j+offset
if flags[id] != 0:
newJ = j/reductionFactor
pointsCoordCross[newI][newJ][0] += X[id]
pointsCoordCross[newI][newJ][1] += Y[id]
pointsCoordCross[newI][newJ][2] += Z[id]
pointsCoordCross[newI][newJ][3] += (j+offset)/float(columns)
pointsCoordCross[newI][newJ][4] += (rows-1-i-offset)/float(rows)
# how many active pixels of the source belong to the new pixel
flagSumCross[newI][newJ] += 1
if spikeRemoval2 != []:
removedNb = 0
p = (spikeRemoval2[0]-1)/2
q = (spikeRemoval2[1]-1)/2
for ii in range(newRowNb-2*q):
i= ii+q
for jj in range(newColNb-2*p):
j = jj+p
validNb = 0
awayNb = 0
sumZ = 0.0
if flagSum[i+q][j+p] == 0:
continue
for a in range(q*2+1): # local matrix
for b in range(p*2+1):
if a == q and b == p: # dont count center point
continue
if flagSum[ii+a][jj+b] == 0:
continue
validNb+=1
#sumZ += pointsCoord[ii+a][jj+b][2]/float(flagSum[ii+a][jj+b]
if abs(pointsCoord[ii+a][jj+b][2]/float(flagSum[ii+a][jj+b])-pointsCoord[i+q][j+p][2]/float(flagSum[i+q][j+p]))>spikeRemoval2[2]:
awayNb += 1
if float(awayNb) > 3*validNb/4.0:
#pointsCoord[i+q][j+p][2] = sumZ/float(validNb)
flagSum[i+q][j+p] = 0
removedNb += 1
#print removedNb
# Divide by the number of values summed for each superpixel
# List the points to keep
points = []
pointsTextureRatio = []
cnt = 1 #OBJ index starts at 1
for i in range(newRowNb):
for j in range(newColNb):
if flagSum[i][j] != 0:
pointsCoord[i][j][0] /= float(flagSum[i][j])
pointsCoord[i][j][1] /= float(flagSum[i][j])
pointsCoord[i][j][2] /= float(flagSum[i][j])
pointsCoord[i][j][3] /= float(flagSum[i][j])
pointsCoord[i][j][4] /= float(flagSum[i][j])
points.append(pointsCoord[i][j][0:3])
pointsTextureRatio.append(pointsCoord[i][j][3:])
pointId[i][j] = cnt
cnt = cnt +1
if crossConnection :
if i