0
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-07-18

前回の記事では、OpenCV2の物体追跡APIの解析結果を、3つのWindowsに分けて、個別出力しました。

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

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

  • 「1」の動画ファイル
  • 「1」の静止画ファイル (総フレーム数個のファイル)
  • 「2」の静止画ファイル (総フレーム数個のファイル)
  • 「3」の静止画ファイル (総フレーム数個のファイル)

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

実行画面

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

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

スクリーンショット 2021-07-18 22.32.51.png

( 拡大した1枚 )

スクリーンショット 2021-07-18 22.46.50.png

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

スクリーンショット 2021-07-18 22.33.03.png

( 拡大した1枚 )

スクリーンショット 2021-07-18 22.49.54.png

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

スクリーンショット 2021-07-18 22.33.11.png

( 拡大した1枚 )

スクリーンショット 2021-07-18 22.51.16.png

( 処理した結果の動画ファイル )

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

スクリーンショット 2021-07-18 22.34.29.png

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

スクリーンショット 2021-07-18 22.34.36.png

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

出力結果

Terminal
electron@diynoMacBook-Pro optical_flow % ls result 
feature_frames          frame_with_trajectory_frames    masked_frames
electron@diynoMacBook-Pro optical_flow %
Terminal
electron@diynoMacBook-Pro optical_flow % ls result/feature_frames | wc -l
      64
electron@diynoMacBook-Pro optical_flow %
Terminal
electron@diynoMacBook-Pro optical_flow % ls result/frame_with_trajectory_frames | wc -l 
      64
electron@diynoMacBook-Pro optical_flow % 
Terminal
electron@diynoMacBook-Pro optical_flow % ls result/masked_frames | wc -l               
      64
electron@diynoMacBook-Pro optical_flow %
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 %
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 %
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 %
Terminal
electron@diynoMacBook-Pro optical_flow % ls masked_frame_movie.mp4 
masked_frame_movie.mp4
electron@diynoMacBook-Pro optical_flow % 

実装コード

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))

# ランダムに色を100個生成(値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

上記の記事を参考に、

fourcc_int = int(cap.get(cv2.CAP_PROP_FOURCC))
print(fourcc_int)

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

['a', 'v', 'c', '1']

と出力された。そこで、

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

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

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

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で連結して動画ファイルに仕立てる方法が無難かもしれない。

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

0
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
0
1