【簡単】QRコードの作成と読み取り in Python

# ➊：はじめに
Pythonで「**QRコード作成、読み取り**」が、簡単に実現できないかなと思って調べました。

## ・やりたいこと
やりたいことは、以下の3つです。

- PNGのQRコード画像作成
- 画像からのQRコード読み取り
- webカメラからのQRコード読み取り

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/e241d5c8-faef-4a96-ccb7-fb9e1812d516.gif' width=400>

## ・ソリューションまとめ
やりたいことに対する解決策を、以下にまとめます。

|やりたいこと|使用するモジュール|備考|
|:-|:-|:-|
|**QRコード画像作成**|**PyQRCode**|PNG化する場合は**pypng**も必要|
|**QRコード読み取り1 (CV2)**|**opencv-python**|Ver4.3以上で複数読込み可能|
|**QRコード読み取り2 (pyzbar)**|**pyzbar**|Macは**zbar**, Linuxは**libzbar0**も必要|
※本ページの開発環境：win10、Python 3.8.5
ということで、以下、実施していきます。

# ➋：QRコード作成
QRコードの作成は、QRコードジェネレーターである**pyqrcodeモジュール**を使用します。QRコードは、SVG、PNG、およびプレーンテキストとして保存可能なようです。PNGで保存する場合は、別途、**pypngモジュール**を使用します。

### ・インストール
以下のコマンドにより、**PyQRCodeモジュール**をインストールします。また、今回はQRコードをPNG画像として保存するため、**pypngモジュール**もインストールします。

``` :コマンドライン
pip install PyQRCode
pip install pypng
```
https://pypi.org/project/PyQRCode/

https://pypi.org/project/pypng/

### ・プログラム
``` .python:sample_qr_generator.py
import pyqrcode

FILE_PNG_A = 'qrcode_A.png'
FILE_PNG_B = 'qrcode_B.png'

# QRコード作成
code = pyqrcode.create('https://qiita.com/PoodleMaster', error='L', version=3, mode='binary')
code.png(FILE_PNG_A, scale=5, module_color=[0, 0, 0, 128], background=[255, 255, 255])

# QRコード作成
code = pyqrcode.create('https://github.com/PoodleMaster', error='L', version=3, mode='binary')
code.png(FILE_PNG_B, scale=5, module_color=[0, 0, 0, 128], background=[255, 255, 255])
```
### ・実行結果
|File|Image|
|:-:|:-:|
|qrcode_A.png|![qrcode_A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/8a84879d-3552-cd08-ce15-8aeb33577205.png)|
|qrcode_B.png|![qrcode_B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/e20b32e0-3665-39cf-8eff-1abaed77f1fb.png)|

# ➌：画像連結
こちらの作業は必須ではないですが、1枚の画像に複数QRコードがある状況を作っておきます。次章の「QRコード読込み」の際に、1枚の画像から複数のQRコードを読み込めることを確かめます。

### ・インストール
**OpenCVモジュール**を使用して画像を連結します。

```
pip install opencv-python
```
https://pypi.org/project/opencv-python/

### ・プログラム
``` python:sample_hconcat.py
import cv2

FILE_PNG_A = 'qrcode_A.png'
FILE_PNG_B = 'qrcode_B.png'
FILE_PNG_AB = 'qrcode_AB.png'

im1 = cv2.imread(FILE_PNG_A)
im2 = cv2.imread(FILE_PNG_B)

im_h = cv2.hconcat([im1, im2])
cv2.imwrite(FILE_PNG_AB, im_h)

cv2.imshow('image', im_h)
cv2.waitKey(0)
```

### ・実施結果
|File|Image|
|:-:|:-:|
|qrcode_AB.png|![qrcode_AB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/b4b07818-c8cf-6efd-b672-07d315e36a13.png)|

# ➍：QRコード読込み
QRコードの読込みは、**OpenCVモジュール**、または**pyzbarモジュール**のどちらかを使用します。

## ➍-1：「OpenCV」によるQRコード読込み

### ・インストール
OpenCVバージョン4.3から`detectAndDecodeMulti`メソッドがコール可能となり、**1枚の画像から複数のQRコードを読み取ることができる**ようになりました。OpenCVは、バージョン4.3以上をインストールしてください。

```
pip install opencv-python
```
https://pypi.org/project/opencv-python/

### ・プログラム
``` python:sample_qr_read_1.py
import cv2
import numpy as np


# -----------------------------------------------------------
# initial
# -----------------------------------------------------------
font = cv2.FONT_HERSHEY_SIMPLEX
FILE_PNG_AB = 'qrcode_AB.png'


# -----------------------------------------------------------
# function_qr_dec
# -----------------------------------------------------------
def function_qrdec_cv2(img_bgr):

    # QRCodeDetectorインスタンス生成
    qrd = cv2.QRCodeDetector()

    # QRコードデコード
    retval, decoded_info, points, straight_qrcode = qrd.detectAndDecodeMulti(img_bgr)

    if retval:
        points = points.astype(np.int)

        for dec_inf, point in zip(decoded_info, points):
            if dec_inf == '':
                continue

            # QRコード座標取得
            x = point[0][0]
            y = point[0][1]

            # QRコードデータ
            print('dec:', dec_inf)
            img_bgr = cv2.putText(img_bgr, dec_inf, (x, y-6), font, .3, (0, 0, 255), 1, cv2.LINE_AA)

            # バウンディングボックス
            img_bgr = cv2.polylines(img_bgr, [point], True, (0, 255, 0), 1, cv2.LINE_AA)

    cv2.imshow('image', img_bgr)
    cv2.waitKey(0)


# -----------------------------------------------------------
# sample program
# -----------------------------------------------------------
img_BGR = cv2.imread(FILE_PNG_AB, cv2.IMREAD_COLOR)
function_qrdec_cv2(img_BGR)
```
※**detectAndDecodeMulti**の結果が、**True**（**retval**が**True**）の場合でも、デコードデータが無い（**dec_inf**が**NULL**）ことがあったので、「**dec_inf == ''**」の場合は、**continue**させています。

### ・実行結果
![スクリーンショット 2021-03-04 162814.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/4692e67e-2581-c877-0738-7bf3c060578f.png)

## ➍-2：「pyzbar」によるQRコード読込み

### ・インストール
Windows環境以外は、以下のモジュールをインストールしてください。

``` :Mac
brew install zbar
```

``` :Linux&ensp;/&ensp;GoogleColab
sudo apt-get install libzbar0
```

以下のコマンドでpyzbarモジュールをインストールします。

``` :Mac&ensp;/&ensp;Linux&ensp;/&ensp;GoogleColab&ensp;/&ensp;Win
pip install pyzbar
```
https://pypi.org/project/pyzbar/

### ・プログラム
``` python:sample_qr_read_2.py
import cv2
from pyzbar.pyzbar import decode, ZBarSymbol


# -----------------------------------------------------------
# initial
# -----------------------------------------------------------
font = cv2.FONT_HERSHEY_SIMPLEX
FILE_PNG_AB = 'qrcode_AB.png'


# -----------------------------------------------------------
# function_qr_dec
# -----------------------------------------------------------
def function_qrdec_pyzbar(img_bgr):

    # QRコードデコード
    value = decode(img_bgr, symbols=[ZBarSymbol.QRCODE])

    if value:
        for qrcode in value:

            # QRコード座標取得
            x, y, w, h = qrcode.rect

            # QRコードデータ
            dec_inf = qrcode.data.decode('utf-8')
            print('dec:', dec_inf)
            img_bgr = cv2.putText(img_bgr, dec_inf, (x, y - 6), font, .3, (255, 0, 0), 1, cv2.LINE_AA)

            # バウンディングボックス
            cv2.rectangle(img_bgr, (x, y), (x + w, y + h), (0, 255, 0), 2)

        cv2.imshow('image', img_bgr)
        cv2.waitKey(0)


# -----------------------------------------------------------
# sample program
# -----------------------------------------------------------
img_BGR = cv2.imread(FILE_PNG_AB, cv2.IMREAD_COLOR)
function_qrdec_pyzbar(img_BGR)

```

### ・実行結果
![スクリーンショット 2021-03-04 162910.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/30cca20c-6e45-fb0f-d8b6-14fd35ef18a3.png)

# ➎：webカメラでQRコード自動認識
最後にwebカメラでQRコードを自動認識するプログラムを載せておきます。


## ➎-1：「OpenCV」プログラム
プログラムは、**「`'q'`」キー押下でquit**します。

``` python:sample_webcam_qrcode_cv2.py
import cv2
import numpy as np


# -----------------------------------------------------------
# Init
# -----------------------------------------------------------
font = cv2.FONT_HERSHEY_SIMPLEX

# -----------------------------------------------------------
# 画像キャプチャ
# -----------------------------------------------------------
# VideoCaptureインスタンス生成
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

# QRCodeDetectorインスタンス生成
qrd = cv2.QRCodeDetector()

while cap.isOpened():
    ret, frame = cap.read()

    if ret:
        # QRコードデコード
        retval, decoded_info, points, straight_qrcode = qrd.detectAndDecodeMulti(frame)

        if retval:
            points = points.astype(np.int32)

            for dec_inf, point in zip(decoded_info, points):
                if dec_inf == '':
                    continue

                # QRコード座標取得
                x = point[0][0]
                y = point[0][1]

                # QRコードデータ
                print('dec:', dec_inf)
                frame = cv2.putText(frame, dec_inf, (x, y - 6), font, .3, (0, 0, 255), 1, cv2.LINE_AA)

                # バウンディングボックス
                frame = cv2.polylines(frame, [point], True, (0, 255, 0), 1, cv2.LINE_AA)

        # 画像表示
        cv2.imshow('cv2', frame)

    # quit
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# キャプチャリソースリリース
cap.release()
```

## ➎-2：「pyzbar」プログラム
プログラムは、**「`'q'`」キー押下でquit**します。

``` python:sample_webcam_qrcode_pyzbar.py
import cv2
from pyzbar.pyzbar import decode, ZBarSymbol


# -----------------------------------------------------------
# Init
# -----------------------------------------------------------
font = cv2.FONT_HERSHEY_SIMPLEX


# -----------------------------------------------------------
# 画像キャプチャ
# -----------------------------------------------------------
# VideoCaptureインスタンス生成
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

while cap.isOpened():
    ret, frame = cap.read()

    if ret:
        # デコード
        value = decode(frame, symbols=[ZBarSymbol.QRCODE])

        if value:
            for qrcode in value:

                # QRコード座標
                x, y, w, h = qrcode.rect

                # QRコードデータ
                dec_inf = qrcode.data.decode('utf-8')
                print('dec:', dec_inf)
                frame = cv2.putText(frame, dec_inf, (x, y-6), font, .3, (255, 0, 0), 1, cv2.LINE_AA)

                # バウンディングボックス
                cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 1)

        # 画像表示
        cv2.imshow('pyzbar', frame)

    # quit
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# キャプチャリソースリリース
cap.release()
```
##➎-3：実行結果
「`OpenCV`」の実行結果は割愛し、「`pyzbar`」の実行結果のみを載せます。「QRコードを表示した紙」をwebカメラで撮り検出させていますが、うまく認識しています！:thumbsup_tone1:
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/e241d5c8-faef-4a96-ccb7-fb9e1812d516.gif' width=400>

# ➏：以上
お疲れ様でした。
モジュールを使うことで、かなり簡単にQRコード作成・読込みツールを作ることができました。