-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathdetectRoof.m
231 lines (184 loc) · 7.17 KB
/
detectRoof.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
function [ClustersOut] = detectRoof(ptCloud,h)
% DETECTROOF
%
% Funtion to detect roof vertices from an aerial point cloud.
%
% Inputs:
% - ptCloud: point cloud data
% - h: mean curvature of each point. Compute using Beksi (2014). If this
% value is not provided, the function will compute it (may take a while)
%
% Outputs:
% - ClustersOut: a struct containing the planar surfaces of the roof.
%
% (c) Arnadi Murtiyoso (INSA Strasbourg - ICube-TRIO UMR 7357)
format long g
normals=ptCloud.Normal;
%% COMPUTE CURVATURE IF NOT PROVIDED
if nargin<2
tree = KDTreeSearcher(ptCloud.Location);
radius = 1;
f=waitbar(0,'Computing curvatures...');
c1=zeros(1,ptCloud.Count);
c2=zeros(1,ptCloud.Count);
h=zeros(1,ptCloud.Count);
for i=1:ptCloud.Count
query = [ptCloud.Location(i,1) ptCloud.Location(i,2) ...
ptCloud.Location(i,3)];
[c1(i), c2(i)] = estimateCurvatures(normals, tree, query, radius);
h(i) = (c1(i)+c2(i))/2; %Mean curvature; if = 0 means point is plane
waitbar(i/ptCloud.Count,f)
end
close(f)
end
% if needed, save the results
%savefile = strcat('curvature_detectRoof.mat');
%save(savefile, 'h');
%% NORMALS-BASED REGION GROWING
% detect the roof patches and store it in a struct
% see also: regiongrowingnormalsOct.m
[RegionsOct]=regiongrowingnormalsOct(ptCloud,normals,h);
%% DISTANCE-BASED REGION GROWING
% this part is used to clean up the resulting patches from noises
% extract patch names
nbRegions=numel(fieldnames(RegionsOct));
f=fieldnames(RegionsOct);
% extract number of patches
RegionCount=nbRegions;
for i=1:nbRegions
RegionName = f{i};
labels=pcsegdist(RegionsOct.(RegionName),0.2);
uniqueLabels=unique(labels);
[nbUniqueLabels,~]=size(uniqueLabels);
for j=1:nbUniqueLabels
[~, boolInd]=ismember(labels,uniqueLabels(j,1));
indexInd=find(boolInd);
newRegion=select(RegionsOct.(RegionName),indexInd);
% if the resulting patch consists of less than 1000 points, delete
% it and do not use for further processing
if newRegion.Count>1000
RegionCount=RegionCount+1;
newRegionName=strcat('Region',string(RegionCount));
RegionsOct.(newRegionName)=newRegion;
else
continue
end
end
RegionsOct=rmfield(RegionsOct,RegionName);
end
%% EXTRACT THE PLANE MESHES
f=fieldnames(RegionsOct);
nbRegions=numel(fieldnames(RegionsOct));
objectList=strings(nbRegions,1);
for i=1:nbRegions
RegionName = f{i};
thisPtCloud=RegionsOct.(RegionName);
inPtCloud = thisPtCloud;
% perform RANSAC to extract the plane
[model,inlierIndices,~] = pcfitplane(inPtCloud,0.2);
plane = select(inPtCloud,inlierIndices);
%convert the Matlab surface model from pcfitplane to geom3d format
a=model.Parameters(1,1);
b=model.Parameters(1,2);
c=model.Parameters(1,3);
d=model.Parameters(1,4);
P1 = [plane.Location(1,1) plane.Location(1,2) 0];
P1(1,3) = (-d-(a*P1(1,1))-(b*P1(1,2)))/c;
planeGeom3d = createPlane(P1, model.Normal); %plane in geom3d format
%Perform 3D Delaunay Triangulation on the point cloud
TR = delaunayTriangulation(double(plane.Location(:,1)),...
double(plane.Location(:,2)),double(plane.Location(:,3)));
%extract the surface of the Delaunay mesh TR (otherwise stored in
%tetrahedral form)
[~,xf] = freeBoundary(TR);
[size_xf,~] = size(xf);
%project the points to the mathematical surface model to get 1 surface
%in the form of the RANSAC plane
prjtdPts = zeros(size_xf,3);
for j=1:size_xf
%create a 3D ray for each foint in the mesh surface
point = [xf(j,1), xf(j,2), xf(j,3)];
%intersect the 3D ray with the mathematical surface
prjtdPt = projPointOnPlane(point, planeGeom3d);
%store the coordinates of the intersection
prjtdPts(j,1) = prjtdPt(1,1);
prjtdPts(j,2) = prjtdPt(1,2);
prjtdPts(j,3) = prjtdPt(1,3);
end
%perform 3D Delaunay Triangulation on the projected points
TR2 = delaunayTriangulation(double(prjtdPts(:,1)),...
double(prjtdPts(:,2)),double(prjtdPts(:,3)));
%extract the surface of the Delaunay mesh of the projected points
[~,xf2] = freeBoundary(TR2);
%further smoothing here
[xf3,F2] = meshParallelSimp(xf2, 0.1);
%store the mesh properties in a struct
Mesh.Vertices = xf3;
Mesh.Faces = F2;
%create a Matlab alphashape
shp = alphaShape(xf2(:,1),xf2(:,2),xf2(:,3),inf);
%store all of this stuff in the another struct called Object
objectList(i,1) = strcat('Object',num2str(i));
Object.PtCloud = plane;
Object.AlphaShp = shp;
Object.Mesh = Mesh;
Object.PlaneGeom3D = planeGeom3d;
Object.PlaneMatlab = model;
Clusters.(objectList{i}) = Object;
end
%% PERFORM 'SNAPPING'
SnappedCluster = meshSnap(Clusters,5);
%% REORDER THE VERTICE LIST TO COMPLY WITH CITYGML
% CityGML vertices must be sorted clockwise from barycentre
figure('name','Detected Roof Vertices - Patch/Polygon')
for i=1:nbRegions
% name of current object
ObjectName = objectList{i};
% retrieve the object
thisObject=SnappedCluster.(ObjectName);
% retrieve the vertices list
thisVertexList=thisObject.Mesh.Vertices;
% initiate a list of bearing angles
[nbVertices,~]=size(thisVertexList);
listBearings=zeros(nbVertices,2);
listBearings2=zeros(nbVertices,2);
% compute the Principal Component Analysis
coeffs = pca(thisVertexList);
% transform the point cloud to PC-aligned system i.e. planar surface
TransformPCA = thisVertexList*coeffs(:,1:3);
% compute the barycenter of the vertices
barycenter=[mean(TransformPCA(:,1)),mean(TransformPCA(:,2)), ...
mean(TransformPCA(:,3))];
% take only the XY (assume the points are coplanar)
Xa=barycenter(1);
Ya=barycenter(2);
for j=1:nbVertices
%indexing
listBearings(j,1)=j;
% retrieve the vertex's XY
Xb=TransformPCA(j,1);
Yb=TransformPCA(j,2);
% compute the bearing from the barycenter to each vertex
[alpha, ~] = bearing_surv(Xa,Ya,Xb,Yb);
listBearings(j,2)=alpha;
end
% sort the vertice list according to this bearing
[sorted,index]=sort(listBearings(:,2),'ascend');
listBearings2(:,1)=index;
listBearings2(:,2)=sorted;
% retrieve the original points, but sorted
sortedVertices=zeros(nbVertices,3);
for j=1:nbVertices
sortedVertices(j,:)=thisVertexList(listBearings2(j,1),:);
end
% update the result with the new sorted values
SnappedCluster.(ObjectName).Mesh.Vertices=sortedVertices;
patch(sortedVertices(:,1),sortedVertices(:,2),sortedVertices(:,3),'g')
view(3)
hold on
pcshow(SnappedCluster.(ObjectName).PtCloud)
hold on
end
% copy the results to the output struct
ClustersOut=SnappedCluster;
end