0. 序文
2022年は新しいブログは書かず、論文執筆を中心に活動していきますので、今年から本格的に回復していきます!
この記事の目的は、ランドマーク (記事の後半ではコントロール ポイントと呼ばれることもあります) に基づいた古典的な画像ワーピング (歪み/変形) アルゴリズムである Thin Plate Spine (TPS) を詳細に分析することです。
TPS はさまざまなタスク、特に人間の顔、動物の顔などの生物学的形状で広く使用されています TPS は 3 次スプラインの 2D 一般化形式です 画像処理で一般的に使用されることは注目に値します アフィン変換は特別なバリアントとして理解できますTPSの。
- 画像の歪み/変形の問題とは何ですか? 2 つの画像に
[3]
対応する制御点 (図の緑色の接続線で示されているランドマーク) がいくつかあるとすると、画像 A (参照画像)に特定の変形を実行して、その制御点が次のようになります。比較画像B(対象テンプレート)のランドマークが重なっています。
TPS は最も古典的な手法の 1 つであり、その基本的な前提条件は次のとおりです。
如果用一个薄钢板的形变来模拟这种2D形变, 在确保landmarks能够尽可能匹配的情况下,怎么样才能使得钢板的弯曲量(deflection)最小。
- 使用例
TPS アルゴリズムの私の実践での使用方法は、画像のランドマーク(下図の左側の黒い三角形) に従って、マッピング関係(緑色の接続線)に従って2D 画像を論理的にワーピング (ワーピング) します。ターゲット テンプレート(下図の右側) )にコピーします。
1. 理論
Thin-Plate-Spline、この記事の残りの部分はその略語TPSに置き換えられます。TPS は実際には数学的な概念です[1]
。
TPS は 1D 3 次スプラインの 2 次元シミュレーションであり、二調和方程式(Biharmonic Equation)の基本解であり[2]
、その形式は次のとおりです。
U ( r ) = r 2 ln ( r ) U(r) = r^2 \ln(r)U ( r )=r2ln ( r )
1.1 U ( r ) U(r)U ( r )の形の起源
では、なぜこのような形になっているのでしょうか? [10]
1989 年に Bookstein によって出版された論文「Principle Warps: Thin-Plate Splines and the Decomposition of Deformation」では、彼はそれを二調和方程式の基本解法で拡張しました。
まず、rrrはx 2 + y 2 \sqrt{x^2+y^2}を表しますバツ2+y2(デカルト座標系)、論文ではブックスタインはU ( r ) = − r 2 ln ( r ) U(r) = -r^2 \ln(r) を使用しています。U ( r )=− r2ln ( r ) 、その目的は視覚化の便宜のためこのポーズでは、上から見るとわずかにへこんでいるように見えますが、それ以外の場合は凸面になります(つまり、中心 X 点付近の領域がへこんでいるように見えます)。).
この関数は当然次の方程式を満たします。
Δ 2 U = ( ∂ 2 ∂ x 2 + ∂ 2 ∂ y 2 ) 2 U ∝ δ ( 0 , 0 ) \Delta^2U = (\frac{\partial ^{2}}{\partial x^{2} } + \frac{\partial ^{2}}{\partial y^{2}})^2 U \propto \delta_{(0,0)}D2U _=(∂ ×2∂2+∂y _2∂2)2U _∝d( 0 , 0 )
式の左辺と(0,0) \delta_{(0,0)} の関数δ (0, 0)d( 0 , 0 )同等 (関数の導入は次のとおり)、δ ( 0 , 0 ) \delta_{(0,0)}d( 0 , 0 )これは (0,0) を除く他の位置では 0 になる関数であり、その積分は 1 になります (ディラック デルタ関数はこの関数の形式として理解されるべきだと思います)。
したがって、二調和方程式の形式はΔ 2 U = 0 \Delta^2U=0であるため、D2U _=0の場合、明らかに、U ( r ) = ( ± ) r 2 ln ( r ) U(r) = (\pm) r^2 \ln(r)U ( r )=( ± ) r2ln ( r )はすべてこの条件を満たすため、二調和関数の基本解。
簡単に言うと、関数は、定義域が関数のセットであり、その値の範囲が実数または複素数であるマッピングです。関数の例はZhihuから借用しています: 2D 平面上の 2 点間の直線距離図に示すように、2次元平面空間において、座標原点(0,0)から点(a,b)までの接続曲線は、y = y ( x ) y = y(x)となります。
[11]
y=y ( x )、および接続曲線の微小要素Δ \DeltaΔまたはds = 1 + (dydx ) 2 dx ds = \sqrt{1+(\frac{dy}{dx})^2dx}ds _=1+(dx _やあ_)2dx __、全長はds dsです。ds在 [ 0 , a ] [0, a] [ 0 ,a ]の積分
: s = ∫ 0 a ( 1 + y ′ 2 ) 1 / 2 dxs = \int_{0}^{a}(1+y^{'2})^{1/2}dxs=∫0あ( 1+y' 2)1/2 dxここ、sssはスカラー、y ' ( x ) y^{'}(x)y' (x)汎関数でありs ( y ' ) s(y^{'})とも書かれます。s (と' ). すると、上記の問題は次のようになります: 曲線y ( x ) y(x)y ( x )は関数s ( y ' ) s(y^{'})s (と' )最小限。
わかりました、UUUの起源と定義が明確になったので、私たちの目標は次のとおりです。
サンプル ポイントのセットが与えられると、各サンプル ポイントを中心とする薄板スプライン (TPS) の重み付けされた組み合わせにより、いわゆる曲げエネルギーを最小限に抑えながら、これらのポイントを正確に通過する内挿関数が得られます 。
では、曲げエネルギーとは何でしょうか?
1.2 曲げエネルギー: 曲げエネルギー
によれば、ここでの曲げエネルギーは、実数体R 2 R^2[1]
に関する二次導関数の 2 乗として定義されます。R2 (私の意見では、ここではR 2 R^2R2 は、 2D 画像の高さと幅の積分として直接理解できます。
I [ f ( x , y ) ] = ∬ ( fxx 2 + 2 fxy 2 + fyy 2 ) dxdy I[f(x, y)] = \iint (f_{xx}^2 + 2f_{xy}^2+ f_{yy}^2)dxdyI [ f ( x ,y )]=∬ ( fxx2+2f _xy2+fやあ2) d x d y
最適化の目標は、I [ f ( x , y ) ] I[f(x, y)]にすることです。I [ f ( x ,y )]を最小化します。
さて、曲げエネルギーの数学的定義はこれで終わりですが、当然、次のような疑問が生じます。
- f ( x , y ) f(x, y)f ( x ,y )はどのように定義されますか?
- 画像のような 2D 平面の場合、曲げエネルギーを最小限に抑えるためにスプラインの重み付けを組み合わせた後の曲げ方向はどうあるべきですか?
まず、曲げの方向の問題を解析して、1.4でf (x, y) f(x, y)を実行してみます。f ( x ,y )定義の紹介。
1.3 曲げ方向
まず、TPS の名前をおさらいしてみましょう。TPS は、薄い金属シートを曲げることという物理的なアナロジーに由来しています。
物理用語では、曲げ(たわみ)の方向はzzです。z軸は 2D 画像平面に垂直な軸であり、
この考え方を実際の座標変換の問題に適用するには、TPS を平板を上げ下げし、その上げ下げした平面を 2D に投影すると画像平面は、参照画像と対象テンプレートのランドマーク対応関係に基づいてワーピング(変形)した後の画像結果です。
以下のように、プレーン上に 4 つのコントロール ポイントを設定し、最後の 1 つはエッジ コーナー ポイントではないので、引っ張ると、プレーンは自然に部分的に盛り上がったり下がったりする効果が得られます。
明らかに、この種のワープは、下図に示すように、参照ランドマークの赤いXXを考慮すると、ある程度の座標変換でもあります。Xとターゲット ポイントの青色⚪ ⚪⚪ . TPS ワーピングはこれらのXXX は⚪ ⚪に完全に移動します⚪上。
ここで質問が来ます。次に、このX → ⚪ X \rightarrow ⚪バツ→⚪モバイル ソリューションはどのように実装されますか?
1.4 2D 平面の座標変換 (別名ワーピング) を実装するにはどうすればよいですか?
以下に示すように[7]
、2D 平面上の座標変換は実際には 2 方向の変化です: X \mathbf{X}X和Y \mathbf{Y}Y方向これら 2 つの方向の変化を実現するための TPS のアプローチは次のとおりです。
2 つのスプライン関数を使用してそれぞれX \mathbf{X}を考慮しますX和Y \mathbf{Y}Y方向の変位。
TPS actually use two splines,
one for the displacement in the X direction
and one for the displacement in the Y direction
これら 2 つのスプライン関数の定義は次のとおりです[7]
( NNN は、上の図に示すように、対応するランドマークの数を指します。N= 5 N=5N=5 ):
各方向 ( X, Y \mathbf{X}, \mathbf{Y}に注意してください)× 、Y )Function(Δ X , Δ Y \mathbf{\Delta X}, \mathbf{\Delta Y}ΔX、ΔY ) はNNとして見ることができますN点の高さマップ (高さマップ)なので、スプラインは下図に示すように点を[7]
。
スプライン関数の定義式では、
- 前3个系数 a 1 , a x , a y a_1, a_x, a_y ある1、ある×、あるはい線形空間の一部(線部分)を表し、 XXを線形空間に当てはめるために使用しますX (xi , yi x_i , y_iバツ私は、y私は) と⚪ ⚪⚪ (xi ' , yi ' x_i^{'}, y_i^{'}バツ私「、y私「)。
- 次の係数wi , i ∈ [ 1 , N ] w_i, i \in [1, N]w私は、私∈[ 1 、N ] は各制御点を表します。iのカーネル重み。制御点XXX (xi , yi x_i , y_iバツ私は、y私は) とその最後のx、yx、y× 、y間の変位。
- 最后的一项是 U ( ∣ ∣ ( x i , y i ) − ( x , y ) ∣ ∣ ) U(|| (x_i, y_i) - (x, y) ||) U ( ∣∣ ( x私は、y私は)−( x ,y ) ∣∣ )、これは制御点XXX (xi , yi x_i , y_iバツ私は、y私は) とその最後のx、yx、y× 、y間の変位。U ( ∣ ∣ ( xi , yi ) − ( x , y ) ∣ ∣ ) U(|| (x_i, y_i) - (x, y) ||) であることに注意してください。U ( ∣∣ ( x私は、y私は)−( x ,y ) ∣∣ ) はL2ノルムを使用します
[8]
。U定义如下: U ( r ) = r 2 ln ( r ) U(r) = r^2 \ln(r) U ( r )=r2ln ( r )ここで、TPS の RBF 関数 (動径基底関数)を再検討する必要があります: U ( r ) = r 2 ln ( r ) U(r) = r^2 \ln(r)U ( r )=r2ln ( r )上記によれば[9]
、RBFのようなガウシアンカーネルは類似性を測定する方法(類似性測定)。
1.5 具体的な計算計画
各方向について ( X , Y \mathbf{X}, \mathbf{Y}× 、Yのスプライン関数のa 1 、 ax 、 ay 、 wi a_1、 a_x、 a_y、 w_iある1、ある×、あるはい、w私はは、次の線形システムを解くことによって取得できます。
ここで、K ij = U ( ∣ ∣ ( xi , yi ) − ( xj , yj ) ∣ ∣ ) K_{ij} = U(|| (x_i, y_i) - ( x_j 、 y_j) ||)Kイジ=U ( ∣∣ ( x私は、y私は)−( ×j、yj) ∣∣ )、PPピーズⅡ_行iは同次表現( 1 , xi , yi ) (1, x_i, y_i)( 1 、バツ私は、y私は)、○○Oはすべて 0 の 3x3 行列です。oooは 3x1 のすべてゼロの列ベクトルです、www和 v v v是 w i w_i w私は和 v i v_i v私は.aaで構成される列ベクトルa是由 [ a 1 , a x , a y ] [a_1, a_x, a_y] [ _1、ある×、あるはい]列ベクトルで構成されます。
具体的には、左側の大きな行列の形式は次のようになります[9-10]
。
N=3 (制御点の数は 3) を例にとると、X \mathbf{X}注: [ U 11 U 21 U 31 1 x 1 y 1 U 12 U
22 U 32 1 x 2 y 2 U 13 U 23 U 33 1 x 3 y 0 0 x 1 x 2 x 3 0 0 0 y 1 y 2 y 3 0 0 0 ] × [ w 1 w 2 w 3 a 1 axay ] = [ x 1 ' x 2 ' x 3 ' 0 0 0 ] \begin {bmatrix} U_{11} & U_{21} & U_{ 31} & 1 & x_1 & y_1\\ U_{12} & U_{22} & U_{32} & 1 & x_2 & y_2\\ U_{1 } & U_{23} & U_{33} & 1 & x_3 & y_3 \\ 1 & 1 & 1 & 0 & 0 & 0 \ \ x_1 & x_2 & x_3 & 0 & 0 & 0 \ \ y_1 & y_2 & y_3 & & 0& 0 \end{bmatrix} \times \begin{bmatrix } w_1 \\ w_2 \\ w_3 \\ a_1 \\ a_x \\ a_y \end{bmatrix} = \begin{bmatrix} x_1^{'} \\x_2 ^{'}\\x_3^{'}\\0 \\0\\0\end{bマトリックス}
U11U12U131バツ1y1U21U22U231バツ2y2U31U32U331バツ3y3111000バツ1バツ2バツ3000y1y2y3000
×
w1w2w3ある1ある×あるはい
=
バツ1「バツ2「バツ3「000
同様に、Y \mathbf{Y}Yのスプライン関数の線形行列式は次
[ U 11 U 21 U 31 1 x 1 y 1 U 12 U 22 U 32 1 x 2 y 2 U 13 U 23 U 33 1 x 3 y 3 1 1 1 0 0 0 x 1 x 2 x 3 0 0 0 y 1 y 2 y 3 0 0 0 ] × [ w 1 w 2 w 3 a 1 axay ] = [ y 1 ' y 2 ' y 3 ' 0 0 0 ] \begin{bmatrix} U_{11} & U_{21} & U_{31} & 1 & x_1 & y_1\\ U_{12} & U_{22} & U_{32} & 1 & x_2 & y_2\\ U_{13} & U_{23} & U_{33} & 1 & x_3 & y_3 \\ 1 & 1 & 1 & 0 & 0& 0 \\ x_1 & x_2 & x_3 & 0 & 0& 0 \\ y_1 & y_2 & y_3 & 0 & 0& 0 \end{bmatrix} \times \begin {bmatrix} w_1 \\ w_2 \\ w_3 \\ a_1 \\ a_x \\ a_y \end{bmatrix} = \begin{bmatrix} y_1^{'} \\ y_2^{'} \\ y_3^{'} \ \ 0 \\ 0 \\ 0 \end{bmatrix} U11U12U131バツ1y1U21U22U231バツ2y2U31U32U331バツ3y3111000バツ1バツ2バツ3000y1y2y3000 × w1w2w3ある1ある×あるはい = y1「y2「y3「000
N+3 個の未知量を解くために N+3 個の関数が使用されることは明らかであり、対応する[ wa ] \begin{bmatrix} w \\ a \end{bmatrix} を取得できます。[wあ]。
2. コードの実装
私が使用する TPS は cheind/py-thin-plate-spline プロジェクトです[6]
。式と実装の対応を理解するために、ここでコードを詳細に分解します。
2.1 コアコンピューティングロジック
コアロジックは関数warp_image_cv
:およびにありtps.tps_theta_from_points
、最も基本的なサンプル コードは次のとおりです。tps.tps_grid
tps.tps_grid_to_remap
def show_warped(img, warped, c_src, c_dst):
fig, axs = plt.subplots(1, 2, figsize=(16,8))
axs[0].axis('off')
axs[1].axis('off')
axs[0].imshow(img[...,::-1], origin='upper')
axs[0].scatter(c_src[:, 0]*img.shape[1], c_src[:, 1]*img.shape[0], marker='^', color='black')
axs[1].imshow(warped[...,::-1], origin='upper')
axs[1].scatter(c_dst[:, 0]*warped.shape[1], c_dst[:, 1]*warped.shape[0], marker='^', color='black')
plt.show()
def warp_image_cv(img, c_src, c_dst, dshape=None):
dshape = dshape or img.shape
theta = tps.tps_theta_from_points(c_src, c_dst, reduced=True)
grid = tps.tps_grid(theta, c_dst, dshape)
mapx, mapy = tps.tps_grid_to_remap(grid, img.shape)
return cv2.remap(img, mapx, mapy, cv2.INTER_CUBIC)
img = cv2.imread('test.jpg')
c_src = np.array([
[0.44, 0.18],
[0.55, 0.18],
[0.33, 0.23],
[0.66, 0.23],
[0.32, 0.79],
[0.67, 0.80],
])
c_dst = np.array([
[0.693, 0.466],
[0.808, 0.466],
[0.572, 0.524],
[0.923, 0.524],
[0.545, 0.965],
[0.954, 0.966],
])
warped_front = warp_image_cv(img, c_src, c_dst, dshape=(512, 512))
show_warped(img, warped1, c_src_front, c_dst_front)
このオープン ソース コードには numpy と torch の 2 つのバージョンがあります。ここでは、GPU を持たない友人がハンズオン テストを実行できるように、私の分析は numpy バージョンで実行されます。
コアTPS
class TPS: @staticmethod def fit(c, lambd=0., reduced=False): n = c.shape[0] U = TPS.u(TPS.d(c, c)) K = U + np.eye(n, dtype=np.float32)*lambd P = np.ones((n, 3), dtype=np.float32) P[:, 1:] = c[:, :2] v = np.zeros(n+3, dtype=np.float32) v[:n] = c[:, -1] A = np.zeros((n+3, n+3), dtype=np.float32) A[:n, :n] = K A[:n, -3:] = P A[-3:, :n] = P.T theta = np.linalg.solve(A, v) # p has structure w,a return theta[1:] if reduced else thete ... @staticmethod def z(x, c, theta): x = np.atleast_2d(x) U = TPS.u(TPS.d(x, c)) w, a = theta[:-3], theta[-3:] reduced = theta.shape[0] == c.shape[0] + 2 if reduced: w = np.concatenate((-np.sum(w, keepdims=True), w)) b = np.dot(U, w) return a[0] + a[1]*x[:, 0] + a[2]*x[:, 1] + b
2.2tps.tps_theta_from_points
この関数の機能は、スプライン関数の[ wa ] \begin{bmatrix} w \\ a \end{bmatrix} を解くことです。[wあ]。
def tps_theta_from_points(c_src, c_dst, reduced=False):
delta = c_src - c_dst
cx = np.column_stack((c_dst, delta[:, 0]))
cy = np.column_stack((c_dst, delta[:, 1]))
theta_dx = TPS.fit(cx, reduced=reduced)
theta_dy = TPS.fit(cy, reduced=reduced)
return np.stack((theta_dx, theta_dy), -1)
-
デルタは、参照画像の制御点とターゲット テンプレートの制御点の間の補間Δ xi 、 Δ yi \Delta x_i、\Delta y_iΔx_ _私は、Δy_ _私は
-
cxとcyは に基づいており、
c_dst
それぞれΔ xi \Delta x_iが追加されます。Δx_ _私は和Δyi \Delta y_iΔy_ _私はの列ベクトル -
theta_dxとtheta_dyの Reduce パラメーターはデフォルトで False/True に設定され、結果は長さ 9/8 の1D ベクトルになります。計算プロセスには
fit
TPS コア クラスの機能が必要です。
①TPS.d(cx, cx, reduced=True)
またはTPS.d(cy, cy, reduced=True)
L2を計算する
@staticmethod
def d(a, b):
# a[:, None, :2] 是把a变成[N, 1, 2]的tensor/ndarray
# a[None, :, :2] 是把a变成[1, N, 2]的tensor/ndarray
return np.sqrt(np.square(a[:, None, :2] - b[None, :, :2]).sum(-1))
その機能は、∣ ∣ ( xi , yi ) − ( x , y ) ∣ ∣ || (x_i, y_i) - (x, y) || を計算することです。∣∣ ( x私は、y私は)−( x ,y ) ∣∣ (L2)、結果は形状がN、NN、Nん、Nさんの中間結果です。
② TPS.u(...)
计算 U ( . . . ) U(...) う( ... )
式とまったく同じです: U ( r ) = r 2 ln ( r ) U(r) = r^2 \ln(r)U ( r )=r2ln ( r ) 、 rr を防ぐためrが小さすぎるため、イプシロン係数が追加されます1 e − 6 1e^{-6}1e _− 6.このステップでは、KKK、形状はN、NN、Nん、N、①と同じ。
def u(r):
return r**2 * np.log(r + 1e-6)
③ と によればcx
、cy
単純なスプライシングで生成できますP
。
P = np.ones((n, 3), dtype=np.float32)
P[:, 1:] = c[:, :2] # c就是cx or cy.
④ 根据 Δ x i \Delta x_i Δx_ _私は(同じ方法でcx
ベクトルの最後の列を取得します)、 vvを取得しますcy
v
# c = cx or cy
v = np.zeros(n+3, dtype=np.float32)
v[:n] = c[:, -1]
⑤アセンブリ行列A
、つまり[10]
論文中のLLLマトリックス。
A = np.zeros((n+3, n+3), dtype=np.float32)
A[:n, :n] = K
A[:n, -3:] = P
A[-3:, :n] = P.T
⑥今LLL和YYYは既知です、Y = [ vo ] Y = \begin{bmatrix}v \\ o \end{bmatrix}Y=[vああ], 那么 W W WWAa 1 、 ax 、 ay a_1、 a_x、a_yある1、ある×、あるはいのベクトルは直接線形的に解くことができます
[ wa ] \begin{bmatrix}w \\ a \end{bmatrix}[wあ] =L − 1 L^{-1}L− 1年Y
class TPS:
@staticmethod
def fit(c, lambd=0., reduced=False):
# 1. TPS.d
U = TPS.u(TPS.d(c, c))
K = U + np.eye(n, dtype=np.float32)*lambd
P = np.ones((n, 3), dtype=np.float32)
P[:, 1:] = c[:, :2]
v = np.zeros(n+3, dtype=np.float32)
v[:n] = c[:, -1]
A = np.zeros((n+3, n+3), dtype=np.float32)
A[:n, :n] = K
A[:n, -3:] = P
A[-3:, :n] = P.T
theta = np.linalg.solve(A, v) # p has structure w,a
return theta[1:] if reduced else theta
@staticmethod
def d(a, b):
return np.sqrt(np.square(a[:, None, :2] - b[None, :, :2]).sum(-1))
@staticmethod
def u(r):
return r**2 * np.log(r + 1e-6)
theta
つまり、関数は[ wa ] \begin{bmatrix}w \\ a \end{bmatrix}を返します。[wあ] . これは両方向 (X, Y) で必要なtheta
ので、
theta = tps.tps_theta_from_points(c_src, c_dst)
返されるシータは(N + 3, 2) (N+3, 2)です。( N+3 、2 )形。
2.3tps.tps_grid
この関数は、x 方向と y 方向のイメージ プレーンのオフセットを解決します。
def warp_image_cv(img, c_src, c_dst, dshape=None):
dshape = dshape or img.shape
# 2.2
theta = tps.tps_theta_from_points(c_src, c_dst, reduced=True)
# 2.3
grid = tps.tps_grid(theta, c_dst, dshape)
# 2.4
mapx, mapy = tps.tps_grid_to_remap(grid, img.shape)
return cv2.remap(img, mapx, mapy, cv2.INTER_CUBIC)
theta
コア コード部分から、 が見つかった場合、つまり[ wa ] \begin{bmatrix}w \\ a \end{bmatrix} であることがわかります。[wあ]tps_grid
.以下の関数を使用して、
機能は次のとおりです。
def tps_grid(theta, c_dst, dshape):
# 1) uniform_grid(...)
ugrid = uniform_grid(dshape)
reduced = c_dst.shape[0] + 2 == theta.shape[0]
# 2) 求dx和dy.
dx = TPS.z(ugrid.reshape((-1, 2)), c_dst, theta[:, 0]).reshape(dshape[:2])
dy = TPS.z(ugrid.reshape((-1, 2)), c_dst, theta[:, 1]).reshape(dshape[:2])
dgrid = np.stack((dx, dy), -1)
grid = dgrid + ugrid
return grid # H'xW'x2 grid[i,j] in range [0..1]
入力は 3 つのパラメーターです。
- シータ
reduced=True
(N+2, 2) またはreduced=False
(N+3, 2) - c_dst (N, 2) は、ターゲット テンプレート上のコントロール ポイントまたはランドマークです。
c_dst = np.array([
[0.693, 0.466],
[0.808, 0.466],
[0.572, 0.524],
[0.923, 0.524],
[0.545, 0.965],
[0.954, 0.966],
])
- dshape (H, W, 3) は、指定された参照イメージの解像度です。
出力は 1 です。
- グリッド(H、W、2)。
その視覚化を参照してください2.3.1
。
2.3.1uniform_grid
tps.tps_grid
関数の最初のステップはugrid = unique_grid(dshape)です。この関数の定義は次のとおりです。その機能は 1 (H, W, 2) (H, W, 2)を作成することです。( H 、わ、2 )グリッド内の値はすべて 0 から 1 までの線形補間ですnp.linspace(0, 1, W(H))
。
def uniform_grid(shape):
'''Uniform grid coordinates.
'''
H,W = shape[:2]
c = np.empty((H, W, 2))
c[..., 0] = np.linspace(0, 1, W, dtype=np.float32)
c[..., 1] = np.expand_dims(np.linspace(0, 1, H, dtype=np.float32), -1)
return c
返される値は(H, W, 2) (H, W, 2)ugrid
です。( H 、わ、2 )グリッド、その X および Y 方向の値のサイズは、下図に示すように、方向に応じて線形に拡張されます。
X方向Y
方向
2.3.2和をTPS.z
求めるために解くdx
dy
# 2) 求dx和dy.
dx = TPS.z(ugrid.reshape((-1, 2)), c_dst, theta[:, 0]).reshape(dshape[:2]) # [H, W]
dy = TPS.z(ugrid.reshape((-1, 2)), c_dst, theta[:, 1]).reshape(dshape[:2]) # [H, W]
dgrid = np.stack((dx, dy), -1) # [H, W, 2]
grid = dgrid + ugrid
次の定義から、この関数がTPS.z
X 方向と Y 方向を解決するスプライン関数であることが簡単にわかります。
f ( x / y ) ' ( x , y ) = a 1 + axx + ayy + ∑ i = 1 N wi U ( ∣ ∣ ( xi , yi ) − ( x , y ) ∣ ∣ ) f_{(x/y )^{'}}(x, y) = a_1 + a_x x + a_y y + \sum_{i=1}^{N} w_i U(|| (x_i, y_i) - (x, y) ||)f( x / y )「( x ,y )=ある1+ある×バツ+あるはいy+i = 1∑Nw私はU ( ∣∣ ( x私は、y私は)−( x ,y ) ∣∣ )
混乱を招く可能性があるのは、( )のときにパラメーターが同じである理由です。x は形状ですが、それでも です2.2
TPS.d()
cx(cy)
(H*W), 2
c
c_dst (N,2)
。私の理解では、2.3
このステップの目標は真に画像面を位置に従って移動させることであるためです。制御点(曲げエネルギーを最小にする)を求めるため、ugrid
平面上でサンプリングした点に対して一律にオフセット計算(dx
和)を行うことでdy
、導出条件を満たすオフセットの解析解を得ることができますdgrid
。
class TPS:
...
@staticmethod
def z(x, c, theta):
x = np.atleast_2d(x)
U = TPS.u(TPS.d(x, c)) # [H*W, N] 本例中H=W=800, N=6
w, a = theta[:-3], theta[-3:]
reduced = theta.shape[0] == c.shape[0] + 2
if reduced:
w = np.concatenate((-np.sum(w, keepdims=True), w))
b = np.dot(U, w)
return a[0] + a[1]*x[:, 0] + a[2]*x[:, 1] + b
ugrid
+の場合、dgrid
画像面全体のスプライン関数に従って計算されたdx、dy dx、dyが得られます。d x 、d y (オフセット) が均一の結果に追加されますX、 Y \mathbf{X}, \mathbf{Y}とugrid
比較されていることがはっきりとわかります。2.3.1
ugrid
× 、Y方向にも対応する変化が生じます
X方向Y
方向
この時点で、このステップで返されるのは実際にはX、Y \mathbf{X}, \mathbf{Y}2.3
の間の値です。× 、対応してY方向にねじれたグリッド (格子) (H、W、2) (H、W、2)( H 、わ、2 )、視覚化の結果は上記のとおりで、値の範囲は-1 から 1。
2.4tps.tps_grid_to_remap
このステップは非常に簡単です。2.3
計算された **グリッド**上でX、Y \mathbf{X}、\mathbf{Y}を押すだけです。× 、Y方向に対応するWWWとHHH.次に、cv2.remap
画像の歪み操作を実行する関数を送信します。
def warp_image_cv(img, c_src, c_dst, dshape=None):
dshape = dshape or img.shape
# 2.2
theta = tps.tps_theta_from_points(c_src, c_dst, reduced=True)
# 2.3
grid = tps.tps_grid(theta, c_dst, dshape)
# 2.4
mapx, mapy = tps.tps_grid_to_remap(grid, img.shape)
return cv2.remap(img, mapx, mapy, cv2.INTER_CUBIC)
2.4.1tps_grid_to_remap
グリッドの幅と高さを単純に乗算する
def tps_grid_to_remap(grid, sshape):
'''Convert a dense grid to OpenCV's remap compatible maps.
Returns
-------
mapx : HxW array
mapy : HxW array
'''
mx = (grid[:, :, 0] * sshape[1]).astype(np.float32)
my = (grid[:, :, 1] * sshape[0]).astype(np.float32)
return mx, my
2.4.2cv2.remap(img, mapx, mapy, cv2.INTER_CUBIC)
ワープ後の結果を取得します。
cv2.remap
ユーザーが独自のマッピング関係を定義できる機能です。アフィン変換や変換行列による透視変換とは異なり、より柔軟なTPS
マッピングです。具体例を参照してください[12]
。
なお、この結果が「まえがき」の結果と異なるのは、「まえがき」ではマスキングにマスクを使用したためです。
要約する
この時点で、TPS
分析は終了します。このアルゴリズムは、顔の痩身やテクスチャ マッピングなどのタスクで最も一般的です。また、非常に柔軟なワーピング アルゴリズムでもあります。今でも広く使用されています。この記事に関しては、お気軽にコメントしてください。下記にご指摘ください、
ありがとうございます^ . ^