3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HTTPのPOSTメソッドで送信された画像をOpenCVで操作するApacheモジュール

Posted at

前回、AWS SDK for C++を使ってS3にある画像をOpenCVで操作してみました。今度はHTTPのPOSTメソッドで送信された画像をOpenCVで操作するApacheモジュールを作ってみたくなったので試してみます。

これを作ります

アップロードされた画像の顔を検出して四角で囲んで表示します。
mov.gif

ApacheのモジュールをC++で書きたい

C++でApacheモジュールを書きたいんです。OpenCVの3.0でC言語関数形式のインターフェイスはメンテナンスが終了しているためC++を使うことが推奨されてますし。openCV will drop C API support soon
「ApacheのモジュールってCで書かれてるんじゃない?」って私も思っていましたが、どうやらC++で書けるみたいなんですね。こちらが非常に参考になりました。

試しに一丁モジュールを作って動かし、C++を使ってApacheのモジュールが書ける喜びを噛み締めましょう。

POSTされたデータをパースして画像をcv::Matにいれる

POSTメソッドで送信された画像をOpenCVで操作するApacheモジュールを作るためには、画像をcv::Matにいれてやることになります。
ここの部分が結構手こずったので、詳しく書いてみたいと思います。

libapreq2を使う

調べたところlibapreq2を使うのがPOSTされたデータを楽にパースする近道っぽかったです。なので、入れましょう。

yum install libapreq2-devel

こちらmypostモジュールを参考にC++で再実装を試してみたところ、見事にPOSTデータのパースに成功しました。
公式ドキュメントを読んでもみましたが、libapreq2を使わないとなるとかなり骨が折れそうなので、私は素直に使うことにしました。

POSTされたデータをパースしたい

POSTされたテキストのパースまでは順調に進めたので、

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>imagesender</title>
</head>
<body>
<form action="/imagereceiver" method="post" enctype="multipart/form-data">
  <input type="file" name="image" size="30" /><br />
  <input type="submit" value="upload" />
</form>
</body>
</html>

こんなフォームを作って今度は画像をPOSTして動かしてみよう、と意気込んてみたはいいものの、ここにかなり手こずってしまいました。詳しく書いてみます。

まず考えたのは「POSTされた画像ってどこにあるんだろう?」ということです。これについてはリファレンスを読むことで辿りつけました。
Apache 2.X Filter Moduleを読み、apreq_handle_apache2()を使うことでrequest_recとcommunicateができるapreqのハンドルであるapreq_handle_tが作れることが分かりました。
そして、include/apreq_module.h File Referenceに書いてありますがapreq_body_get()を使えばapreq_param_t構造体のbody paramをapreq_handle_tからフェッチできます。apreq_param_tはメンバとしてuploadを持っています(apreq_param_t Struct Reference)。POSTされた画像はこの中に入っているみたいです。

どうやらapr_bucket_brigade構造体であるuploadの中身をcv::Matに入れられれば良さそうだな、というところまではわかりました。
では、どうやって入れるのかという話ですが、私はこの辺からちんぷんかんぷんでした。いろいろ調べてみました。

apr_bucket_brigadeapr_bucketについて

Apacheモジュールではデータの入出力は双方向循環リストを通して行います。
apr_bucket_brigadeは内部にapr_bucketを持っており、apr_bucketは前後のapr_bucketへのポインタを持っているリング構造です。
apr_bucket_brigadeのnextは先頭のapr_bucketを指しており、そのapr_bucketのnextは次のapr_bucketを指します。どんどん辿っていくと、先頭のapr_bucketを指しているapr_bucket_brigadeに行きつきます。
逆も一緒で、apr_bucket_brigadeのprevは末尾のapr_bucketを指しており、どんどん辿っていくと末尾のapr_bucketを指しているapr_bucket_brigadeに行きつきます。apr_bucket_brigadeが番兵として機能します。

uploadcv::Matに入れる

なので、apr_bucket_brigade構造体であるuploadの中身をcv::Matに入れるコードはこんな感じになりました。


cv::Mat bb2Mat(request_rec *r, apr_bucket_brigade *upload) {

    apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    apreq_brigade_copy(bb, upload);
    std::vector<char> vec;
    for (apr_bucket *e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
        const char *data;
        apr_size_t len;
        if (apr_bucket_read(e, &data, &len, APR_BLOCK_READ) != APR_SUCCESS) {
            throw std::runtime_error("failed to read bucket");
        }
        const char *dup_data = apr_pstrmemdup(r->pool, data, len);
        vec.insert(vec.end(), dup_data, dup_data + len);
        apr_bucket_delete(e);
    }

    cv::Mat ret = cv::imdecode(cv::Mat(vec), CV_LOAD_IMAGE_COLOR);
    if (ret.data == NULL) {
        throw std::runtime_error("buffer is too short or contains invalid data");
    }
    return ret;
}

cv::Matに入れられたらあとはこっちのもんです。好きなようにしましょう。

cv::Matapr_bucket_brigadeに入れる

この画像を表示してやるためには、今度はcv::Matapr_bucket_brigadeに入れる必要があります。
やり方としては、

1.cv::Matをエンコードしてstd::stringにする
2.std::stringapr_bucket_brigadeに入れる

こんな感じになります。imagecv::Matの変数です。

    // 1.cv::Matをエンコードしてstd::stringにする
    std::vector<int> params{CV_IMWRITE_JPEG_QUALITY, 100};
    std::vector<unsigned char> buf;
    cv::imencode(".jpg", image, buf, params);
    std::string data(buf.begin(), buf.end());

    // 2.std::stringをapr_bucket_brigadeに入れる
    apr_bucket *bucket = apr_bucket_pool_create(data.c_str(), data.length(), r->pool, r->connection->bucket_alloc);
    apr_bucket_brigade *bucket_brigate = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bucket_brigate, bucket);

余談

途中、見慣れない単語が出てきたので意味を調べたりしたところ、

  • brigade:【陸海軍, 軍事】 旅団
  • sentinel:歩哨, 哨兵

と、出てきました。ここにきてApacheという名前の戦闘ヘリがあることを思い出し、「なんて軍事的なんだ・・・」と思ったりもしましたが、"bucket brigade"で一つの言葉で意味はバケツリレー、そしてsentinelとは番兵のことでした。

Dockerで動かしてみる

目的のApacheモジュールを書いてみました。
さくっと動かせるように、Docker Hubで公開してみました。こちらです(ちなみにmod_imagereceiverって名前はいまいちだったなーと思います。でもいいや、と思っています)。

以下のコマンドで動かせます。

sudo docker run -dp 8080:80 d9magai/mod_imagereceiver

http://localhost:8080/form.html を開けばファイルアップロードのフォームが表示されるので、顔が映っている画像をアップロードしてみましょう。顔を検出して四角で囲んで表示されると思います。

おわりに

HTTPのPOSTメソッドで送信された画像をOpenCVで操作するApacheモジュールを書けました。またApacheのデータ構造もおぼろげながら理解できたので面白かったです。
そして前回と組み合わせることでPOSTされた画像とS3の画像をOpenCVでなんやかんやするApacheモジュールも書けそうです。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?