Atom's tech blog

OpenCVとPILを使って動画の動いている部分だけ消してみた

概要

OpenCVとPILを使って画像全領域の最頻値を検出し、動いて部分だけ消すことができるか試してみた。 画像の全領域を1ピクセル毎にCounter.most_common()を使って最頻値を取り出し、その最頻値のRGB 値を最新の画像に書き込みすることで、動いている箇所を動いていない時のRGB値に書き換える仕組み。

環境条件

結果

結論を先に言うと成功せず。いろいろ試したので残す事にする。 最初は画像サイズを640×480で実施してみたがループ処理に時間がかかり、入力動画のフレームレートが出ず、ほとんど動かない動画になってしまった。一回の画像キャプチャーループで「4sec」という結果。しかし、なんとなーーく動いている箇所は消えそうな感じに見えた。

画像サイズを320×240にしたところ、処理時間は約550msで、だいたい2FPS程度。見た感じでは動きは速くなったが、動いている人はなかなか消えそうにない。もっと処理時間を速くすることで消えるかトライ。マルチスレッド、遅いところを関数化してnamba(@jit)も試みたが、いまいち速くならず。動作している時のコアの空きがありそうにも見えるが。。。

f:id:iAtom:20201008160201j:plain

もしかして何か勘違い(初歩的な間違い)しているかもしれない。(バグがあるのかも)

イメージ

f:id:iAtom:20201008160249j:plainf:id:iAtom:20201008160304j:plain
f:id:iAtom:20201008160328j:plainf:id:iAtom:20201008160340j:plain

ソースコード

フレーム毎に画像データを保存する処理を入れているのでご注意を(動画が長いと画像ファイルが大量に保存してしまうため)。 不要な場合は「 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履歴を選択

f:id:iAtom:20201008160415j:plain