OpenCV2で物体追跡 〜 特徴点の移動軌跡をフレーム画像から切離してファイル出力

__[前回の記事](https://qiita.com/electronics_diy721/items/ad9bb5a095ba90e96e37)__では、OpenCV2の物体追跡APIの解析結果を、3つのWindowsに分けて、個別出力しました。

1. オリジナルの画像に、特徴点の移動軌跡を重ねたフレームを、時系列で再生したウインドウ
2. オリジナルの画像に、特徴点を小さな●で重ねた表示したフレームを、時系列で再生したウインドウ
3. 特徴点の移動の軌跡ラインだけを、背景真っ黒な画面に表示させたものを、時系列で再生したウインドウ

今回は、Windowsが閉じた後に、後で繰り返し上記の1〜３を閲覧できるように、ファイル出力を行いました。

- 「１」の__動画__ファイル
- 「１」の__静止画__ファイル （総フレーム数個のファイル）
- 「２」の__静止画__ファイル （総フレーム数個のファイル）
- 「３」の__静止画__ファイル （総フレーム数個のファイル）

前回の記事に引き続いて、__次の記事で公開されているコード__を参考にしました。

- https://qiita.com/hitomatagi/items/772549d0fc3c89fb3cc4

##実行画面

```bash:Terminal
electron@diynoMacBook-Pro optical_flow % python3 optical_flow_save_files.py dance.mp4
次の動画ファイルを解析します。 ：dance.mp4
フレームに追跡すべき特徴点が見つからなくなりました。
electron@diynoMacBook-Pro optical_flow %
```

##出力ファイル

###（ 各フレームを処理した結果の静止画ファイル ）

動画の総フレーム数分、ファイルが吐き出されます。

![スクリーンショット 2021-07-18 22.32.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/6c344784-ac97-1850-aff2-c760363d37ce.png)

####__（ オリジナルの画像に、特徴点を小さな●で重ねた表示したフレーム画像  )__

![スクリーンショット 2021-07-18 22.32.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/79be9bbb-0b0c-fc18-8e5e-6cd88ade2f81.png)

####__（ 拡大した１枚  )__

![スクリーンショット 2021-07-18 22.46.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/123fff6f-e684-c2b7-3209-ebe3fa045881.png)

####__（ オリジナルの画像に、特徴点の移動軌跡を重ねたフレーム画像  )__

![スクリーンショット 2021-07-18 22.33.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/72bb0a36-4425-48b9-2dbe-57ed38171162.png)

####__（ 拡大した１枚  )__

![スクリーンショット 2021-07-18 22.49.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/b27f26e3-0e1c-99a3-26b4-6214807704a6.png)

####__（ 特徴点の移動の軌跡ラインだけを、背景真っ黒な画面に表示させた画像  )__

![スクリーンショット 2021-07-18 22.33.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/236dad78-21b9-0386-2ba5-0a2b1b6f4a10.png)

####__（ 拡大した１枚  )__

![スクリーンショット 2021-07-18 22.51.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/c5b1edd6-bd16-311c-2d88-b49cf645fb2a.png)


###（ 処理した結果の動画ファイル ）

１ファイルが吐き出されます。

![スクリーンショット 2021-07-18 22.34.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/16012d88-7740-d18e-d768-5cf41a90dc98.png)

__MacbookのQuickTimeでは再生できなかった。__

![スクリーンショット 2021-07-18 22.34.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/e7f09407-caf5-9411-bceb-57b0d90a7ad6.png)

動画については、fmtオブジェクトに格納する動画コーデックを色々試したが、いずれもうまく行かなかった。
この記事の最後を参照ください。

##出力結果

```bash:Terminal
electron@diynoMacBook-Pro optical_flow % ls result 
feature_frames			frame_with_trajectory_frames	masked_frames
electron@diynoMacBook-Pro optical_flow %
```


```bash:Terminal 
electron@diynoMacBook-Pro optical_flow % ls result/feature_frames | wc -l
      64
electron@diynoMacBook-Pro optical_flow %
```

```bash:Terminal 
electron@diynoMacBook-Pro optical_flow % ls result/frame_with_trajectory_frames | wc -l 
      64
electron@diynoMacBook-Pro optical_flow % 
```

```bash:Terminal 
electron@diynoMacBook-Pro optical_flow % ls result/masked_frames | wc -l               
      64
electron@diynoMacBook-Pro optical_flow %
```

```bash:Terminal  
electron@diynoMacBook-Pro optical_flow % ls result/feature_frames | head               
feature_frame0.jpeg
feature_frame1.jpeg
feature_frame10.jpeg
feature_frame11.jpeg
feature_frame15.jpeg
feature_frame16.jpeg
feature_frame17.jpeg
feature_frame18.jpeg
feature_frame19.jpeg
feature_frame20.jpeg
electron@diynoMacBook-Pro optical_flow %
```

```bash:Terminal  
electron@diynoMacBook-Pro optical_flow % ls result/frame_with_trajectory_frames | head 
trajectory_frame0.jpeg
trajectory_frame1.jpeg
trajectory_frame10.jpeg
trajectory_frame11.jpeg
trajectory_frame15.jpeg
trajectory_frame16.jpeg
trajectory_frame17.jpeg
trajectory_frame18.jpeg
trajectory_frame19.jpeg
trajectory_frame20.jpeg
electron@diynoMacBook-Pro optical_flow %
```

```bash:Terminal
electron@diynoMacBook-Pro optical_flow % ls result/masked_frames | head                
masked_frame0.jpeg
masked_frame1.jpeg
masked_frame10.jpeg
masked_frame11.jpeg
masked_frame15.jpeg
masked_frame16.jpeg
masked_frame17.jpeg
masked_frame18.jpeg
masked_frame19.jpeg
masked_frame20.jpeg
electron@diynoMacBook-Pro optical_flow %
```

```bash:Terminal
electron@diynoMacBook-Pro optical_flow % ls masked_frame_movie.mp4 
masked_frame_movie.mp4
electron@diynoMacBook-Pro optical_flow % 
```



##実装コード

```Python3:optical_flow_save_files.py
import numpy as np
import cv2, argparse, os


# 動画ファイル名をコマンドライン引数から受け取る
parser = argparse.ArgumentParser(description='')    #
parser.add_argument('file_name')  
args = parser.parse_args()  

movie_file = args.file_name
print('次の動画ファイルを解析します。 ：' + movie_file)
cap = cv2.VideoCapture(movie_file)

# Shi-Tomasiのコーナー検出パラメータ
feature_params = dict( maxCorners = 100,
                    qualityLevel = 0.3,
                    minDistance = 7,
                    blockSize = 7 )

# Lucas-Kanade法のパラメータ
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# ランダムに色を１００個生成（値0～255の範囲で100行3列のランダムなndarrayを生成）
color = np.random.randint(0, 255, (100, 3))

# 最初のフレームの処理
end_flag, frame = cap.read()
gray_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
feature_prev = cv2.goodFeaturesToTrack(gray_prev, mask = None, **feature_params)
mask = np.zeros_like(frame)

# 各フレームを解析した結果を動画として保存
# https://rikoubou.hatenablog.com/entry/2019/01/15/174751
frame_rate = 24.0 # フレームレート
size = (640, 480) # 動画の画面サイズ
fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # ファイル形式(ここではmp4)
masked_frame_writer = cv2.VideoWriter('masked_frame_movie.mp4', fmt, frame_rate, size) # ライター作成

# 各フレームを解析した結果を画像ファイルとして保存
# https://note.nkmk.me/python-makedirs-exist-ok/
os.makedirs('result/masked_frames')
os.makedirs('result/feature_frames')
os.makedirs('result/frame_with_trajectory_frames')

while(end_flag):
    if feature_prev is None:
        print("フレームに追跡すべき特徴点が見つかりませんでした。")
        break

    i = 1
    # グレースケールに変換
    gray_next = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # オプティカルフロー検出
    feature_next, status, err = cv2.calcOpticalFlowPyrLK(gray_prev, gray_next, feature_prev, None, **lk_params)

    # フレーム前後でトラックが成功した特徴点のみを
    if feature_next is None:
        print("フレームに追跡すべき特徴点が見つからなくなりました。")
        break

    # オプティカルフローを検出した特徴点を選別（0：検出せず、1：検出した）
    good_prev = feature_prev[status == 1]
    good_next = feature_next[status == 1]

    # オプティカルフローを描画
    for i, (next_point, prev_point) in enumerate(zip(good_next, good_prev)):
        prev_x, prev_y = prev_point.ravel()
        next_x, next_y = next_point.ravel()
        mask = cv2.line(mask, (int(next_x), int(next_y)), (int(prev_x), int(prev_y)), color[i].tolist(), 2)
        frame = cv2.circle(frame, (int(next_x), int(next_y)), 5, color[i].tolist(), -1)

    img = cv2.add(frame, mask)

    # ウィンドウに表示
    cv2.imshow('window', img)
    cv2.imshow('window2', frame)
    cv2.imshow('window3', mask)

    # ESCキー押下で終了
    if cv2.waitKey(30) & 0xff == 27:
        break

    #動画ファイルに各フレームを出力
    masked_frame_writer.write(mask) # 画像を1フレーム分として書き込み
    #静止画ファイルとして各フレームを出力
    #これはOK output_mask_file_name = 'masked_frame' + str(int(i)) + '.jpeg'
    output_mask_file_name = 'result/masked_frames/masked_frame' + str(int(i)) + '.jpeg'
    cv2.imwrite(output_mask_file_name, mask)

    output_feature_file_name = 'result/feature_frames/feature_frame' + str(int(i)) + '.jpeg'
    cv2.imwrite(output_feature_file_name, frame)

    output_trajectory_file_name = 'result/frame_with_trajectory_frames/trajectory_frame' + str(int(i)) + '.jpeg'
    cv2.imwrite(output_trajectory_file_name, img)
    
    # 次のフレーム、ポイントの準備
    gray_prev = gray_next.copy()
    feature_prev = good_next.reshape(-1, 1, 2)
    end_flag, frame = cap.read()
    
    i += 1

# 終了処理
cv2.destroyAllWindows()
cap.release()

# 動画出力ファイルを閉じる
masked_frame_writer.release()

```

##（ 捕捉 ）
###動画ファイルの出力について

https://qiita.com/sey323/items/d7da4cee448ed5be8149
https://blog.mktia.com/save-video-with-opencv-python/
https://gist.github.com/takuma7/44f9ecb028ff00e2132e

上記の記事を参考に、

```Python3:
fourcc_int = int(cap.get(cv2.CAP_PROP_FOURCC))
print(fourcc_int)
```

したが、出力されたMP4ファイルは、MacbookのQuickTimeアプリで開なかった。


```Python3:
['a', 'v', 'c', '1']
```


と出力された。そこで、

```Python3:
fmt = cv2.VideoWriter_fourcc("avc1") # ファイル形式(ここではmp4)
fmt = cv2.VideoWriter_fourcc('A','V','C','1')
```


の両者を試してが、いずれも出力されたMP4ファイルは、MacbookのQuickTimeアプリで開なかった。

結論として、以下すべてうまく行かなかった。

```Python3:
fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # ファイル形式(ここではmp4)
fmt = cv2.VideoWriter_fourcc(*"mp4v") # ファイル形式(ここではmp4)
fmt = cv2.VideoWriter_fourcc("avc1") # ファイル形式(ここではmp4)
fmt = cv2.VideoWriter_fourcc('A','V','C','1')
```

以下の記事のように、OpenCV側で処理した後の加工済みフレーム画像をファイルに吐き出したのちに、フレーム総数分だけ吐き出された静止画像を、ffmpegで連結して動画ファイルに仕立てる方法が無難かもしれない。

- [OpenCVで編集した動画が保存できなくて、もう遠回りした](https://qiita.com/daikiclimate/items/04752fb0ccda4e46976e)

今回は、3種類の加工後の各フレーム画像を、フォルダ（ディレクトリ）を分けて出力しているため、ffmpegを各フォルダに対して実行するだけでよい。