Streamlit使ってみた

友達の家に行くと本棚と冷蔵庫の中身をついつい見てしまいます。
最近Streamlitで簡単にウェブアプリを作れると聞いたので、本棚の画像からその人の趣味を検出するアプリを作ってみました。

環境

  • Google Colaboratory
  • Streamlit

完成品

本棚解析
コード

外面のいい本棚を作るため頑張りました。

ロジック

1. 写真からテキストを抽出する

Google Vision APIを借りることにします(参考:GCPの登録とVision APIで物体検出)。
Vision APIのテキスト抽出方法は2種類あります

これらは返ってくる結果の形式が微妙に違います。今回はひとかたまりの文字列の座標(下図の緑色のボックス)が欲しかったので、DOCUMENT_TEXT_DETECTIONを用いました。

2. 検索ワードを決める

このように取得したテキストは、本のタイトル以外にも、著者、出版社、読み取り間違えたテキストなど多数の情報を含んでいます。これを一冊一冊グループにまとめて、タイトルや著者など明瞭な情報のみを書籍検索系のAPIに入れたいと思います。
今回は長方形の形をしているテキストボックスを、それを貫く傾きと切片の値に集約して、この値をk-meansで分類してみました。クラスター数はユーザーに入力してもらうことにします。

クラスター数を自動で推定する方法(X-means法)なども試したのですが、あまり上手く行きませんでした。改めて物体検知をすれば自動化できるかもしれません。

グループ分けした単語の中で一番画像内の面積の大きい単語をタイトルとして推定し、検索ワードとしました。
ちなみにグループ分けした後、全ての単語を検索ワードとして放り込む方法も試したのですが、不必要な単語や精度の悪い解析結果が入っていると検索結果が0件になるケースが散見されたので絞ることにしました。

3. 書籍情報検索APIを叩く

書籍検索APIは以下を試しました。

Google Books APIはStreamlitから叩く上で権限の問題が解決できなかったので、楽天ブックスのAPIを使いました。
必要な列を絞ってデータフレームを表示します。このデータフレームは、アプリ上では最後にcsvとしてダウンロードできるようにしたいと思います。

4. 要約する

楽天APIから本の説明文が返ってくるので、最後にwordcloudを表示します。日本語を表示できるよう、フォントデータが必要なので、IPAexフォントを利用させていただきました。

Streamlitへ

Google Colaboratoryで上記の流れが作れたので、コードをpyファイルにコピペして、Streamlitで動かす準備をしていきます。

データフレームの表示

st.dataframe(st.session_state.book_df)

ダウンロードボタン

st.download_button(label='Download file', data=st.session_state.book_csv, 
    file_name="bookshelf.csv")

あまりに簡単なので説明することがありません。公式ドキュメント読んで下さい。

状態の管理

Streamlitでは、ボタンを押すなどして変数に値が代入されるとページ全体の再読み込みが行われます。今回のケースだと、csvをダウンロードしようとしてボタンを押すと、同じ画像に対してGoogle Vision APIが叩かれてしまいます。
そこで以下サイトを参考に、解析が終わったかどうかのフラグと解析結果をst.session_state内に保持することにします。
【プログラミング】手早くデータを可視化・共有できるstreamlitの使い方
st.session_stateの辞書の中に入った変数は、ページが再読み込みされても保持されます(公式ドキュメント)。これで、解析が終わっている場合はst.session_state内のデータフレームや画像を表示するようになり、解析を行いません。

requirements.txt

colabに入っているパッケージのバージョンを見つつローカル環境を整えたのですが、いくつかのパッケージについてはバージョンの依存関係で怒られたので調整しておきます。
ローカル環境を作った後はpip freezeコマンドでrequirements.txtを作りましたが、Streamlitにデプロイした際に以下のパッケージが使えないと言われたので、エラーメッセージに従って消しておきます。

  • pywin32
  • pywinpty
  • nbconvert

逆に追加するパッケージは以下の通りです。

  • streamlit==1.11.1

鍵の管理

ここまでファイルが揃ったら、Githubにデプロイします。StreamlitのCommunity Cloudのサインインし、レポジトリと連携します。
一個までならプライベートレポジトリと連携したアプリを作成することができるのですが、一般にはAPIへのアクセスキーなどはGitHubには置けないので、APPの設定画面からSecretsに進み、こちらに記述します(公式ドキュメント)。
今回はGoogle Vision APIと楽天ブックス総合検索APIを置くので、以下のように記述してあります。

rakuten_api_id = XXX

google_credentials = '''
{
"type": "service_account",
"project_id": XXX,
"private_key_id": XXX,
"private_key": XXX,
…

}
'''

ここに記述した内容は、st.session_stateと同じように、st.secretsという辞書の中に入ります。例えばGoogle Vision APIの認証情報(json形式)は以下のように呼び出すことができます。

credentials_dict = json.loads(st.secrets["google_credentials"])
client = vision.ImageAnnotatorClient.from_service_account_info(info=credentials_dict)

感想

streamlitの使い勝手のよさには驚きました。ページの再読み込みには若干癖がありますが、直感的にダッシュボードなどが作れそうです。デザインもかわいい。

一方で精度についてはまだ改善の余地があり、記事に使った画像では26冊写っているにも関わらず、正しくタイトルを検出できた本は8冊でした。タイトルの文字列自体は検出できているのですが、タイトルだけをうまく切り出して書籍検索することができていないようです。
画像の中の位置情報や中の自然言語など、利用できる情報が多いので、この辺りのロジックはまだまだ工夫しがいがありそうです。