AWS SDK for C++を使ってS3にある画像をOpenCVで操作する

AWS SDK for C++を使ってS3にある画像を取得して、OpenCVで操作してみようと思います。
https://github.com/awslabs/aws-sdk-cpp
OSはCentoOS7.1です。

#環境構築

OpenCVとAWS SDK for C++をインストールします。

##OpenCVのインストール

まずはOpenCVをインストールします。折角なので3.0.0を入れましょう。
必要なものをあらかじめ入れておきます。

```bash
$ sudo yum install -y gcc gcc-c++ cmake # コンパイルに必要
$ sudo yum install -y gtk3 gtk3-devel # GTKがないとウィンドウを表示できないよ
```

OpenCVを`/opt/opencv`にインストールします。

```bash
$ curl -o opencv-3.0.0.tar.gz https://codeload.github.com/Itseez/opencv/tar.gz/3.0.0
$ tar xvf opencv-3.0.0.tar.gz
$ mkdir opencv-3.0.0/build
$ cd opencv-3.0.0/build/
$ cmake -DCMAKE_INSTALL_PREFIX=/opt/opencv ..
$ make -j4
$ sudo make install
$ echo '/opt/opencv/lib/' | sudo tee -a /etc/ld.so.conf
$ sudo ldconfig
```

これでOpenCVが入りました。

### PKG_CONFIG_PATHの設定

さて、OpenCVを使ったコードをコンパイルするとき、ヘッダファイルが何処にあるのか、ライブラリが何処にあるのかを指定する必要があります。これらをリストアップしてくれるのが`pkg-config`というコマンドです。
このコマンドが使えるように設定します。

```bash
$ echo 'export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/opt/opencv/lib/pkgconfig' >> .bashrc
$ exec -l $SHELL
$ pkg-config --libs --cflags opencv
-I/opt/opencv/include/opencv -I/opt/opencv/include  -L/opt/opencv/lib -lopencv_calib3d -lopencv_core -lopencv_features2d -lopencv_flann -lopencv_hal -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_shape -lopencv_stitching -lopencv_superres -lopencv_video -lopencv_videoio -lopencv_videostab
```

`pkg-config --libs --cflags opencv`を実行すると動作を確認できます。

## AWS SDK for C++のインストール

次はAWS SDK for C++のインストールです。実は少々てこずりました。
まずはgithubからダウンロードしてきます。

```bash
$ curl -LkO https://github.com/awslabs/aws-sdk-cpp/archive/master.zip
$ sudo yum install -y unzip # unzipがなかったら入れましょう
$ unzip master.zip
```

READMEを読むと`libcurl`と`libopenssl`が必要そうなので、入れます。

```bash
$ sudo yum install -y libcurl-devel openssl-devel
```

さて、`cmake`と行きたいところですが・・・

```bash
$ mkdir aws-sdk-cpp-master/build
$ cd aws-sdk-cpp-master/build/
$ cmake -DCMAKE_INSTALL_PREFIX=/opt/aws-sdk-cpp ..
```

こんなことを言われてしまい、私は悲しみに包まれました。

```
CMake Error at CMakeLists.txt:16 (cmake_minimum_required):
  CMake 3.2.3 or higher is required.  You are running version 2.8.11


-- Configuring incomplete, errors occurred!
```

### cmakeを入れます

`cmake`のバージョンが低いといわれてしまったので、入れなおします。
ソースからビルドする方法を書いていましたが、yumで入れられることを知ったので、そちらの方法を取ろうと思います。

### cmake 3.4.1をさくっとyumで入れます

リポジトリを追加してcmake 3.4.1を入れます。

```
$ sudo yum install -y epel-release
$ curl -sL https://copr.fedoraproject.org/coprs/g/kdesig/cmake3_EPEL/repo/epel-7/heliocastro-cmake3_EPEL-epel-7.repo | sudo tee /etc/yum.repos.d/heliocastro-cmake3_EPEL-epel-7.repo
$ sudo yum install -y cmake3
```

パスを通します。

```
$ echo 'export PATH=/usr/lib/cmake3/bin/:$PATH' >> ~/.bashrc
$ exec -l $SHELL
$ cmake --version
cmake version 3.4.1

CMake suite maintained and supported by Kitware (kitware.com/cmake).
```

さくっとインストールできました。

### 改めてAWS SDK for C++のインストール

`aws-sdk-cpp-master/build/`に移動して改めて`cmake`からやり直します。

```bash
$ cd ~/aws-sdk-cpp-master/build/
$ cmake -DCMAKE_INSTALL_PREFIX=/opt/aws-sdk-cpp ..
$ make -j4
$ sudo make install
$ echo '/opt/aws-sdk-cpp/lib/linux/intel64/' | sudo tee -a /etc/ld.so.conf
$ sudo ldconfig
```

やっと入りました。

# S3から画像を取得して表示するプログラム

ほいでは環境構築が済んだのでS3から画像を取得して表示するプログラムを書いて実行してみましょう。
https://github.com/awslabs/aws-sdk-cpp
https://aws.amazon.com/jp/blogs/aws/introducing-the-aws-sdk-for-c/
公式ブログやgithubのREADMEを参考にしました。

```cpp:prog.cpp
#include <sstream>
#include <opencv2/opencv.hpp>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/GetObjectRequest.h>

const Aws::String AWS_ACCESS_KEY_ID = "XXXXXXXXXXXXXXXXXXXX";
const Aws::String AWS_SECRET_ACCESS_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

int main(int argc, char** argv) {

    Aws::Client::ClientConfiguration config;
    config.scheme = Aws::Http::Scheme::HTTPS;
    config.connectTimeoutMs = 30000;
    config.requestTimeoutMs = 30000;
    config.region = Aws::Region::AP_NORTHEAST_1;

    Aws::S3::S3Client s3Client(Aws::Auth::AWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), config);
    Aws::S3::Model::GetObjectRequest getObjectRequest;
    getObjectRequest.SetBucket("mybucket");
    getObjectRequest.SetKey("path/to/img.jpg");

    auto getObjectOutcome = s3Client.GetObject(getObjectRequest);
    if (!getObjectOutcome.IsSuccess()) {
        std::cerr << "File download failed from s3 with error " << getObjectOutcome.GetError().GetMessage() << std::endl;
        exit(1);
    }

    std::stringstream ss;
    ss << getObjectOutcome.GetResult().GetBody().rdbuf();
    std::string str = ss.str();
    std::vector<char> vec(str.begin(), str.end());
    cv::Mat img = cv::imdecode(cv::Mat(vec), CV_LOAD_IMAGE_COLOR);
    cv::imshow("window", img);
    cv::waitKey(0);

    return 0;
}
```

`AWS_ACCESS_KEY_ID`や`AWS_SECRET_ACCESS_KEY`、バケットや画像は各自で用意してくださいね。

コンパイルして実行し、目的の画像が表示されれば成功です。

```bash
$ g++ -DAWS_CUSTOM_MEMORY_MANAGEMENT -std=c++11 prog.cpp -I/opt/aws-sdk-cpp/include -L/opt/aws-sdk-cpp/lib/linux/intel64/ -laws-cpp-sdk-core -laws-cpp-sdk-s3 `pkg-config --libs --cflags opencv`
$ ./a.out
```

## プログラムとコンパイルオプションについて

よく分かっていないところもありますが、自分なりの理解を書いてみます。

### コンパイルオプションについて
`C++11`を使っているので、`-std=c++11`が必要なことと、ヘッダとライブラリの場所を指定するために`-I/opt/aws-sdk-cpp/include -L/opt/aws-sdk-cpp/lib/linux/intel64/`が必要なことは別に問題ないですね。
AWS SDK for C++では独自のメモリ管理システムを使っている(?)んですかね。`-DAWS_CUSTOM_MEMORY_MANAGEMENT `こちらのオプションが必要です(READMEを読むと`cmake`で`-DCUSTOM_MEMORY_MANAGEMENT=1`を指定しろ、みたいに書いてあるんですが、指定すると何か変わるのかよくわかりませんでした)。

### プログラムについて
まず、必要なヘッダをインクルードするのと、アクセスキーとシークレットキーを定義してます。
`Aws::Client::ClientConfiguration config`の`connectTimeoutMs`や`requestTimeoutMs `は、 https://github.com/awslabs/aws-sdk-cpp#using-a-service-client ここをパクりました。`region`に`Aws::Region::AP_NORTHEAST_1`を指定したのは、目的のバケットが東京リージョンにあるからです。

んで、そのあとは公式ブログを真似して`Aws::S3::S3Client s3Client`と`GetObjectRequest getObjectRequest;`を作って画像を取得します。

画像が取得ができたら`getObjectOutcome.GetResult().GetBody().rdbuf();` これでunderlying bufferにアクセスできます。ただ、OpenCVでこの画像を扱うためには`cv::Mat`に変換する必要があるんですよね。以下の方法でやります。


```cpp
    std::stringstream ss;
    ss << getObjectOutcome.GetResult().GetBody().rdbuf();
    std::string str = ss.str();
    std::vector<char> v(str.begin(), str.end());
    cv::Mat img = cv::imdecode(cv::Mat(v), CV_LOAD_IMAGE_COLOR);
```

`std::stringstream`→`std::string`→`std::vector<char>`→`cv::Mat`という順番で変換すればOKです。自分でもなんだか回りくどいような気がしているのでもっといい方法がないか、と思っています(`std::vector<char> v(ss.str.begin(), ss.str.end())` ってやれば良くね?と最初思いましたがダメでした。 http://stackoverflow.com/a/12267277 )。

# おしまい

いろいろ理解不足なところもありますが、これでS3にある画像をOpenCVで操作できるようになりました。