---
title: JavaCVで画像処理
tags: ["Java", "JavaCV", "OpenCV"]
categories: ["Programming", "Java", "org", "bytedeco", "javacpp", "opencv"]
date: 2015-01-29T14:51:45Z
updated: 2015-01-29T14:51:45Z
---

JavaでOpenCVが使いたくなったので調べてみた。

次の2通りあるっぽい

* [OpenCVの公式Javaラッパー](http://docs.opencv.org/doc/tutorials/introduction/desktop_java/java_dev_intro.html)
* [JavaCV](https://github.com/bytedeco/javacv)

セットアップの手軽さ(Mavenだけで使える)を重視し、JavaCVを使う。
OpenCVはセットアップが面倒なイメージがあったが、JavaCV使うとさくっと使える。

JavaCVは[JavaCPP](https://github.com/bytedeco/javacpp)というC++のソースから自動生成してできるブリッジのようなもので作られているみたい。

### pom.xmlの設定

ネイティブライブラリを選ぶために、`classifier`を指定する必要があるが、Mavenのprofile機能で切り替えられる。

``` xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>hello-javacv</artifactId>
    <packaging>jar</packaging>
    <version>1.0.0-SNAPSHOT</version>
    <name>hello-javacv</name>
    <properties>
        <javacv.version>0.10</javacv.version>
        <opencv.version>2.4.10-${javacv.version}</opencv.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>${javacv.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv</artifactId>
            <version>${opencv.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv</artifactId>
            <version>${opencv.version}</version>
            <classifier>${classifier}</classifier>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <profile>
            <id>macosx-x86_64</id>
            <activation>
                <os>
                    <family>mac</family>
                    <arch>x86_64</arch>
                </os>
            </activation>
            <properties>
                <classifier>macosx-x86_64</classifier>
            </properties>
        </profile>
        <profile>
            <id>linux-x86_64</id>
            <activation>
                <os>
                    <family>unix</family>
                    <arch>amd64</arch>
                </os>
            </activation>
            <properties>
                <classifier>linux-x86_64</classifier>
            </properties>
        </profile>
        <profile>
            <id>windows-x86_64</id>
            <activation>
                <os>
                    <family>windows</family>
                    <arch>amd64</arch>
                </os>
            </activation>
            <properties>
                <classifier>windows-x86_64</classifier>
            </properties>
        </profile>
        <profile>
            <id>windows-x86</id>
            <activation>
                <os>
                    <family>windows</family>
                    <arch>x86</arch>
                </os>
            </activation>
            <properties>
                <classifier>windows-x86</classifier>
            </properties>
        </profile>
    </profiles>
</project>
```

### 簡単な画像処理プログラミング

とりあえず画像をリサイズ(1/2)する例

``` java
package com.example;

import java.net.URISyntaxException;
import java.nio.file.Paths;

import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_highgui.*;
import static org.bytedeco.javacpp.opencv_imgproc.*;

public class App {
    public static void main(String[] args) throws URISyntaxException {
        String filepath = args.length > 0 ? args[0] : Paths.get(
                App.class.getResource("/lena.png").toURI()).toString();
        resize(filepath);
    }

    public static void resize(String filepath) {
        IplImage source = cvLoadImage(filepath, CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
        System.out.println("path = " + filepath);
        System.out.println("image = " + source);
        if (source != null) {
            IplImage dest = cvCreateImage(cvSize(source.width() / 2, source.height() / 2), source.depth(), source.nChannels());
            cvResize(source, dest, CV_INTER_NN);
            cvSaveImage("half-" + Paths.get(filepath).getFileName().toString(), dest);
            cvReleaseImage(source);
            cvReleaseImage(dest);
        }
    }
}
```

定番のlenaで実行

``` console
$ mvn compile exec:java -Dexec.mainClass=com.example.App -Dexec.args=lena.png
```

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/428eb7c4-e88d-2295-c43b-79d98909384c.png)

できた。

[ソース](https://github.com/making/hello-cv)

### 顔検出

OpenCV定番の顔検出をやってみる。今度はOpenCV2系のC++用APIを使ってみる。


``` java
package com.example;

import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_highgui.*;
import static org.bytedeco.javacpp.opencv_objdetect.*;

import java.net.URISyntaxException;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) throws URISyntaxException {
        String filepath = args.length > 0 ? args[0] : Paths.get(
                App.class.getResource("/lena.png").toURI()).toString();
        faceDetect(filepath);
    }

    public static void faceDetect(String filepath) throws URISyntaxException {
        String classifierName = Paths.get(
                App.class.getResource("/haarcascade_frontalface_default.xml")
                        .toURI()).toString();
        CascadeClassifier faceDetector = new CascadeClassifier(classifierName);
        System.out.println("load " + filepath);
        Mat source = imread(filepath);
        Rect faceDetections = new Rect();
        faceDetector.detectMultiScale(source, faceDetections);
        int numOfFaces = faceDetections.limit();
        System.out.println(numOfFaces + " faces are detected!");
        for (int i = 0; i < numOfFaces; i++) {
            Rect r = faceDetections.position(i);
            rectangle(source, new Point(r.x(), r.y()), new Point(r.x()
                    + r.width(), r.y() + r.height()), new Scalar(0, 0, 255, 0));
        }
        imwrite("faces.png", source);
    }
}
```

実行

``` console
$ mvn compile exec:java -Dexec.mainClass=com.example.App -Dexec.args=lena.png
```

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/03fc19c2-8829-686d-132f-c9eee3b0bf1b.png)

できた。

[ソース](https://github.com/making/hello-cv/tree/facedetect)

### Duke化
ちょっと遊んでみましょう。
赤枠で囲む代わりにDukeっぽくしてみます。

あと、`org.bytedeco.javacpp.opencv_core.Mat`クラスを使って直接読み書きするのはJavaプログラミングとしては応用し辛くなるので、
`java.awt.image.BufferedImage`を介するようにする。

``` java
package com.example;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;

import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_objdetect.*;

public class App {
    public static void main(String[] args) throws URISyntaxException, IOException {
        String filepath = args.length > 0 ? args[0] : Paths.get(
                App.class.getResource("/lena.png").toURI()).toString();
        faceDetect(filepath);
    }

    public static void faceDetect(String filepath) throws URISyntaxException, IOException {
        String classifierName = Paths.get(
                App.class.getResource("/haarcascade_frontalface_default.xml")
                        .toURI()).toString();
        CascadeClassifier faceDetector = new CascadeClassifier(classifierName);
        System.out.println("load " + filepath);
        Mat source = Mat.createFrom(ImageIO.read(new File(filepath))); // BufferedImage -> Mat

        Rect faceDetections = new Rect();
        faceDetector.detectMultiScale(source, faceDetections);
        int numOfFaces = faceDetections.limit();
        System.out.println(numOfFaces + " faces are detected!");
        for (int i = 0; i < numOfFaces; i++) {
            Rect r = faceDetections.position(i);
            ducker(source, r);
        }

        BufferedImage image = source.getBufferedImage(); // Mat -> BufferedImage

        try (OutputStream out = Files.newOutputStream(Paths
                .get("duked-faces.png"))) {
            ImageIO.write(image, "png", out);
        }
    }

    public static void ducker(Mat source, Rect r) {
        int x = r.x(), y = r.y(), h = r.height(), w = r.width();
        // make the face Duke
        rectangle(source, new Point(x, y), new Point(x + w, y + h / 2),
                new Scalar(0, 0, 0, 0), -1, CV_AA, 0);
        rectangle(source, new Point(x, y + h / 2), new Point(x + w, y + h),
                new Scalar(255, 255, 255, 0), -1, CV_AA, 0);
        circle(source, new Point(x + h / 2, y + h / 2), (w + h) / 12,
                new Scalar(0, 0, 255, 0), -1, CV_AA, 0);
    }
}
```

実行

``` console
$ mvn compile exec:java -Dexec.mainClass=com.example.App -Dexec.args=lena.png
```

![image](https://qiita-image-store.s3.amazonaws.com/0/1852/c87fb02f-3e6b-a694-d7da-9cfdc2adc5c8.png)

できた。


[ソース](https://github.com/making/hello-cv/tree/duker)

これで色々遊べますね。

[ここのリファレンス](http://book.mynavi.jp/support/pc/opencv2/c3/index.html)がほぼそのまま使えます。ありがたい。
