I have an unordered set of 2D points which represents the corners of a building. I need to connect them to get the outline of the building.
The points were obtained by c
So, I could find a solution using my below-mentioned idea.
Remarks: I added one missing point manually. And, I removed the two tiny corners at the bottom. Either, these must be four corners in total, or they can be treated as no corners at all.
I explicitly state that, because my idea incorporates the assumption, that corners typically have a 90 degree angle.
General approach
I realized that by using a for loop over all points, resulting in drawing all lines two times. Also, a lot of the calculations might by vectorized and moved from the loop. Optimization wasn't my intention right now. ;-)
% Point data of building corners; modified!
X = [285.400 372.267 397.067 408.133 382.471 379.533 199.412 195.267 184.385 168.643 157.533 174.500 108.533 99.333 150.733 184.800 138.105 179.474 218.278 232.133 267.714 306.929 312.143 357.733 421.333 431.000 371.867 364.533];
Y = [130.150 233.360 228.627 286.693 314.541 292.960 348.671 326.693 269.308 330.857 274.493 226.786 239.200 193.467 182.760 101.893 111.000 80.442 74.356 140.360 64.643 56.857 77.786 69.493 133.293 180.427 142.160 192.027];
% Place approximative center of building at (0, 0)
X = X - mean(X);
Y = Y - mean(Y);
C = [mean(X), mean(Y)];
% Sort points by angle with respect to center
[~, idx] = sort(atan2(X, Y));
% Rearrange points with respect to sorted angles
X = X(idx);
Y = Y(idx);
% Number of data points
n = numel(X);
% Calculate direction vectors for X and Y coordinates
dvX = repmat(X.', 1, n);
dvX = dvX - dvX.';
dvY = repmat(Y.', 1, n);
dvY = dvY - dvY.';
% Calculate distances
dst = sqrt(dvX.^2 + dvY.^2);
% Number of "neighbouring" points to be considered with respect to the order
nn = 8;
figure(1);
hold on;
% Center
plot(C(1), C(2), 'kx', 'MarkerSize', 15);
% Plain points
plot(X, Y, '.', 'MarkerSize', 15);
for k = 1:n
% Index
text(X(k) + 0.05, Y(k) + 0.05, num2str(k), 'FontSize', 12);
% Set up neighbourhood
nbh = mod([k-nn/2:k-1 k+1:k+nn/2], n);
nbh(nbh == 0) = n;
% Calculate angles and total distance arrays
ang = Inf(nn);
len = Inf(nn);
for ii = 1:nn
l = nbh(ii);
d1 = [dvX(k, l) dvY(k, l)];
for jj = ii+1:nn
m = nbh(jj);
d2 = [dvX(k, m) dvY(k, m)];
len(ii, jj) = dst(k, l) + dst(k, m);
ang(ii, jj) = abs(pi/2 - acos(dot(d1, d2) / (norm(d1) * norm(d2))));
end
end
% Find candidates with angle difference < 10 degree
cand = find(ang < pi/18);
% For these candidates, find the one with the shortest total distance
[~, I] = min(len(cand));
% Get corresponding indices
[I, J] = ind2sub([nn nn], cand(I));
cand = nbh([I J]);
% Lines
plot([X(k) X(cand(1))], [Y(k) Y(cand(1))], 'b', 'LineWidth', 1);
plot([X(k) X(cand(2))], [Y(k) Y(cand(2))], 'b', 'LineWidth', 1);
end
hold off;
Output image:
An approximative(!) solution is to determine the center of the contour described by the found points, and use atan2
with respect to the center to order the points by angle. See the following code snippet for visualization:
% Points
X = 2 * rand(1, 15) - 1;
Y = 2 * rand(1, 15) - 1;
% Center
C = [0, 0];
% Determine indices
[~, idx] = sort(atan2(X, Y));
figure(1);
hold on;
% Center
plot(C(1), C(2), 'kx', 'MarkerSize', 15);
% Plain points
plot(X, Y, '.', 'MarkerSize', 15);
% Indices and lines
for k = 1:numel(X)
text(X(idx(k)) + 0.05, Y(idx(k)) + 0.05, num2str(k), 'FontSize', 12);
if (k == numel(X))
plot([X(idx(k)) X(idx(1))], [Y(idx(k)) Y(idx(1))], 'b');
else
plot([X(idx(k)) X(idx(k+1))], [Y(idx(k)) Y(idx(k+1))], 'b');
end
end
hold off;
Gives the following output:
Although I'm sure, that a certain amount of the concavities will be correctly handled, I'm afraid, that it'll fail for the given example (especially the upper part). This is, because the image is not a perfect top view, thus angles are kinda "distorted".
Nevertheless, maybe the ordering can boost your minimum distance approach.