給定兩張圖片中一些相互對應的關鍵點,如何能夠將其中一張圖片形變到另外一張圖片上使得這些關鍵點都對應重合?這就是TPS方法所要解決的問題,TPS可以對表面進行柔性的變形。
Thin Plate Spline(TPS,薄板樣條)插值是常用的2D插值方法。它的物理意義是:假設在原形狀中有個點
,這
個點在形變之后新坐標之下對應新的
個點
。用一個薄鋼板的形變來模擬2D形變,確保這
個點能夠正確匹配,那么怎樣的形變,可以使鋼板的彎曲能量最小?TPS插值是這個問題的數值解法。
幾乎所有的生物有關的形變都是可以用TPS來近似,Bookstein本人就是生物形態計量的大師。
樣條曲線插值
線性插值對每個區間使用線性函數。 樣條插值在每個間隔中使用低階多項式,并選擇多項式以使它們平滑地吻合在一起。 結果函數被稱為樣條曲線。 例如,三次樣條是分片段立方,兩次連續可微。此外,它的二階導數在終點為零。
薄板樣條
薄板樣條插值使薄板的彎曲能量最小,不過把2D圖像看成薄板沒錯,但是彎曲的能量并不是個點形變對應位置所產生在薄板內的“彎曲”。實際上,這個樣條函數是對每一個維度的坐標分別進行插值,在后面也會看到代碼會對坐標進行一個展平的操作。每一維的形變施加于垂直于薄板的方向,簡單地說就是針對二維xy平面在它的z方向上進行變換,讓在這個維度上的薄板往上凸或者往下凹從而完成這個薄板的形變。
考慮這樣一個插值問題:自變量 是2維空間中的一點,函數值
也是2維空間中的一點,并且都在笛卡爾坐標系下表示。給定
個自變量
和對應的函數值
,求插值函數。[1]
已知K個控制點 用徑向基函數[2]可以將原來的坐標變換到另一個坐標去:
其中 是徑向基函數核。也可以看到每個新的值都是會受到所有其他非一一對應控制點的影響。
這里是2維空間,所以是兩個插值函數,如果是D維那就是求D個插值函數,可以寫成向量形式:
其中插值函數可以寫成:
其中是標量,
因為例子中維度為2,
,函數向量
在文獻[3]已經給出證明,這種形式的插值函數是使彎曲能量最小的
在TPS插值函數中有
個參數,其中
在這里為2,所以再加上維度的約束:
和
分別表示點
的
坐標值和
坐標值,可以將內容簡寫成:
其中 ,
表示值全為1的
維列向量
把矩陣簡化表示,令
如果 是非奇異矩陣,則
也是非奇異矩陣,可以解得參數為:
然后把各個維度的 函數的參數都計算出來則有
把 寫成下面的形式有
矩陣 為彎曲能量矩陣,秩為
,
作為仿射矩陣,實現平移和旋轉。
將這些形變應用到所有其他點 上需要和用于計算形變參數的控制點
一起構造對應的計算矩陣:
其中 維度為
得到結果為:
Numpy 實現
首先構造一個上文中的 矩陣:
def makeT(cp):
# cp: [K x 2] control points
# T: [(K+3) x (K+3)]
K = cp.shape[0]
T = np.zeros((K+3, K+3))
T[:K, 0] = 1
T[:K, 1:3] = cp
T[K, 3:] = 1
T[K+1:, 3:] = cp.T
# compute every point pair of points
R = squareform(pdist(cp, metric='euclidean'))
R = R * R
R[R == 0] = 1 # a trick to make R ln(R) 0
R = R * np.log(R)
np.fill_diagonal(R, 0)
T[:K, 3:] = R
return T
然后構造一個和 進行計算的矩陣,將待轉換的點對構造成矩陣形式
def liftPts(p, cp):
# p: [N x 2], input points
# cp: [K x 2], control points
# pLift: [N x (3+K)], lifted input points
N, K = p.shape[0], cp.shape[0]
pLift = np.zeros((N, K+3))
pLift[:,0] = 1
pLift[:,1:3] = p
R = cdist(p, cp, 'euclidean')
R = R * R
R[R == 0] = 1
R = R * np.log(R)
pLift[:, 3:] = R
return pLift
對TPS矩陣進行求解,分別得到x和y維度的形變矩陣
def tps_transform(gallery, probe):
"""
Compute the new points coordination with Thin-Plate-Spline algorithm
"""
src_pt_xs = probe[:, 0]
src_pt_ys = probe[:, 1]
cps = np.vstack([src_pt_xs, src_pt_ys]).T
# construct T
T = makeT(cps)
# solve cx, cy (coefficients for x and y)
tar_pt_xt = gallery[:, 0]
tar_pt_yt = gallery[:, 1]
xtAug = np.concatenate([tar_pt_xt, np.zeros(3)])
ytAug = np.concatenate([tar_pt_yt, np.zeros(3)])
cx = np.linalg.solve(T, xtAug) # [K+3]
cy = np.linalg.solve(T, ytAug)
return cx, cy
-
Kent, J. T. and Mardia, K. V. (1994a). The link between kriging and thin-plate splines. In: Probability, Statistics and Optimization: a Tribute to Peter Whittle (ed. F. P. Kelly), pp 325–339. John Wiley & Sons, Ltd, Chichester. page 282, 287, 311 ?