Skip to content

Commit 34a8e9a

Browse files
authored
Merge pull request #122 from tatsuya-ogawa/feature/bcpd
Fix BCPD convergence issues and align with the original C implementation
2 parents 674e791 + 150b3de commit 34a8e9a

1 file changed

Lines changed: 33 additions & 14 deletions

File tree

probreg/bcpd.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ class BayesianCoherentPointDrift:
3535
source (numpy.ndarray, optional): Source point cloud data.
3636
"""
3737

38-
def __init__(self, source=None):
38+
def __init__(self, source=None, use_debias=False):
3939
self._source = source
40+
self._use_debias = use_debias
4041
self._tf_type = None
4142
self._callbacks = []
4243

@@ -58,14 +59,20 @@ def expectation_step(self, t_source, target, scale, alpha, sigma_mat, sigma2, w=
5859
pmat = np.exp(-pmat / (2.0 * sigma2))
5960
pmat /= (2.0 * np.pi * sigma2) ** (dim * 0.5)
6061
pmat = pmat.T
61-
pmat *= np.exp(-(scale**2) / (2 * sigma2) * np.diag(sigma_mat) * dim)
62+
if self._use_debias and sigma_mat is not None:
63+
pmat *= np.exp(-(scale**2) / (2 * sigma2) * np.diag(sigma_mat) * dim)
6264
pmat *= (1.0 - w) * alpha
63-
den = w / target.shape[0] + np.sum(pmat, axis=1)
65+
bb_range = target.max(axis=0) - target.min(axis=0)
66+
bb_range = np.maximum(bb_range, 1e-10)
67+
volume = np.prod(bb_range)
68+
outlier_term = w / volume
69+
den = outlier_term + np.sum(pmat, axis=1)
6470
den[den == 0] = np.finfo(np.float32).eps
6571
pmat = np.divide(pmat.T, den)
6672

6773
nu_d = np.sum(pmat, axis=0)
6874
nu = np.sum(pmat, axis=1)
75+
nu = np.maximum(nu, 1e-20)
6976
nu_inv = 1.0 / np.kron(nu, np.ones(dim))
7077
px = np.dot(np.kron(pmat, np.identity(dim)), target.ravel())
7178
x_hat = np.multiply(px, nu_inv).reshape(-1, dim)
@@ -101,32 +108,39 @@ def registration(self, target, w=0.0, maxiter=50, tol=0.001):
101108

102109

103110
class CombinedBCPD(BayesianCoherentPointDrift):
104-
def __init__(self, source=None, lmd=2.0, k=1.0e20, gamma=1.0):
105-
super(CombinedBCPD, self).__init__(source)
111+
def __init__(self, source=None, lmd=2.0, k=1.0e20, gamma=1.0, beta=1.0, kernel="imq", use_debias=False):
112+
super(CombinedBCPD, self).__init__(source, use_debias)
106113
self._tf_type = tf.CombinedTransformation
107114
self.lmd = lmd
108115
self.k = k
109116
self.gamma = gamma
117+
self.beta = beta
118+
self.kernel = kernel
110119

111120
def _initialize(self, target):
112121
m, dim = self._source.shape
113-
self.gmat = mu.inverse_multiquadric_kernel(self._source, self._source)
122+
if self.kernel == "imq":
123+
self.gmat = mu.inverse_multiquadric_kernel(self._source, self._source, self.beta)
124+
elif self.kernel == "rbf":
125+
self.gmat = mu.rbf_kernel(self._source, self._source, self.beta)
126+
else:
127+
raise ValueError("Invalid kernel type: %s" % self.kernel)
114128
self.gmat_inv = np.linalg.inv(self.gmat)
115-
sigma2 = self.gamma * mu.squared_kernel_sum(self._source, target)
129+
sigma2 = (self.gamma ** 2) * mu.squared_kernel_sum(self._source, target)
116130
q = 1.0 + target.shape[0] * dim * 0.5 * np.log(sigma2)
117131
return MstepResult(self._tf_type(np.identity(dim), np.zeros(dim)), None, np.identity(m), 1.0 / m, sigma2)
118132

119133
def maximization_step(self, target, rigid_trans, estep_res, sigma2_p=None):
120134
return self._maximization_step(
121-
self._source, target, rigid_trans, estep_res, self.gmat_inv, self.lmd, self.k, sigma2_p
135+
self._source, target, rigid_trans, estep_res, self.gmat_inv, self.lmd, self.k, sigma2_p, self._use_debias
122136
)
123137

124138
@staticmethod
125-
def _maximization_step(source, target, rigid_trans, estep_res, gmat_inv, lmd, k, sigma2_p=None):
139+
def _maximization_step(source, target, rigid_trans, estep_res, gmat_inv, lmd, k, sigma2_p=None, use_debias=False):
126140
nu_d, nu, n_p, px, x_hat = estep_res
127141
dim = source.shape[1]
128142
m = source.shape[0]
129-
s2s2 = rigid_trans.scale**2 / (sigma2_p**2)
143+
s2s2 = rigid_trans.scale**2 / sigma2_p
130144
sigma_mat_inv = lmd * gmat_inv + s2s2 * np.diag(nu)
131145
sigma_mat = np.linalg.inv(sigma_mat_inv)
132146
residual = rigid_trans.inverse().transform(x_hat) - source
@@ -140,19 +154,24 @@ def _maximization_step(source, target, rigid_trans, estep_res, gmat_inv, lmd, k,
140154
u_m = np.sum(nu * u_hat.T, axis=1) / n_p
141155
u_hm = u_hat - u_m
142156
s_xu = np.matmul(np.multiply(nu, (x_hat - x_m).T), u_hm) / n_p
143-
s_uu = np.matmul(np.multiply(nu, u_hm.T), u_hm) / n_p + sigma2_m * np.identity(dim)
157+
s_uu = np.matmul(np.multiply(nu, u_hm.T), u_hm) / n_p
158+
if use_debias:
159+
s_uu += sigma2_m * np.identity(dim)
144160
phi, _, psih = np.linalg.svd(s_xu, full_matrices=True)
145161
c = np.ones(dim)
146162
c[-1] = np.linalg.det(np.dot(phi, psih))
147163
rot = np.matmul(phi * c, psih)
148-
tr_rsxu = np.trace(np.matmul(rot, s_xu))
164+
tr_rsxu = np.sum(rot * s_xu)
149165
scale = tr_rsxu / np.trace(s_uu)
150166
t = x_m - scale * np.dot(rot, u_m)
151-
y_hat = rigid_trans.transform(source + v_hat)
167+
y_hat = scale * np.dot(u_hat, rot.T) + t
152168
s1 = np.dot(target.ravel(), np.kron(nu_d, np.ones(dim)) * target.ravel())
153169
s2 = np.dot(px.ravel(), y_hat.ravel())
154170
s3 = np.dot(y_hat.ravel(), np.kron(nu, np.ones(dim)) * y_hat.ravel())
155-
sigma2 = (s1 - 2.0 * s2 + s3) / (n_p * dim) + scale**2 * sigma2_m
171+
sigma2 = (s1 - 2.0 * s2 + s3) / (n_p * dim)
172+
if use_debias:
173+
sigma2 += rigid_trans.scale**2 * sigma2_m
174+
sigma2 = max(abs(sigma2), np.finfo(np.float64).eps)
156175
return MstepResult(tf.CombinedTransformation(rot, t, scale, v_hat), u_hat, sigma_mat, alpha, sigma2)
157176

158177

0 commit comments

Comments
 (0)