EV3(leJOS)でカメラの映像をPCに送って顔認識をする③

# 画像処理をする
ここからOpenCVのカスケード分類機を用いて顔認識をしていきます．
本記事ではOpenCVのバージョンは2.4.13で行います．
まずは画像処理を行うための下準備をしましょう．

## BufferedImageをMatに変換するメソッド
```java
    private Mat bufferedImageToMat(BufferedImage image) throws IOException {
    	ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    	ImageIO.write(image,"jpg",byteArrayOutputStream);
    	byteArrayOutputStream.flush();
    	return Highgui.imdecode(new MatOfByte(byteArrayOutputStream.toByteArray()),Highgui.IMREAD_UNCHANGED);
	}
```

## MatをBufferedImageに変換するメソッド
```java
    public BufferedImage Mat2BufferedImage(Mat matrix)throws IOException {
        MatOfByte mob=new MatOfByte();
        Highgui.imencode(".jpg", matrix, mob);
        return ImageIO.read(new ByteArrayInputStream(mob.toArray()));
    }
```

## カスケード分類機で顔認識をする
本記事では，OpenCVについているカスケードファイルを使いました．（haarcascade_frontalface_alt2.xml）
まずはこのファイルのパスを探してください．
CameraFrame.javaのファイルに追記するところに***ここから***,***ここまで***と書いてあるのでその箇所を追記してください．

```java:CameraFrame.java
public class CameraFrame {
    private static final int WIDTH = 160;
    private static final int HEIGHT = 120;
    private static final int NUM_PIXELS = WIDTH * HEIGHT;
    private static final int BUFFER_SIZE = NUM_PIXELS * 2;
    private static final int PORT = 55555;

    private ServerSocket ss;
    private Socket sock;
    private byte[] buffer = new byte[BUFFER_SIZE];
    private BufferedInputStream bis;
    private BufferedImage image;
    private CameraPanel panel = new CameraPanel();
    private JFrame frame;

    //***ここから***
    private String cascadeFilePath = "カスケードファイルのパス";
    private CascadeClassifier cascade;
    //***ここまで***

　　　　  //ServerSocket,Socket,BufferedInputStreamの準備
        public CameraFrame() {

            //***ここから***
            cascade = new CascadeClassifier(cascadeFilePath);
            //***ここまで***

            try {
                ss = new ServerSocket(PORT);
                sock = ss.accept();
                bis = new BufferedInputStream(sock.getInputStream());
            } catch (Exception e) {
                System.err.println("Failed to connect: " + e);
                System.exit(1);
            }
            image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
    }

        //フレームを作成
        public void createAndShowGUI() {
            frame = new JFrame("EV3 Camera View");//JFrameのオブジェクトを作成しタイトルを設定する
            frame.getContentPane().add(panel);//送られてきた画像を描くpanelをframeに追加する．
            frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));//フレームサイズの指定
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//フレームを閉じるときの動作の指定
            //windowをクローズ処理中のときの動作
            frame.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {    
                    close();//詳細は下のcloseメソッドを参照
                }
            });
            frame.pack();//frameのサイズをフレーム中のコンポーネントに合わせて調節してくれる
            frame.setVisible(true);//フレームを表示する
        }

        //openしたものをすべてcloseする
        public void close() {
            try {
                if (bis != null)
                    bis.close();
                if (sock != null)
                    sock.close();
                if (ss != null)
                    ss.close();
            } catch (Exception e1) {
                System.err.println("Exception closing window: " + e1);
            }
        }

        //YUVフォーマットをRGBフォーマットへ変換
        private int convertYUVtoARGB(int y, int u, int v) {
            int c = y - 16;
            int d = u - 128;
            int e = v - 128;
            int r = (298 * c + 409 * e + 128) / 256;
            int g = (298 * c - 100 * d - 208 * e + 128) / 256;
            int b = (298 * c + 516 * d + 128) / 256;
            r = r > 255 ? 255 : r < 0 ? 0 : r;
            g = g > 255 ? 255 : g < 0 ? 0 : g;
            b = b > 255 ? 255 : b < 0 ? 0 : b;
            return 0xff000000 | (r << 16) | (g << 8) | b;
        }

        //BufferedInputStreamから１フレーム分のデータを読み取り，imageにRGB形式で格納する．
        public void run() throws IOException {
           while (true) {
               synchronized (this) {
                   try {
                       //bisからデータを読み込む.読み取るデータがまだ入力ストリームに残っているかもしれないのでwhile文を回す．
                       int offset = 0;
                       while (offset < BUFFER_SIZE) {
                           offset += bis.read(buffer, offset, BUFFER_SIZE - offset);
                       }

                       //各ピクセルのyuvフォーマットからrgbに変換してimageへセットする．
                       for (int i = 0; i < BUFFER_SIZE; i += 4) {
                           int y1 = buffer[i] & 0xFF;
                           int y2 = buffer[i + 2] & 0xFF;
                           int u = buffer[i + 1] & 0xFF;
                           int v = buffer[i + 3] & 0xFF;
                           int rgb1 = convertYUVtoARGB(y1, u, v);
                           int rgb2 = convertYUVtoARGB(y2, u, v);
                           image.setRGB((i % (WIDTH * 2)) / 2, i / (WIDTH * 2), rgb1);
                           image.setRGB((i % (WIDTH * 2)) / 2 + 1, i / (WIDTH * 2), rgb2);
                       }
                   } catch (Exception e) {
                       break;
                   }

                   //***ここからOpenCVの処理***
                   Mat mat = bufferedImageToMat(image);//BufferedImageをMatに変換
                   Mat gray = new Mat();//カスケード分類で使うためにはグレー画像に変換する必要があるので
                   Imgproc.cvtColor(mat,gray,Imgproc.COLOR_BGR2GRAY);//グレー画像に変換

                   MatOfRect faces = new MatOfRect();//認識された顔を囲う四角形の座標を格納する変数

                   //cascade.detectMutiScale(Mat src, MatOfRect objects, double scaleFactor, int minNeighbors, int flags, Size minSize, Size maxSize)
                   //Mat src ・・・　顔認識をする対象の画像
                   //Mat objects ・・・　検出された物体の四角形を格納する配列
                   //double scaleFactor ・・・　いわゆる検出の丁寧さ． 1.0より大きい値．
                   //int minNeighbors　・・・　物体かもしれない予測がminNeighbors個以上重なった場合それを物体として識別する．
                   //Size minSize ・・・ 検出される物体の最小サイズ
                   //Size maxSize ・・・ 検出される物体の最大サイズ.特に指定がない場合は`new Size()`だけでよい

                   cascade.detectMultiScale(gray, faces, 1.1, 3, 2, new Size(1,1), new Size());
                   Rect[] facesArray = faces.toArray();
                   //検出された物体を線で囲う．
                   for (Rect faceRect : facesArray) {
                       //Core.rectangle(Mat img, Point pt1, Point pt2, Scalar color, int thickness);
                       //img ・・・ 対象の画像
                       //pt1 ・・・ 四角形の左上の頂点
                       //pt2 ・・・ 四角形の右下の頂点
                       //color ・・・ 線の色．`new Scalar(R,G,B)`
                       //thickness ・・・ 線の太さ．
                       Core.rectangle(mat, faceRect.tl(), faceRect.br(), new Scalar(255,0,0),2);
　　　　　　　　　　　　　　//faceRect.tl()・・・左上の頂点(Top Left)
                       //faceRect.br()・・・右下の頂点(Bottom Right)
                   }
                   //最後にMatからBufferedImageに変換する
                   image = Mat2BufferedImage(mat);
                   //***ここまで***

               }
               //panelの再描画（内部的にCameraPanel#paintComponent()が呼び出される）
               panel.repaint(1);
           }
        }

        //カメラのフレームを描画するJPanelの定義
        class CameraPanel extends JPanel {
            private static final long serialVersionUID = 1L;
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                // imageが更新されている間はpanelに描画さないようにする
                synchronized (CameraFrame.this) {
                    g.drawImage(image, 0, 0, null);
                }
            }
        }

        public static void main(String[] args) throws IOException {
　　　　　　　 //OpenCVを使うためにはこれはマスト！！
            System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
            // コンスタントラクタ
            final CameraFrame cameraFrame = new CameraFrame();

            
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    cameraFrame.createAndShowGUI();
                }
            });
            cameraFrame.run();
        }
}
```