LoginSignup
2
1

numpyを使ったバンドル調整の実装例

Last updated at Posted at 2023-05-17

1. はじめに

  • バンドル調整の理論を整理した上でnumpyで実装してみる
  • バンドル調整はレーベンバーグ・マーカンタイル法を用いる
  • 実装で用いるdatasetはOxfordのDinosaur
  • 今回はrotation, translate, カメラ内部行列は既知のものを使い、対象物の3次元座標のみを最適化する

2. 参考文献・図書

  • レーベンバーグの原論文
    1944に発表されたこの手法の最初の論文。Wikipediaによると、その後何人かによって再発見されてるみたい
    [1]Levenberg, Kenneth (1944). “A method for the solution of certain non-linear problems in least squares”. Quarterly of Applied Mathematics 2 (2): 164–168
    https://www.ams.org/journals/qam/1944-02-02/S0033-569X-1944-10666-0/

    • よく使うコンピュータ・ビジョン本の解説(1)
      [2] Simon J. D. Prince. "Computer Vision models, learning, and inference". cambridge.
      16.6.1やAppendix B2.3にGauss-Newton法を使うバンドル調整が記載されてるが、今回の実装とは少し異なる。
  • よく使うコンピュータ・ビジョン本の解説(2)
    [3] Richard Hartley, Andrew Zisserman. "Multiple View Geometry in computer vision". cambridge.
    P600のAppendixのA6.2にレーベンバーグ・マーカンタイル法を用いたバンドル調整が記載されているが、Gauss-Newton法の応用例としている。

  • 金谷先生の本(1)
    [4]金谷健一著 "3次元回転" 共立出版
    勉強会でも使わせてもらった金谷先生の回転本。6.8にレーベンバーグ・マーカンタイル法を用いたバンドル調整が記載されているが、上記の文献と若干異なる。

  • 金谷先生の本(2)
    [5] 金谷健一ら "3次元コンピュータビジョン計算ハンドブック" 森北出版
    11章にレーベンバーグ・マーカンタイル法を用いたバンドル調整とOxfordのDinosaur datasetを用いた実装例に関して記載されている。

  • 金谷先生系の資料
    [6] 岩本祐輝ら "3次元復元のためのバンドル調整の実装と評価" 情報処理学会報告 IPSJ SIG Technical Report
    上記[4]と内容はほぼ同じ

今回は[4]、[5]、[6]に沿った内容とする。

multiple_view_geometry.jpeg 3次元回転本.jpeg 3次元コンピュータビジョン計算ハンドブック.jpeg

3. 用いるdataset

OxfordのMulti-view data
https://www.robots.ox.ac.uk/~vgg/data/mview/
これの一番上のDinosaur。
下図のようにゴジラのおもちゃを1周回転させる間に画像を複数枚撮影している。画像は36枚。(10度ずつある?)

viff000.jpg viff003.jpg viff006.jpg

この特徴点に対応する画像上の座標一覧と、36枚の画像に対応するカメラ行列が与えられている。特徴点は全部で4,983点。

4. バンドル調整を超ざっくり整理

4.1 問題設定

  1. M枚の画像、それにわたる特徴点 x が N個与えられた状況を考える
  2. 未知数はカメラの内部パラメータ ${\bf K}$、回転 ${\bf R}$、遷移 ${\bf t}$、各特徴点 $\alpha$ の3D world座標 ${\bf w}_\alpha$ とする。
  3. これをレーベンバーグ・マーカンタイル法を用いたバンドル調整で求める

4.2 最適化問題の定式化

特徴点を ${\bf w}$ を画像にprojectして ${\bf x}$ となるとして、この対数尤度を最大化させることを考える。

\newcommand{\argmax}{\mathop{\rm arg~max}\limits}
\hat{\bf \xi} = \argmax_{\bf \xi} \left[ \sum_{\alpha=1}^N \sum_{\kappa=1}^M \log \left[ Pr({\bf x}_{\alpha, \kappa} | {\bf w}_\alpha, {\bf K}, {\bf R}_{\kappa}, {\bf t}_{\kappa}) \right] \right] \tag{4.2.1}

${\bf \xi}$ は未知のパラメータ全て。

projectした特徴点と実際の画像状の特徴点との誤差が理想的なpinholeモデルに対して正規分布に従うと考えて最小二乗法で解く。

\newcommand{\argmax}{\mathop{\rm arg~max}\limits}
\newcommand{\argmin}{\mathop{\rm arg~min}\limits}

\begin{eqnarray}
\hat{\bf \xi} &=& \argmax_{\bf \xi} \left[ \sum_{\alpha=1}^N \sum_{\kappa=1}^M \log \left[ {\rm Norm}_{{\bf x}_{\alpha, \kappa} } \left[  \left[ {\bf K}{\bf R}_{\kappa}^{\top} ( {\bf I} \ | - {\bf t}_{\kappa}){\bf w}_\alpha \right] ,  \sigma^2 {\bf I} \right]  \right] \right]  \tag{4.2.2} \\


&=& \argmin_{\bf \xi} \left[ \sum_{\alpha=1}^N \sum_{\kappa=1}^M \left[ \left( {\bf x}_{\alpha, \kappa} - {\bf K}{\bf R}_{\kappa}^{\top} ( {\bf I}\  | - {\bf t}_{\kappa}){\bf w}_\alpha \right)^{\top} \left( {\bf x}_{\alpha, \kappa} - {\bf K}{\bf R}_{\kappa}^{\top} ( {\bf I} \ | - {\bf t}_{\kappa}){\bf w}_\alpha \right) \right] \right]  \tag{4.2.3} \\

&=& \argmin_{\bf \xi} E \tag{4.2.4}

\end{eqnarray}

5. レーベンバーグ・マーカント法による解法とその実装例

5.1

ここでカメラ行列な部分をまとめて

\begin{eqnarray}
{\bf K}{\bf R}_{\kappa}^{\top} \left[ {\bf I} \ | - {\bf t}_{\kappa} \right]
 &=& {\bf P}_{\kappa} \\
&=&
\begin{bmatrix}
P^{11} & P^{12} & P^{13} & P^{14}\\
P^{21} & P^{22} & P^{23} & P^{24}\\
P^{31} & P^{32} & P^{33} & P^{34}\\
\end{bmatrix} \tag{5.1.1}
\end{eqnarray}

とするとpinholeモデルの関係は

\begin{eqnarray}
{\bf x} &\simeq& {\bf P}_{\kappa} {\bf w} \\

\begin{bmatrix}
x \\
y \\
1 \\
\end{bmatrix}
 &\simeq&
\begin{bmatrix}
P^{11} & P^{12} & P^{13} & P^{14}\\
P^{21} & P^{22} & P^{23} & P^{24}\\
P^{31} & P^{32} & P^{33} & P^{34}\\
\end{bmatrix}
\begin{bmatrix}
X \\
Y \\
Z \\
1 \\

\end{bmatrix} \tag{5.1.2}
\end{eqnarray}

なのでこれを x と y に関してまとめると

\begin{eqnarray}
x_{\alpha} &=& \frac{P_{\kappa}^{11} X_{\alpha} + P_{\kappa}^{12} Y_{\alpha} + P_{\kappa}^{13} Z_{\alpha} + P_{\kappa}^{14}}{P_{\kappa}^{31} X_{\alpha} + P_{\kappa}^{32} Y_{\alpha} + P_{\kappa}^{33} Z_{\alpha} + P_{\kappa}^{34}}, \tag{5.1.3} \\
&=& \frac{p_{\alpha \kappa}}{r_{\alpha \kappa}}, \tag{5.1.4} \\
\\


y_{\alpha} &=& \frac{P_{\kappa}^{21} X_{\alpha} + P_{\kappa}^{22} Y_{\alpha} + P_{\kappa}^{23} Z_{\alpha} + P_{\kappa}^{24}}{P_{\kappa}^{31} X_{\alpha} + P_{\kappa}^{32} Y_{\alpha} + P_{\kappa}^{33} Z_{\alpha} + P_{\kappa}^{34}}, \tag{5.1.5} \\
&=& \frac{q_{\alpha \kappa}}{r_{\alpha \kappa}}, \tag{5.1.6} \\

\end{eqnarray}

最後の $p_{\alpha \kappa}, q_{\alpha \kappa}, r_{\alpha \kappa}$ は[4]などに従って後の計算のためにまとめた。

この表記で (4.2.3)、(4.2.4) の目的関数 E を書き直すと、

\begin{eqnarray}
E &=&  \sum_{\alpha=1}^N \sum_{\kappa=1}^M I_{\alpha, \kappa} \left[ \left( \frac{p_{\alpha \kappa}}  {r_{\alpha \kappa}} - x_{\alpha, \kappa} \right)^2 + \left( \frac{q_{\alpha \kappa}}  {r_{\alpha \kappa}} - y_{\alpha, \kappa} \right)^2 \right]   \tag{5.1.7} \\

\end{eqnarray}

となる。$I_{\alpha, \kappa}$ は Indicator関数で 第 $\alpha$ 点が第 $\kappa$ 画像に写っているときに 1 、そうでないときは 0 とする。

5.2

ニュートン法的な最適化だと、

{\bf \xi}^{t+1} = {\bf \xi}^{t} - \lambda \cdot \left( \nabla_{\bf \xi}^2 E({\bf \xi})  \right)^{-1} \cdot \nabla_{\bf \xi} E({\bf \xi})   \tag{5.2.1} \\

としたいので、目的関数 E を各パラメータ ${\bf \xi}$ で1回微分した値、2回微分した値

\nabla_{\bf \xi}^2 E({\bf \xi}), \nabla_{\bf \xi} E({\bf \xi}) \tag{5.2.2} \\

がほしい。だが、いきなりこれを求めると大変なので、[4], [5], [6]などに従って $p_{\alpha \kappa}, q_{\alpha \kappa}, r_{\alpha \kappa}$ を間にかませる。

(5.1.7)式をパラメータの k 番目で微分すると

\begin{eqnarray}
\frac{\partial E}{\partial {\bf \xi}_k} &=&  \sum_{\alpha=1}^N \sum_{\kappa=1}^M \frac{2}{r_{\alpha \kappa}^2} \left[ 

\left( \frac{p_{\alpha \kappa}}  {r_{\alpha \kappa}} - x_{\alpha, \kappa} \right)

 \left( r_{\alpha \kappa} \frac{\partial p_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} - p_{\alpha \kappa} \frac{\partial r_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} \right) \right. \\

 &+& \left. \left( \frac{q_{\alpha \kappa}}  {r_{\alpha \kappa}} - y_{\alpha, \kappa} \right)

\left( r_{\alpha \kappa} \frac{\partial q_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} - q_{\alpha \kappa} \frac{\partial r_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} \right)

  \right]   \tag{5.2.3} \\

\end{eqnarray}

次に2回目を微分するが、ここで反復が進行すると

\begin{eqnarray}
\left( \frac{p_{\alpha \kappa}}  {r_{\alpha \kappa}} - x_{\alpha, \kappa} \right) &\simeq& 0, \\
\left( \frac{q_{\alpha \kappa}}  {r_{\alpha \kappa}} - y_{\alpha, \kappa} \right) &\simeq& 0,\tag{5.2.4} \\

\end{eqnarray}

となるので、その項は削除する。これをガウス・ニュートン近似というらしい。

\begin{eqnarray}
\frac{\partial E}{\partial^2 {\bf \xi}_k {\bf \xi}_l} &=&  \sum_{\alpha=1}^N \sum_{\kappa=1}^M \frac{2}{r_{\alpha \kappa}^4} \left[ 

\left( r_{\alpha \kappa} \frac{\partial p_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} - p_{\alpha \kappa} \frac{\partial r_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} \right)

 \left( r_{\alpha \kappa} \frac{\partial p_{\alpha \kappa}}  {\partial {\bf \xi}_{l}} - p_{\alpha \kappa} \frac{\partial r_{\alpha \kappa}}  {\partial {\bf \xi}_{l}} \right) \right. \\

 &+& \left. \left( r_{\alpha \kappa} \frac{\partial q_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} - q_{\alpha \kappa} \frac{\partial r_{\alpha \kappa}}  {\partial {\bf \xi}_{k}} \right)

\left( r_{\alpha \kappa} \frac{\partial q_{\alpha \kappa}}  {\partial {\bf \xi}_{l}} - q_{\alpha \kappa} \frac{\partial r_{\alpha \kappa}}  {\partial {\bf \xi}_{l}} \right)

  \right]   \tag{5.2.5} \\

\end{eqnarray}

そうすると、1回微分、2回微分ともに

\frac{\partial p_{\alpha \kappa}}  {\partial {\bf \xi}}, \frac{\partial q_{\alpha \kappa}}  {\partial {\bf \xi}}, \frac{\partial r_{\alpha \kappa}}  {\partial {\bf \xi}} \tag{5.2.6}

のみの関数となる。よって次章では、これらの値を求めることで、目的関数の各パラーメータによる微分値を求める。

6. 各パラメータによる目的関数の微分

式(5.1.1)を各パラメータに分解すると

\begin{eqnarray}
{\bf P}_{\kappa}
&=&
\begin{bmatrix}
P^{11} & P^{12} & P^{13} & P^{14}\\
P^{21} & P^{22} & P^{23} & P^{24}\\
P^{31} & P^{32} & P^{33} & P^{34}\\
\end{bmatrix} \tag{5.1.1} \\


 &=& {\bf K}{\bf R}_{\kappa}^{\top} \left[ {\bf I} \ | - {\bf t}_{\kappa} \right] \\


&=& \begin{bmatrix}
f & 0 & u_0\\
0 & f & v_0\\
0 & 0 & 1\\
\end{bmatrix} 


\begin{bmatrix}
R^{11} & R^{12} & R^{31} & -t_x \\
R^{12} & R^{22} & R^{32} & -t_y \\
R^{13} & R^{23} & R^{33} & -t_z \\
\end{bmatrix} \tag{6.1.1}


\end{eqnarray}

となる。よってパラメータは焦点距離 $f$、光軸点 $u_0, v_0$、回転のパラメータ、並進のパラメータ $t$ となる。これらに各特徴量の 3dワールド座標関を加えたものに関する微分値を求める。

6.1 3次元ワールド座標に関する微分

$p_{\alpha \kappa}, q_{\alpha \kappa},r_{\alpha \kappa}$ のそれぞれは、各特徴量の3次元点 $(X, Y, Z)$ と $P$ との線形和となっているので、クロネッカーのデルタ $\delta_{\alpha \beta}$ を用いて

\begin{matrix}
\frac{\partial p_{\alpha \kappa}}{\partial X_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{11}, & 
\frac{\partial p_{\alpha \kappa}}{\partial Y_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{12}, & 
\frac{\partial p_{\alpha \kappa}}{\partial Z_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{13}, \\

\frac{\partial q_{\alpha \kappa}}{\partial X_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{21}, & 
\frac{\partial q_{\alpha \kappa}}{\partial Y_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{22}, & 
\frac{\partial q_{\alpha \kappa}}{\partial Z_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{23}, \\

\frac{\partial r_{\alpha \kappa}}{\partial X_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{31}, & 
\frac{\partial r_{\alpha \kappa}}{\partial Y_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{32}, & 
\frac{\partial r_{\alpha \kappa}}{\partial Z_{\beta}}  = \delta_{\alpha \beta} P_\kappa^{33}, \\

\end{matrix} \tag{6.1.2}

となる。

7 レーベンバーグ・マーカート法

7.1 初期値 X を最小二乗法で求める。

def get_initial_3d_points(df_x, df_y, mat):
    XYZs = np.zeros((3, 0), dtype=np.float32)
    for p_idx in range(len(df_x)):
        XYZ = get_initial_3d_point1(df_x, df_y, mat, p_idx)
        XYZs = np.concatenate((XYZs, XYZ), axis=1)
    return XYZs

def get_initial_3d_point1(df_x, df_y, mat, p_idx=0):
    x_se_valid = df_x.loc[p_idx, (df_x.loc[p_idx] != -1)]
    y_se_valid = df_y.loc[p_idx, (df_y.loc[p_idx] != -1)]

    A = []
    b = []
    for t_idx, v_x in x_se_valid.items():
        time = int(t_idx.split('_', 1)[0])
        v_y = y_se_valid['{}_y'.format(time)]
        Pt = mat['P'][0][time]

        A.append([v_x * Pt[2, 0] - Pt[0, 0], v_x * Pt[2, 1] - Pt[0, 1], v_x * Pt[2, 2] - Pt[0, 2]])
        A.append([v_y * Pt[2, 0] - Pt[1, 0], v_y * Pt[2, 1] - Pt[1, 1], v_y * Pt[2, 2] - Pt[1, 2]])

        b.append([- v_x * Pt[2, 3] + Pt[0, 3]])
        b.append([- v_y * Pt[2, 3] + Pt[1, 3]])

    A = np.array(A, dtype=np.float32)
    b = np.array(b, dtype=np.float32)

    XYZ0 = np.linalg.inv(A.T.dot(A)).dot(A.T).dot(b)
    return XYZ0

さらに初期の再投影誤差 E を求める。またc = 0.0001 とする。

def get_loss(XYZs, df_x, df_y, mat, sample_num=10, time_sample=2):
    E = 0.
    for time1, Pt in enumerate(mat['P'][0]):
        x_act = df_x.loc[:, '{}_x'.format(time1)].values
        y_act = df_y.loc[:, '{}_y'.format(time1)].values

        valid_idx = np.where(x_act!=-1)
        x_act = x_act[valid_idx[0]]
        y_act = y_act[valid_idx[0]]

        xyk = Pt.dot(XYZs[:, valid_idx[0]])
        x_projected = xyk[0] / xyk[2]
        y_projected = xyk[1] / xyk[2]
        E += np.sum((x_act - x_projected) ** 2 + (y_act - y_projected) ** 2)
    return E

E = get_loss(XYZs, df_x, df_y, mat, sample_num=sample_num)
print("E: ", E) # E: 51006.08...

7.2 Xに関するEのgradientとヘッセ行列を求める。

def make_gradient_hessian(param_set, p_mat, q_mat, r_mat, x_mat, y_mat, mask):
    """

    :param param_set: list including several dicts
                     ex) [{'type':'X', 'value':(dp_dX, dq_dX, dr_dX)}, {..}, ..]
    :param p_mat: shape:(4983, 36)
    :param q_mat: shape:(4983, 36)
    :param r_mat: shape:(4983, 36)
    :param x_mat: shape:(4983, 36)
    :param y_mat: shape:(4983, 36)
    :return:
    """
    gradient_list = []
    param_order = []

    r_mat = r_mat + 1e-8
    if 'X' in param_set: # calculate dE / dX
        dp_d, dq_d, dr_d = param_set['X']
        dE_dparam = (2 * 1. / (r_mat ** 2) * mask * ((p_mat / r_mat * mask - x_mat) * (r_mat * dp_d - p_mat * dr_d) + (q_mat / r_mat * mask - y_mat)
                                                  * (r_mat * dq_d - q_mat * dr_d))) * mask
        dE_dparam = np.sum(dE_dparam, axis=1) # -> (4983,)
        gradient_list.append(dE_dparam)
        param_order.append('X')
        
    if 'Y' in param_set: # calculate dE / dY
        dp_d, dq_d, dr_d = param_set['Y']
        dE_dparam = (2 * 1. / (r_mat ** 2) * mask * ((p_mat / r_mat * mask - x_mat) * (r_mat * dp_d - p_mat * dr_d) + (q_mat / r_mat * mask - y_mat)
                                                   * (r_mat * dq_d - q_mat * dr_d))) * mask
        dE_dparam = np.sum(dE_dparam, axis=1)  # -> (4983,)
        gradient_list.append(dE_dparam)
        param_order.append('Y')
        
    if 'Z' in param_set: # calculate dE / dZ
        dp_d, dq_d, dr_d = param_set['Z']
        dE_dparam = (2 * 1. / (r_mat ** 2) * mask * ((p_mat / r_mat * mask - x_mat) * (r_mat * dp_d - p_mat * dr_d) + (q_mat / r_mat * mask - y_mat)
                                                     * (r_mat * dq_d - q_mat * dr_d))) * mask
        dE_dparam = np.sum(dE_dparam, axis=1)  # -> (4983,)
        gradient_list.append(dE_dparam)
        param_order.append('Z')
        
        
    gradient_np = np.array(gradient_list, dtype=np.float32).reshape(-1)[:, np.newaxis] # ((4983 x 3, 1)... (dE / dX(4983), dE / dY(4983), dE / dZ(4983)), 1)

    term0 = []  # -> [[4983, 36], [4983, 36], [4983, 36]]
    term1 = []
    term2 = []
    term3 = []
    term4 = []
    for num_c, param_col1 in enumerate(param_order):

        if param_col1 == 'X' or param_col1 == 'Y' or param_col1 == 'Z':
            dp_dk, dq_dk, dr_dk = param_set[param_col1] # (4983, 36), (4983, 36), (4983, 36)
            term0.append(2 * 1. / (r_mat ** 4) * mask) # (4983, 36)
            term1.append((r_mat * dp_dk - p_mat * dr_dk) * mask) # (4983, 36)
            term2.append((r_mat * dp_dk - p_mat * dr_dk) * mask) # (4983, 36)
            term3.append((r_mat * dq_dk - q_mat * dr_dk) * mask) # (4983, 36)
            term4.append((r_mat * dq_dk - q_mat * dr_dk) * mask) # (4983, 36)

        else:
            pass

    term0 = np.array(term0, dtype=np.float32).reshape(r_mat.shape[0]*3, r_mat.shape[1]) # (4983 x 3, 36)
    term1 = np.array(term1, dtype=np.float32).reshape(r_mat.shape[0]*3, r_mat.shape[1]) # (4983 x 3, 36)
    term2 = np.array(term2, dtype=np.float32).reshape(r_mat.shape[0]*3, r_mat.shape[1]) # (4983 x 3, 36)
    term3 = np.array(term3, dtype=np.float32).reshape(r_mat.shape[0]*3, r_mat.shape[1]) # (4983 x 3, 36)
    term4 = np.array(term4, dtype=np.float32).reshape(r_mat.shape[0]*3, r_mat.shape[1]) # (4983 x 3, 36)
    print("term0.shape:{}, term1.shape:{}, term2.shape:{}, term3.shape:{}, term4.shape:{}".format(term0.shape, term1.shape, term2.shape, term3.shape, term4.shape))

    hessian_np = np.zeros((term0.shape[0], term0.shape[0]), dtype=np.float32) # -> (4983 x 3, 4983 x 3)
    term0 = np.transpose(term0) # (4983 x 3, 36) -> (36, 4983 x 3)
    term1 = np.transpose(term1) # (4983 x 3, 36) -> (36, 4983 x 3)
    term2 = np.transpose(term2) # (4983 x 3, 36) -> (36, 4983 x 3)
    term3 = np.transpose(term3) # (4983 x 3, 36) -> (36, 4983 x 3)
    term4 = np.transpose(term4) # (4983 x 3, 36) -> (36, 4983 x 3)

    for num, (term0_1, term1_1, term2_1, term3_1, term4_1) in enumerate(zip(term0, term1, term2, term3, term4)): # (4983 x 3)
        print("now num:{}".format(num))
        term0_1 = np.tile(term0_1[:, np.newaxis], (1, term1_1.shape[0]))  # (4983 x 3, 4983 x 3)
        term1_1 = np.tile(term1_1[:, np.newaxis], (1, term1_1.shape[0]))  # (4983 x 3, 4983 x 3)
        term3_1 = np.tile(term3_1[:, np.newaxis], (1, term3_1.shape[0]))  # (4983 x 3, 4983 x 3)
        term2_1 = np.tile(term2_1[np.newaxis, :], (term2_1.shape[0], 1))  # (4983 x 3, 4983 x 3)
        term4_1 = np.tile(term4_1[np.newaxis, :], (term4_1.shape[0], 1))  # (4983 x 3, 4983 x 3)
        term12 = term1_1 * term2_1
        term34 = term3_1 * term4_1

        hessian_np += term0_1 * (term12 + term34)  # (4983 x 3, 4983 x 3)
    
    print("gradient_np.shape:{}, hessian_np.shape:{}".format(gradient_np.shape, hessian_np.shape))
    return gradient_np, hessian_np


gradient_np, hessian_np = make_gradient_hessian(param_set, p_mat, q_mat, r_mat, x_mat, y_mat, mask)

7.3 連立方程式を解いて X の更新量を求める

以下の連立方程式を解いて X の更新量を求める。

スクリーンショット 2023-05-17 17.48.42.png

# step 3) solve a system of equations
def solve_a_system_of_equations_from_hessian_gradient(gradient_np, hessian_np_ori, c):
    delta_param = {}
    hessian_np = copy.deepcopy(hessian_np_ori)
    mul = np.ones(hessian_np.shape, dtype=hessian_np.dtype)
    mul += np.eye(hessian_np.shape[0], dtype=hessian_np.dtype) * c
    hessian_np *= mul
    delta_XYZ = - np.linalg.inv(hessian_np).dot(gradient_np)
    delta_param['X'] = delta_XYZ[:len(delta_XYZ)//3, 0]
    delta_param['Y'] = delta_XYZ[len(delta_XYZ)//3:len(delta_XYZ)//3*2, 0]
    delta_param['Z'] = delta_XYZ[-len(delta_XYZ)//3:, 0]

    return delta_param


delta_param = solve_a_system_of_equations_from_hessian_gradient(gradient_np, hessian_np, c=c)

7.4 パラメータを更新する

パラメータ X を更新する。

# step 4) update parameters
def update_params(XYZs, delta_param):
    # TODO ... only applied for XYZs
    XYZs_mod = copy.deepcopy(XYZs)
    if 'X' in delta_param:
        XYZs_mod[0, :] += delta_param['X']
    if 'Y' in delta_param:
        XYZs_mod[1, :] += delta_param['Y']
    if 'Z' in delta_param:
        XYZs_mod[2, :] += delta_param['Z']
    return XYZs_mod

XYZs_tmp = update_params(XYZs, delta_param)

7.5 誤差が小さくなるまで繰り返す

更新したパラメータで再投影誤差 E' を求め、これが元の誤差 E より小さくなっていなければ $c = c \times 10$ として 7.3 からやり直す。

E_mod = copy.deepcopy(E) + 1.
c = 0.0001
    while E_mod > E:

        # step 3) solve a system of equations
        delta_param = solve_a_system_of_equations_from_hessian_gradient(gradient_np, hessian_np, c=c)

        # step 4) update parameters
        XYZs_tmp = update_params(XYZs, delta_param)

        # step 5) calculate Error with modified parameters
        E_mod = get_loss(XYZs_tmp, df_x, df_y, mat, sample_num=sample_num)
        print("E_mod, ", E_mod)

        c *= 10.

7.6 誤差の変化がなくなるまで続ける

7.5による誤差の変化量が $|E' - E| < \delta$ (δは小さい値)と小さくなれば終了する。

numpy でgradientとヘッセ行列を算出するさいの工夫。

(5.2.3)式、(5.2.5)式を for ループを使わず、numpyの行列計算で一気に演算したい。

そこでgradientは以下のように3D点 X 方向と画像方向に展開した行列を作り、(5.2.3)式のシグマ内を計算した上で画像方向に集計する。

スクリーンショット 2023-05-17 18.33.02.png

ヘッセ行列を求める(5.2.5)式に関しては以下のように(5.2.5)式の小括弧ごとに3D点X方向、画像方向の2次元配列を作り、それを np.tile() で1軸展開する。その上で(5.2.5)式の大括弧内を計算し、それを画像方向に渡って集計する。

スクリーンショット 2023-05-17 18.36.54.png

1回のiterationで誤差が 51006.08.. から 51000.82.. になった。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1