前回の記事で、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]
簡単ですね!
ソースはこちら。これをベースに色々面白いことができそうです。
追記
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にもデプロイ可能です!