MENU

python 自然言語処理、英語、日本語、タイ語の言語判定を行うプログラム

 

f:id:hanamichi_sukusuku:20210112192244p:plain

実行結果

f:id:hanamichi_sukusuku:20210112192314p:plain

このプログラムは日本語、英語、タイ語の言語判定を行うプログラム。

 

モジュールインポート

import numpy as np
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score

 

numpyは強力な数値計算ライブラリ。

アルゴリズムチートシートを確認するとLinearSVCアルゴリズムでは上手に分類できない場合でテキストデータを扱う場合、NaiveBayesを利用することが推奨されている。

そして、scikit-learnでは3種類のNaiveBayes分類器が用意されているが今回はシンプルに利用できるGaussianNBを利用する。

 

Unicodeのコードポイント頻度測定

def count_codePoint(str):
 # Unicodeのコードポイントをアドレスとする配列を用意 --- (*2)
 counter = np.zeros(65535)

  for i in range(len(str)):
   # 各文字をUnicodeのコードポイントに変換 --- (*3)
   code_point = ord(str[i])
    if code_point > 65535 :
      continue
    # 対応するアドレスの出現回数をインクリメント --- (*4)
   counter[code_point] += 1

  # 各要素を文字数で割って正規化 --- (*5)
  counter = counter/len(str)
   return counter

 

Unicodeとは符号化文字集合と言われ世界中の文字に対して割り当てられ管理されている番号の集合体。ここで割り当てられている番号をコードポイントという。なぜ世界中の文字を番号に振り分けているかというと人がわかる文字をコンピューターが認識するのに一度数字に変換してから読み取るからである。

例えば

pythonではord()でコードでコードポイントが取得できる。

>>> ord("あ")
12354

この様に取得できる。

 

ord(str[i])で引数で受けとった文字の先頭から末尾までの文字を取得してコードポイントに変換。

if文でコードポイントが65535より大きい数字のものの場合処理を飛ばす。

初期化したcounterに格納されている配列のインデックスがコードポイントの箇所に1を足す。こうすることでどの文字がどのくらいの頻度で使用されているかわかるので文字の出現頻度による言語判定ができる。

counter/len(str) では正規化というより全体の文字数に対しての割合を格納しなおしていると考えた方が良い。

最後に呼び出し元に文字の出現頻度を示した割合の配列を返す。

 

学習用データの作成

x_train = [count_codePoint(ja_str),count_codePoint(en_str),count_codePoint(th_str)]
y_train = ['ja','en','th']

 

x_trainではリスト内で関数を呼び出してデータを格納している。

 

学習

clf = GaussianNB()
clf.fit(x_train, y_train)

 

評価用データの用意

ja_test_str = 'こんにちは'
en_test_str = 'Hello'
th_test_str = 'สวัสดี'

x_test = [count_codePoint(en_test_str),count_codePoint(th_test_str),
      count_codePoint(ja_test_str)]
y_test = ['en', 'th', 'ja']

 

評価

y_pred = clf.predict(x_test)
print(y_pred)
print("正解率 = " , accuracy_score(y_test, y_pred))

 

 

 

 

 

 

 

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でウィンドウ閉じる。

 

 

 

 

 

 

 

 

 

 

 

python 魚が映っている時と映っていない時の画像で機械学習にかけて学習モデルの作成

import cv2
import os, glob, pickle
from sklearn.model_selection import train_test_split
from sklearn import datasets, metrics
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# 画像の学習サイズやパスを指定
image_size = (64, 32)
path = os.path.dirname(os.path.abspath(__file__))
path_fish = path + '/fish'
path_nofish = path + '/nofish'
x = # 画像データ
y = # ラベルデータ

# 画像データを読み込んで配列に追加 --- (*1)
def read_dir(path, label):
 files = glob.glob(path + "/*.jpg")
  for f in files:
   img = cv2.imread(f)
   img = cv2.resize(img, image_size)
   img_data = img.reshape(-1, ) # 一次元に展開
   x.append(img_data)
   y.append(label)

# 画像データを読み込む
read_dir(path_nofish, 0)
read_dir(path_fish, 1)

# データを学習用とテスト用に分割する --- (*2)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

# データを学習 --- (*3)
clf = RandomForestClassifier()
clf.fit(x_train, y_train)

# 精度の確認 --- (*4)
y_pred = clf.predict(x_test)
print(accuracy_score(y_test, y_pred))

# データを保存 --- (*5)
with open("fish.pkl", "wb") as fp:
pickle.dump(clf, fp)

 

前提としてある動画から魚が映っている画像と映っていない画像を150枚ずつにわけてfishフォルダ、nofishフォルダを作成してある。

 

モジュールインポート

import cv2
import os, glob, pickle
from sklearn.model_selection import train_test_split
from sklearn import datasets, metrics
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

 

globモジュールでは、引数に指定されたパターンにマッチするファイルパス名を取得することが出来る。特定のディレクトリに存在するファイルに処理を加えたい場合などに、使用する。

pickleモジュールでは学習済みデータを保存するためにインポートする。

 

train_test_splitはデータを学習用、テスト用に分けるためにインポート。

学習モデルを構築するためのアルゴリズムとしてRandomForestClassifierをインポート。高い精度を出すアルゴリズムの一つ。

 

accuracy_scoreは予測結果とテスト結果を比較し精度を確認することができる。

 

画像の学習サイズやパスを指定

image_size = (64, 32)
path = os.path.dirname(os.path.abspath(__file__))
path_fish = path + '/fish'
path_nofish = path + '/nofish'
x = # 画像データ
y = # ラベルデータ

機械学習では学習するデータは同じサイズである必要があるため全て64✖️32のサイズに指定する。64✖️32なのは横長の画像が多かったから。

 

os.path.dirnameは, 引数で渡したパス名からディレクトリ名の部分だけ抜き出す関数。

>>> os.path.dirname("/User/Program/test.py")
'/User/Program'

>>> os.path.dirname("/User/Program")
'/User'

os.path.abspathは引数で与えたパス名の絶対パスを取得する関数

>>> os.path.abspath("test.py")
'/User/Program/test.py'
>>> os.path.abspath("/User/Program/test.py")
'/User/Program/test.py'

path = os.path.dirname(os.path.abspath(__file__))この記述では実行中のスクリプトディレクトリの絶対パスを取得することができる。

print('abspath:     ', os.path.abspath(__file__))
print('abs dirname: ', os.path.dirname(os.path.abspath(__file__)))

実行結果

# abspath:      /Users/mbp/Documents/my-project/python-snippets/notebook/data/src/file_path.py
# abs dirname:  /Users/mbp/Documents/my-project/python-snippets/notebook/data/src

 

画像データを読み込んで配列に追加

def read_dir(path, label):
 files = glob.glob(path + "/*.jpg")
  for f in files:
   img = cv2.imread(f)
   img = cv2.resize(img, image_size)
   img_data = img.reshape(-1, ) # 一次元に展開
   x.append(img_data)
   y.append(label)

# 画像データを読み込む
read_dir(path_nofish, 0)
read_dir(path_fish, 1)

path_nofishにはnofish(魚が映っていないファイル)のパスが格納されている。

path_fishは魚が映っているファイルのパス。

 

glob.glob()でnofish、fishディレクトリの.jpgと一致するファイルパスを全て取得している。

cv2.imread()で画像の読み込み。

reshape()で画像データを一次元の配列に変換、(-1,)第一引数に-1を指定することで1行の配列にすることができる。

 

append()でx,yにそれぞれの配列を追加。

xに画像データ、yに画像データに対応したラベル番号。

 

テスト用、学習用にデータを分ける

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

 

データの学習

clf = RandomForestClassifier()
clf.fit(x_train, y_train)

変数にオブジェクト格納。

fit()で学習。

 

精度の確認

y_pred = clf.predict(x_test)
print(accuracy_score(y_test, y_pred))

 

predict()で予測。

accuracy_score()で予測結果とテストデータを比較。

 

データの保存

with open("fish.pkl", "wb") as fp:
pickle.dump(clf, fp)

 

with openに"wb"を指定して書き込み用でファイルを開く。

pickle.dump()で学習済みデータを保存。第一引数に保存したいオブジェクト、第二引数に保存先のファイル。

 

 

 

python  動画から熱帯魚が映った場面を抽出(基本)

import cv2, os

img_last = None # 前回の画像
no = 0 # 画像の枚数
save_dir = "./exfish" # 保存ディレクトリ名
os.mkdir(save_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))
  # 白黒画像に変換 --- (*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) # --- (*3)
   cnts = cv2.findContours(frame_diff,
   cv2.RETR_EXTERNAL,
   cv2.CHAIN_APPROX_SIMPLE)[1]
   # 差分があった領域をファイルに出力 --- (*4)
    for pt in cnts:
     x, y, w, h = cv2.boundingRect(pt)
      if w < 100 or w > 500: continue # ノイズを除去
     # 抽出した領域を画像として保存
     imgex = frame[y:y+h, x:x+w]
     outfile = save_dir + "/" + str(no) + ".jpg"
     cv2.imwrite(outfile, imgex)
     no += 1
  img_last = img_b
cap.release()
print("ok")

*今回のプログラムでは魚以外のものも抽出してしまうため今後の記事でより詳細なプログラムを紹介していく。ただ、基本を抑える意味でこのプログラムについて触れていく。

 

 

osモジュールはOSに依存している様々な機能を利用するためのモジュール。

os.mkdir()は新しいディレクトリを作成する関数。

os.makedirs()という関数もあり、os.mkdir()では直上にディレクトリが存在しないとエラーになったり、現在存在しないディレクトリ直下に新規のディレクトリを作成することはできないがos.makedirs()では引数に何か指定したりすることでos.mkdir()でできないことができるようになる。

 

../で一つ上の階層を指定。二つ上の階層なら../../こう。

今回の場合は同じ階層なので./こう。

 

画像の取得

is_ok, frame = cap.read()
if not is_ok: break

read()は画像が取得できたかどうか(画像が取得できたらTrue、できなければFalse)と画像データを返すのでis_okにはTrue,False、frameには画像データが格納されている。

 

if not でis_okの値がFalseの時処理を実行するようにしている。

*画像にresizeに関しての補足

このリサイズでサイズ指定する時、元画像の高さ、幅から割合を計算して指定する。

今回の場合だと元の動画サイズが幅960✖️高さ540で 、1.7777......8: 1という割合になるのでそれを元に高さ300で考えるとそれに対応した幅を計算するには300✖️1.7777...8をすることで640という値を求めることができる。

白黒画像に変換

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]

グレースケールに変換。

cv2.GaussianBlur()でぼかし処理。これを行うことで物体の大枠の範囲がわかる。

cv2.threshold()で画像の二値化。白黒の画像データにして0と255のみのデータにしている。

 

前の画像との差分の確認

if not img_last is None:
 frame_diff = cv2.absdiff(img_last, img_b) # --- (*3)
 cnts = cv2.findContours(frame_diff,
 cv2.RETR_EXTERNAL,
 cv2.CHAIN_APPROX_SIMPLE)[1]
 # 差分があった領域をファイルに出力 --- (*4)
 for pt in cnts:
  x, y, w, h = cv2.boundingRect(pt)
   if w < 100 or w > 500: continue # ノイズを除去
 # 抽出した領域を画像として保存
  imgex = frame[y:y+h, x:x+w]
  outfile = save_dir + "/" + str(no) + ".jpg"
  cv2.imwrite(outfile, imgex)
  no += 1

if not img_last is None:では前回の画像にデータが存在しなければ実行しないようにしている。

cv2.absdiff()で前の画像と今の画像の差分を比較しcv2.findContours()で輪郭を抽出している。

その後輪郭に外接するデータをboundingRect()で取得、元画像が格納されているframeから取得したデータの座標を示す部分を切り抜き。

cv2.imwrite()でファイル書き出し。

 

img_last = img_b

この部分で現在の画像データを変数に格納し次の画像データの処理を行う。

 

 

 

python 動画ファイルの書き出し

 

import cv2
import numpy as np

# カメラからの入力を開始
cap = cv2.VideoCapture(0)
# 動画書き出し用のオブジェクトを生成
fmt = cv2.VideoWriter_fourcc('m','p','4','v')
fps = 20.0
size = (640, 360)
writer = cv2.VideoWriter('test.m4v', fmt, fps, size) # --- (*1)

while True:
_, frame = cap.read() # 動画を入力
# 画像を縮小
frame = cv2.resize(frame, size)
# 画像を出力 --- (*2)
writer.write(frame)
# ウィンドウ上にも表示
cv2.imshow('frame', frame)
# Enterキーが押されたらループを抜ける
if cv2.waitKey(1) == 13: break
 
writer.release()
cap.release()
cv2.destroyAllWindows() # ウィンドウを破棄

 

動画ファイルの書き出しを行うプログラム。

 

動画書き出し用のオブジェクトを生成

fmt = cv2.VideoWriter_fourcc('m','p','4','v')
fps = 20.0
size = (640, 360)
writer = cv2.VideoWriter('test.m4v', fmt, fps, size)

 

cv2.VideoWriter_fourcc()で動画コーデックの指定を行う。今回の('m', 'p', '4', 'v')を指定しており他にも様々なコーデックの指定ができる。使用するコーデックによってはvideoタグなどで再生できないものもあるので保存した動画が再生されない場合にはコーデックの変換も視野にいれるといいかも。

 

cv2.VideoWriter()では動画書き出し用のオブジェクトを生成している。

後述するがこの後の処理でここで生成したオブジェクトに各画像データを書き出して動画ファイルを作成することができる。

第一引数には保存するファイル名、第二引数には動画書き出し用のフォーマット(ファイル形式つまりコーデック), 第三引数はフレームレートつまり1秒間で見せる画像の枚数,第四引数が画面サイズ。

 

動画から画像読み込みなどループ処理

while True:
 _, frame = cap.read() # 動画を入力
 # 画像を縮小
  frame = cv2.resize(frame, size)
 # 画像を出力 --- (*2)
  writer.write(frame)
 # ウィンドウ上にも表示
 cv2.imshow('frame', frame)
 # Enterキーが押されたらループを抜ける
 if cv2.waitKey(1) == 13: break

read()で動画から画像読み込み。

write()メソッドで書き出す画像を指定して先ほど生成したオブジェクトに出力。

cv2.imshow()でウィンドウ表示。

 

カメラ、ファイルを閉じる。

writer.release()
cap.release()

writer.release()でファイルを閉じる。

cap.release()でカメラ閉じる。

 

 

 

 

 

 

 

 

python 画面に動きがあった部分の抽出

 

 import cv2

 cap = cv2.VideoCapture(0)
 img_last = None # 前回の画像を記憶する変数 --- (*1)
 green = (0, 255, 0)

 while True:
  # 画像を取得
  _, frame = cap.read()
  frame = cv2.resize(frame, (500, 300))
  # 白黒画像に変換 --- (*2)
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  gray = cv2.GaussianBlur(gray, (9, 9), 0)
  img_b = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)[1]
  # 差分を確認する
  if img_last is None:
  img_last = img_b
   continue
 frame_diff = cv2.absdiff(img_last, img_b) # --- (*3)
 cnts = cv2.findContours(frame_diff,
 cv2.RETR_EXTERNAL,
 cv2.CHAIN_APPROX_SIMPLE)[1]
  # 差分があった点を画面に描く --- (*4)
  for pt in cnts:
   x, y, w, h = cv2.boundingRect(pt)
    if w < 30: continue # 小さな変更点は無視
   cv2.rectangle(frame, (x, y), (x+w, y+h), green, 2)
  # 今回のフレームを保存 --- (*5)
 img_last = img_b
  # 画面に表示
 cv2.imshow("Diff Camera", frame)
  cv2.imshow("diff data", frame_diff)
  if cv2.waitKey(1) == 13: break
cap.release()
cv2.destroyAllWindows()

実行結果(左から右に動いた時の出力結果)

f:id:hanamichi_sukusuku:20210107214555p:plain

以下、cv2.absdiff()関数の結果

f:id:hanamichi_sukusuku:20210107214647p:plain

 

画像の取得

_, frame = cap.read()
frame = cv2.resize(frame, (500, 300))

 

白黒画像に変換

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

グレースケールに変換。

cv2.GaussianBlur()でぼかし処理。

cv2.threshold()で二値化。

 

前の画像と差分を確認

if img_last is None:
 img_last = img_b
  continue
frame_diff = cv2.absdiff(img_last, img_b) # --- (*3)
cnts = cv2.findContours(frame_diff,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[1]

if img_last is None:について

python演算子には同値性と同一性という概念がある。

値がNoneかを判定するには == 演算子ではなく、is演算子もしくはis not演算子を利用する。is演算子はisの左右が同一のオブジェクトであるかを比較する演算子である。

同値性は ==

これは同じ値とみなしてよければTrueとするという考え方

このみなして良いかの部分はカスタマイズできるので何を同じ値と判定するかはオブジェクトによって異なる。

同一性は is , is not

これは完全に同じものだったらTrueにするという考え方

 

==とisで同じ出力結果得られることもあるがバグを減らしたりする意味でもNoneの判定はis,is notを使用する。

 

img_last = img_bで前の画像を記憶する変数に今の画像を格納。

 

cv2.absDiffについて

cv2.absdiff()関数は画像ごとの差分を抽出する。

cv2.absdiff(変化前の画像, 変化後の画像)

上記の結果の画像を見ると動きがあった部分が濃い白い線で表示されている。

この輪郭のデータをcv2.findContours()関数で取得する。

今回第二引数で指定しているcv2.RETR_EXTERNALは一番外側の輪郭を取得するオプション。RETR_LISTだと内側の輪郭も取得してしまうためこちらを指定。第三引数のcv2.CHAIN_APPROX_SIMPLEは省略できる点は省略するオプション。

 

差分があった点を画面に描く

for pt in cnts:
 x, y, w, h = cv2.boundingRect(pt)
  if w < 30: continue # 小さな変更点は無視
  cv2.rectangle(frame, (x, y), (x+w, y+h), green, 2)

 

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

cv2.rectangle()で描画。

 

 

あとはcv2.imshow()で表示してcv2.waitKey()で繰り返し処理終了の記述。

release()でカメラ終了。

cv2.destoryAllWindows()で全てのウィンドウを閉じておしまい。

 

 

 

 

python 赤っぽいところを白色で表示する(HSV色空間)

 import cv2
 import numpy as np

 # Webカメラから入力を開始
 cap = cv2.VideoCapture(0)
 while True:
  # 画像を取得して縮小する
  _, frame = cap.read()
  frame = cv2.resize(frame, (500,300))
 # 色空間をHSVに変換 --- (*1)
  hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV_FULL)
 # HSVを分割する --- (*2)
  h = hsv[:, :, 0]
  s = hsv[:, :, 1]
  v = hsv[:, :, 2]
 # 赤色っぽい色を持つ画素だけを抽出 --- (*3)
  img = np.zeros(h.shape, dtype=np.uint8)
  img[*1 & (s > 100)] = 255
 # ウィンドウに画像を出力 --- (*4)
  cv2.imshow('RED Camera', img)
  if cv2.waitKey(1) == 13: break

cap.release() # カメラを解放
cv2.destroyAllWindows() # ウィンドウを破棄

実行結果(めちゃくちゃわかりにくくてごめんなさい)

f:id:hanamichi_sukusuku:20210106161712p:plain

 

実行結果は自分が目のところに眼鏡ケースを持っているライブ映像をスクショしたもの。

 

webカメラ起動

cap = cv2.VideoCapture(0)

 

while True:
# 画像を取得して縮小する
_, frame = cap.read()
frame = cv2.resize(frame, (500,300))
# 色空間をHSVに変換 --- (*1)
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV_FULL)
# HSVを分割する --- (*2)
h = hsv[:, :, 0]
s = hsv[:, :, 1]
v = hsv[:, :, 2]
# 赤色っぽい色を持つ画素だけを抽出 --- (*3)
img = np.zeros(h.shape, dtype=np.uint8)
img[*2 & (s > 100)] = 255
# ウィンドウに画像を出力 --- (*4)
cv2.imshow('RED Camera', img)
if cv2.waitKey(1) == 13: break

whileでワンフレームごとに出力している処理。

read()でワンフレーム取得。

resizeでサイズ変更。(大きな画像は必要ないから)

cv2.cvtColor()で色空間をHSVに変換。HSVは色相、彩度、明度の3つのパラメーターで色を表現する方式。RGB色空間は、赤緑青の原色による色の組み合わせで色を表現しているので色の変化がわかりにくいい。それに対してHSV色空間は彩度や明度を用いて色を表現するので、感覚的に色を指定できる。色相は360度の円形で右回りに赤、緑、青、赤のように色の円形で表現する。

f:id:hanamichi_sukusuku:20210106163258p:plainHSV色空間_Wikipedia参照

h = 色相

s = 彩度

v = 明度

 

h = hsv[:, :, 0]

この部分では全ピクセルの色相の値をhに格納している。画像データは[高さ, 幅, 色]

で表現されていて今回この配列の色の部分にはHSV色空間のデータが[色相,彩度,明度]のように格納されているのでインデックス番号0を指定することで色相のデータを取得している。

s = [:, :, 1]

v = [:, :, 2]

上記2つも同様。

 

赤色っぽい色を持つ画素だけを抽出

 img = np.zeros(h.shape, dtype=np.uint8)
img[*3 & (s > 100)] = 255

 

np.zeros()では引数に指定した値だけ0が格納された配列を作成するメソッドである。

h.shapeで色相のリストの要素数を取得している。dtypeはデータ型を指定しておりイメージしやすいのだとintやstr、unitは符号なしの整数[0 0 0]こーゆーやつ、[0. 0. 0.]これじゃないやつ。unit8の8は8ビットという意味で色を0~255で表現するものらしい。

 

img[*4 & (s > 100)] = 255は、imgのデータのうち「hが50未満」または「hが200より大きく、しかもsが100以上」のところを255にする、というもの。つまいhには要素数300の色相のデータが入っていてhの中身の要素が50未満、200より大きいかつsの中身のデータが100以上のものの位置(インデックス番号といった方がわかりやすいかも)とimgの中身の同じ位置を255にしているということ。色相の50~200は角度なので上の画像で確認すればなんとなく赤い部分を指定していることがわかる。

Numpyではブール演算子のandやorではなく|や&のビット演算子を使う。

imgの中身を確認すると0と255のみで配列が作成されているため白黒画像になる。

 

cv2.imshow()でウィンドウ表示して画像を表示。

cv2.waitKey()で繰り返し処理の中止。

 

release()でカメラ終了。

cv2.destoryAllWindow()でウィンドウ閉じる。

 

 

 

 

*1:h < 50) | (h > 200

*2:h < 50) | (h > 200

*3:h < 50) | (h > 200

*4:h <50 ) | (h > 200