Skip to content

Commit ca49798

Browse files
committed
Publish the code.
1 parent 7d69118 commit ca49798

26 files changed

+489899
-1
lines changed

.DS_Store

8 KB
Binary file not shown.

README.md

+30-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,30 @@
1-
# feature-graph-learning
1+
# Feature Graph Learning for 3D Point Cloud Denoising
2+
3+
This repository is the official MATLAB implementation of the following paper:
4+
5+
Wei Hu, Xiang Gao, Gene Cheung, Zongming Guo, "Feature Graph Learning for 3D Point Cloud Denoising," IEEE Transactions on Signal Processing (TSP), February, 2020.
6+
7+
# Usage
8+
9+
This code is tested on `MATLAB R2018a` under Windows 10 x64 platform. You can also run the following command in the MATLAB command line on Linux/macOS:
10+
11+
```
12+
MATLAB> main
13+
```
14+
15+
**Note:** you can directly run the script from the MATLAB command window.
16+
17+
To support more datasets and different noise levels, you can run the `add_gaussian_noise` script to generate your own data and place them in the `models` folder.
18+
19+
# Reference
20+
21+
Please cite our paper if you use any part of the code from this repository:
22+
23+
```
24+
@article{hu2019feature,
25+
title={Feature Graph Learning for 3D Point Cloud Denoising},
26+
author={Hu, Wei and Gao, Xiang and Cheung, Gene and Guo, Zongming},
27+
journal={IEEE Transactions on Signal Processing (TSP)},
28+
year={2020}
29+
}
30+
```

add_gaussian_noise.m

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
function add_gaussian_noise(cloud, shapename, variance)
2+
pts = cloud.Location;
3+
SAMPLING_SET = 200;
4+
srf = struct('X', pts(:, 1), 'Y', pts(:, 2), 'Z', pts(:, 3));
5+
% estimate diameter
6+
ifps = fps_euc(srf, SAMPLING_SET);
7+
Dfps = pdist2(pts(ifps, :), pts(ifps, :));
8+
diam = sqrt(max(Dfps(:)));
9+
10+
sig = diam * variance;
11+
new_x = pts + randn(size(pts)) * sig;
12+
new_pc = pointCloud(new_x);
13+
new_pc.Color = cloud.Color;
14+
new_pc.Normal = cloud.Normal;
15+
16+
ply_filename = [shapename, '_gaussian_noise_', num2str(variance)];
17+
pcwrite(new_pc, ['models/noise/', ply_filename, '.ply']);
18+
disp(['models/noise/', ply_filename, '.ply saved.']);
19+
end

addnoise.m

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
%% Create currupt version of the model
2+
% Takes a shape filename (assumed to be of the name XXX_GT.mat), and
3+
% creates noisy versions of it.
4+
% options is a structure with the following optional fields:
5+
% 'GaussianNoise' - a list of standard deviations for Gaussian additive corrupted versions of the surface. The standard deviation is expressed as a fraction of the surface' diameter
6+
% 'ShotNoiseProbability','ShotNoiseNumber','ShotNoiseMagnitude' - parameter lists for shot noise (i.e point are replaced by arbitrary 3D values. Parameter lists should be of equal size
7+
% 'DepthShotNoiseProbability','DepthShotNoiseMagnitude','DepthShotNoiseNumber' - for depth shot noise (i.e point are perturbed in the direction of the ray leading from the range scanner to the object
8+
% 'CameraCenter' - assumed camera location, for depth noise
9+
10+
function X = addnoise(X_gt, shapename, options)
11+
if (exist('options','var')==0)
12+
options=[];
13+
end
14+
% defaults=struct('GaussianNoise',[0.00125 0.0025 0.005 0.01 ],...
15+
% 'ShotNoiseProbability',[0 0],...
16+
% 'ShotNoiseNumber',[10 100],...
17+
% 'ShotNoiseMagnitude',[1 1]*0.2,...
18+
% 'DepthShotNoiseProbability',0,...
19+
% 'DepthShotNoiseMagnitude',0.2,...
20+
% 'DepthShotNoiseNumber',100,...
21+
% 'CameraCenter',[ 0 0 -6]); % optical center for GIP camera
22+
% options=incorporate_defaults(options,defaults);
23+
24+
SAMPLING_SET=200;
25+
srf=struct('X',X_gt(:,1),'Y',X_gt(:,2),'Z',X_gt(:,3));
26+
% estimate diameter
27+
ifps=fps_euc(srf,SAMPLING_SET);
28+
Dfps=pdist2(X_gt(ifps,:),X_gt(ifps,:));
29+
diam=sqrt(max(Dfps(:)))
30+
31+
% % Create surface with holes
32+
% for i=1:length(options.DepthShotNoiseProbability)
33+
% prb=options.DepthShotNoiseProbability(i);
34+
% X=X_gt;
35+
% if (prb>0)
36+
% idx=find(rand(size(X_gt(:,1)))<prb);
37+
% prb_name=num2str(prb);
38+
% else
39+
% idx=randperm(size(X_gt,1),options.DepthShotNoiseNumber(i));
40+
% prb_name=num2str(options.DepthShotNoiseNumber(i));
41+
% end
42+
% dr=(X_gt(idx,:)-repmat(options.CameraCenter(:)',[numel(idx),1]));
43+
% ndr=sqrt(sum(dr.^2,2)+1e-6);
44+
% X(idx,:)=X_gt(idx,:)+(repmat(randn(size(X_gt(idx,1))),[1 3]).*bsxfun(@rdivide,dr,ndr)*diam*options.DepthShotNoiseMagnitude(i));
45+
% mat_filename=[shapename,'_depth_shot_noise_',prb_name,'.mat'];
46+
% save(mat_filename,'X');
47+
% ply_filename=[shapename,'_depth_shot_noise_',prb_name,'.ply'];
48+
% write_ply_only_points(X,ply_filename);
49+
% end
50+
51+
% Create a Gaussian noised version of the surface
52+
%for i=1:length(options.GaussianNoise)
53+
sig=diam*options.GaussianNoise
54+
X=X_gt+randn(size(X_gt))*sig;
55+
%mat_filename=[shapename,'_gaussian_noise_',num2str(sig),'.mat'];
56+
%save(mat_filename,'X');
57+
ply_filename=[shapename,'_gaussian_noise_',num2str(options.GaussianNoise)];
58+
%ply_filename=[shapename,'_gaussian_noise_',num2str(sig),'.ply'];
59+
%#####################################
60+
pcwrite(pointCloud(X), ['models/noise/' ply_filename '.ply']);
61+
%####################################
62+
%end
63+
64+
% % Create a 3D shot noise version of the surface
65+
% for i=1:length(options.ShotNoiseProbability)
66+
% prb=options.ShotNoiseProbability(i);
67+
% X=X_gt;
68+
% if (prb>0)
69+
% idx=find(rand(size(X_gt(:,1)))<prb);
70+
% prb_name=num2str(prb);
71+
% else
72+
% idx=randperm(size(X_gt,1),options.ShotNoiseNumber(i));
73+
% prb_name=num2str(options.ShotNoiseNumber(i));
74+
% end
75+
% X(idx,:)=X_gt(idx,:)+randn(size(X_gt(idx,:)))*diam*options.ShotNoiseMagnitude(i);
76+
% mat_filename=[shapename,'_shot_noise_',prb_name,'.mat'];
77+
% save(mat_filename,'X');
78+
% ply_filename=[shapename,'_shot_noise_',prb_name,'.ply'];
79+
% write_ply_only_points(X,ply_filename);
80+
% end
81+
82+
end

block_gradient_descent.m

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
function [M, weights, cur_val, min_iter] = block_gradient_descent( ...
2+
patch_s, patch_t, use_normal, use_cosine, use_color, C, log_file)
3+
4+
if ~exist('use_normal', 'var')
5+
use_normal = true;
6+
end
7+
if ~exist('use_cosine', 'var')
8+
use_cosine = false;
9+
end
10+
if ~exist('use_color', 'var')
11+
use_color = false;
12+
end
13+
if ~exist('C', 'var')
14+
C = 5.0;
15+
end
16+
if ~exist('log_file', 'var')
17+
log_file = './log.txt';
18+
end
19+
20+
indices = 1:size(patch_s, 1);
21+
22+
% initialize variables
23+
diff = patch_s(indices, 4:6) - patch_t(indices, 4:6); % 9
24+
if use_normal && use_cosine
25+
norm_s = sum(patch_s(indices, 7:9).^2, 2).^0.5;
26+
norm_t = sum(patch_t(indices, 7:9).^2, 2).^0.5;
27+
cos_diff = sum(patch_s(indices, 7:9) .* patch_t(indices, 7:9), 2);
28+
cos_diff = 1.0 - abs(cos_diff ./ norm_s ./ norm_t);
29+
diff = [diff, cos_diff];
30+
elseif use_normal
31+
diff = [diff, patch_s(indices, 7:9) - patch_t(indices, 7:9)];
32+
end
33+
if use_color
34+
diff = [diff, patch_s(indices, 10:12) - patch_t(indices, 10:12)];
35+
end
36+
dist = sum(abs(patch_s(indices, 1:3) - patch_t(indices, 1:3)).^2, 2);
37+
38+
[node_dim, feat_dim] = size(diff);
39+
40+
max_algo_iter = 10;
41+
max_diag_iter = 10;
42+
max_block_iter = 10;
43+
step = 1e-6;
44+
45+
M = eye(feat_dim) * C / feat_dim;
46+
min_val = Inf;
47+
min_M = M;
48+
min_iter = 1;
49+
cur_val = zeros(max_algo_iter, 1);
50+
diag_val = zeros(max_diag_iter, 1);
51+
52+
for iter = 1 : max_algo_iter
53+
% optimize block
54+
for f = 1 : feat_dim
55+
M21 = [M(1:f-1, f); M(f+1:end, f)];
56+
M22 = [M(1:f-1, 1:f-1), M(1:f-1, f+1:end); ...
57+
M(f+1:end, 1:f-1), M(f+1:end, f+1:end)];
58+
g1 = diff(:, f);
59+
g2 = [diff(:, 1:f-1), diff(:, f+1:end)];
60+
theta_min = eigs(M22, 1, 'smallestabs');
61+
dist_hat = exp(-sum(g2*M22.*g2, 2)) .* dist;
62+
m11 = M(f, f);
63+
for block_iter = 1 : max_block_iter
64+
exp_obj = exp(-(g1.^2*m11 + 2.0*sum(g1*M21'.*g2,2)));
65+
grad = -2.0 * sum(g1.*g2.*exp_obj.*dist_hat);
66+
M21 = M21 - step * grad';
67+
if norm(M21, 2) > sqrt(theta_min * m11)
68+
M21 = M21 * sqrt(theta_min * m11) / norm(M21, 2);
69+
end
70+
end
71+
M(1:f-1, f) = M21(1:f-1);
72+
M(f+1:end, f) = M21(f:end);
73+
M(f, 1:f-1) = M21(1:f-1)';
74+
M(f, f+1:end) = M21(f:end)';
75+
end
76+
77+
% optimize diagonal
78+
for diag_iter = 1 : max_diag_iter
79+
diag_M = diag(M);
80+
dist_tilde = exp(-sum(diff * (M - diag(diag_M)) .* diff, 2)) .* dist;
81+
row_M = sum(abs(M), 2) - abs(diag_M);
82+
83+
grad_M = zeros(feat_dim, 1);
84+
temp = 0;
85+
for f = 1 : feat_dim
86+
temp = temp + diff(:, f).^2 * diag_M(f);
87+
end
88+
temp = exp(-temp);
89+
for f = 1 : feat_dim
90+
grad_M(f) = -sum(temp .* diff(:, f).^2 .* dist_tilde);
91+
end
92+
diag_M = diag_M - step * grad_M;
93+
temp_diag = min(max(diag_M, row_M), C);
94+
if sum(temp_diag) <= C
95+
diag_M = temp_diag;
96+
else
97+
diag_M = diag_M - (sum(diag_M) - C) / feat_dim;
98+
diag_M = min(max(diag_M, row_M), C);
99+
end
100+
M(1:feat_dim+1:end) = diag_M;
101+
102+
weights = exp(-sum((diff * M) .* diff, 2));
103+
diag_val(diag_iter) = sum(weights .* dist);
104+
end
105+
106+
weights = exp(-sum((diff * M) .* diff, 2));
107+
iter_val = sum(weights .* dist);
108+
cur_val(iter) = iter_val;
109+
if iter_val < min_val
110+
min_val = iter_val;
111+
min_iter = iter;
112+
min_M = M;
113+
end
114+
115+
write_log(log_file, [' ', int2str(iter), '/', int2str(max_algo_iter), ' done'], false);
116+
end
117+
118+
M = min_M;
119+
weights = exp(-sum((diff * M) .* diff, 2));
120+
121+
end

bool2str.m

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function result = bool2str(value)
2+
if value
3+
result = 'true';
4+
else
5+
result = 'false';
6+
end
7+
end

build_patches.m

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function [result] = build_patches(cloud, centers, size)
2+
result = zeros(length(centers), size);
3+
for i = 1 : length(centers)
4+
[indices, ~] = findNearestNeighbors(cloud, centers(i, :), size);
5+
result(i, :) = indices';
6+
end
7+
end

comput_total_variations.m

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function [result] = comput_total_variations(cloud_normal, patches)
2+
[rows, ~] = size(patches);
3+
result = zeros(rows, 1);
4+
for index = 1 : rows
5+
patch = patches(index, :);
6+
sum_angle = 0.0;
7+
pair_count = 0;
8+
for i = 1 : length(patch)
9+
na = cloud_normal(patch(i), :);
10+
for j = 1 : length(patch)
11+
if patch(i) == patch(j)
12+
continue;
13+
end
14+
nb = cloud_normal(patch(j), :);
15+
sum_angle = sum_angle + (1.0 - abs(na * nb' / (norm(na) * norm(nb))));
16+
pair_count = pair_count + 1;
17+
end
18+
end
19+
sum_angle = pair_count;
20+
result(index) = sum_angle / pair_count;
21+
end
22+
end

compute_principal_normals.m

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function [result] = compute_principal_normals(cloud_normal, patches)
2+
[rows, ~] = size(patches);
3+
result = zeros(rows, 3);
4+
for i = 1 : rows
5+
patch_normal = cloud_normal(patches(i, :), :);
6+
sum_normal = sum(patch_normal);
7+
normalized = norm(sum_normal);
8+
if normalized < 1e-8
9+
normalized = 1.0;
10+
end
11+
result(i, :) = sum_normal / normalized;
12+
end
13+
end

compute_snr.m

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function [snr] = compute_snr(cloud, mse)
2+
s = mean(sum(cloud.Location .^ 2, 2) .^ 0.5);
3+
snr = 10.0 * log(s / mse);
4+
end

0 commit comments

Comments
 (0)