1. 序章

このチュートリアルでは、 OpenCVコンピュータービジョンライブラリをインストールして使用し、それをリアルタイムの顔検出に適用する方法を学習します。

2. インストール

プロジェクトでOpenCVライブラリを使用するには、opencvMaven依存関係pom.xmlに追加する必要があります。

<dependency>
    <groupId>org.openpnp</groupId>
    <artifactId>opencv</artifactId>
    <version>3.4.2-0</version>
</dependency>

Gradleユーザーの場合、build.gradleファイルに依存関係を追加する必要があります。

compile group: 'org.openpnp', name: 'opencv', version: '3.4.2-0'

ライブラリを依存関係に追加した後、OpenCVが提供する機能を使用できます。

3. ライブラリの使用

OpenCVの使用を開始するには、ライブラリを初期化する必要があります。これは、メインのメソッドで実行できます。

OpenCV.loadShared();

OpenCVは、さまざまなプラットフォームやアーキテクチャのOpenCVライブラリに必要なネイティブパッケージのロードに関連するメソッドを保持するクラスです。

ドキュメントの動作が少し異なることに注意してください。

System.loadLibrary(Core.NATIVE_LIBRARY_NAME)

これらのメソッド呼び出しは両方とも、実際に必要なネイティブライブラリをロードします。

ここでの違いは、後者ではネイティブライブラリをインストールする必要があるということです。 ただし、前者は、特定のマシンでライブラリが使用できない場合、ライブラリを一時フォルダにインストールできます。 この違いにより、 loadSharedメソッドは、通常、を実行するための最良の方法です。

ライブラリを初期化したので、それを使って何ができるか見てみましょう。

4. 画像の読み込み

まず、OpenCVを使用してディスクからサンプルイメージをロードしましょう。

public static Mat loadImage(String imagePath) {
    Imgcodecs imageCodecs = new Imgcodecs();
    return imageCodecs.imread(imagePath);
}

このメソッドは、指定された画像を行列表現であるMatオブジェクトとしてロードします。

以前にロードした画像を保存するには、 Imgcodecsクラスのimwrite()メソッドを使用できます。

public static void saveImage(Mat imageMatrix, String targetPath) {
    Imgcodecs imgcodecs = new Imgcodecs();
    imgcodecs.imwrite(targetPath, imageMatrix);
}

5. Haarカスケード分類子

顔認識に飛び込む前に、これを可能にするコアコンセプトを理解しましょう。

簡単に言えば、分類子は、過去の経験に依存するグループに新しい観測値を配置しようとするプログラムです。 カスケード分類器は、複数の分類器の連結を使用してこれを実行しようとします。後続の各分類器は、前の出力を追加情報として使用し、分類を大幅に改善します。

5.1. Haarの特徴

OpenCVでの顔検出は、Haar機能ベースのカスケード分類器によって行われます。

Haar機能は、画像のエッジと線を検出するために使用されるフィルターです。フィルターは、白黒の色の正方形として表示されます。

これらのフィルターは、ピクセルごとに画像に複数回適用され、結果は単一の値として収集されます。 この値は、黒い四角の下のピクセルの合計と白い四角の下のピクセルの合計の差です。

6. 顔検出

一般に、カスケード分類器は、何でも検出できるように事前にトレーニングする必要があります。

トレーニングプロセスは長くなる可能性があり、大きなデータセットが必要になるため、OpenCVが提供する事前トレーニング済みモデルの1つを使用します。 このXMLファイルをリソースフォルダーに配置して、簡単にアクセスできるようにします。

顔を検出するプロセスをステップスルーしてみましょう。

赤い長方形で輪郭を描いて顔を検出しようとします。

開始するには、ソースパスからMat形式で画像を読み込む必要があります。

Mat loadedImage = loadImage(sourceImagePath);

次に、 MatOfRect オブジェクトを宣言して、見つけた顔を保存します。

MatOfRect facesDetected = new MatOfRect();

次に、認識を行うためにCascadeClassifierを初期化する必要があります。

CascadeClassifier cascadeClassifier = new CascadeClassifier(); 
int minFaceSize = Math.round(loadedImage.rows() * 0.1f); 
cascadeClassifier.load("./src/main/resources/haarcascades/haarcascade_frontalface_alt.xml"); 
cascadeClassifier.detectMultiScale(loadedImage, 
  facesDetected, 
  1.1, 
  3, 
  Objdetect.CASCADE_SCALE_IMAGE, 
  new Size(minFaceSize, minFaceSize), 
  new Size() 
);

上記のパラメータ1.1は、使用するスケールファクターを示し、各画像スケールで画像サイズがどれだけ縮小されるかを指定します。 次のパラメータ3は、 minNeighborsです。これは、候補の長方形を保持するために必要なネイバーの数です。

最後に、面をループして結果を保存します。

Rect[] facesArray = facesDetected.toArray(); 
for(Rect face : facesArray) { 
    Imgproc.rectangle(loadedImage, face.tl(), face.br(), new Scalar(0, 0, 255), 3); 
} 
saveImage(loadedImage, targetImagePath);

ソース画像を入力すると、すべての面が赤い長方形でマークされた出力画像を受け取るはずです。

7. OpenCVを使用したカメラへのアクセス

これまで、ロードされた画像で顔検出を実行する方法を見てきました。 しかし、ほとんどの場合、リアルタイムで実行したいと考えています。 そのためには、カメラにアクセスする必要があります。

ただし、カメラからの画像を表示できるようにするには、明らかなものとは別に、カメラといういくつかの追加機能が必要です。 画像を表示するには、JavaFXを使用します。

ImageView を使用してカメラが撮影した写真を表示するため、OpenCVマットをJavaFX画像変換する方法が必要です。

public Image mat2Img(Mat mat) {
    MatOfByte bytes = new MatOfByte();
    Imgcodecs.imencode("img", mat, bytes);
    InputStream inputStream = new ByteArrayInputStream(bytes.toArray());
    return new Image(inputStream);
}

ここでは、 Mat をバイトに変換してから、バイトをImageオブジェクトに変換しています。

まず、カメラビューをJavaFXStageにストリーミングします。

それでは、loadSharedメソッドを使用してライブラリを初期化しましょう。

OpenCV.loadShared();

次に、ステージVideoCaptureImageViewで作成して、Imageを表示します。

VideoCapture capture = new VideoCapture(0); 
ImageView imageView = new ImageView(); 
HBox hbox = new HBox(imageView); 
Scene scene = new Scene(hbox);
stage.setScene(scene); 
stage.show();

ここで、0は使用したいカメラのIDです。 また、画像の設定を処理するには、 AnimationTimerを作成する必要があります。

new AnimationTimer() { 
    @Override public void handle(long l) { 
        imageView.setImage(getCapture()); 
    } 
}.start();

最後に、 getCapture メソッドは、マットから画像への変換を処理します。

public Image getCapture() { 
    Mat mat = new Mat(); 
    capture.read(mat); 
    return mat2Img(mat); 
}

これで、アプリケーションはウィンドウを作成し、カメラからimageViewウィンドウにビューをライブストリーミングする必要があります。

8. リアルタイムの顔検出

最後に、すべてのドットを接続して、リアルタイムで顔を検出するアプリケーションを作成できます。

前のセクションのコードは、カメラから画像を取得してユーザーに表示する役割を果たします。 これで、 CascadeClassifier クラスを使用して、取得した画像を処理してから画面に表示するだけです。

getCapture メソッドを変更して、顔検出も実行してみましょう。

public Image getCaptureWithFaceDetection() {
    Mat mat = new Mat();
    capture.read(mat);
    Mat haarClassifiedImg = detectFace(mat);
    return mat2Img(haarClassifiedImg);
}

ここで、アプリケーションを実行すると、顔が赤い長方形でマークされているはずです。

カスケード分類器の欠点もわかります。顔をどの方向にも向けすぎると、赤い長方形が消えます。 これは、顔の正面を検出するためだけにトレーニングされた特定の分類器を使用したためです

9. 概要

このチュートリアルでは、JavaでOpenCVを使用する方法を学びました。

事前にトレーニングされたカスケード分類器を使用して、画像上の顔を検出しました。 JavaFXの助けを借りて、カメラからの画像を使用して分類器にリアルタイムで顔を検出させることができました。

いつものように、すべてのコードサンプルはGitHubにあります。