OpenCVとPILを使って動画の動いている部分だけ消してみた
概要
OpenCVとPILを使って画像全領域の最頻値を検出し、動いて部分だけ消すことができるか試してみた。 画像の全領域を1ピクセル毎にCounter.most_common()を使って最頻値を取り出し、その最頻値のRGB 値を最新の画像に書き込みすることで、動いている箇所を動いていない時のRGB値に書き換える仕組み。
環境条件
- MacBook Pro
- CPU:2.4 GHz クアッドコアIntel Core i5
- メモリ:8 GB 2133 MHz LPDDR3
- MacOS Catalina(10.15.4)
- python 3.5.6
- OpenCV 3.4.2
- VS code 1.43.1
- numpy 1.17.3
- Pillow 5.2.0
結果
結論を先に言うと成功せず。いろいろ試したので残す事にする。 最初は画像サイズを640×480で実施してみたがループ処理に時間がかかり、入力動画のフレームレートが出ず、ほとんど動かない動画になってしまった。一回の画像キャプチャーループで「4sec」という結果。しかし、なんとなーーく動いている箇所は消えそうな感じに見えた。
画像サイズを320×240にしたところ、処理時間は約550msで、だいたい2FPS程度。見た感じでは動きは速くなったが、動いている人はなかなか消えそうにない。もっと処理時間を速くすることで消えるかトライ。マルチスレッド、遅いところを関数化してnamba(@jit)も試みたが、いまいち速くならず。動作している時のコアの空きがありそうにも見えるが。。。
もしかして何か勘違い(初歩的な間違い)しているかもしれない。(バグがあるのかも)
イメージ
ソースコード
フレーム毎に画像データを保存する処理を入れているのでご注意を(動画が長いと画像ファイルが大量に保存してしまうため)。 不要な場合は「 cv2.imwrite("./frame"+str(frame_cnt)+".jpg",frame)」をコメントアウトしてください。
# coding: utf-8 import cv2 import numpy as np from collections import Counter from PIL import Image _height= 240 _width= 320 _sample_cnt =3 # 動画ファイルのキャプチャ cap = cv2.VideoCapture('768x576.avi') # 最初のフレームを背景画像に設定 ret, frame = cap.read() frame = cv2.resize(frame, dsize=(_width,_height)) # ndarray型をPILのイメージオブジェクトに変換 image = Image.fromarray(frame) #画素データを1列にまとめて取得 imgdata = image.getdata() # 保存用配列データ作成 pixcel_save = [["" for i2 in range(_sample_cnt)] for i1 in range(_height*_width+_width)] pixcel_list = ["" for i in range(_sample_cnt)] # 初期画像のピクセルを保存する for y in range(_height): for x in range(_width): # ピクセルデータアクセス用の演算 Coordinate=(y * _width + x) # ピクセルデータ取得(RGB) pixval = imgdata[Coordinate] # RGB値を一つの値にまとめる rgb = '{0:03d}'.format(pixval[0])+'{0:03d}'.format(pixval[1])+'{0:03d}'.format(pixval[2]) # 最頻値検出用にRGB値を保存するループ for i in range(_sample_cnt): # RGB値を保存 pixcel_save[Coordinate][i]=rgb # 最頻値検出用の配列番号を初期化 counter=0 # 画像保存用名称番号を初期化 frame_cnt=0 while(cap.isOpened()): # フレームの取得 ret, frame = cap.read() # フレームサイズを変更 frame = cv2.resize(frame, dsize=(_width,_height)) # ndarray型をPILのイメージオブジェクトに変換 image = Image.fromarray(frame) # 画素データを1列にまとめて取得 imgdata = image.getdata() for y in range(_height): for x in range(_width): # ピクセルデータアクセス用の演算 Coordinate=(y * _width + x) # ピクセルデータ取得(RGB) pixval = imgdata[Coordinate] # RGB値を一つの値にまとめる rgb = '{0:03d}'.format(pixval[0])+'{0:03d}'.format(pixval[1])+'{0:03d}'.format(pixval[2]) # RGB値を保存 pixcel_save[Coordinate][counter]=rgb # 最頻値検出用にRGB値を取得するループ for i in range(_sample_cnt): pixcel_list[i] = pixcel_save[Coordinate][i] # 最頻値のRGB値を取得 mycounter = Counter(pixcel_list) most_str = mycounter.most_common(1) word = most_str[0][0] # 画像のフレームデータに最頻値のRGB値を書き込み frame[y, x] = [int(word[:3]), int(word[3:6]), int(word[6:9])] # 最頻値保存用の変数インクリメント counter +=1 if counter >= _sample_cnt : # 最頻値保存用の変数初期化 counter = 0 # 画面に画像表示 cv2.imshow("Flame", frame) # 画像ファイルに保存する frame_cnt += 1 cv2.imwrite("./frame"+str(frame_cnt)+".jpg",frame) # "qボタンで処理を抜ける" if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
参考
・ numbaライブラリを使うとPythonのコードを簡単に高速化できる。意外と簡単で「from numba import jit」でインポート、あとは速くしたいメソッド「def xxxx」の上に「@jit」を書くだけでいける。
・ループ処理の高速化は【Python】PILで高速に全画素アクセスを行うを参考にした。
・MACのコアの使用率は以下で参照可能 →アクティブモニターを起動 →メニューの「ウインドウ」からCPU履歴を選択