本節課我們將瞭解到以下內容:
基本的PCL中的資料型別;
使用PCL進行簡單程式設計:寫檔案與讀檔案。
一、PCL庫基本資料型別
上一節課,我們使用PCL庫在本地寫入了一個名為test_pcd。pcd的檔案。我們劃分一下程式的任務步驟:
構造pcd檔案格式;
寫入檔案;
列印輸出。
步驟二和步驟三的內容屬於基本的C++知識。而步驟一中的pcd檔案格式是我們所不熟悉的,所以要先看一看pcd原始檔案。
在命令列中輸入:
more test_pcd。pcd
可以看到pcd檔案的內部結構如下圖所示。
三維點雲資料最簡單的形式是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
至此執行成功,結果如下:
或許大家會感到疑惑為什麼RGB轉成了一個大整數?這裡為了方便儲存使用了移位拼接的方法。下節課我們繼續討論視覺化等更深入的內容。
參考材料:
http://www。
pointclouds。org/documen
tation/tutorials/writing_pcd。php#writing-pcd
http://
pointclouds。org/documen
tation/tutorials/pcd_file_format。php