【 OpenCV2 】物体追跡コードのエラー対処法 〜 line, circleの引数はint型、特徴点未検出のフレームは飛ばす

OpenCV2のオブジェクト・トラッキングに関する記事です。

ウェブサイトに公開されている実装コードに、任意の動画ファイルを食わせたところ、__複数の公開コード__に__共通する「コードの修正ポイント」__を見つけました。

__原因と解決策__について、検討がついたので、知見を共有したいと思います。

###サンプルコードの例
- https://qiita.com/hitomatagi/items/772549d0fc3c89fb3cc4
- https://ohke.hateblo.jp/entry/2019/08/10/230000
- https://algorithm.joho.info/programming/python/opencv-optical-flow-lucas-kanade-py/

###（直面したエラー１）

```bash:Terminal
Traceback (most recent call last):
  File "/Users/electron/Desktop/optical_flow/optical_flow.py", line 72, in <module>
    mask = cv2.line(mask, (p1_x, p1_y), (p0_x, p0_y), color[i].tolist(), 2)
cv2.error: OpenCV(4.5.3) :-1: error: (-5:Bad argument) in function 'line'
> Overload resolution failed:
>  - Can't parse 'pt1'. Sequence item with index 0 has a wrong type
>  - Can't parse 'pt1'. Sequence item with index 0 has a wrong type
```


```bash:Terminal
Traceback (most recent call last):
  File "/Users/electron/Desktop/optical_flow/optical_flow.py", line 74, in <module>
    frame = cv2.circle(frame, (p1_x, p1_y), 5, color[i].tolist(), -1)
cv2.error: OpenCV(4.5.3) :-1: error: (-5:Bad argument) in function 'circle'
> Overload resolution failed:
>  - Can't parse 'center'. Sequence item with index 0 has a wrong type
>  - Can't parse 'center'. Sequence item with index 0 has a wrong type
```

###（解決策１）

cv2.lineメソッドとcv2.circleメソッドの引数は、int型である必要がある。
この制約を満たすために、座標点が整数値(integer）になるように、int()で型変換する。

```Python:修正前
        mask = cv2.line(mask, (next_x, next_y), (prev_x, prev_y), color[i].tolist(), 2)
        frame = cv2.circle(frame, (next_x, next_y), 5, color[i].tolist(), -1)
```

```Python:修正後
        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)
```

---

###（直面したエラー２）

オブジェクトがemptyであるエラー

```bash:Terminal
Traceback (most recent call last):
  File "/Users/electron/Desktop/optical_flow/trial.py", line 22, in <module>
    gray_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.error: OpenCV(4.5.3) /private/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/pip-req-build-2id2kgtb/opencv/modules/imgproc/src/color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'
```

新たに読み込んだコマのフレームに、OpenCV2が特徴点を見つけられなかった際に、特徴点オブジェクトがNoneになるために発生する。

###（解決策２）

次のif文を挿入する。

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

```Python:修正前
	# グレースケールにしてコーナ特徴点を抽出
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_RGB2GRAY)

feature_params = {
    "maxCorners": 200,  # 特徴点の上限数
    "qualityLevel": 0.2,  # 閾値　（高いほど特徴点数は減る)
    "minDistance": 12,  # 特徴点間の距離 (近すぎる点は除外)
    "blockSize": 12  # 
}
p0 = cv2.goodFeaturesToTrack(prev_gray, mask=None, **feature_params)

 （ ・・・途中省略 ・・・）

	# OpticalFlowの計算
	p1, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, frame_gray, p0, None, **lk_params)
	
	identical_p1 = p1[status==1]
	identical_p0 = p0[status==1]
```

```Python:修正後
	# グレースケールにしてコーナ特徴点を抽出
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_RGB2GRAY)

feature_params = {
    "maxCorners": 200,  # 特徴点の上限数
    "qualityLevel": 0.2,  # 閾値　（高いほど特徴点数は減る)
    "minDistance": 12,  # 特徴点間の距離 (近すぎる点は除外)
    "blockSize": 12  # 
}
p0 = cv2.goodFeaturesToTrack(prev_gray, mask=None, **feature_params)

 （ ・・・途中省略 ・・・）
 
	# OpticalFlowの計算
	p1, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, frame_gray, p0, None, **lk_params)
	
	# フレーム前後でトラックが成功した特徴点のみを
	if (p1 is None) or (p0 is None) :
		print("フレームに追跡すべき特徴点が見つからなくなりました。")
		break
	
	identical_p1 = p1[status==1]
	identical_p0 = p0[status==1]
```

#エラー回避できるコード

次のコードを参考にしました。

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

```Python3:new_opt.py
import numpy as np
import cv2
import argparse    

# 動画ファイル名をコマンドライン引数から受け取る
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)

while(end_flag):
    if feature_prev is None:
	    print("フレームに追跡すべき特徴点が見つかりませんでした。")
	    break
    
    # グレースケールに変換
    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)

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

    # 次のフレーム、ポイントの準備
    gray_prev = gray_next.copy()
    feature_prev = good_next.reshape(-1, 1, 2)
    end_flag, frame = cap.read()

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

###実行結果

```bash:Terminal
electron@diynoMacBook-Pro optical_flow % python3 new_opt.py dance.mp4                             
次の動画ファイルを解析します。 ：dance.mp4
^Z
zsh: suspended  python3 new_opt.py dance.mp4
electron@diynoMacBook-Pro optical_flow % exit
```

![スクリーンショット 2021-07-18 14.56.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/1f60ae57-8400-a890-e179-8335afc52851.png)

（ 約１秒経過後 ）

![スクリーンショット 2021-07-18 14.56.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/d414c48e-03ba-4d55-5834-4837e5497249.png)

（ 約１０秒経過後 ）

![スクリーンショット 2021-07-18 14.57.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1754134/fa17a4f0-a80e-55d5-7581-021502f18880.png)


###使った動画ファイル（MP4）

```bash:Terminal
electron@diynoMacBook-Pro % youtube-dl -F https://youtu.be/T0valuAksuo                    
[youtube] T0valuAksuo: Downloading webpage
[info] Available formats for T0valuAksuo:
format code  extension  resolution note
249          webm       audio only tiny   52k , webm_dash container, opus @ 52k (48000Hz), 1.29MiB
250          webm       audio only tiny   68k , webm_dash container, opus @ 68k (48000Hz), 1.70MiB
140          m4a        audio only tiny  129k , m4a_dash container, mp4a.40.2@129k (44100Hz), 3.20MiB
251          webm       audio only tiny  134k , webm_dash container, opus @134k (48000Hz), 3.33MiB
394          mp4        256x144    144p   70k , mp4_dash container, av01.0.00M.08@  70k, 30fps, video only, 1.74MiB
278          webm       256x144    144p   92k , webm_dash container, vp9@  92k, 30fps, video only, 2.28MiB
160          mp4        256x144    144p  104k , mp4_dash container, avc1.4d400c@ 104k, 30fps, video only, 2.59MiB
395          mp4        426x240    240p  153k , mp4_dash container, av01.0.00M.08@ 153k, 30fps, video only, 3.80MiB
242          webm       426x240    240p  216k , webm_dash container, vp9@ 216k, 30fps, video only, 5.34MiB
133          mp4        426x240    240p  243k , mp4_dash container, avc1.4d4015@ 243k, 30fps, video only, 6.01MiB
396          mp4        640x360    360p  329k , mp4_dash container, av01.0.01M.08@ 329k, 30fps, video only, 8.14MiB
243          webm       640x360    360p  396k , webm_dash container, vp9@ 396k, 30fps, video only, 9.80MiB
134          mp4        640x360    360p  588k , mp4_dash container, avc1.4d401e@ 588k, 30fps, video only, 14.53MiB
397          mp4        854x480    480p  611k , mp4_dash container, av01.0.04M.08@ 611k, 30fps, video only, 15.10MiB
244          webm       854x480    480p  734k , webm_dash container, vp9@ 734k, 30fps, video only, 18.14MiB
135          mp4        854x480    480p 1007k , mp4_dash container, avc1.4d401f@1007k, 30fps, video only, 24.88MiB
398          mp4        1280x720   720p 1246k , mp4_dash container, av01.0.05M.08@1246k, 30fps, video only, 30.80MiB
247          webm       1280x720   720p 1472k , webm_dash container, vp9@1472k, 30fps, video only, 36.38MiB
136          mp4        1280x720   720p 1963k , mp4_dash container, avc1.4d401f@1963k, 30fps, video only, 48.49MiB
399          mp4        1920x1080  1080p 2326k , mp4_dash container, av01.0.08M.08@2326k, 30fps, video only, 57.47MiB
248          webm       1920x1080  1080p 2588k , webm_dash container, vp9@2588k, 30fps, video only, 63.92MiB
137          mp4        1920x1080  1080p 4141k , mp4_dash container, avc1.640028@4141k, 30fps, video only, 102.30MiB
18           mp4        640x360    360p  742k , avc1.42001E, 30fps, mp4a.40.2 (44100Hz), 18.35MiB (best)
electron@diynoMacBook-Pro optical_flow % 
```

```bash:Terminal
electron@diynoMacBook-Pro optical_flow % youtube-dl -f18 -o dance.mp4 https://youtu.be/T0valuAksuo
[youtube] T0valuAksuo: Downloading webpage
[download] Destination: dance.mp4
[download] 100% of 18.35MiB in 00:02
electron@diynoMacBook-Pro optical_flow % ls
768x576.avi		fi.mp4			opt_flow_test.py	test.py			trump.mp4
dance.mp4		new_opt.py		optical_flow.py		trial.py
doraemon.mp4		olynpic.mp4		pedestrian.mp4		trump.avi
electron@diynoMacBook-Pro optical_flow % 
electron@diynoMacBook-Pro optical_flow % python3 new_opt.py dance.mp4                             
次の動画ファイルを解析します。 ：dance.mp4
^Z
zsh: suspended  python3 new_opt.py dance.mp4
electron@diynoMacBook-Pro optical_flow % exit
```