AWS Lambda (Python 3.7) 向けに OpenCV 3.4 & NumPy の Lambda Layers をビルドする

## はじめに

AWS Lambda で OpenCV を使用するにはいくつかの方法がある。

- Lambda 関数のデプロイパッケージに OpenCV を含める
- OpenCV 用の [Lambda Layers](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html) をビルドして使用する

前者の「Lambda 関数のデプロイパッケージに OpenCV を含める」方法の場合は、Lambda のデプロイパッケージサイズの上限 50 MB のうち、そのほとんどが OpenCV のために使用されてしまう。後者の [Lambda Layers](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html) を使用すると、ライブラリをデプロイパッケージに含める必要がなくなり、パッケージのサイズを小さくすることができる。また、ライブラリを外部化することで、OpenCV のバージョンアップなども行いやすくなると考えられる。

そこで本稿では、AWS Lambda (Python 3.7) 向けに OpenCV 3.4 の Lambda Layers をビルドし、実際にビルドした Layers を使用して OpenCV 3.4 を Lambda で動作させるまでの手順をまとめた。

## OpenCV 3.4 Layer の作成

https://qiita.com/hajimen/items/4419ba0182fc6d008896 を参考に Docker を使用したビルドを行なったが、Python 3.7 と OpenCV 3.4.7 の組み合わせでは、先の記事の方法では上手くいかなかった。そこで、以下のような `Dockerfile` を作成した。

```dockerfile:Dockerfile
FROM lambci/lambda:build-python3.7

ENV OPENCV_VERSION 3.4.7

RUN curl -fsSLO https://github.com/opencv/opencv/archive/$OPENCV_VERSION.tar.gz \
  && tar -zxf $OPENCV_VERSION.tar.gz \
  && mv opencv-$OPENCV_VERSION opencv

RUN curl -fsSLO https://github.com/opencv/opencv_contrib/archive/$OPENCV_VERSION.tar.gz \
  && tar -zxf $OPENCV_VERSION.tar.gz \
  && mv opencv_contrib-$OPENCV_VERSION opencv_contrib

RUN yum install -y cmake3

RUN pip install --upgrade pip && pip install numpy

RUN mkdir opencv/build \
  && cd opencv/build \
  && cmake3 \
    -DBUILD_SHARED_LIBS=NO \
    -DCMAKE_BUILD_TYPE=RELEASE \
    -DCMAKE_INSTALL_PREFIX=../../python \
    -DOPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
    -DPYTHON3_EXECUTABLE=/var/lang/bin/python .. \
  && make install

RUN find python/lib -name *.so | xargs -n 1 strip -s

RUN zip -ry9 cv2.zip python/lib

CMD cp cv2.zip /share
```

同じ要領で `docker-compose.yml` も作成。

```yml:docker-compose.yml
version: '2'
services:
  app:
    build:
      context: .
    volumes:
      - .:/share
```

この 2 個のファイルが存在するディレクトリで以下のコマンドを順に実行すると、カレントディレクトリに `cv2.zip` が生成される。

```bash
docker-compose build
docker-compose up
```

生成された `cv2.zip` を AWS Lambda マネジメントコンソールから Layers としてアップロードすれば完了。

### 動作確認

以下の Lambda 関数を作成し、AWS により用意されているレイヤー AWSLambda-Python37-SciPy1x（これに NumPy が含まれている）および上で作成した OpenCV 3.4 のレイヤーを使用するように設定し、実行した。

```python
import numpy as np
import cv2

def lambda_handler(event, context):
    print(cv2.__version__)
```

その結果、以下のようなエラーが発生した。

```
[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': numpy.core.multiarray failed to import
```

どうやら NumPy のバージョンが古い場合に発生するエラーらしい。そこで NumPy の Layer も自作することに。

## NumPy 1.17 Layer の作成

先ほどと同じ要領で以下の `Dockerfile` を作成した。

```dockerfile:Dockerfile
FROM lambci/lambda:build-python3.7

RUN pip install --upgrade pip && pip install -t python numpy

RUN zip -ry9 numpy.zip python

CMD cp numpy.zip /share
```

このあとは OpenCV のときと同様の手順を実行すればカレントディレクトリに `numpy.zip` が生成されるので、これを AWS Lambda マネジメントコンソールから Layers としてアップロードすれば完了。

### 動作確認

以下の Lambda 関数を作成し、自作の 2 つのレイヤーを使用するように設定し、実行した。

```python
import numpy as np
import cv2

def lambda_handler(event, context):
    print(cv2.__version__)
```

その結果、以下の出力を得た。

```
3.4.7
```

OpenCV のバージョンが出力されていることから、AWS Lambda (Python 3.7) で OpenCV 3.4 が正常に動作したことが確認できた。