IK.AM

@making's tech note


Spring BootとOpenCV(JavaCV)で簡単画像処理サーバー 【Docker対応

🗃 {Programming/Java/org/springframework/boot}
🏷 Docker 🏷 Java 🏷 JavaCV 🏷 OpenCV 🏷 Spring 🏷 Spring Boot 🏷 Spring MVC 
🗓 Updated at 2015-01-29T16:42:29Z  🗓 Created at 2015-01-29T16:42:29Z   🌎 English Page

前回の記事で、JavaCVを使って画像中の顔をDukeに変換する処理を書きました。 今回はSpring Bootを使って、この処理をサーバー化しましょう。

ほんの100行程度で画像処理サーバーが書けます。

package facedukuer;

import org.bytedeco.javacpp.opencv_core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.BufferedImageHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.Part;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.function.BiConsumer;

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

@SpringBootApplication
@RestController
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    @Autowired
    FaceDetector faceDetector;

    @Bean
    BufferedImageHttpMessageConverter bufferedImageHttpMessageConverter() {
        return new BufferedImageHttpMessageConverter();
    }

    @RequestMapping(value = "/")
    String hello() {
        return "Hello World!";
    }

    @RequestMapping(value = "/duker", method = RequestMethod.POST)
    BufferedImage duker(@RequestParam Part file) throws IOException {
        Mat source = Mat.createFrom(ImageIO.read(file.getInputStream()));
        faceDetector.detectFaces(source, FaceTranslator::duker);
        return source.getBufferedImage();
    }
}

@Component
class FaceDetector {
    @Value("${classifierFile:classpath:/haarcascade_frontalface_default.xml}")
    File classifierFile;

    CascadeClassifier classifier;

    Logger log = LoggerFactory.getLogger(FaceDetector.class);

    public void detectFaces(Mat source, BiConsumer<Mat, Rect> detectAction) {
        Rect faceDetections = new Rect();
        classifier.detectMultiScale(source, faceDetections);
        int numOfFaces = faceDetections.limit();
        log.info("{} faces are detected!", numOfFaces);
        for (int i = 0; i < numOfFaces; i++) {
            Rect r = faceDetections.position(i);
            detectAction.accept(source, r);
        }
    }

    @PostConstruct
    void init() throws IOException {
        if (log.isInfoEnabled()) {
            log.info("load {}", classifierFile.toPath());
        }
        this.classifier = new CascadeClassifier(classifierFile.toPath()
                .toString());
    }
}

class FaceTranslator {
    public static void duker(Mat source, Rect r) {
        int x = r.x(), y = r.y(), h = r.height(), w = r.width();
        // make the face Duke
        // black upper rectangle
        opencv_core.rectangle(source, new Point(x, y), new Point(x + w, y + h
                / 2), new Scalar(0, 0, 0, 0), -1, CV_AA, 0);
        // white lower rectangle
        opencv_core.rectangle(source, new Point(x, y + h / 2),
                new Point(x + w, y + h), new Scalar(255, 255, 255, 0), -1,
                CV_AA, 0);
        // red center circle
        opencv_core.circle(source, new Point(x + h / 2, y + h / 2),
                (w + h) / 12, new Scalar(0, 0, 255, 0), -1, CV_AA, 0);
    }
}

前回BufferedImageを使ったのがポイントで、Spring MVCではBufferedImageHttpMessageConverterを使うことで簡単に画像を出力(ダウンロード)できます。また入力はServletのjavax.servlet.http.Partが使えます。

実行はいつもの通り、mvn spring-boot:runで、画像の送信(アップロード)はこんな感じ。

$ curl -v -F 'file=@lena.png' localhost:8080/duker > foo.png
> POST /duker HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: */*
> Content-Length: 620836
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=----------------------------8762db9f53c4
>
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: image/png;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 29 Jan 2015 15:48:58 GMT
<
{ [data not shown]

簡単ですね!

image

ソースはこちら。これをベースに色々面白いことができそうです。

追記

Dockerに対応させました。

boot2dockerで起動する方法

[host] $ mvn clean package -P linux-x86_64
[host] $ scp target/app.zip docker@`boot2docker ip`:~/
(default password is tcuser)

boot2docker sshでboot2dockerのVMに入ります。

[boot2docker] $ mkdir app
[boot2docker] $ unzip app.zip -d app
[boot2docker] $ docker build -t faceduker app
[boot2docker] $ docker run -p 8080:8080 -t faceduker

サービスが起動しました。以下のように呼び出します。

[host] $ curl -v -F 'file=@foo.png' `boot2docker ip`:8080/duker.png > foo.png

mvn packageでできたapp.zipはそのままAWS Elastic Beanstalkにもデプロイ可能です


✒️️ Edit  ⏰ History  🗑 Delete