MENU

python 動画ファイルから魚が映った場面を抽出する

import cv2, os, copy, pickle

# 学習済みデータを取り出す
with open("fish.pkl", "rb") as fp:
 clf = pickle.load(fp)
output_dir = "./bestshot"
img_last = None # 前回の画像
fish_th = 3 # 画像を出力するかどうかのしきい値
count = 0
frame_count = 0
if not os.path.isdir(output_dir): os.mkdir(output_dir)

# 動画ファイルから入力を開始 --- (*1)
cap = cv2.VideoCapture("fish.mp4")
while True:
 # 画像を取得
 is_ok, frame = cap.read()
  if not is_ok: break
 frame = cv2.resize(frame, (640, 360))
 frame2 = copy.copy(frame)
 frame_count += 1
 # 前フレームと比較するために白黒に変換 --- (*2)
 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 gray = cv2.GaussianBlur(gray, (15, 15), 0)
 img_b = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)[1]
  if not img_last is None:
   # 差分を得る
   frame_diff = cv2.absdiff(img_last, img_b)
   cnts = cv2.findContours(frame_diff,
   cv2.RETR_EXTERNAL,
   cv2.CHAIN_APPROX_SIMPLE)[1]
  # 差分領域に魚が映っているか調べる
   fish_count = 0
    for pt in cnts:
     x, y, w, h = cv2.boundingRect(pt)
     if w < 100 or w > 500: continue # ノイズを除去
  # 抽出した領域に魚が映っているか確認 --- (*3)
    imgex = frame[y:y+h, x:x+w]
    imagex = cv2.resize(imgex, (64, 32))
    image_data = imagex.reshape(-1, )
    pred_y = clf.predict([image_data]) # --- (*4)
     if pred_y[0] == 1:
      fish_count += 1
      cv2.rectangle(frame2, (x, y), (x+w, y+h), (0,255,0), 2)
  # 魚が映っているか? --- (*5)
   if fish_count > fish_th:
    fname = output_dir + "/fish" + str(count) + ".jpg"
    cv2.imwrite(fname, frame)
    count += 1
 cv2.imshow('FISH!', frame2)
  if cv2.waitKey(1) == 13: break
 img_last = img_b
cap.release()
cv2.destroyAllWindows()
print("ok", count, "/", frame_count)

 

このプログラムでは一つ前の記事で作成した学習モデルを使用して動画から魚が映った場面を長方形の枠で囲むという処理を行っている。

 

import cv2, os, copy, pickle

copyモジュールではオブジェクトごとコピーするために使用する。

そもそもオブジェクトには2種類あって

  • ミュータブルなオブジェクト(再代入可能なオブジェクト。大部分のオブジェクト)
  • イミュータブルなオブジェクト(再代入が不可能なオブジェクト。文字列型や数字型、タプル型など)

が存在する。

この2種類のオブジェクトは、一度オブジェクトとして生成された後、別の値を再度代入する(値を更新する)際の動作が異なる。

 

 

ミュータブルなオブジェクトの場合

a = [o, 1, 2]

b = a

a.append(3)

 

print(a)  -- [0,1,2,3]

print(b)  --[0,1,2,3]

このように変数bの値も更新されてしまう。これはid()で調べるとわかるが同じオブジェクトIDを参照しているからである。

 

イミュータブルなオブジェクトの場合

a = "abc"

b = a

a = a + "def"

 

print(a) -- abcdef

print(b) -- abc

このようになるのはa+"def"を行ったときに新しいオブジェクトIDが作成され変数bと変数aのオブジェクトIDが異なるからである。

 

そしてcopyを使用してミュータブルなオブジェクトをコピーすると

 

a = [1, 2]
b = copy.copy(a)
a.append(3)

 

print(a) -- [1,2,3]

print(b) -- [1,2]

copy.copy()を使用することで値は同じだが新しいオブジェクトIDを作成することができたので変数bの値は更新されない。

 

copy.deepcopy()

リストや辞書を使った多層構造(多次元構造)の構造体や、オブジェクトをコピーする場合には、copy.copyだと「別のオブジェクトを作成したつもりが、その表面以外は全部同じデータを参照してた」ということがおこる。

例えば

a = [[0,1],[2,3]]

b = a

このような二次元配列を扱う場合、b[0],b[1]と個別で見ていくと元の変数aのa[0],a[1]と同じオブジェクトになってしまうのでこのような時はcopy.deepcopy()を使用する。

 

学習済みデータを取得

with open("fish.pkl", "rb") as fp:
clf = pickle.load(fp)

 

if not os.path.isdir(output_dir): os.mkdir(output_dir)

この部分ではos.path.isdir()で指定しているパスが存在しているディレクトリかどうかをTrue,Falseで返す。ファイルの場合存在していてもFalseを返す。

os.mkdir()で新しいディレクトリ(フォルダ)を作成。

 

動画ファイルから入力を開始

cap = cv2.VideoCapture("fish.mp4")

 

ループ処理内で画像取得

# 画像を取得
is_ok, frame = cap.read()
if not is_ok: break
frame = cv2.resize(frame, (640, 360))
frame2 = copy.copy(frame)
frame_count += 1

read()で画像取得。

is_okでの条件分岐は画像が取得できなかった時Falseが格納されているのでそのタイミングでbreakで処理を終了させている。

copy.copy()で描画ようの画像データ用に別の変数に値をコピー。

frame_countは最後に何枚の画像を取得したかを出力するための変数。

 

前のフレームと比較するため画像を二値化

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (15, 15), 0)
img_b = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)[1]

 

前のフレームとの差分を取得

if not img_last is None:
 # 差分を得る
  frame_diff = cv2.absdiff(img_last, img_b)
  cnts = cv2.findContours(frame_diff,
 cv2.RETR_EXTERNAL,
 cv2.CHAIN_APPROX_SIMPLE)[1]

 

if notで前の画像がNoneではないときに実行。

cv2.absdiff()で前のフレームと今のフレームの差分を取得。

cv2.findContours()で輪郭を取得。

 

差分領域に魚が映っているか機械学習を用いて調べる

fish_count = 0
for pt in cnts:
  x, y, w, h = cv2.boundingRect(pt)
  if w < 100 or w > 500: continue # ノイズを除去
 # 抽出した領域に魚が映っているか確認 --- (*3)
 imgex = frame[y:y+h, x:x+w]
 imagex = cv2.resize(imgex, (64, 32))
 image_data = imagex.reshape(-1, )
 pred_y = clf.predict([image_data]) # --- (*4)
  if pred_y[0] == 1:
   fish_count += 1
   cv2.rectangle(frame2, (x, y), (x+w, y+h), (0,255,0), 2)
 # 魚が映っているか? --- (*5)
if fish_count > fish_th:
 fname = output_dir + "/fish" + str(count) + ".jpg"
 cv2.imwrite(fname, frame)
 count += 1

 

fish_countは映っている魚の数。

cv2.boundingRect()で輪郭に外接する長方形のデータを取得。

if w < 100 or w > 500: continueで小さいもの大きいすぎるものの場合処理をしない。

frame[y:y+h, x:x+w]で抽出した部分を切り抜き。

resizeでサイズ統一。

reshape()で一次元配列に変換。

predict()で結果予測。if pred_y[0] == 1で1を指定しているのはラベルデータには0と1があり今回の学習モデルは0が魚が映っていないとき、1が魚が映っているときを指すからである。

 

if fish_count > fish_th:では魚が3匹より多く映っていたときcv2.imwrite()で画像を書き出し。変数countはファイル名に使用。

 

最後に

 cv2.imshow('FISH!', frame2)
 if cv2.waitKey(1) == 13: break
img_last = img_b
cap.release()
cv2.destroyAllWindows()
print("ok", count, "/", frame_count)

 

画像表示。

変数img_lastに今回の画像データを格納してループ。

 

cap.release()でカメラ閉じてcv2.destroyAllWindowsでウィンドウ閉じる。