Mac×OpenCV×JavaでWEBカメラに映る顔を認識してみた。

<h2>はじめに</h2>

こんにちは。ちゃむじです。
前回OpenCVの環境構築をしてから、OpenCVが自分の中でちょっとしたマイブームになっていましたので、
今回はWEBカメラに映る顔を認識してみました。
備忘録がてら投稿します。

環境構築は前回の投稿にあるので割愛します。
詳しくは[こちら](https://qiita.com/Chamuji/items/dacf3aa0de17aa6cc883)



<h2>完成ファイル</h2>



```
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;

public class FaceDetection {

    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    private JFrame mFrame;
    private JLabel mJLabel;
    private CascadeClassifier mCascadeClassifier;

    public static void main(String[] args) {
        FaceDetection app = new FaceDetection();
        app.init();
        app.loadCascade();
        app.camLoop(args);
    }

    private void loadCascade() {
        mCascadeClassifier =
         //　なぜか反応しなかったので別の特徴量ファイル使う
         //new CascadeClassifier("resource/haarcascade_frontalface_alt_tree.xml");
        new CascadeClassifier("resource/haarcascade_frontalface_default.xml");

    }


    private void init() {
        mFrame = new JFrame("Webcam OpenCV");
        mFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mFrame.setResizable(false);
        mFrame.setSize(400, 400);
        mJLabel = new JLabel();
        mFrame.add(mJLabel);
        mFrame.setVisible(true);
    }

    private void camLoop(String[] args) {
        Mat mMat = new Mat();
        Image mImage;
        VideoCapture mVideoCapture = new VideoCapture(0);

        mVideoCapture.set(Videoio.CAP_PROP_FRAME_WIDTH, 640);
        mVideoCapture.set(Videoio.CAP_PROP_FRAME_HEIGHT, 480);

        if (mVideoCapture.isOpened()) {
            while (true) {
                mVideoCapture.read(mMat);
                if (!mMat.empty()) {
                    findAndDraw(mMat);
                    mImage = toBufferedImage(mMat);
                    ImageIcon imageIcon = new ImageIcon(mImage, "Webcam frame");
                    mJLabel.setIcon(imageIcon);
                    mFrame.pack();
                } else {
                    System.err.println("Webcam frame empty!");
                    break;
                }
            }
        } else {
            System.err.println("Couldn't open webcam.");
        }
    }

    private BufferedImage toBufferedImage(Mat mMat) {
        int type = BufferedImage.TYPE_BYTE_GRAY;
        if (mMat.channels() > 1) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }

        int bufferSize = mMat.channels() * mMat.cols() * mMat.rows();
        byte[] buffer = new byte[bufferSize];
        mMat.get(0, 0, buffer);
        BufferedImage image = new BufferedImage(mMat.cols(), mMat.rows(), type);
        final byte[] targetPixels =
                ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        System.arraycopy(buffer, 0, targetPixels, 0, buffer.length);

        return image;
    }

    private void findAndDraw(Mat mMat) {

        MatOfRect faceDetections = new MatOfRect();
        mCascadeClassifier.detectMultiScale(mMat, faceDetections);
        for (Rect rect : faceDetections.toArray()) {
            Imgproc.rectangle(mMat, new Point(rect.x, rect.y),
                    new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0));
        }
    }
}

```


<h2>今回使用したライブラリ</h2>

<h3>javax.swing</h3>

http://java.it-manual.com/application/swing.html

<h3>java.awt</h3>

https://www.tohoho-web.com/java/awt.htm

業務でjavaを扱ったことがないからわからないけど、多分javaxもawtも使われてない気がするので
レガシーコードを書いてしまったかも・・・・？


<h2>処理の説明</h2>

<h3>main</h3>

```
    public static void main(String[] args) {
        FaceDetection app = new FaceDetection();
        //初期化
        app.init();
        //loadCascade関数呼び出し
        app.loadCascade();
        //映像表示
        app.camLoop(args);
    }
```

<h3>loadCascade()</h3>

```
//顔の特徴量ファイル呼び出し
private void loadCascade() {
        mCascadeClassifier =
        new CascadeClassifier("resource/haarcascade_frontalface_default.xml");

    }
```

<h3>init()</h3>

```
//JFrame初期化
private void init() {
        mFrame = new JFrame("Webcam OpenCV");
        mFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mFrame.setResizable(false);
        mFrame.setSize(400, 400);
        mJLabel = new JLabel();
        mFrame.add(mJLabel);
        mFrame.setVisible(true);
    }
```

<h3>camLoop</h3>

```
//Webカメラから映像を取得・映像幅・高さを設定し、カメラが終了するまでループ
    //映像データが存在する場合に画像データに変換し、顔の部分に枠線を引く関数を呼び出す
    private void camLoop(String[] args) {
        Mat mMat = new Mat();
        Image mImage;

        //https://pystyle.info/opencv-videoio/#outline__2
        VideoCapture mVideoCapture = new VideoCapture(0);

        //取得した映像の横幅と高さを設定
        mVideoCapture.set(Videoio.CAP_PROP_FRAME_WIDTH, 640);
        mVideoCapture.set(Videoio.CAP_PROP_FRAME_HEIGHT, 480);

        //カメラ映像が開始しているか判定
        if (mVideoCapture.isOpened()) {
            while (true) {
                mVideoCapture.read(mMat);
                if (!mMat.empty()) {
                    findAndDraw(mMat);
                    mImage = toBufferedImage(mMat);
                    ImageIcon imageIcon = new ImageIcon(mImage, "Webcam frame");
                    mJLabel.setIcon(imageIcon);
                    mFrame.pack();
                } else {
                    System.err.println("Webcam frame empty!");
                    break;
                }
            }
        } else {
            System.err.println("Couldn't open webcam.");
        }
    }
```

<h3>toBufferedImage(Mat mMat)</h3>

```
//映像データを画像データとして変換する。
    //javaで画像処理を行うなら絶対に必要なクラス。
    // https://magazine.techacademy.jp/magazine/22471
    private BufferedImage toBufferedImage(Mat mMat) {
        int type = BufferedImage.TYPE_BYTE_GRAY;
        if (mMat.channels() > 1) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }

        int bufferSize = mMat.channels() * mMat.cols() * mMat.rows();
        byte[] buffer = new byte[bufferSize];
        mMat.get(0, 0, buffer);
        BufferedImage image = new BufferedImage(mMat.cols(), mMat.rows(), type);
        final byte[] targetPixels =
                ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        System.arraycopy(buffer, 0, targetPixels, 0, buffer.length);

        return image;
    }

    //顔部分の枠を描画
    private void findAndDraw(Mat mMat) {

        MatOfRect faceDetections = new MatOfRect();
        mCascadeClassifier.detectMultiScale(mMat, faceDetections);
        for (Rect rect : faceDetections.toArray()) {
            Imgproc.rectangle(mMat, new Point(rect.x, rect.y),
                    new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0));
        }
    }
```

```camloop```と```toBuffereadImage```が今回の処理の核となる部分ですね。



<h2>　動作結果</h2>

- 映像が出力されて、顔を認識したら**緑色の枠線**が表示されます。
 **※モザイク機能はないです。チー牛フェイスのためモザイクを入れさせていただきました。**

![mosaic_20221123152246.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487318/25a90f1e-694e-44f4-284a-d098c8776083.png)




<h2>　終わり</h2>

以上で「Mac×OpenCV×JavaでWEBカメラに映る顔を認識してみた」でした。

実装してみて思ったこと
- Javaがそもそも画像処理に強い言語じゃない
- BuffereadImageがちょっと面倒

上記をあんまり考えないでサクサク作れるのがPythonなのかな〜という感じですね。
自己満足って感じでした。

長々と見ていただきありがとうございました。