# 3D Rehabilation System Instruction ![](https://i.imgur.com/9EURn1e.jpg) [理論](https://hackmd.io/@yQgYhimORGmkQrmqS-yKJg/HywPNkS4L) ## 安裝OpenPose Package 可參考[Github Repository](https://github.com/CMU-Perceptual-Computing-Lab/openpose) 1. 解壓縮程式碼 2. `cd openpose`,移動到`openpose`資料夾下。 3. 確認系統環境符合以下條件: * Cuda 8.0 * Cudnn 5.1.0 * Gcc 5.4 * Protoc 3.2.0 * OpenCV 3.2.0 * cmake-gui with cmake version==3.5.1 4. `cmake-gui`打開圖形化介面後,依照下面圖片操作。 ![](https://i.imgur.com/6fVZNDM.png) ![](https://i.imgur.com/cA1dxGC.png) ![](https://i.imgur.com/E00BFdo.png) ![](https://i.imgur.com/KG8eL87.png) 5. 關閉`cmake-gui`,`cd build`進入`build`資料夾。 6. 編譯程式 ```bash make -j8 `nproc` ``` ## 使用`OpenPose`校正相機內在參數 ### 實驗設備 1. Logitech C310 x 3 2. Type A USB 2.0 延長線 x 3 3. 腳架 x 3 4. 校正板 * Aruco Marker * 棋盤格 5. OpenPose Package ### 確認相機編號 相機USB口連接主機USB孔後,用`python3 camIdx.py --cam_idx <cam_idx>`確認可以正常連接。 * 參考範例: 0. `cd ../../Aruco`,移動到`Aruco`資料夾下。 1. 連接相機A 2. `python3 camIdx.py --cam_idx 0` → 確認相機A可正常使用後,相機A的編號則是`0`,之後的程式都以這個編號讀取相機A。 3. 連接相機B 4. `python3 camIdx.py --cam_idx 1` → 確認相機B可正常使用後,相機B的編號則是`1`,之後的程式都以這個編號讀取相機B。 5. 連接相機C 6. `python3 camIdx.py --cam_idx 2` → 確認相機C可正常使用後,相機C的編號則是`2`,之後的程式都以這個編號讀取相機C。 ### 步驟 0. `cd ../openpose`,移動到`openpose`資料夾下。 1. 產生校正板 * 參考[Camera-Calibration-Pattern-Generator](https://github.com/ProximaB/Camera-Calibration-Pattern-Generator),`python3 CheckerboardCreator.py -r 8 -c 7`產生校正版 * 實驗用校正板大小:8x7校正板,50.0mm。 * 將校正板黏在平面上 ![](https://i.imgur.com/3rRoafG.jpg) 2. 拍攝~450張內含校正板的照片。 * ```python3 RecordInt.py --cam_idx_in <idx_in> --cam_idx_out <idx_out>``` 用途:使用OpenCV讀取編號為`<idx_in>`的相機,每秒拍攝一張照片,並且將照片存到`IntrinsicsImgs/<idx_out>`資料夾裡面。 * --cam_idx_in : OpenCV讀相機所需要的相機編號。 * --cam_idx_out : 照片儲存位置的名稱,建議設定成跟`--cam_idx_in`相同。 * 參考範例:```python3 RecordInt.py --cam_idx_in 0 --cam_idx_out 0``` * 每秒讀取編號為`0`的相機,並將照片儲存到`IntrinsicsImgs/0` * 注意事項 * 校正板不能旋轉超過30°。 * 拍攝時將校正版由近處移動到遠處。 * 拍攝時校正板要涵蓋整個照片。 * 參考: [OpenPose Calibration Package](https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/doc/modules/calibration_module.md) 3. 使用`OpenPose Calibration Package`校正相機內在參數 * 修改`mScripts/calibrate_intrinsics.sh` ```./build/examples/calibration/calibration.bin --mode 1 --grid_square_size_mm <grid_size> --grid_number_inner_corners <hxw> --camera_serial_number <idx_out> --calibration_image_dir ./IntrinsicsImgs/<idx_out>``` * --grid_square_size_mm : 棋盤格大小,單位為mm。 * --grid_number_inner_corners : 棋盤格內層點的數量,算法如下圖。 ![](https://i.imgur.com/gRKhX0E.jpg) * --camera_serial_number : 將校正得到的相機內在參數存到`models/cameraParameters/flir/<idx_out>.xml`,`<idx_out>`需要和拍攝校正板時的`<idx_out>`參數相同。 * --calibration_image_dir : 從`./IntrinsicsImgs/<idx_out>`讀取上一步拍攝的照片,`<idx_out>`需要和拍攝校正板時的`<idx_out>`參數相同。 * 參考範例```./build/examples/calibration/calibration.bin --mode 1 --grid_square_size_mm 50.0 --grid_number_inner_corners 7x6 --camera_serial_number 0 --calibration_image_dir ./IntrinsicsImgs/0``` * 棋盤格子大小:`50.0mm` * 棋盤內圈點數量(直,橫):`(7,6)` * 輸出檔案名稱:`models/cameraParameters/flir/0.xml` * 棋盤格照片:`./IntrinsicsImgs/0` 4. `bash mScripts/calibrate_intrinsics.sh`,使用修改過的Script校正內在參數。生成檔案放在`models/cameraParameters/flir/<idx_out>.xml` * 注意事項 * 450張相片大概需要花9小時做校正。 * 每一台相機都要校正一次,也就是每一台相機都要跑一次步驟2(拍照)+步驟3(改script)+步驟4(執行script)。 * 每台相機校正一次後就不需要再做內在參數校正。 ## 架設相機 實驗環境,下圖為俯視圖: ![](https://i.imgur.com/Eh8GLs4.jpg) * 三台攝影機高度最高到兩公尺。 * 三台攝影機夾角最大到45°。 * 注意事項: * 攝影機必須能夠看到的`Aruco Marker`,下圖為校正成功的最大夾角之下`Aruco Marker`在相機之中的樣子。 ![](https://i.imgur.com/msMd0Fr.jpg) * 攝影機必須完整照到受測者。 * 受測者面向至中的攝影機。 ## 使用`Aruco`校正外在參數 1. `cd ../Aruco`,移動到`../Aruco`資料夾下。 2. 安裝`python 3.5.2`, `opencv-python`, `opencv-contrib-python`, `numpy` 3. 使用`Aruco Marker` * 檔名:`Aruco_DICT_6x6_250.png` * 實驗用Marker大小:26.8x26.8 cm。 * 將Marker黏在平面上。 * ![](https://i.imgur.com/OuPG1CS.jpg) 4. 每台攝影機拍攝15秒內含`Aruco Marker`的影片。 * ```python3 Record.py --cam_num <cam_num> --dur 15.0``` 用途:同時讀取編號為`0`~`cam_num-1`的攝影機畫面,並錄製長度為15.0秒的影片(Aruco Marker不需要移動)並且存到`Videos/camera_*.avi`裡面。 * --cam_num : 同時讀取編號為`0`~`cam_num-1`的攝影機。 * 範例影片 {%youtube WteZrxS3lDc%} {%youtube aaGWR_HKYoc%} {%youtube et1DHLObgRs%} 5. 拍攝計算誤差用的照片。 * `python3 RecordImg.py --cam_num <cam_num>` 用途:同時讀取編號為`0`~`cam_num-1`的攝影機畫面,每秒各拍攝一張照片(要移動Aruco Marker),並將儲存到`ReprojError/`裡面。 * --cam_num : 同時讀取編號為`0`~`cam_num-1`的攝影機。 * 範例照片 ![](https://i.imgur.com/vjuiA67.jpg) 6. 新增一個資料夾:`Params`,並且將`../OpenPose/models/cameraParameters/flir/*.xml`移到`Params/`裡面。 7. 產生校正外在參數所需要到`Map Configuration` * `python3 Config.py` 用途:指定`Root Camera`,並且指定其他相機和Root Camera連結之間的關係。將關係輸出到`config.json` 使用方法(互動式Script): 1. `#Camera` : 輸入相機數量。注意相機編號應為`0`~`cam_num-1`。 2. `Root index` : 輸入指定成`Root Camera`的相機的編號。 3. `Config` : 輸入其他相機跟`Root Camera`之間的連結關係。 > 格式: \<A\>-\<B\> \<A\>-\<C\> > 意思: 編號B相機是編號A相機的Child; 編號C相機是編號A相機的Child > * 參考範例 ![](https://i.imgur.com/Qzl54qN.png) 使用三台相機,`Root Camera`是相機編號0,編號1和編號2連結到編號0。 8. 校正相機外在參數 * `python3 Map.py --length <marker_length meter>` 用途:使用`Step 4`拍攝的15秒影片,`Step 6`移動的內在參數檔案,`Step 7`產生的`config.json`計算出外在參數。產生出內含相機參數的檔案生成到`Outputs/*.xml` * `*.xml`範例 ![](https://i.imgur.com/37054qE.jpg) 9. 計算投影誤差 * `python3 Error.py --length <marker_length meter>` 用途:使用`Step 5`拍攝的照片,`Step 8`得到的相機參數檔案,`Step 7`產生的`config.json`計算出外在參數。計算外在參數的投影誤差,投影誤差會顯示在`Terminal`上面。 * 參考輸出結果: ```bash camera id: Average Reprojection Error {0: 0.586689, 1: 0.366055, 2: 2.751398} # Camera 0 → Reprojection Error : 0.586689 # Camera 1 → Reprojection Error : 0.366055 # Camera 2 → Reprojection Error : 2.751398 ``` * 實驗成功之三台相機的最大投影誤差: `0.5869; 0.3661; 2.7514` 10. 修改`ROI`以限制骨架偵測範圍。 ![](https://i.imgur.com/9M52I46.jpg) * `0 640` : 最小有效範圍寬度/最大有效範圍寬度 * `0 380` : 最小有效範圍高度/最大有效範圍高度 ## 使用OpenPose進行骨架分析 1. 將相機參數從`Outputs/*.xml`移動到`../openpose/models/cameraParameters/flir/`裡面取代原本的檔案。 2. 移動到`../openpose/` 3. 執行程式。 `bash mScripts/skeleton.sh` 成功執行後會出現兩個視窗: 1. 使用OpenGL繪製3D骨架產生的視窗,在這個視窗可以用滑鼠滾輪縮放3D骨架大小。 ![](https://i.imgur.com/ZTXAFaQ.png) 2. 使用OpenCV產生的視窗,這個視窗整合每個相機預測的2D骨架,由OpenGL繪製的3D骨架,以及計算出給使用者使用的資訊。使用者主要使用這個視窗。 ![](https://i.imgur.com/XFYNcxX.png) ## OpenPose細節 針對3D骨架預測的修改主要分成四個部分: 1. 3D : 3D骨架重建。 2. Kalman : 加入Kalman Filter。 3. Angle : 計算角度。 4. GUI : 繪製GUI。 5. ROI : 限制每台相機中可以偵測2D骨架的區域。 這四個部分可以用`grep -rnw './<include/src>' -e '\[<3D/Kalman/Angle/GUI>\]'`找的對應的程式碼。 ### 3D * `examples/tutorial_api_cpp/18_3d_reconstruction.cpp` * `Line 98, 103, 170, 171` : `// [3D] Read from videos/webcams` 從攝影機或是影片讀取資料。(Realtime or not) * `src/openpose/3d/poseTriangulation.cpp` * `Line 525` : `// [3D] Collect detected keypoints and camera parameters` 將每台相機偵測到的2D關節點以及相機參數放到`xyPointsElement(vector), cameraMatricesElement(vector)`裡面。 * `Line 541` : `// [3D] Collect keypoints that are visible from at least two cameras. ...` 從上一部得到的`Vector`裡面找出至少在兩台相機中被預測的到的關節點。將該關節點在相機中預測位置,關節點的編號,以及預測相機的相機參數放到`xyPoints(vector), indexesUsed(vector), cameraMatricesPerPoint(vector)`裡面。 * 等同於移除無法重建的關節點 * `Line 553` : `// [3D] Do 3D reconstruction` 使用上一步得到的`Vector`重建3D座標並放到`xyzPoints(vector)`。 * `Line 301` : `// [3D] RanSac for >= 3 cameras` 使用`RanSac`對投影誤差超過threshold的點做最佳化。 * `Line 588` : `// [3D] Remove outliers` 移除 (1) 重建失敗 (2)投影誤差過大的重建3D座標。 * `Line 596` : `// [3D] Save reconstructed 3D keypoints` 將最佳會後的3D關節點存入指標中。 ``` keypoints3D[baseIndex] = xyzPoints[index].x // x position keypoints3D[baseIndex+1] = xyzPoints[index].y // y position keypoints3D[baseIndex+2] = xyzPoints[index].z // z position keypoints3D[baseIndex+3] = 1.0 // Score of this keypoint ``` ### Kalman * `include/openpose/pose/wPoseExtractor.hpp` * `Line 100` : `// [Kalman] Implement Kalman filter` 初始化`Kalman filter` * `Line 144` : `// [Kalman] Apply Kalman filter` 使用`Kalman filter`對關節點做最佳化。 ### Angle * `src/openpose/3d/poseTriangulation.cpp` * `Line 613, 43` : `// [Angle] Calculate Angle` 計算角度。先用關節點計算出代表肢體的向量,再計算向量之間的夾角。關節對照圖可參考[OpenPose 的文件](https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/doc/output.md#pose-output-format-body_25)。 ![](https://i.imgur.com/nWVXPUO.jpg) * `Line 625, 753` : `// [Angle] Return Angle` 以`pair`回傳計算角度。計算角度放在`second mamber variable`。 ### GUI * `src/openpose/3d/poseTriangulation.cpp` * `Line 116` : `// [GUI] Pair labels and results` 用`std::map`儲存GUI上顯示名稱和計算出的角度。 * `Line 582` : `// [GUI] Log the missing frame rate in the terminal` 再terminal上顯示`missing frame rate` * `include/openpose/pose/wPoseExtractor.hpp` * `Line 193` : `// [GUI] Remove unwanter keypoints` 移除五官/左腳掌/右腳掌的3D關節點。 * `src/openpose/gui/gui3D.cpp` * `Line 58` : `// [GUI] Default skeleton size` 3D 骨架初始大小。數字小->骨架大。 * `include/openpose/gui/wGui3D.hpp` * `Line 88` : `// [GUI] Concat image, Display angle(Entry)` 將名稱和角度顯示在畫面上的函式之進入點。 * `src/openpose/gui/frameDisplayer.cpp` * `Line 145` : `// [GUI] Concat image, Display angle` 用`cv::putText`將名稱和角度顯示在畫面上。 ### ROI * `include/openpose/3d/wPoseTriangulation.hpp` * `Line 89` : `// [ROI] Save ROI` 檔案裡讀取每台相機的`ROI` * `src/openpose/3d/poseTriangulation.hpp` * `Line 467` : `// [ROI] Restrict ROI` 使用ROI限制偵測所得關節點,若關節點在ROI之外則是`invalid keypoint`