PythonのOpenCVを使って動画から映像を取得し、飛んでいる飛行機を検知します。
今回はピクセルを比較する方法で動体検知します。
とてもシンプルな手法ですが、飛んでいる飛行機を検知する上では意外と正確に検知出来たので記事にしました。
結果
![](https://piccalog.net//wp-content/uploads/2023/01/57eb9ce8-c141-af98-bdc8-2bf254fe1b17-1.gif)
![](https://piccalog.net//wp-content/uploads/2023/01/f1b06bc5-3b02-26c5-c9a4-934d9c17fb50.gif)
![](https://piccalog.net//wp-content/uploads/2023/01/10eb193c-4eee-3dd8-d77c-018bfcaa8a27-1024x516.jpeg)
※使用させていただいた動画のリンクは記事の最後に記載しました。
前提条件
python3.9
opencv-pytho 4.5.5
仕組み
- 1.動画を読み込む。
- 2.グレースケールに変換
- 3.比較用にフレームを保存しておく
- 4.ガウシアンブラーでノイズを軽減
- 5.画像を比較して差分の画像を生成する
- 6.差分の画像を2値化する(閾値処理)
- 7.輪郭を検知する
- 8.輪郭がはいった配列から、領域が小さすぎる要素は無視する
- 9.輪郭情報から短形で領域を囲み、描画する
コード
import cv2
filepath = "sample.mov"
cap = cv2.VideoCapture(filepath)
avg = None
while True:
# 1フレームずつ取得する。
ret, frame = cap.read()
if not ret:
break
# グレースケールに変換
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 比較用のフレームを取得する
if avg is None:
avg = gray.copy().astype("float")
continue
# ブラーを掛けてノイズを軽減する
blur = cv2.GaussianBlur(gray, (1, 1), 1)
# 現在のフレームと移動平均との差を計算
cv2.accumulateWeighted(blur, avg, 0.7)
frameDelta = cv2.absdiff(blur, cv2.convertScaleAbs(avg))
# デルタ画像を閾値処理を行う
thresh = cv2.threshold(frameDelta, 3, 255, cv2.THRESH_BINARY)[1]
# 画像の閾値に輪郭線を入れる
contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
for i in range(0, len(contours)):
if len(contours[i]) > 0:
# しきい値より小さい領域は無視する
if cv2.contourArea(contours[i]) < 100:
continue
# 短形で領域を囲む
rect = contours[i]
x, y, w, h = cv2.boundingRect(rect)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 4)
# 結果を出力
cv2.imshow("Frame", frame)
# Escキーで終わる
key = cv2.waitKey(30)
if key == 27:
break
cap.release()
cv2.destroyAllWindows()
コード解説
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
グレースケールに変換します。
OpenCVでは画像のチャンネルの並び順はRGBではなくBGRです。
blur = cv2.GaussianBlur(gray, (1, 1), 1)
ブラーを掛けてノイズを軽減します。(1,1),1
がブラーの強さになりますが、大きくする(大きくぼかす)と輪郭の情報もボケてしまって検出しにくくなります。
cv2.accumulateWeighted(blur, avg, 0.7)
2つの画像blur,avg
の移動した値を求めます。0.7
は入力画像の重みです。
重みは更新速度(どのくらいの早さで,以前の画像を「忘れる」か)を調節します。*参照 opencv.jp
重みを調整することでより正確に検出できることがあります。
contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
輪郭を検知します。contours
にはオブジェクトの輪郭座標を保持している配列。hierarchy
にはオブジェクトの階層構造情報を保持している配列。
が入ります。
for i in range(0, len(contours)):
if len(contours[i]) > 0:
# しきい値より小さい領域は無視する
if cv2.contourArea(contours[i]) < 100:
continue
# 短形で領域を囲む
rect = contours[i]
x, y, w, h = cv2.boundingRect(rect)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 4)
forで回して順番に処理します。cv2.contourArea
は領域の大きさを返します。しきい値100
より小さいなら無視します。cv2.boundingRect(rect)
で短形の領域を計算し、cv2.rectangle
で描画します。(0, 255, 0)
は色(RGB)で、4
は線の太さです。
cv2.imshow("Frame", frame)
でウインドウに描画します。
欠点
動画の条件によっては上手くいかない事もあります。
![](https://piccalog.net//wp-content/uploads/2023/01/10eb193c-4eee-3dd8-d77c-018bfcaa8a27-1-1024x516.jpeg)
↑カメラが動いていてるせいで、地表の建物も差分として検出されている
正確に検出できない条件は以下の通りです
- 地表の構造物が写っている。なおかつ(and) 画角が大きく動いている。
- 動画には空と飛行機しか写っていないが、雲が存在する。なおかつ(and) 画角が大きく動いている。
そして常に飛行機を1つのかたまりとして認識するのが苦手です。
窓やアンテナ、ギアやランプ等のパーツごとに認識されます。
![](https://piccalog.net//wp-content/uploads/2023/01/3f2b7c6c-52b9-52c5-eb1a-a895e2b55d26-1024x620.jpeg)
また旅客機の様な2色以上の塗装が含まれている飛行機は色ごとに分かれて検出される傾向があります。
つまりカメラは固定されている方が確実に有利です。
発展
Webカメラの映像をソースとして使う
良く自宅から飛行機が通るのが見える方は、ラズパイ + 画角の広いWebカメラ
を組み合わせて実行すると面白いです。
高精度化 with 機械学習
様々な物体を検知する機械学習モデルが沢山あるそうです。
色々勉強する必要はありそうですが、完全にこのプログラムの上位互換ですね。
音声分析
風向き等もありますが、飛行機はかなりの音量があります。
エンジンによって音に特性があるので、音声分析をガチったらエンジン名を特定できるでしょう。
検証に使用した動画素材サイト様のURL
参考
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Fpublic%2Fadvent-calendar-ogp-background-7940cd1c8db80a7ec40711d90f43539e.jpg?ixlib=rb-4.0.0&w=1200&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk3MiZoPTM3OCZ0eHQ9T3BlbkNWJUUzJTgxJUE3JUU2JTg5JThCJUUzJTgxJUEzJUU1JThGJTk2JUUzJTgyJThBJUU2JTk3JUE5JUUzJTgxJThGJUU1JThCJTk1JUU0JUJEJTkzJUU2JUE0JTlDJUU3JTlGJUE1JUUzJTgxJTk3JUUzJTgxJUE2JUUzJTgxJUJGJUUzJTgxJTlGJnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnR4dC1jb2xvcj0lMjMzQTNDM0MmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9NTYmcz0wNmVmOThhMWJmN2I4NGY0OTA2OWQwMmRhYmJiMTBiNA&mark-x=120&mark-y=96&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZoPTc2Jnc9OTcyJnR4dD0lNDBLTWl1cmE5NSZ0eHQtY29sb3I9JTIzM0EzQzNDJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTM2JnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnM9M2E5ZjEwY2Y0MWE3ODBmMTc4NzNkNzJjMjY3MGMwOTY&blend-x=120&blend-y=445&blend-mode=normal&txt64=aW4gSW9UTFQ&txt-width=972&txt-clip=end%2Cellipsis&txt-color=%233A3C3C&txt-font=Hiragino%20Sans%20W6&txt-size=36&txt-x=134&txt-y=546&s=74bebe1f76418b82a0ac191158e32ff0)
![](https://developers.cyberagent.co.jp/blog/wp-content/themes/onepress-child/assets/images/og/og.png)
コメント