PCL實(shí)現(xiàn)了大量點(diǎn)云相關(guān)的通用算法和高效數(shù)據(jù)結(jié)構(gòu),涉及到點(diǎn)云獲取、濾波、分割、配準(zhǔn)、檢索、特征提取、識(shí)別、追蹤、曲面重建、可視化等。主要應(yīng)用于機(jī)器人研究應(yīng)用領(lǐng)域,隨著各個(gè)算法模塊的積累,整合而成。
基本接口程序:
1、創(chuàng)建處理對(duì)象:過(guò)濾、特征估計(jì)、分割等。
2、使用setInputCloud通過(guò)輸入點(diǎn)云數(shù)據(jù),處理模塊。
3、設(shè)置算法相關(guān)參數(shù)。
4、調(diào)用計(jì)算(或過(guò)濾、分割等)得到輸出。
被分成了無(wú)數(shù)個(gè)小的代碼庫(kù),使其模塊化:
libpcl filters:如采樣、去離群、特征提取、擬合估計(jì)等數(shù)據(jù)實(shí)現(xiàn)過(guò)濾器。
libpcl?features:實(shí)現(xiàn)多種三維特征,如曲面法線、曲率、邊界點(diǎn)估計(jì)、矩不變量、主曲率,PFH和FPFH特征,旋轉(zhuǎn)圖像、積分圖像,NARF描述子,RIFT,相對(duì)標(biāo)準(zhǔn)偏差,數(shù)據(jù)強(qiáng)度的篩選等等。
libpcl?I/O:實(shí)現(xiàn)數(shù)據(jù)的輸入和輸出操作,例如點(diǎn)云數(shù)據(jù)文件(PCD)的讀寫(xiě)。
libpcl?segmentation:實(shí)現(xiàn)聚類(lèi)提取,如通過(guò)采樣一致性方法對(duì)一系列參數(shù)模型(如平面、柱面、球面、直線等)進(jìn)行模型擬合點(diǎn)云分割提取,提取多邊形棱鏡內(nèi)部點(diǎn)云等等。
libpcl?surface:實(shí)現(xiàn)表面重建技術(shù),如網(wǎng)格重建、凸包重建、移動(dòng)最小二乘法平滑等。
libpcl?register:實(shí)現(xiàn)點(diǎn)云配準(zhǔn)方法,如ICP等;
libpclkeypoints:實(shí)現(xiàn)不同的關(guān)鍵點(diǎn)的提取方法,這可以用來(lái)作為預(yù)處理步驟,決定在哪兒提取特征描述符。
libpcl?range:實(shí)現(xiàn)支持不同點(diǎn)云數(shù)據(jù)集生成的范圍圖像。
PCD(點(diǎn)云)文件格式
已經(jīng)有的多種點(diǎn)云數(shù)據(jù)格式:
1、PLY:一種多邊形文件格式,
2、STL:是3D?Systems公司創(chuàng)建的,主要應(yīng)用于CAD和CAM領(lǐng)域。
3、OBJ:幾何學(xué)上定義的文件格式。
4、X3D:符合ISO標(biāo)準(zhǔn)的基于XML的文件格式,表示3D計(jì)算機(jī)圖形數(shù)據(jù)。
每個(gè)PCD文件包含一個(gè)文件頭,它確定和聲明文件中存儲(chǔ)的點(diǎn)云數(shù)據(jù)的某種特性。PCD文件頭必須用ASCII碼來(lái)編碼。PCD文件中指定的每一個(gè)文件頭字段以及ascii點(diǎn)數(shù)據(jù)都用一個(gè)新行分開(kāi)。
VERSION -指定PCD文件版本
FIELDS -指定一個(gè)點(diǎn)可以有的每一個(gè)維度和字段的名字。例如:
? ? ? ?FIELDS x y z? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?# XYZ data
? ? ? ?FIELDS x y z rgb? ? ? ? ? ? ? ? ? ? ? ? ? ?# XYZ + colors
? ? ? ?...
SIZE -用字節(jié)數(shù)指定每一個(gè)維度的大小。例如:
? ? ? ?unsigned char/char has 1 byte
? ? ? ?unsigned short/short has 2 bytes?
TYPE -用一個(gè)字符指定一個(gè)維度的類(lèi)型,如:
? ? ? ?I -表示有符號(hào)類(lèi)型int8(char)、int16(short)、和int32(int);
? ? ? ?U -表示無(wú)符號(hào)類(lèi)型;
? ? ? ?F -表示浮點(diǎn)類(lèi)型。
COUNT -指定每一個(gè)維度包含的元素?cái)?shù)目。例如,x這個(gè)數(shù)據(jù)通常有一個(gè)元素,但是像VFH這樣的特征描述子就有308個(gè)。實(shí)際上在給每一點(diǎn)引入n維直方圖描述符的方法,把它們當(dāng)做單個(gè)的連續(xù)存儲(chǔ)塊。默認(rèn)情況下,如果沒(méi)有COUNT,所有維度的數(shù)目被設(shè)置成1.
WIDTH -用點(diǎn)的數(shù)量表示點(diǎn)云數(shù)據(jù)集的寬度。根據(jù)是有序點(diǎn)云還是無(wú)序點(diǎn)云,WIDTH有兩層解釋?zhuān)?/p>
(1)它能確定無(wú)序數(shù)據(jù)集的點(diǎn)云中點(diǎn)的個(gè)數(shù)(如下面的POINTS一樣);
(2)它能確定有序點(diǎn)云數(shù)據(jù)集的寬度(一行中點(diǎn)的數(shù)目)。(如圖像的結(jié)構(gòu),數(shù)據(jù)分為行和列。這種點(diǎn)云數(shù)據(jù)優(yōu)勢(shì)在于,預(yù)先了解相鄰點(diǎn)的關(guān)系——類(lèi)似于像素點(diǎn),鄰域操作更加高效,這樣就能加速計(jì)算并降低PCL中某些算法的成本)
HEIGHT -用電的數(shù)目表示點(diǎn)云數(shù)據(jù)集的高度。與WIDTH相似,HEIGHT也有兩層解釋。
(1)有序點(diǎn)云數(shù)據(jù)集的高度(行的總數(shù))。
(2)對(duì)于無(wú)序數(shù)據(jù)集被設(shè)置為1(便于檢查一個(gè)數(shù)據(jù)集是有序還是無(wú)序)。
VIEWPOINT -指定數(shù)據(jù)集中點(diǎn)云的獲取視點(diǎn)??赡茉诓煌鴺?biāo)系轉(zhuǎn)換是應(yīng)用,在輔助獲取其他特征時(shí)也比較有用,例如曲面法線,在判斷方向一致性時(shí),需要知道視點(diǎn)的方位,視點(diǎn)信息被指定為平移+四元數(shù)。默認(rèn)值是:
VIEWPOINT 0 0 0 1 0 0 0
POINTS -指定點(diǎn)云中點(diǎn)的總數(shù)。(開(kāi)始顯得多余,但不能省去)。
DATA -指定存儲(chǔ)點(diǎn)云數(shù)據(jù)的數(shù)據(jù)類(lèi)型。目前支持的較多為ascii和二進(jìn)制。
從DATA的下一個(gè)字節(jié)就被看作為點(diǎn)云的數(shù)據(jù)部分了。
注意:PCD文件的文件頭部必須以上面的順序精確指定,也就是如下順序:
VERSION、FIELDS、SIZE、TYPE、COUNT、WIDTH、HEIGHT、VIEWPOINT、POINTS、DATA之間用換行隔開(kāi)。
數(shù)據(jù)存儲(chǔ)類(lèi)型
1、ASCII形式,每一點(diǎn)占據(jù)一個(gè)新行:
p_1
p_2
...
p_n
從PCL1.0.1版本開(kāi)始,用nan表示NaN。
2、二進(jìn)制形式,是數(shù)組pcl::PointCloud.points的一份完整拷貝。
相比較其他文件格式,PCD有以下幾個(gè)明顯的優(yōu)勢(shì):
》存儲(chǔ)和處理有序點(diǎn)云數(shù)據(jù)集的能力——這一點(diǎn)對(duì)于實(shí)時(shí)應(yīng)用,例如增強(qiáng)現(xiàn)實(shí)、機(jī)器人學(xué)等領(lǐng)域十分重要;
》二進(jìn)制mmap/munmap數(shù)據(jù)類(lèi)型是把數(shù)據(jù)下載和存儲(chǔ)到磁盤(pán)上最快的方法;
》存儲(chǔ)不同的數(shù)據(jù)類(lèi)型(支持所有的基本類(lèi)型:char,short,int,float,double)——使得點(diǎn)云數(shù)據(jù)在存儲(chǔ)和處理過(guò)程中適應(yīng)性強(qiáng)并且高效,器重?zé)o效的點(diǎn)的通常存儲(chǔ)為NAN類(lèi)型;
》特征描述子的n維直方圖——對(duì)于3D識(shí)別和計(jì)算機(jī)視覺(jué)應(yīng)用十分重要。
并且PCD是PCL中的內(nèi)部文件格式,方便PCL的應(yīng)用。
PCD?主要點(diǎn)云數(shù)據(jù)結(jié)構(gòu)如下:
1、PointXYZ(float x,y,z)
union
{
? ? ? float data[4];
? ? ? struct
? ? ? {
? ? ? ? ? ? float x;
? ? ? ? ? ? float y;
? ? ? ? ? ? float z;
? ? ? };
};
PCL?的example通常如此定義點(diǎn)云:
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
訪問(wèn)某一個(gè)點(diǎn):cloud->points[i].x。如果想要往cloud中加入一個(gè)點(diǎn)的信息,則需要通過(guò)vector的push_back,加入到points這個(gè)變量里面。
pcl::PointXYZ point;
point.x =2.0f - y;
point.y =y;
point.z =z;
cloud.points.push_back(point);
如果有兩個(gè)坐標(biāo)相同的點(diǎn),則顏色信息以最后一個(gè)為準(zhǔn)。
2、PointXYZI
成員變量:float x,y,z,intensity;
在XYZ坐標(biāo)中加入了intensity的point類(lèi)型。理想情況下,這四個(gè)變量將新建單獨(dú)一個(gè)結(jié)構(gòu)體,但是point的大部分操作會(huì)把data[4]元素設(shè)置成0或1(用于變換),不能讓intensity直接與xyz在一個(gè)結(jié)構(gòu)體中,會(huì)被覆蓋。因此,需要構(gòu)建三個(gè)額外的浮點(diǎn)數(shù)使其符合存儲(chǔ)對(duì)齊要求,存儲(chǔ)效率較低,運(yùn)行效率較高。
union
{
? ? ? ?float data[4];
? ? ? ?struct
? ? ? ?{
? ? ? ? ? ? ? ?float x;
? ? ? ? ? ? ? ?float y;
? ? ? ? ? ? ? ?float z;
? ? ? ? };
};
union
{
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ?float intensity;
? ? ? ? };
? ? ? ? ?float data_c[4];
};
3、PointXYZRGBA
成員變量:float x,y,z;uint32_t rgba;
除了rgba信息被包含在一個(gè)整型變量中,其它的和PointXYZ類(lèi)似。
union
{
? ? ? ?float data[4];
? ? ? ?struct
? ? ? ?{
? ? ? ? ? ? ? ?float x;
? ? ? ? ? ? ? ?float y;
? ? ? ? ? ? ? ?float z;
? ? ? ? };
};
union
{
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ?unit32_t rgba;
? ? ? ? };
? ? ? ? ?float data_c[4];
};
4、PointXYZRGB
成員變量:float x,y,z,rgb;
除了rgb信息被包含在一個(gè)浮點(diǎn)型變量中,其它的和PointXYZRGB類(lèi)似。rgb數(shù)據(jù)被壓縮到一個(gè)浮點(diǎn)數(shù)里的原因是早期如此使用。
union
{
? ? ? ?float data[4];
? ? ? ?struct
? ? ? ?{
? ? ? ? ? ? ? ?float x;
? ? ? ? ? ? ? ?float y;
? ? ? ? ? ? ? ?float z;
? ? ? ? };
};
union
{
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ?float?rgb;
? ? ? ? };
? ? ? ? ?float data_c[4];
};
5、PointXY
成員變量:float x,y;
struct
{
? ? ? ? float x;
? ? ? ? float y;
};
6、InterestPoint
成員變量:float x,y,z,strength
除了strength表示關(guān)鍵點(diǎn)的強(qiáng)度的測(cè)量值,其它的和PointXYZ類(lèi)似。
union
{
? ? ? ?float data[4];
? ? ? ?struct
? ? ? ?{
? ? ? ? ? ? ? ?float x;
? ? ? ? ? ? ? ?float y;
? ? ? ? ? ? ? ?float z;
? ? ? ? };
};
union
{
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ?float?strength;
? ? ? ? };
? ? ? ? ?float data_c[4];
};
7、Normal
成員變量:float normal[3], curvature;
Normal結(jié)構(gòu)體表示給定點(diǎn)所在樣本曲面上的法線方向,以及對(duì)應(yīng)曲率的測(cè)量值。在PCL中對(duì)曲面法線的操作很普通,還是用第四個(gè)元素來(lái)占位。
union
{
? ? ? ? ?float data_n[4];
? ? ? ? ?float normal[3];
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float normal_x;
? ? ? ? ? ? ? ? float normal_y;
? ? ? ? ? ? ? ? float normal_z;
? ? ? ? ?};
};
union
{
? ? ? ?struct
? ? ? ?{
? ? ? ? ? ? ? ?float curvature;
? ? ? ? };
? ? ? ?float data_c[4];
}
8、PointNormal
成員變量:float x,y,z;float normal[3], curvature;
PointNormal是存儲(chǔ)XYZ數(shù)據(jù)的point結(jié)構(gòu)體,并且包括采樣點(diǎn)對(duì)應(yīng)法線和曲率。
union
{
? ? ? ? float data[4];
? ? ? ? struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float x;
? ? ? ? ? ? ? ? float y;
? ? ? ? ? ? ? ? float z;
? ? ? ? ?};
};
union
{
? ? ? ? ?float data_n[4];
? ? ? ? ?float normal[3];
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float normal_x;
? ? ? ? ? ? ? ? float normal_y;
? ? ? ? ? ? ? ? float normal_z;
? ? ? ? ? };
};
union
{
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ?float curvature;
? ? ? ? ? };
? ? ? ? ? float data_c[4];
};
9、PointXYZRGBNormal
成員變量:float x,y,z,rgb,normal[3],curvature;
union
{
? ? ? ? float data[4];
? ? ? ? struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float x;
? ? ? ? ? ? ? ? float y;
? ? ? ? ? ? ? ? float z;
? ? ? ? ?};
};
union
{
? ? ? ? ?float data_n[4];
? ? ? ? ?float normal[3];
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float normal_x;
? ? ? ? ? ? ? ? float normal_y;
? ? ? ? ? ? ? ? float normal_z;
? ? ? ? ? };
};
union
{
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ?float rgb;
? ? ? ? ? ? ? ?float curvature;
? ? ? ? ? };
? ? ? ? ? float data_c[4];
};
10、PointXYZINormal
成員變量:float x,y,z, intensity, normal[3], curvature;
PointXYZINormal存儲(chǔ)XYZ數(shù)據(jù)和強(qiáng)度值的point結(jié)構(gòu)體,并且包括曲面法線和曲率。
union
{
? ? ? ? float data[4];
? ? ? ? struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float x;
? ? ? ? ? ? ? ? float y;
? ? ? ? ? ? ? ? float z;
? ? ? ? ?};
};
union
{
? ? ? ? ?float data_n[4];
? ? ? ? ?float normal[3];
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float normal_x;
? ? ? ? ? ? ? ? float normal_y;
? ? ? ? ? ? ? ? float normal_z;
? ? ? ? ? };
};
union
{
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ?float intensity;
? ? ? ? ? ? ? ?float curvature;
? ? ? ? ? };
? ? ? ? ? float data_c[4];
};
11、PointXYZL
成員變量:float x,y,z, uin32_t label
12、PointXYZRGBL
成員變量:float x,y,z, rgb, uint32_t label
13、PointXYZLNormal
成員變量:float x,y,z, label, normal[3], curvature
14、PointXYZHSV
成員變量:float x,y,z, h,s,v
15、PointWithRange
成員變量:float x,y,z(union with float point[4]), range;
PointWithRange除了range包含從所獲得的視點(diǎn)到采樣點(diǎn)的距離測(cè)量值之外,其它與PointXYZI類(lèi)似。
union
{
? ? ? ? ?float data[4];
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ? ?float x;
? ? ? ? ? ? ? ? ?float y;
? ? ? ? ? ? ? ? ?float z;
? ? ? ? ? };
};
union
{
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ? float range;
? ? ? ? ?};
? ? ? ? ?float data_c[4];
};
16、PointWithViewpoint
成員變量:float x,y,z,vp_x,vp_y,vp_z;
PointWithViewpoint除了vp_x,vp_y,vp_z以三維點(diǎn)表示所獲得的視點(diǎn)之外,其它與PointXYZI一樣。
union
{
? ? ? ? float data[4];
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ? float x;
? ? ? ? ? ? ? ? float y;
? ? ? ? ? ? ? ? float z;
? ? ? ? ?};
};
union
{
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ? float vp_x;
? ? ? ? ? ? ? ? float vp_y;
? ? ? ? ? ? ? ? float vp_z;
? ? ? ? };
? ? ? ? float data_c[4];
};
17、Momentlnvariants
成員變量:float j1, j2, j3;
Momentlnvariants是一個(gè)包含采樣曲面上面片的三個(gè)不變矩的point類(lèi)型,描述面片上質(zhì)量分布情況。查看MomentlnvariantsEstimation以獲得更多信息。
struct
{
? ? ? ? ?float j1, j2, j3;
};
18、PrincipalRadiiRSD
成員變量:float r_min, r_max;
PrincipalRadiiRSD是一個(gè)包含曲面塊上兩個(gè)RSD半徑的point類(lèi)型,查看RSDEstimation以獲得更多信息。
struct
{
? ? ? ? ? float r_min, r_max;
};
19、Boundary
成員變量:uint8_t boundary_point;
存儲(chǔ)一個(gè)點(diǎn)是否位于曲面邊界上的簡(jiǎn)單point類(lèi)型。
struct
{
? ? ? ? ?unit8_t boundary_point;
};
20、PrincipalCurvatures
成員變量:float principal_curvature[3], pc1, pc2;
PrincipalCurvatures包含給定點(diǎn)主曲率的簡(jiǎn)單point類(lèi)型。查看PrincipalCurvaturesEstimation來(lái)獲得更多信息。
struct
{
? ? ? ? ?union
? ? ? ? ?{
? ? ? ? ? ? ? ? ?float principal_curvature[3];
? ? ? ? ? ? ? ? ?struct
? ? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? ? ? ? ?float principal_curvature_x;
? ? ? ? ? ? ? ? ? ? ? ? ? ?float principal_curvature_y;
? ? ? ? ? ? ? ? ? ? ? ? ? ?float principal_curvature_z;
? ? ? ? ? ? ? ? ?};
? ? ? ? ? };
? ? ? ? ? float pc1;
? ? ? ? ? float pc2;
};
21、PFHSignature125
成員變量:float pfh[125];
包含給定點(diǎn)的點(diǎn)特征直方圖的簡(jiǎn)單point類(lèi)型。
struct
{
? ? ? ? float?histogram[125];
};
22、FPFHSignature33
成員變量:float fpfh[33];
FPFHSignature33包含給定點(diǎn)的FPFH(快速點(diǎn)特征直方圖)的簡(jiǎn)單point類(lèi)型。
struct
{
? ? ? ? float histogram[308];
};
24、Narf36
成員變量:float x,y,z, roll, pitch, yaw; float descriptor[36];
包含給定點(diǎn)NARF(歸一化對(duì)齊半徑特征)的簡(jiǎn)單point類(lèi)型。
struct
{
? ? ? ? ?float x,y,z, roll, pitch, yaw;
? ? ? ? ?float descriptor[36];
};
25、BorderDescription
成員變量:int x, y; BorderTraits traits;
包含給定點(diǎn)邊界類(lèi)型的簡(jiǎn)單point類(lèi)型
struct
{
? ? ? ? ?int x, y;
? ? ? ? ?BorderTraitstraits;
};
26、IntensityGradient
成員變量:float gradient[3];
IntensityGradient包含給定點(diǎn)強(qiáng)度的梯度point類(lèi)型。
struct
{
? ? ? ? ?union
? ? ? ? ?{
? ? ? ? ? ? ? ? ?float gradient[3];
? ? ? ? ? ? ? ? ?struct
? ? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? ? ? ?float gradient_x;
? ? ? ? ? ? ? ? ? ? ? ? ?float gradient_y;
? ? ? ? ? ? ? ? ? ? ? ? ?float gradient_z;
? ? ? ? ? ? ? ? ? } ;
? ? ? ? ?};
};
27、Histogram
成員變量:float histogram[N];
用來(lái)存儲(chǔ)一般用途的n維直方圖。
template<int N>
struct Histogram
{
? ? ? ? ?float histogram[N];
};
28、PointWithScale
成員變量:float x, y, z, scale;
除了scale表示某點(diǎn)用于幾何操作的尺度,其他與PointXYZI一樣。
struct
{
? ? ? ? ?union
? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?float data[4];
? ? ? ? ? ? ? ? ? ?struct
? ? ? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? ? ? ? ? float x;
? ? ? ? ? ? ? ? ? ? ? ? ? ? float y;
? ? ? ? ? ? ? ? ? ? ? ? ? ? float z;
? ? ? ? ? ? ? ? ? ?};
? ? ? ? ? };
? ? ? ? ? ?float scale;
};
29、PointSurfel
成員變量:float x, y, z, normal[3], rgba, radius, confidence, curvature;
存儲(chǔ)XYZ坐標(biāo)、曲面法線、RGB信息、半徑、可信度和曲面曲率的復(fù)雜point類(lèi)型。
union
{
? ? ? ? float data[4];
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ? float x;
? ? ? ? ? ? ? ? float y;
? ? ? ? ? ? ? ? float z;?
? ? ? ? };
};
union
{
? ? ? ? float data_n[4];
? ? ? ? float normal[3];
? ? ? ? struct
? ? ? ? {
? ? ? ? ? ? ? ? ?float normal_x;
? ? ? ? ? ? ? ? ?float normal_y;
? ? ? ? ? ? ? ? ?float normal_z;
? ? ? ? };
};
union
{
? ? ? ? ?struct
? ? ? ? ?{
? ? ? ? ? ? ? ? ?uint32_trgba;
? ? ? ? ? ? ? ? ?float radius;
? ? ? ? ? ? ? ? ?float confidence;
? ? ? ? ? ? ? ? ?float curvature;
? ? ? ? ?};
? ? ? ? ? float data_c[4];
};
[1]http://www.pclcn.org/study/shownews.php?lang=cn&id=29
[2]http://www.pclcn.org/study/shownews.php?lang=cn&id=54
[3]https://blog.csdn.net/u013925378/article/details/83537844