2018/08/24

【09.基礎4】OpenCVを学ぶ

どうも、ディープなクラゲです。
ゼロから学ぶディープラーニング推論」シリーズの9回目記事です。
このシリーズでは、Neural Compute StickとRaspberryPiの使い方をゼロから徹底的に学び、成果としてディープラーニングの推論アプリケーションが作れるようになることを目指しています。

第9回目はOpenCVについて学びます。
本シリーズでは、主にUSBカメラからの映像を入力画像として取り扱うためにOpenCVを使います。

【 目次 】


OpenCVとは

CVとはComputer Visionの略です。
OpenCVはインテルが開発したオープンソースのComputer Vision向けのライブラリです。
具体的には、画像処理、画像解析、機械学習などが行えます。
今回のシリーズでは主にUSBカメラ映像の入力と表示がメインです。

OpenCVを使うためには cv2 モジュールを importする必要があります
ちなみにクラゲのラズパイにインストールされているOpenCVのバージョンは3.3.0です。cv2の2はバージョンとは関係ないのでご注意ください

import cv2

画像ファイルの読み込みと表示

GoogLeNet画像認識で既に使いましたが、cv2.imreadで画像ファイルを読み込めます。
また、cv2.imshowで画像を表示させることができます。第一引数は画像表示ウィンドウの名前で、任意の文字列を付けることができます。第二引数にimreadで読み込んだ画像を指定します。

import cv2
 
image_file = '/home/pi/workspace/ncsdk/examples/data/images/cat.jpg'
 
img = cv2.imread(image_file)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

実行すると、ターミナルとは別に以下のようなウィンドウが現れます(サイズが大きいため、下の画像は縮小して載せています)
このウィンドウ上で何かキーを押すとプログラムが終了します。

cv2.waitKey(0)はキーボードが押されるまで処理を待つ関数です。
この関数がないと、プログラムが終了してしまうため、画像表示を見ることができません。
cv2.waiKeyは引数の数値だけ処理を待つという関数です。数値の単位はms(ミリセカンド)です。例えば cv2.waitKey(1000)であれば1秒待ちます。引数が0の場合は特別で、0秒待つという意味ではなく、キーボードが押されるまでずっと処理を待つという意味になります。

最後のcv2.destroyAllWindowsは、全てのウィンドウを閉じるという関数です。
実はこの記述は無くても、プログラム終了と同時にウィンドウは閉じられます。

画像サイズ変更

cv2.resizeを使うと画像サイズを変更できます
cv.resizeは第一引数に入力画像、第二引数に サイズ(width, height)を指定します。
以下のコードは、先程のコードに1行追加しただけです。実行すると画像ウィンドウが200×200のサイズに小さくなって表示されることが確認できます。

import cv2
 
image_file = '/home/pi/workspace/ncsdk/examples/data/images/cat.jpg'
 
img = cv2.imread(image_file)
img = cv2.resize(img, (200200))
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

カメラ映像の読み込みと表示

今までは静止画でしたが、カメラ映像の動画を読み込んで表示します。
まずはラズパイにUSBカメラを接続しましょう

以下のコードを実行してみて下さい

import cv2
 
cap = cv2.VideoCapture(0)
 
while True:
    ret, frame = cap.read()
    cv2.imshow('image', frame)
    key = cv2.waitKey(1)
    if key != -1:
        break
 
cap.release()
cv2.destroyAllWindows()

カメラ映像が動画としてリアルタイムに表示されていると思います。
画像が表示されているウィンドウで何らかのキーを押すとプログラムは終了できます。

上記のコードの解説です。

VideoCapture object取得

cv2.VideoCaptureは引数にデバイス番号を指定してVideoCapture objectを取得する関数です。
ラズパイに接続されているカメラは1つだけなので、デバイス番号は 0 を指定します。
なお、ターミナルで以下のコマンドを実行することによりデバイス番号を確かめることもできます。

ls /dev/video*
 
# 実行結果例
# /dev/video0

無限ループ

OpenCVでカメラ映像を取得する手順は、動画として一括入手ではなく、リアルタイム静止画像の 読み込み と 表示 をずっと繰り返すという処理の流れになります。while による無限ループを使用している理由はそのためです。

画像の読み込みと表示

取得したVideoCapture object の cap を使って、cap.readとすることで、カメラからの静止画像を読み込みます。
静止画像は2番目の戻り値 frame へ代入されます。1番目の戻り値 ret には正しく静止画像が読み込みできたか否かのブール値(True/False)が入ります。
画像を表示させる手順は画像ファイル表示のときと同じで、cv2.imshowを使います。

キー入力

無限ループのままだとプログラムが終了できないため、キーボード入力があったときに break させループから抜け出します。
画像ファイルの表示のときのように cv.waitKey(0)にしてしまうと、そこで止まって動画にならないため、引数を最小値の 1 に設定しています。当然数値を大きくするとカクカクした映像になります。
キーが押されていないときの cv.waitKeyの戻り値は -1 です。つまり -1 以外のときは、何らかのキーが押されたということになりますので、それを判別して break しています。

VideoCapture object解放

不要になったVideoCapture objectは、最後にcap.releaseで解放します。

OpenCVによるカメラ映像の読み込みと表示に関しては、ほぼこのコードのまとまりをテンプレートとして使えます。

文字や図形の描画

OpenCVでは、画像を表示しているウィンドウに文字や図形を重ね合わせて描画することができます。

テキスト描画

cv2.putTextは文字列を描画する関数です。第1引数は出力先画像、第2引数以降は以下が指定できます。

表示文字列, 表示位置(X, Y), フォント, フォント倍率, 色(B, G, R), 線の太さ

※表示位置は、テキストの左下座標が基準



次のコードは、「画像サイズ変更」のサンプルコードにcv2.putTextを1行追加したコードです

import cv2
 
image_file = '/home/pi/workspace/ncsdk/examples/data/images/cat.jpg'
 
img = cv2.imread(image_file)
img = cv2.resize(img, (200200))
cv2.putText(img, 'cat', (50150), cv2.FONT_HERSHEY_SIMPLEX, 2, (02550), 5)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

長方形描画

cv2.rectangleは長方形を描画する関数です。第1引数は出力先画像、第2引数以降は以下が指定できます。

左上の座標(X, Y), 右下の座標(X, Y), 色(B, G, R), 線の太さ

※線の太さを -1 にすると、長方形の中身が塗りつぶしになります



次のコードは、「画像サイズ変更」のサンプルコードにcv2.rectangletを1行追加したコードです

import cv2
 
image_file = '/home/pi/workspace/ncsdk/examples/data/images/cat.jpg'
 
img = cv2.imread(image_file)
img = cv2.resize(img, (200200))
cv2.rectangle(img, (5050), (150100), (00255), 2)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

他にもcv2.lineで直線描画、cv2.circleで円描画、cv2.ellipseで楕円描画などありますので、気になる人は調べてみて下さい。
なお、サンプルソースは静止画の例でしたが、カメラ映像にも同様に表示させることができます。

その他

画像のコピー

copyを使うと画像をコピーできます。
以下は、img を img2 にコピーしているコード例です

import cv2
 
image_file = '/home/pi/workspace/ncsdk/examples/data/images/cat.jpg'
 
img = cv2.imread(image_file)
img2 = img.copy()

カメラ映像表示でウィンドウの閉じるボタン有効化

カメラ映像表示時に、画像表示ウィンドウの 閉じるボタン(×ボタン)を押しても一瞬閉じるだけで、実際には閉じることはできません。
理由は、1回閉じてもwhileループで再びすぐに cv.imshowで表示されるためです。
この閉じるボタン押下を検知するためにcv2.getWindowPropertyを活用します。
cv2.getWindowPropertyはウィンドウのパラメーターを取得する関数で、第2引数に cv2.WND_PROP_ASPECT_RATIO を設定することで ウィンドウのアスペクト比 を戻り値として得ることができます。閉じるボタンが押されると戻り値は -1.0 になります。
これを利用して、if文の条件式に追加することで、閉じるボタン押し時もbreakできるようにさせています。
※ or は論理和と呼ばれる演算子で、前後のどちらか一方が True であれば True となります

import cv2
 
cap = cv2.VideoCapture(0)
 
while True:
    ret, frame = cap.read()
    cv2.imshow('image', frame)
    key = cv2.waitKey(1)
    prop_val = cv2.getWindowProperty('image', cv2.WND_PROP_ASPECT_RATIO)
    if (key != -1) or (prop_val < 0):
        break
 
cap.release()
cv2.destroyAllWindows()

ちょっとこれは特殊な使い方なので、こういうものだという程度の理解でOKです


次回は今回学んだOpenCVを活用して、GoogLeNet画像認識をUSBカメラ画像を入力としてリアルタイムで行います!
以上、「OpenCVを学ぶ」でした。