画像処理をする
ここからOpenCVのカスケード分類機を用いて顔認識をしていきます.
本記事ではOpenCVのバージョンは2.4.13で行います.
まずは画像処理を行うための下準備をしましょう.
BufferedImageをMatに変換するメソッド
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に変換するメソッド
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のファイルに追記するところにここから,ここまでと書いてあるのでその箇所を追記してください.
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から1フレーム分のデータを読み取り,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();
}
}