PythonはComputer Visionの分野で多くのアプリケーションを持っており、典型的なのはDeep Learningです。
ドキュメントのOCRからロボットの「見る」ことまで、Computer Visionはエキサイティングで挑戦的な分野です。
OpenCVはオープンソースのクロスプラットフォームフレームワークで、リアルタイムComputer Visionを指向するライブラリとして開発されました。
クロスプラットフォームであるため、OSに関係なく、C++、Python、Javaでインターフェースすることができます。
Computer Visionは広い分野であり、あなたが取り組むことのできる個々のタスクや問題がたくさんあります。
大きなものでは、物体検出があります。
注:オブジェクト検出とは、画像、ビデオ、ストリーム中のオブジェクトの分類(ラベリング)、位置検出、輪郭検出(通常、バウンディングボックスのような粗いもの)を指します。
これらは3つの異なるタスクであり、それぞれ独自の観点でトピックとなり得ます。
粗くないアウトラインの検出は、画像をそれぞれの異なるオブジェクトに分割する場合、画像分割とも呼ばれますが、画像分割はこのアプリケーションに限定されるものではありません。
このガイドでは、PythonでOpenCVを使用して物体検出を行う方法を学びます。
事前に学習したHaar-Cascade分類器を用いて、画像やビデオファイル、リアルタイムで検出されたオブジェクトを読み取り、検出し、表示する方法を取り上げます。
さっそくOpenCVをインストールしてみましょう
OpenCVによる物体検出
OpenCV をまだインストールしていない場合は,Python のドライバを pip
で簡単にインストールすることができます.
$ pip install opencv-python
これで完了です.これで,OpenCV とその依存関係がすべてインストールされました.
注意: もし、インストールでエラーが発生した場合は、代わりに opencv-contrib-python
をインストールしてみてください。
ライブラリのセットアップが完了したので、物体認識の最初のステップとして、OpenCVで画像を読み込んで表示します。
好きな画像を使用できますが、このガイドでは thispersondoesnotexist.com から取得した face_image.jpg
を使用することにします。
このウェブサイトでは、StyleGanを使用して「想像上の人物」を生成しています。
OpenCV を表す cv2
モジュールの imread()
メソッドを使用して、画像を読み込むことができます。
そして – ウィンドウに表示することができます。
import cv2
image_path = "generic-face.webp" # Put an absolute/relative path to your image
window_name = f"Detected Objects in {image_path}" # Set name of window that shows image
original_image = cv2.imread(image_path) # Read image in memory
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO) # Create window and set title
cv2.imshow(window_name, original_image) # Load image in window
cv2.resizeWindow(window_name, (400, 400)) # Resize window
cv2.waitKey(0) # Keep window open indefinitely until any keypress
cv2.destroyAllWindows() # Destroy all open OpenCV windows
このコードを実行すると、このようなウィンドウが表示されます。
注意: OSによっては、Windowが画面の前面に表示されないことがあり、コードがいつまでも実行されているように見えることがあります。
コードを実行してもウィンドウが表示されない場合は、開いているウィンドウを循環させるようにしてください。
imread()メソッドは画像を読み込み、
imshow()メソッドは画像をウィンドウに表示するために使用されます。
nameWindow() と resizeWindow()
メソッドは、ウィンドウと画像のサイズに関連した不一致があった場合に、画像用のカスタムウィンドウを作成するために使用されます。
waitKey()メソッドは、与えられたミリ秒の間、またはキーが押されるまでウィンドウを開いたままにします。
基本的なセットアップが完了したので,次は OpenCV を用いてオブジェクトを検出する手順を説明します.理解する必要があるのは
- OpenCV を用いた描画方法(オブジェクトが検出されたときに、「ローカライズ」/「アウトライン化」する方法).
- Haar Cascade Classifiers (OpenCVがどのようにオブジェクトを区別するのか)
OpenCVで絵を描くには?
OpenCV は,矩形,円,線など様々な形状を描画することができます.また, putText()
メソッドを利用することで,図形にラベルを付けることもできます.ここでは,位置引数,色,図形の太さを受け取る rectangle()
メソッド を用いて,画像に簡単な矩形を描画してみましょう.
イメージを読み込んだ後、ウィンドウに名前を付ける前に、長方形を作成するための行を追加します。
# Reading the image
...
original_image = cv2.imread(image_path)
rectangle = cv2.rectangle(original_image,
(200, 100), # X-Y start
(900, 800), # X-Y end
(0, 255, 0),
2)
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)
# Naming the window
...
さて、コードを再実行すると、イメージの上に矩形が描かれるのがわかります。
ここでは, cv2.rectangle()
呼び出しによって矩形の位置を固定しました.これらの位置は,画像から推測されるものであり,推測されるものではありません.そこで,OpenCV が力仕事をしてくれます.一旦それが行われると,検出されたオブジェクトの周囲に矩形を描くために,この正確なメソッドを利用することができます.
このように矩形(または円)を描くことは、物体検出の重要なステップです。
検出された物体に明確な注釈(ラベル)を付けることができるからです。
さて、OpenCVによる描画が終わったところで、Haar Cascade Classifierの概念、その仕組み、そして画像内のオブジェクトを識別する方法について見ていきましょう!
ハールカスケード分類器
Haar-Cascade Classifier は,Haar 特徴を用いて動作する機械学習分類器です.これは, cv2.CascadeClassifier
クラスで表現されます.OpenCV には,いくつかの XML ファイルがあらかじめパッケージングされており,それぞれが異なるオブジェクトに対する Haar 特徴を保持しています.
これらのファイルには,異なるオブジェクトに対する Haar 特徴が保存されています.
Haar特徴量は、通常のCNN(Convolutional Neural Networks)の特徴マップと同様の方法で機能します。
特徴量は画像の多くの領域で計算され、画素の強度が合計された後、これらの合計の差が計算されます。
このように画像をダウンサンプリングすることで、画像中のパターン検出に利用できる簡略化された特徴マップを得ることができる。
注意:世の中には、Haar-Cascade分類器よりも精度が高く柔軟性のある非常に強力なネットワークを含む、多くのパターン認識オプションがあります。
Haar機能とHaar-Cascade分類器の最大の魅力は、その速さです。
OpenCV をインストールすると,以下のような Haar 機能を持つ XML ファイルにアクセスできるようになります.
- 目
- 顔の正面
- 全身
- 上半身
- 下半身
- 猫
- ストップサイン
- ナンバープレート など
GitHubの公式リポジトリでファイル名を確認することができます。
これらは、かなり幅広い用途をカバーしています 例えば、目の分類器を読み込んで、読み込んだ画像から目を検出し、検出された物体を矩形で囲んでみることにしましょう。
import cv2
image_path = "face_image.jpg"
window_name = f"Detected Objects in {image_path}"
original_image = cv2.imread(image_path)
# Convert the image to grayscale for easier computation
image_grey = cv2.cvtColor(original_image, cv2.COLOR_RGB2GRAY)
cascade_classifier = cv2.CascadeClassifier(
f"{cv2.data.haarcascades}haarcascade_eye.xml")
detected_objects = cascade_classifier.detectMultiScale(image_grey, minSize=(50, 50))
# Draw rectangles on the detected objects
if len(detected_objects) != 0:
for (x, y, width, height) in detected_objects:
cv2.rectangle(original_image, (x, y),
(x + height, y + width),
(0, 255, 0), 2)
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)
cv2.imshow(window_name, original_image)
cv2.resizeWindow(window_name, 400, 400)
cv2.waitKey(0)
cv2.destroyAllWindows()
このコードを実行すると、以下のような表示になるはずです。
ここでは、計算量を減らすために分類器用に画像をグレイスケールしています(情報が多ければ多いほど計算量も増えます)。
この検出では色はあまり重要ではありません。
目を定義するパターンは色が付いていてもいなくてもほとんど同じに見えるからです。
cascade_classifierは
CascadeClassifier` のインスタンスで、目のための Haar 特徴をロードしています。
detectMultiScale()メソッドは実際の検出を行うもので、画像上の同じオブジェクトをスケールに関係なく検出することができます。
これは,検出されたオブジェクトの座標のリストを,矩形(タプル)の形で返します.これによって,検出されたオブジェクトの輪郭を,まあ矩形で表現することが自然になります detected_objects にある (x, y, width, height)
の各タプルに対して、矩形を描画することができます。
引数 minSize
は、考慮するオブジェクトの最小サイズを定義します。
このサイズを本当に小さく設定した場合、分類器は画像上の多くの偽陽性を拾ってしまう可能性があります。
これは通常、扱う画像の解像度と平均的なオブジェクトのサイズに依存します。
実際には、うまく動作するようになるまで合理的にサイズをテストすることに集約されます。
最小サイズを (0, 0)
に設定して、何がピックアップされるかを見てみましょう。
この画像では、目として誤分類されるようなものは他にないので、実際には2つの誤分類があるだけです。
目の部分と顎の部分です。
画像の解像度や内容にもよりますが、小さいサイズを設定すると、かなりの部分を間違って強調してしまう可能性があります。
他の画像についても、オブジェクトを検出するプロセスは同じです。
正しく学習された分類器をロードし、 detectMultiScale()
を実行し、 detected_objects
の上に描画を行います。
注目すべきは、複数の分類器を組み合わせることができることです! 例えば、個人の正面顔、目、口を別々に検出し、その上に描画することができます。
これらの分類器を読み込んで、同じ画像でオブジェクトの種類ごとに色を変えて使ってみましょう。
import cv2
image_path = "face_image.jpg"
window_name = f"Detected Objects in {image_path}"
original_image = cv2.imread(image_path)
# Convert the image to grayscale for easier computation
image_grey = cv2.cvtColor(original_image, cv2.COLOR_RGB2GRAY)
eye_classifier = cv2.CascadeClassifier(
f"{cv2.data.haarcascades}haarcascade_eye.xml")
face_classifier = cv2.CascadeClassifier(
f"{cv2.data.haarcascades}haarcascade_frontalface_alt.xml")
smile_classifier = cv2.CascadeClassifier(
f"{cv2.data.haarcascades}haarcascade_smile.xml")
detected_eyes = eye_classifier.detectMultiScale(image_grey, minSize=(50, 50))
detected_face = face_classifier.detectMultiScale(image_grey, minSize=(50, 50))
detected_smile = smile_classifier.detectMultiScale(image_grey, minSize=(200, 200))
# Draw rectangles on eyes
if len(detected_eyes) != 0:
for (x, y, width, height) in detected_eyes:
cv2.rectangle(original_image, (x, y),
(x + height, y + width),
(0, 255, 0), 2)
# Draw rectangles on eyes
if len(detected_face) != 0:
for (x, y, width, height) in detected_face:
cv2.rectangle(original_image, (x, y),
(x + height, y + width),
(255, 0, 0), 2)
# Draw rectangles on eyes
if len(detected_smile) != 0:
for (x, y, width, height) in detected_smile:
cv2.rectangle(original_image, (x, y),
(x + height, y + width),
(0, 0, 255), 2)
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)
cv2.imshow(window_name, original_image)
cv2.resizeWindow(window_name, 400, 400)
cv2.waitKey(0)
cv2.destroyAllWindows()
ここでは、笑顔、目、顔の3つの分類器をロードしています。
それぞれの分類器を画像上で実行し、検出されたオブジェクトの周りに矩形を描き、オブジェクトのクラスで矩形に色を付けます。
笑顔はあまり検出されませんでした。
おそらく、この画像の笑顔はかなりニュートラルなものだからでしょう。
笑顔の幅が広くないので、分類器を狂わせることができたのでしょう。
OpenCVによる映像中の物体検出
画像中の物体検出が終わったところで、動画に話を移そう。
動画といっても、画像が連続しているだけなので、ほとんど同じ処理が適用されます。
ただし、今回はフレーム単位での処理となります。
動画中の物体を検出するためには、まず動画ファイルをプログラムに読み込みます。
ビデオファイルを読み込んだら、フレームごとにビデオデータを分離して、先ほどと同じようにオブジェクト検出を行う必要があります。
OpenCVを用いた動画の読み込み
このガイドでは、木の上の猫の動画を、ローカルに cat-on-tree.mp4
として保存し、自由に使えるようにしたものを使用します。
このファイルは、ビデオの作成者によると、無料で利用できるそうです。
まず、動画を読み込んで表示してみましょう。
import cv2
import time
video_path = "cat-on-tree.mp4"
window_name = f"Detected Objects in {video_path}"
video = cv2.VideoCapture(video_path)
while True:
# read() returns a boolean alongside the image data if it was successful
ret, frame = video.read()
# Quit if no image can be read from the video
if not ret:
break
# Resize window to fit screen, since it's vertical and long
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.imshow(window_name, frame)
if cv2.waitKey(1) == 27:
break
# Sleep for 1/30 seconds to get 30 frames per second in the output
time.sleep(1/30)
video.release()
cv2.destroyAllWindows()
このコードはビデオファイルを読み込んで、Esc
キーが押されるまでその内容を表示します。
ビデオファイルをパスから読み込むために VideoCapture()
を使用しますが、このメソッドで 0
という値を与えると、ウェブカメラを開いて入力からフレームを読み込むようになります。
これは後で行うことにして、今はローカルのビデオファイルを扱います。
さて、先ほどと同じようにHaar-Cascade Classifierをビデオの各画像に適用することができます。
import cv2
import time
video_path = "cat-on-tree.mp4"
window_name = f"Detected Objects in {video_path}"
video = cv2.VideoCapture(video_path)
while True:
# read() returns a boolean alongside the image data if it was successful
ret, frame = video.read()
# Quit if no image can be read from the video
if not ret:
break
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
# Greyscale image for classification
image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Define classifier
cascade_classifier = cv2.CascadeClassifier(
f"{cv2.data.haarcascades}haarcascade_frontalcatface.xml")
# Detect objects
detected_objects = cascade_classifier.detectMultiScale(
image, minSize=(50, 50))
# Draw rectangles
if len(detected_objects) != 0:
for (x, y, height, width) in detected_objects:
cv2.rectangle(
frame, (x, y), ((x + height), (y + width)), (0, 255, 0), 15)
#Show image
cv2.imshow(window_name, frame)
if cv2.waitKey(1) == 27:
break
video.release()
cv2.destroyAllWindows()
この分類器は猫の正面画像に対して学習されているので、横顔を検出することはできません。
動画の大部分では、猫は横顔で撮影されているので、顔をカメラの方に向けるまでは、たくさんの誤判定が発生するはずです。
たまたま背景がぼやけていて、その中に猫の顔と思われる特徴があったので、それを分類しています。
しかし、頭を動かしてみると、明らかに顔をロックしています。
これが、猫が横を向いているときの分類です。
そして、猫がカメラの方を向いているときに、どのように正しく分類したのか。
このように、映像の中にある箱をリアルタイムに検出しているのです。
また、検出されたオブジェクト(これも数字の羅列です)を保存しておき、フレームごとに「オフライン」で描画し、ビデオを再レンダリングすることで、検出中のCPUパワーを節約することができます。
OpenCVによるリアルタイム物体検出機能
リアルタイムビデオでの物体検出は、やはり動画からの検出や画像からの検出と何ら変わりはありません。
動画でリアルタイムに猫の顔を検出しましたが、動画はローカルでしたね。
ウェブカメラからビデオストリームを取得しよう ウェブカメラから入力を得るには、VideoCapture()
の呼び出しを少し変更する必要があります。
先に述べたように、ファイルパスを与える代わりに、番号を与えます(ほとんどの場合、ウェブカメラが1つの場合は 0
)。
import cv2
window_name = "Detected Objects in webcam"
video = cv2.VideoCapture(0)
while video.isOpened():
ret, frame = video.read()
if not ret:
break
cv2.imshow(window_name, frame)
if cv2.waitKey(1) == 27:
break
video.release()
cv2.destroyAllWindows()
注意: macOS では、ターミナルまたはターミナルを実行するプログラムにウェブカメラを使用する許可を与えてからでないと動作しない場合があります。
リアルタイムにオブジェクトを検出するには、ビデオファイルと同じように、フレームごとに分離し、フレームごとにオブジェクトを検出し、それらを統合して表示することができます。
import cv2
window_name = "Detected Objects in webcam"
video = cv2.VideoCapture(0)
while video.isOpened():
ret, frame = video.read()
if not ret:
break
image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cascade_classifier = cv2.CascadeClassifier(
f"{cv2.data.haarcascades}haarcascade_frontalface_default.xml")
detected_objects = cascade_classifier.detectMultiScale(
image, minSize=(20, 20))
if len(detected_objects) != 0:
for (x, y, height, width) in detected_objects:
cv2.rectangle(
frame, (x, y), ((x + height), (y + width)), (0, 255, 0), 5)
cv2.imshow(window_name, frame)
if cv2.waitKey(1) == 27:
break
video.release()
cv2.destroyAllWindows()
上記のコードを実行すると、ウェブカメラからストリーミングされたウィンドウがポップアップし、あなたの顔を強調する矩形が表示されます! ウェブカメラは一般にそれほど高い解像度を持っていないので、これらの画像は計算コストがはるかに低いからです。
明るい部屋に座っているか、少なくとも光源が顔の方に向いていると助かります。
結論
このガイドでは、PythonでHaar-Cascade分類器を使い、OpenCVで物体検出を行いました。
分類器やHaarの特徴について紹介し、画像や動画、Webカメラからのビデオストリームに対してオブジェクト検出を行いました。
OpenCVを使った物体検出の次のステップは、Yoloやmobilenetv3といった他の分類器を調べることです。
なぜなら、Haarカスケードから得られる精度は、ディープニューラルネットワークの代替品と比べると不十分なものだからです。