最近流行のstreamlitを使って簡単な画像処理アプリを作ったので備忘録

デモページ

ソースページ (Github)

アプリデモ

パッケージ管理にはpipenvを使用。

顔検出モデル

opencv/data/haarcascades at master · opencv/opencv

かなり昔に書いたのコードを流用した。

streamlit どうやって Heroku にデプロイするの?

Procfileに以下のように記載。

web: streamlit run --server.enableCORS false --server.port $PORT app.py

Hosting streamlit on Heroku - Questions - Streamlit

より詳細は以下記事参照。

Streamlit を Heroku にデプロイする。

Python ファイル分割しないの?

streamlit の面白いところは以下のようにcloneとかなしに直接ローカル環境で試すことができる点だと思う。

$ streamlit run https://github.com/tomowarkar/stapp/blob/master/app.py

これをするにあたってできるだけシングルページにまとめた方がいいかな(分けるにしてもそのあたりを考えつつ)という意図。

Heroku で OpenCV が使えない

何も知らずに Heroku で OpenCV をつかおうとすると以下のエラーが…

...
import cv2
from .cv2 import *
...
ImportError: libSM.so.6: cannot open shared object file: No such file or directory

Heroku のビルドパックに heroku-buildpack-apt を追加

heroku/heroku-buildpack-apt

$ heroku buildpacks:add --index 1 https://github.com/heroku/heroku-buildpack-apt

Aptfile を追加し、OpenCV の依存関係を入れる

Aptfileを作成し、 libsm6, libxrender1, libfontconfig1, libice6 の 4 つのライブラリを 1 行毎に記載

libsm6
libxrender1
libfontconfig1
libice6

python - How to use OpenCV with Heroku - Stack Overflow

heroku で OpenCV を利用する [Python3] - Qiita

Heroku の無料枠を最大限使いたい

Free Dyno Hours | Heroku Dev Center

Heroku のアプリはアクセスや内部実行がなければ 30 分間でスリープ状態に入り、スリープからの起動には少し時間がかかる。

無料枠を超えない範囲でアプリをスリープさせないようにトラフィックを送ってやればいい。

定期実行できればなんでもいいが、コードと一緒に管理できるGitHub Actionsが個人的に扱いやすいのでこれで行う。

GitHub Actions について - GitHub ヘルプ

GitHub Actions で 20 分毎にアプリにトラフィックを送る

Procfile と 同階層に.github/workflows/heroku.ymlを作成する。

heroku.ymlに以下のように記載。

name: Heroku Alarm Clock

on:
  schedule:
    - cron: "*/20 * * * *"

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Alarm
        run: curl YOUR_HEROKU_APP_URL

cron を 20 分おきに実行し、curl でアプリにアクセスするというだけの内容だ。これだとデフォルトの無料枠は超えてしまうので好きに変更されたし。

ちなみに Heroku の個人アカウントにはデフォルトで毎月 550 時間の無料枠が割り当てられていて(アカウントに紐づく全てのアプリの総起動時間と思われる。)、超過すると月の残りは強制スリープになるので注意。

できなかったこと

クライアント側のパラメータに応じてその場で動画を生成し、表示すること。

/tmpfilenameを作成しそれを元にst.video(video)で表示する。

fourcc = cv2.VideoWriter_fourcc(*"avc1")
video = cv2.VideoWriter(filename, fourcc, fps, (width, height))

steps = fps * 10
for i in range(steps):
    img = np.random.randint(0, 256, (width, height, 3), np.uint8)
    video.write(img)

video.release()

残骸

ローカルでは動作したけど、heroku上でうまく/tmpディレクトリにファイルを作れず動作しなかった。