PLY格式介绍与读取

  • Post author:
  • Post category:其他



一、

格式介绍





PLY

是一种电脑档案格式,全名为

多边形档案(Polygon File Format)



斯坦福三角形档案(Stanford Triangle Format)


史丹佛大学



The Digital Michelangelo Project

计划采用PLY格式储存极高分辨率之

米开朗基罗

的作品”

大卫

“雕塑。

该格式主要用以储存立体扫描结果的三维数值,透过多边形片面的集合描述三维物体,与其他格式相较之下这是较为简单的方法。它可以储存的资讯包含颜色、透明度、表面法向量、材质座标与资料可信度,并能对多边形的正反两面设定不同的属性。

在档案内容的储存上PLY有两种版本,分别是纯文字(ASCII)版本与二元码(binary)版本,其差异在储存时是否以ASCII编码表示元素资讯。


档案格式

(本文并未提供完整的格式描述,以下仅介绍PLY的基本概念与格式)

每个PLY档都包含档头(header),用以设定网格模型的“元素”与“属性”,以及在档头下方接着一连串的元素“数值资料”。一般而言,网格模型的“元素”就是顶点(vertices)、面(faces),另外还可能包含有边(edges)、深度图样本(samples of range maps)与三角带(triangle strips)等元素。无论是纯文字与二元码的PLY档,档头资讯都是以ASCII编码编写,接续其后的数值资料才有编码之分。PLY档案以此行:


ply

开头作为PLY格式的识别。接着第二行是版本资讯,目前有三种写法:


format ascii 1.0

format binary_little_endian 1.0

format binary_big_endian 1.0

其中ascii, binary_little_endian, binary_big_endian是档案储存的编码方式,而1.0是遵循的标准版本(现阶段仅有PLY 1.0版)。在档头中可使用’comment’作为一行的开头以编写注解,例如:


comment This


is


a comment!

描述元素及属性,必须使用’element’及’property’的关键字,一般的格式为element下方接着属性列表,例如:


element <element name> <number


in


file>



property <data_type> <property name 1>



property <data_type> <property name 2>



property <data_type> <property name 3>

‘property’不仅定义了资料的型态,其出现顺序亦定义了资料的顺序。内定的资料形态有两种写法:一种是

char uchar short ushort int uint float double

,另外一种是具有位元长度的

int8 uint8 int16 uint16 int32 uint32 float32 float64

。 例如,描述一个包含12个

顶点

的物体,每个顶点使用3个单精度浮点数 (x,y,z)代表点的座标,使用3个unsigned char代表顶点颜色,颜色顺序为 (B, G, R),则档头的写法为:


element vertex 12

property


float


x

property


float


y

property


float


z

property uchar blue

property uchar green

property uchar red

其中vertex是内定的元素类型,接续的6行property描述构成vertex元素的数值字段顺序代表的意义,及其资料形态。

另一个常使用的元素是



。由于一个面是由3个以上的顶点所组成,因此使用一个“顶点列表”即可描述一个面, PLY格式使用一个特殊关键字’property list’定义之。 例如,一个具有10个面的物体,其PLY档头可能包含:


element face 10



property list uchar


int


vertex_indices

‘property list’表示该元素face的特性是由一行的顶点列表来描述。列表开头以uchar型态的数值表示列表的项目数,后面接着资料型态为int的顶点索引值(vertex_indices),顶点索引值从0开始。

最后,标头必须以此行结尾:


end_header

档头后接着的是元素资料(端点座标、拓朴连结等)。在ASCII格式中各个端点与面的资讯都是以独立的一行描述,而二元编码格式则连续储存这些资料,加载时须以’element’定义的元素数目以及’property’中设定的资料形态计算各笔字段的长度。


范例







一个典型的PLY档案结构分成三部分:


档头 (从ply开始到end_header)

顶点元素列表

面元素列表

其中的

顶点元素列表

一般以x y z方式排列,形态如档头所定义;而

面元素列表

是以下列格式表示。


<組成面的端點數N> <端點#1的索引> <端點#2的索引> ... <端點#N的索引>

例如画出一个有4个顶点,4个面的

四面体

,档案内容为:


ply

format ascii 1.0

comment這是一個正四面體

element vertex 4

property


float


x

property


float


y

property


float


z

element face 4

property list uchar


int


vertex_index

end_header

0 3 0

2.449 -1.0 -1.414

0 -1 2.828

-2.449 -1.0 -1.414

3 0 1 3

3 0 2 1

3 0 3 2

3 1 2 3

其中1~10行是档头, 11~14行是

顶点元素列表

, 15~18行则是

面元素列表

其中: 0 3 0是顶点


历史









PLY格式发展于90年代中期,在史丹佛大学图学实验室的Marc Levoy教授指导下,由Greg Turk及其他成员开发出来。PLY格式受

Wavefront .obj

格式的启发,但改进了Obj格式所缺少的对任意属性及群组的扩充性。因此PLY格式发明了”property”及”element”这两个关键词,来概括“顶点、面、相关资讯、群组”的概念。


注意


ply文件不支持中文格式的文件名字,所以在使用过程中避免使用中文来命名。





二、C语言读取PLY文件


// Try to read a plyfile, returning vertices and faces
bool read_ply(const char *plyfile,
	      int &numleaves, QTree_Node * &leaves,
	      int &numfaces, face * &faces,
	      bool &have_colors,
	      std::string &comments)
{
	bool have_faces=false, have_tstrips=false;
	int tstripdatalen=0, *tstripdata = NULL;
	int other_prop_len, color_offset;
	char buf[255];
	int i, result;

	have_colors = false;  numleaves = numfaces = 0;
	leaves = NULL;  faces = NULL;

	FILE *f = fopen(plyfile, "r");
	if (!f) {
		fprintf(stderr, "Can't open plyfile %s\n", plyfile);
		return false;
	}
	printf("Reading %s...\n", plyfile);


	// Read header
	if (!fgets(buf, 255, f) || strncmp(buf, "ply", 3)) {
		fprintf(stderr, "Not a ply file.\n");
		return false;
	}

#define GET_LINE() if (!fgets(buf, 255, f)) goto plyreaderror
#define LINE_IS(text) !strncasecmp(buf, text, strlen(text))

	GET_LINE();
	if (!LINE_IS("format binary_big_endian 1.0")&&!LINE_IS("format binary_little_endian 1.0")) {
		fprintf(stderr, "Can only read binary  ply files.\n");
	}

	while (1) {
		GET_LINE();
		if (LINE_IS("obj_info")) {
			continue;
		} else if (LINE_IS("comment")) {
			comments += buf+8;
			continue;
		} else {
			break;
		}
	}

	result = sscanf(buf, "element vertex %d\n", &numleaves);
	if (result != 1) {
		fprintf(stderr, "Expected \"element vertex\"\n");
		goto plyreaderror;
	}

	GET_LINE();
	if (!LINE_IS("property float x")) {
		fprintf(stderr, "Expected \"property float x\"\n");
		goto plyreaderror;
	}

	GET_LINE();
	if (!LINE_IS("property float y")) {
		fprintf(stderr, "Expected \"property float y\"\n");
		goto plyreaderror;
	}

	GET_LINE();
	if (!LINE_IS("property float z")) {
		fprintf(stderr, "Expected \"property float z\"\n");
		goto plyreaderror;
	}

	other_prop_len = 0;
	GET_LINE();
	while (LINE_IS("property")) {
		if (LINE_IS("property char") ||
		    LINE_IS("property uchar")) {
			other_prop_len += 1;
		} else if (LINE_IS("property int") ||
			   LINE_IS("property uint") ||
			   LINE_IS("property float")) {
			other_prop_len += 4;
		} else {
			fprintf(stderr, "Unsupported vertex property: %s\n", buf);
			goto plyreaderror;
		}

		if (LINE_IS("property uchar diffuse_red")) {
			have_colors = true;
			color_offset = other_prop_len - 1;
		}

		GET_LINE();
	}


	result = sscanf(buf, "element face %d", &numfaces);
	if (result == 1) {
		have_faces = true;
		GET_LINE();
		if (!LINE_IS("property list uchar int vertex_indices"))
			goto plyreaderror;
		GET_LINE();
	} else if (LINE_IS("element tristrips 1")) {
		have_tstrips = true;
		GET_LINE();
		if (!LINE_IS("property list int int vertex_indices"))
			goto plyreaderror;
		GET_LINE();
	}

	if (!LINE_IS("end_header")) {
		fprintf(stderr, "Expected \"end_header\"\n");
		goto plyreaderror;
	}


	// OK, we think we've parsed the header. Slurp in the actual data...
	leaves = new QTree_Node[numleaves];

	printf(" Reading %d vertices... ", numleaves); fflush(stdout);
	for (i=0; i < numleaves; i++) {

		if (!fread((void *)&(leaves[i].pos[0]), 12, 1, f))
			goto plyreaderror;
		if (other_prop_len && !fread((void *)buf, other_prop_len, 1, f))
			goto plyreaderror;

		if (have_colors) {
			memcpy((void *)&(leaves[i].col[0]),
			       buf + color_offset,
			       sizeof(color));
		}
	}
	printf("Done.\n");

	if (have_tstrips) {
		printf(" Reading triangle strips... "); fflush(stdout);

		if (!fread((void *)&tstripdatalen, 4, 1, f))
			goto plyreaderror;
		FIX_LONG(tstripdatalen);

		tstripdata = new int[tstripdatalen];
		if (!fread((void *)tstripdata, 4*tstripdatalen, 1, f))
			goto plyreaderror;
		for (int t=0; t < tstripdatalen; t++)
			FIX_LONG(tstripdata[t]);
	} else if (have_faces) {
		printf(" Reading %d faces... ", numfaces); fflush(stdout);
		faces = new face[numfaces];
		for (i=0; i < numfaces; i++) {
			if (!fread((void *)buf, 1, 1, f))
				goto plyreaderror;
			if (buf[0] != 3) {
				fprintf(stderr, "Non-triangle found in mesh.\n");
			}

			if (!fread((void *)faces[i], 12, 1, f))
				goto plyreaderror;

		}
	}
	printf("Done.\n");
	if (tstripdatalen) {
		unpack_tstrips(tstripdatalen, tstripdata, numfaces, faces);
		delete [] tstripdata;
	}

	fgets(buf, 2, f);
	if (!feof(f)) {
		fprintf(stderr, "Warning: ignored excess garbage at end of ply file.\n");
	}

	fclose(f);


	return true;

plyreaderror:
	fclose(f);
	fprintf(stderr, "Error reading plyfile.\n");
	if (leaves) delete [] leaves;
	if (faces) delete [] faces;
	if (tstripdata) delete [] tstripdata;
	return false;
}




三、利用QSplatmake将PLY文件转化为QS文件时需要注意little_endian与Big_endian的区别主要在FIXFLOAT和FIX_LONG等会受到影响,可以直接忽略这句语句。其具体有什么用我还不太明了,猜想应该是控制数值的形式。对于

little_endian与Big_endian分别所代表的含义可自行百度,这里就不太累述。



版权声明:本文为my_lord_原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。