Videopose3D ビルド自分メモ＆スケルトンビューワー

（2019.09.18更新）
@timtoronto634 さんのおかげで、DetectronとVideopose3Dのビルドが成功。感謝感謝。
この元記事を必ず参照のこと
https://qiita.com/timtoronto634/items/ee018ac89e6b9f779194
ここで記されている、condaで作る環境、どのパッケージのどのバージョンを入れるかがキモ。@timtoronto634さんもそこをかなりご苦労された模様。

僕の場合は、上のやり方で環境を整え、それより前に成功していたVideopose3D公式のビルドから必要そうなものを都度もってきてみた。
Pythonだから、エラーが出るたびにそれをすれば進んでいくんだが、Cythonで作ったオブジェクトをもってくるところで詰まった。Cythonだもんね。Pythonのコードではない。
なので、公式の方法でもう一度makeし(cythonの部分がそれで作られる）、そのフォルダー群を、tobiasczさんのコードでビルドできたフォルダーであるVideoPose3Dフォルダー以下にコピーした。
detectrpm_tools/detectronフォルダーがtobiasczさんの実装での本体なので、ここに多くをコピーすることになるが、公式のを一度ビルドすれば、どこに置くべきかすぐわかる。
なので，一回は，公式の方法でビルドしてみるのを強くオススメ

ここで一つ、ご注意。
上記のやり方で順にやってきた直後だと問題ないのだが、後日また処理をしようとすると、caffe2まわりでエラーが出ることが多い。
その場合、@timtoronto634さん作成のcondaの環境になっているかどうか、再確認必要。さまざまなライブラリーの様々なバージョンを使うことになるので、それがふとバージョン違いがおきていることがある。以下で確認、バージョン違いだったら

```
conda list
conda install あるいは uninstall [package名]
```
後日動かすと、なぜかエラーというのはcaffe2まわりではおおいらしく、先ほど動かすと、

```
ImportError: No module named past.builtins
```
というのが出た。これは以下で簡単に解決

```
$ pip install future
```
https://github.com/facebookarchive/caffe2/issues/712
から。なぜこれで解決するのか、ここの書き込みの最後の人も僕も？？なのだが、まあ古いcaffe2なので気にしないようにしよう。

Detectronで、自分のビデオを処理することになるが、ここで
AttributeError: 'NoneType' object has no attribute 'astype'
に悩まされた。
https://github.com/alexgkendall/SegNet-Tutorial/issues/87
と、ここにあるリンク先を見ると、OpenCVではよくあるそうで、入力するイメージやビデオのファイルのあるフォルダーへのパスの記述が融通きかない

僕のケースでは、~/VideoPose3D/detectron_tools　の下に skateframesというフォルダ（ムービーから書きだしたpngがある）と、demo/outputフォルダーがある。~/VideoPose3D/detectron_toolsにおいて、上のコマンドでdetectronを起動した場合、普通ならこれらのフォルダ指定に"./"が不要のはずが必要だった。

```
python infer_simple.py --cfg e2e_keypoint_rcnn_R-101-FPN_s1x.yaml --output-dir ./demo/output --image-ext png --wts model_final.pkl ./skateframes
```

うまく行くと、demo/outputのなかに、スケルトンがオーバーレイされたpdfファイル（フレームの数だけある）と、data_2d_detections.npzファイルが生成される

#3D推定部分(Videopose3D本体）
~/VideoPose3D/ で
out_cutted.mp4は、detectronの入力用とした（ffmpegでバラすまえの）動画ファイル名、output_video.mp4は、その動画の横に、推定したスケルトンの動画をおいた出力動画ファイル名

```
python run_wild.py -k detections -arc 3,3,3,3,3 -c checkpoint --evaluate d-pt-243.bin --render --viz-subject S1 --viz-action Directions --viz-video out_cutted.mp4 --viz-camera 0 --viz-output output_video.mp4 --viz-size 5 --viz-downsample 1 --viz-skip 0
```

なお、僕の場合、detectronのpython2.7 conda環境と、videopose3Dのpython3.6 conda環境を何度も行ったり来たりしていると、caffe2が謎エラーを出すようになるので、ここはちょっと注意。rebootで治るけど．
行ったり来たりよりは，ターミナル２つ開いて，condaもdetectronとvideopose3Dのconda環境がどちらも走っている状態でやったほうが，僕の場合は安定しています


#推定された座標データの出力
https://github.com/facebookresearch/VideoPose3D/issues/40
3次元np.arrayに格納されているで、binaryでないと出せない　flatten()してテキストファイルに出す方法もあるが，構造を壊さず尊重する方針にしました．バイナリーだと，えぇ〜と
おもってしまうのですが，
np.arrayのバイナリーでの読み書きの解説はこちらです。簡単です
https://note.nkmk.me/python-numpy-load-save-savez-npy-npz/

本体であるrun_wild.pyの304行目に、以下の行をいれただけです

```
    # save predicted marker positions (np.array) to np binary file
    np.save('predictions',prediction)

```
（内輪連絡〜 ゼミの諸君は、うえのパッチをあてたmyrun_wild.pyをつかってね）


#推定されたスケルトンのビューワー
Jupyter notebookで動くように分割して書いてしまいましたが，
ひとまとめにしても動くとおもいます．
自分が作ったデータ形式ではないので．確認しながら動かすのが吉．．

```
    import numpy as np
    import pandas as pd
    import matplotlib
    matplotlib.use('TkAgg') # Need to use in order to run on mac",
    from matplotlib import pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib.colors import cnames
    from matplotlib import animation
    %matplotlib notebook

    arrayMarker3Dcoordinates = np.load('../data/predictions.npy')
```

一旦，２次元にする．列が，marker1x, marker1y, marker1z, marker2x, marker2y.....の形. マーカーは17種類
マーカーデータの並びは，なかなかにカオスで，どうしてこういう並びにしてるんだ？？
僕の場合は，描画しながら特定していきました．間違いがあったらぜひ教えて
なお，３次元テンソルのデータを，一度，２Dの表の形式にしてしまうのは，プログラム的には無駄ですが，他の伝統的なバイオメカニクスのデータ分析と互換性をもたせるためと，xz,yz,xyの平面ごとの射影でみる，細かい分析のためにやっています

```
arrayMarker3Dcoordinates.shape
df = pd.DataFrame(arrayMarker3Dcoordinates.reshape(584,17*3),
                  columns=['pelvisX','pelvisY','pelvisZ', 'R-troX','R-troY','R-troZ', 'R-kneeX','R-kneeY','R-kneeZ', 'R-ankleX','R-ankleY','R-ankleZ',  'L-troX','L-troY','L-troZ', 'L-kneeX','L-kneeY','L-kneeZ','L-ankleX','L-ankleY','L-ankleZ','centerX', 'centerY','centerZ', 'neckX','neckY','neckZ','noseX','noseY','noseZ', 'headX','headY','headZ',  'L-shoX','L-shoY','L-shoZ', 'L-elbX','L-elbY','L-elbZ', 'L-wristX','L-wristY','L-wristZ','R-shoX','R-shoY','R-shoZ','R-elbX','R-elbY','R-elbZ', 'R-wristX', 'R-wristY','R-wristZ'])
```

3Dアニメーションを書くためのnumpy array x_tは，[マーカー数[フレーム数[x,y,z] ] ]の３次元テンソルになっているので，これに座標データを入れ直しています．

Stickpicture animationは
https://stackoverflow.com/questions/21367541/3d-animation-with-matplotlib-connect-points-to-create-moving-stick-figure を下敷きにして実装しました

```
 import numpy as np
    import pandas as pd
    import matplotlib
    #matplotlib.use('TkAgg') # Need to use in order to run on mac
    from matplotlib import pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib.colors import cnames
    from matplotlib import animation
    %matplotlib notebook
    
    t_start = 0 # start frame
    t_end = df.shape[0] 
    
    N_tag = df.shape[1]# nr of tags used (all)
    #print(N_tag)
    N_trajectories = N_tag
    
    t = np.linspace(0,t_end/30,df.shape[0]) # pseudo time-vector for first walking activity
    x_t = np.zeros(shape=(int(N_tag),df.shape[0],3)) # empty animation array (3D)
    
    for tag in range(17):
        # store data in numpy 3D array: (tag,time-stamp,xyz-coordinates)
        x_t[tag,:,:] = df.iloc[:,tag*3 :tag*3+3]
        
    #===STICK-LINES========================================================================================
    #xx = [x_t[1,:,0],x_t[2,:,0]]
    #yy = [x_t[1,:,1],x_t[2,:,1]]
    #zz = [x_t[1,:,2],x_t[2,:,2]] 
    #======================================================================================================
    
     
    # Set up figure & 3D axis for animation
    fig = plt.figure()
    ax = fig.add_axes([0, 0, 1, 1], projection='3d')
    ax.axis('on')
    
    # choose a different color for each trajectory
    colors = plt.cm.jet(np.linspace(0, 1, int(N_trajectories)))
    #colors
    # set up trajectory lines
    lines = sum([ax.plot([], [], [], '-', c=c) for c in colors], [])
    # set up points
    pts = sum([ax.plot([], [], [], 'o', c=c) for c in colors], [])
    # set up lines which create the stick figures
    
    stick_defines = [
        (0,1),
        (0,4),
        (4,5),
        (5,6),
        (0,7),
        (1,2),
        (2,3),
        (7,8),
        (8,9),
        (9,10),
        (8,14),
        (8,11),
        (11,12),
        (12,13),
        (14,15),
        (15,16)
    ]
    
    stick_lines = [ax.plot([], [], [], 'k-')[0] for _ in stick_defines] #上で定義されたstick_definesにある点の間に線をひく
    
    # Automatically set axes limits
    # prepare the axes limits 
    #ax.set_xlim(df_minmax.loc['x'].values)
    #ax.set_ylim(df_minmax.loc['y'].values) 
    #ax.set_zlim(df_minmax.loc['z'].values) 
    
    # Setting the axes properties
    # https://matplotlib.org/2.1.2/gallery/animation/simple_3danim.html
    mid_x =0
    x_range = 1
    ax.set_xlim3d(mid_x - x_range, mid_x + x_range)
    ax.set_xlabel('X')
    
    mid_y =0
    y_range = 1
    ax.set_ylim3d(mid_y - x_range, mid_y + x_range)
    ax.set_ylabel('Y')
    
    mid_z = 0.5
    z_range = 1
    ax.set_zlim3d(mid_z - z_range, 1.5)
    ax.set_zlabel('Z')
    
    ax.set_title('markers in 3D')
    
    # set point-of-view: specified by (altitude degrees, azimuth degrees)
    ax.view_init(20,45)
    
    # initialization function: plot the background of each frame
    def init():
        for line, pt in zip(lines, pts): # lines, ptsのリストから，同時にi番目のデータを取得する．linesからline, pts からpt
            # trajectory lines
            line.set_data([], [])
            line.set_3d_properties([])
            # points
            pt.set_data([], [])
            pt.set_3d_properties([])
        return lines + pts + stick_lines
    
    # animation function.  This will be called sequentially with the frame number
    def animate(i):
        # 1 * i means show every frame, 2*i means one frame show one frame no-show
        i = (1 * i) % x_t.shape[1] 
    
        for line, pt, xi in zip(lines, pts, x_t):
            x, y, z = xi[:i].T # note ordering of points to line up with true exogenous registration (x,z,y)
            print(x)
            pt.set_data(x[-1:], y[-1:])
            pt.set_3d_properties(z[-1:])
    
        for stick_line, (sp, ep) in zip(stick_lines, stick_defines):
            stick_line._verts3d = x_t[[sp,ep], i, :].T.tolist()
    
        #ax.view_init(30, 0.3 * i)
        ax.view_init(20,60+i*2)
        fig.canvas.draw()
        return lines + pts + stick_lines
    
    # instantiate the animator.
    anim = animation.FuncAnimation(fig, animate, init_func=init, frames=t_end, interval=1, repeat = False, blit=True)
    
    # GIF animationで保存の場合．画面描画と両立する．
    #anim.save('3Dplotanimation.gif', writer='pillow', fps=10)
    
    plt.show()
```
自分メモなので，足りないところたくさんあると思います．
ご質問ください．
下のGIFアニメが自動で出なかったら，白いところをクリックして別ウインドウで見てください↓
![3Dplotanimation-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58434/db125394-e818-423e-565f-501241712259.gif)