Streamlit×OpenCVでQRコードを読み取る

## この記事の概要

StreamlitとOpenCVを組み合わせることで、Webカメラで撮影した写真内のQRコードを読み取る機能を手軽に実装することができます。  

## サンプルコード

サンプルコードは以下の通りです。  

```py:st_qrcode.py
import streamlit as st
import cv2
import numpy as np

# QRコード検出器をロード
@st.cache_resource
def load_qrcode_detector():
    qrcode_detector = cv2.QRCodeDetector()
    return qrcode_detector

# カメラから画像を取得
img_file_buffer = st.camera_input("ボタンを押してQRコードをスキャンしてください", label_visibility="collapsed")

if img_file_buffer is not None:
    # OpenCV形式の画像データに変換する
    bytes_data = img_file_buffer.getvalue()
    cv2_img = cv2.imdecode(np.frombuffer(bytes_data, np.uint8), cv2.IMREAD_COLOR)

    # QRコードを検出
    qrcode_detector = load_qrcode_detector()
    detected, decoded_info, points, _ = qrcode_detector.detectAndDecodeMulti(cv2_img)

    if detected:
        # QRコードの情報が読み取れているかどうか確認
        count = 0
        for data in decoded_info:
            if data == "":
                continue
            count += 1
        if count == 0:
            st.warning("QRコードが見つかりませんでした")
            st.stop()

        st.caption("QRコードに含まれている情報")
        idx = 0
        for data in decoded_info:
            if data == "":
                continue
            idx += 1
            st.write(f"{idx}: ", data)

        img = cv2.polylines(cv2_img, points.astype(int), True, (0, 255, 0), 3)
        st.caption("QRコードの検出結果")
        st.image(img)
    else:
        st.warning("QRコードが見つかりませんでした")
```

実際に動作するアプリは[こちら](https://st-qrcode-reader.streamlit.app/)です。  

https://st-qrcode-reader.streamlit.app/

![Example](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/4c4d5570-3c67-135b-9d42-6b5779885eac.png)

## ポイント解説

### Webカメラによる撮影

StreamlitではWebカメラによる撮影のために```st.camera_input```が用意されています。  
これはユーザがボタンを押したときに画像を返してくれる関数です。  

```py
img_file_buffer = st.camera_input("ボタンを押してQRコードをスキャンしてください", label_visibility="collapsed")
```

### QRコードの読み取り

OpenCVではQRコードを読み取るために```cv2.QRCodeDetector```という検出器が準備されています。  
この検出器にOpenCV形式の画像データを渡すことで、QRコードを検出して、QRコードに含まれる情報を読み取ることができます。  

```py
detected, decoded_info, points, _ = qrcode_detector.detectAndDecodeMulti(cv2_img)
```

- detected: QRコードを検出したかどうか
- decoded_info: QRコードからデコードした文字列。複数のQRコードの同時読み取りに対応していて、文字列の配列が返してくれる
- points: 画像の中のどこからQRコードを検出したかを示す座標

### 画像フォーマットの変換

```st.camera_input```が返す画像をOpenCV形式の画像データに変換する必要があります。  

```py
bytes_data = img_file_buffer.getvalue()
cv2_img = cv2.imdecode(np.frombuffer(bytes_data, np.uint8), cv2.IMREAD_COLOR)
```

詳細は[Stremalitのドキュメント](https://docs.streamlit.io/develop/api-reference/widgets/st.camera_input)を確認してください。  
OpenCV以外に、Pillow、TensorFlow、Torchvision、PyTorchで画像を扱うサンプルが掲載されています。  

### ライブラリのインストール

必要なライブラリは以下の通りです。  

```sh
pip install streamlit opencv-python numpy
```

Streamlit Community Cloudのようなデスクトップがない環境だとOpenGLなどが使えないため、以下のように別のライブラリを用いる必要があるようです。  

```sh
pip install streamlit opencv-python-headless numpy
```

## まとめ

StreamlitでWebカメラによる撮影ができるようになったことで、OpenCVの豊富な機能を活かした色んなアプリが作れそうですね！  
今回はシンプルなQRコードリーダーを実装してみました。  

ちなみに、今回はユーザがボタンを押したときにQRコードを読み取るというものでしたが、Webカメラに写っているものをリアルタイムにチェックして自動的にQRコードを読み取りたいというときは[streamlit-webrtc](https://github.com/whitphx/streamlit-webrtc)という3rdパーティーコンポーネントを利用するのがよさそうです。  
ぜひチャレンジしてみてください！