本節課我們將瞭解到以下內容:

基本的PCL中的資料型別;

使用PCL進行簡單程式設計:寫檔案與讀檔案。

一、PCL庫基本資料型別

上一節課,我們使用PCL庫在本地寫入了一個名為test_pcd。pcd的檔案。我們劃分一下程式的任務步驟:

構造pcd檔案格式;

寫入檔案;

列印輸出。

PCL入門系列三——PCL進行資料讀寫

步驟二和步驟三的內容屬於基本的C++知識。而步驟一中的pcd檔案格式是我們所不熟悉的,所以要先看一看pcd原始檔案。

在命令列中輸入:

more test_pcd。pcd

可以看到pcd檔案的內部結構如下圖所示。

PCL入門系列三——PCL進行資料讀寫

三維點雲資料最簡單的形式是x y z的三維點空間座標。稍微複雜一些可以加上intensity、RGB等屬性。但是這種表達方式有什麼缺點呢?

一方面直接以文字形式儲存點雲的三個座標在記憶體上比較浪費空間,另一方面如果使用者要取得點雲的一些基本資訊如點雲個數,使用者還要自己去查詢。如果有一個檔案頭能夠簡潔的描述點雲資料資訊,那麼無論是在儲存效率還是資料資訊提取等方面都會有一定幫助。

pcd格式可以視作“檔案格式頭+三維點資訊”。

請看一個具體pcd檔案的例子:

# 。PCD v0。7 - Point Cloud Data file format

VERSION 0。7

FIELDS x y z

SIZE 4 4 4

TYPE F F F

COUNT 1 1 1

WIDTH 5

HEIGHT 1

VIEWPOINT 0 0 0 1 0 0 0

POINTS 5

DATA ascii

0。35222197 -0。15188313 -0。10639524

-0。3974061 -0。47310591 0。29260206

………………………………

這個pcd檔案裡有著不同的欄位。常用的有以下幾種。

VERSION - PCD檔案的版本號(通常為0。7);

FIELDS - 每個點所包含的維度或欄位(例如x y z);

SIZE - 每個資料維度所佔據的位元組(比如float為4位元組)

TYPE -每個資料維度的資料型別(I= signed,U= unsigned,F=float)

COUNT - 每個資料維度上包含的元素個數。(比如,x, y, or z的count為1,但是hisogram的count包含N)

WIDTH - 點雲的寬度

HEIGHT - 點雲的高度

VIEWPOINT - 點雲的視角 translation (tx ty tz) +quaternion (qw qx qy qz)

POINTS -點雲的數量

DATA -具體的資料儲存格式(ascii or binary)

在之前的內容中我們忽略了一個小小的但是相對重要的知識點:待填充的資料怎麼表示?請注意這裡不能簡單的使用字串xyz的形式進行填充,如果仍舊使用字串的格式進行填充,那麼pcd檔案格式在儲存上的優勢根本無法體現。一種簡單的方法是為點雲資料定義一個結構體,詳細的情況後文再敘,這裡先羅列一下最常用的資料型別(當然自定義資料型別也可)。

Point Types

PointXYZ- float x, y, z

PointXYZI- float x, y, z, intensity

PointXYZRGB- float x, y, z, rgb

PointXYZRGBA- float x, y, z, uint32t rgba

Normal- float normal[3], curvature

PointNormal- float x, y, z, normal[3], curvature

Histogram- float histogram[N]

………………

二、利用PCL寫點雲檔案

現在詳細看一下生成點雲寫入檔案的過程。

#include

#include

#include

int

main

int

argc

char

**

argv

{

pcl

::

PointCloud

<

pcl

::

PointXYZ

>

cloud

// Fill in the cloud data

cloud

width

=

5

cloud

height

=

1

cloud

is_dense

=

false

cloud

points

resize

cloud

width

*

cloud

height

);

for

size_t

i

=

0

i

<

cloud

points

size

();

++

i

{

cloud

points

i

]。

x

=

1024

*

rand

()

/

RAND_MAX

+

1。0f

);

cloud

points

i

]。

y

=

1024

*

rand

()

/

RAND_MAX

+

1。0f

);

cloud

points

i

]。

z

=

1024

*

rand

()

/

RAND_MAX

+

1。0f

);

}

pcl

::

io

::

savePCDFileASCII

“test_pcd。pcd”

cloud

);

std

::

cerr

<<

“Saved ”

<<

cloud

points

size

()

<<

“ data points to test_pcd。pcd。”

<<

std

::

endl

for

size_t

i

=

0

i

<

cloud

points

size

();

++

i

std

::

cerr

<<

“ ”

<<

cloud

points

i

]。

x

<<

“ ”

<<

cloud

points

i

]。

y

<<

“ ”

<<

cloud

points

i

]。

z

<<

std

::

endl

return

0

);

}

關鍵的部分可以分為以下內容:生成點雲、寫入檔案。

生成點雲可以視作一個欄位填充的過程,我們來看看具體幹了什麼事情。

初始化定義一個點雲物件,此處我們建立了一個PointXYZ物件

pcl

::

PointCloud

<

pcl

::

PointXYZ

>

cloud

設定pcd檔案欄位,將想要填的內容填進去。

// Fill in the cloud data

cloud

width

=

5

cloud

height

=

1

cloud

is_dense

=

false

cloud

points

resize

cloud

width

*

cloud

height

);

填充具體的值

for

size_t

i

=

0

i

<

cloud

points

size

();

++

i

{

cloud

points

i

]。

x

=

1024

*

rand

()

/

RAND_MAX

+

1。0f

);

cloud

points

i

]。

y

=

1024

*

rand

()

/

RAND_MAX

+

1。0f

);

cloud

points

i

]。

z

=

1024

*

rand

()

/

RAND_MAX

+

1。0f

);

}

寫入檔案,簡單的一句話

pcl

::

io

::

savePCDFileASCII

“test_pcd。pcd”

cloud

);

三、讀取pcd檔案

讀寫不分家。讀檔案的過程同樣並不複雜。

#include

#include

#include

int

main

int

argc

char

**

argv

{

pcl

::

PointCloud

<

pcl

::

PointXYZ

>::

Ptr

cloud

new

pcl

::

PointCloud

<

pcl

::

PointXYZ

>

);

if

pcl

::

io

::

loadPCDFile

<

pcl

::

PointXYZ

>

“test_pcd。pcd”

*

cloud

==

-

1

//* load the file

{

PCL_ERROR

“Couldn‘t read file test_pcd。pcd

\n

);

return

-

1

);

}

std

::

cout

<<

“Loaded ”

<<

cloud

->

width

*

cloud

->

height

<<

“ data points from test_pcd。pcd with the following fields: ”

<<

std

::

endl

for

size_t

i

=

0

i

<

cloud

->

points

size

();

++

i

std

::

cout

<<

“ ”

<<

cloud

->

points

i

]。

x

<<

“ ”

<<

cloud

->

points

i

]。

y

<<

“ ”

<<

cloud

->

points

i

]。

z

<<

std

::

endl

return

0

);

}

核心工作就是兩步:

定義一個控制代碼用來存放待讀取的點雲。

pcl

::

PointCloud

<

pcl

::

PointXYZ

>::

Ptr

cloud

new

pcl

::

PointCloud

<

pcl

::

PointXYZ

>

);

讀取點雲

pcl

::

io

::

loadPCDFile

<

pcl

::

PointXYZ

>

“test_pcd。pcd”

*

cloud

==

-

1

對!就是這麼簡單。

四、xyz檔案轉成pcd檔案

現在讓我們研究一個小小的案例。有時候,我們的原始點雲資料是存放在txt檔案裡的。也許它長成這樣:

20。623 40。276 -1。999 -1031 127 141 154

20。362 40。375 -2。239 -941 130 141 159

20。36 40。376 -2。402 -1083 139 151 165

20。374 40。367 -2。405 -1122 131 147 163

20。372 40。366 -2。405 -1165 132 145 161

20。375 40。364 -2。404 -1036 133 149 165

20。358 40。371 -2。405 -1137 139 151 165

20。359 40。374 -2。404 -1086 139 151 165

…………

前三個欄位為xyz,第四個欄位為intensity,後三個欄位為rgb。如何將這樣的資料轉成pcd檔案呢?先上程式碼,執行起來再說。將下列程式碼複製並儲存到xyz2cpp檔案。這個程式將xyzirgb資料轉成pcd格式的xyzrgb資料。如果想從xyz的資料轉成pcd資料,那麼只需要剔除若干填充欄位即可。

#include

#include

#include

using

namespace

std

using

namespace

pcl

using

namespace

pcl

::

io

using

namespace

pcl

::

console

void

printHelp

int

char

**

argv

{

print_error

“Syntax is: %s input。xyz output。pcd

\n

argv

0

]);

}

bool

loadCloud

const

string

&

filename

PointCloud

<

PointXYZRGB

>

&

cloud

{

ifstream

fs

fs

open

filename

c_str

(),

ios

::

binary

);

if

fs

is_open

()

||

fs

fail

())

{

PCL_ERROR

“Could not open file ’%s‘! Error : %s

\n

filename

c_str

(),

strerror

errno

));

fs

close

();

return

false

);

}

string

line

vector

<

string

>

st

while

fs

eof

())

{

getline

fs

line

);

// Ignore empty lines

if

line

empty

())

continue

// Tokenize the line

boost

::

trim

line

);

boost

::

split

st

line

boost

::

is_any_of

\t\r

),

boost

::

token_compress_on

);

if

st

size

()

!=

7

continue

pcl

::

PointXYZRGB

point

point

x

=

float

atof

st

0

]。

c_str

()));

point

y

=

float

atof

st

1

]。

c_str

()));

point

z

=

float

atof

st

2

]。

c_str

()));

point

r

=

uint8_t

atof

st

4

]。

c_str

()));

point

g

=

uint8_t

atof

st

5

]。

c_str

()));

point

b

=

uint8_t

atof

st

6

]。

c_str

()));

cloud

push_back

point

);

}

fs

close

();

cloud

width

=

uint32_t

cloud

size

());

cloud

height

=

1

cloud

is_dense

=

true

return

true

);

}

int

main

int

argc

char

**

argv

{

print_info

“Convert a simple XYZ file to PCD format。 For more information, use: %s -h

\n

argv

0

]);

if

argc

<

3

{

printHelp

argc

argv

);

return

-

1

);

}

// Parse the command line arguments for 。pcd and 。ply files

vector

<

int

>

pcd_file_indices

=

parse_file_extension_argument

argc

argv

“。pcd”

);

vector

<

int

>

xyz_file_indices

=

parse_file_extension_argument

argc

argv

“。xyz”

);

if

pcd_file_indices

size

()

!=

1

||

xyz_file_indices

size

()

!=

1

{

print_error

“Need one input XYZ file and one output PCD file。

\n

);

return

-

1

);

}

// Load the first file

PointCloud

<

PointXYZRGB

>

cloud

if

loadCloud

argv

xyz_file_indices

0

]],

cloud

))

return

-

1

);

// Convert to PCD and save

pcl

::

io

::

savePCDFileASCII

argv

pcd_file_indices

0

]],

cloud

);

}

建立CMakeList。txt檔案

cmake_minimum_required

VERSION 2。8 FATAL_ERROR

project

xyz2pcd

find_package

PCL 1。2 REQUIRED

include_directories

${

PCL_INCLUDE_DIRS

}

link_directories

${

PCL_LIBRARY_DIRS

}

add_definitions

${

PCL_DEFINITIONS

}

add_executable

xyz2pcd xyz2pcd。cpp

target_link_libraries

xyz2pcd

${

PCL_LIBRARIES

}

建立build資料夾按照前一節課所講的內容進行編譯。

執行下列命令。/xyz2pcd smalldata。xyz smalldata。pcd

其中smalldata。xyz為待轉換的檔案,smalldata。pcd為目標檔案。如果沒有資料可以將下列資料存為smalldata。xyz與xyz2pcd 放在同一級目錄下。請注意這裡intensity這個欄位並沒有用到,如果想用intensity欄位,請自定義資料型別。

測試資料

20。623 40。276 -1。999 -1031 127 141 154

20。362 40。375 -2。239 -941 130 141 159

20。36 40。376 -2。402 -1083 139 151 165

20。374 40。367 -2。405 -1122 131 147 163

20。372 40。366 -2。405 -1165 132 145 161

20。375 40。364 -2。404 -1036 133 149 165

20。358 40。371 -2。405 -1137 139 151 165

20。359 40。374 -2。404 -1086 139 151 165

20。354 40。375 -2。404 -1106 139 148 163

20。356 40。374 -2。404 -1059 139 151 165

20。359 40。374 -2。399 -1059 138 150 166

20。359 40。374 -2。395 -1014 138 150 166

20。354 40。375 -2。395 -1042 138 150 166

20。356 40。374 -2。396 -1050 138 150 166

20。354 40。374 -2。393 -1031 137 149 165

20。356 40。374 -2。393 -1045 137 149 165

20。359 40。374 -2。392 -957 137 149 165

20。374 40。365 -2。402 -1093 133 149 165

20。375 40。367 -2。401 -1096 133 149 165

20。374 40。367 -2。399 -1158 133 149 165

20。371 40。367 -2。399 -1093 133 146 162

20。365 40。371 -2。405 -1283 133 145 159

20。361 40。371 -2。404 -1211 133 145 159

20。362 40。372 -2。405 -1314 133 145 159

20。36 40。373 -2。405 -1213 139 151 165

20。361 40。375 -2。404 -1116 139 151 165

20。363 40。375 -2。402 -1063 133 145 159

20。367 40。371 -2。405 -1231 133 145 159

20。365 40。375 -2。405 -1311 133 145 159

20。367 40。372 -2。404 -1173 133 145 159

至此執行成功,結果如下:

PCL入門系列三——PCL進行資料讀寫

或許大家會感到疑惑為什麼RGB轉成了一個大整數?這裡為了方便儲存使用了移位拼接的方法。下節課我們繼續討論視覺化等更深入的內容。

參考材料:

http://www。

pointclouds。org/documen

tation/tutorials/writing_pcd。php#writing-pcd

http://

pointclouds。org/documen

tation/tutorials/pcd_file_format。php