【Python・OpenCV】QRコードを読み取るには(cv2.QRCodeDetector,cv2.QRCodeDetectorAruco)

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】QRコードを読み取るには(cv2.QRCodeDetector,cv2.QRCodeDetectorAruco)

はじめに

QRコードは生活のあらゆる場面で利用されている便利なツールです。商品のURLや会員情報など、さまざまな情報を格納できるQRコードを、プログラムで読み取ることができれば、その用途は無限大です。
この記事では、OpenCVを使用してQRコードをスキャンする手法を解説します。
OpenCVでは、QRコードをスキャンする機能としてQRCodeDetectorクラスとQRCodeDetectorArucoクラスが提供されていて、これらの違いと使い分けについて解説します。

また、OpenCVを使ったQRコードの生成方法については、こちらの記事で紹介していますのでご参考ください。

QRコード, QR Codeはデンソーウェーブの登録商標です。

QRコードの読み取り

OpenCVで、QRコードを読み取ることができるQRCodeDetectorクラスとQRCodeDetectorArucoクラスについて説明します。
これらの主な違いは以下の通りです。
おおむね、QRCodeDetectorArucoクラスの方が高性能となっています。

機能QRCodeDetectorクラスQRCodeDetectorArucoクラス
検出できるコードQRコードのみQRコードとArUcoマーカー
処理速度低速高速
検出精度
機能シンプル拡張性が高い
対応しているOpenCVのバージョン3.4以降
detectAndDecodeMultiメソッドは4.3以降
4.8以降

QRCodeDetectorクラスとQRCodeDetectorArucoクラスはGraphicalCodeDetectorクラスを継承したものとなっています。
GraphicalCodeDetectorクラスにはデコードのみを行う、decodeメソッド、decodeMultiメソッド、QRコードの検出のみを行う、detectメソッド、detectMultiメソッド、1つのQRコードの検出とデコードを行うdetectAndDecodeメソッド、複数のQRコードの検出とデコードを一度で行うdetectAndDecodeMultiメソッドがあります。

本記事では最も汎用性のあるdetectAndDecodeMultiメソッドについてのみ解説します。

QRCodeDetectorクラスやQRCodeDetectorArucoクラスを使用したQRコードの読み取りは以下の手順となります。

  1. QRコードの画像の取得
  2. QRCodeDetectorクラスまたは、QRCodeDetectorArucoクラスのオブジェクトを作成する
  3. detectAndDecodeMultiメソッドで、QRコードの検出を実行

detectAndDecodeMulti()メソッド

detectAndDecodeMultiメソッドはOpenCVのバージョン3.4以降で利用可能な機能です。

detectAndDecodeメソッドとは異なり、画像内に複数のQRコードを同時に検出することができます。

detectAndDecodeMulti(入力画像,[, points[, straight_code]])

引数

名称説明
入力画像(必須)QRコードにエンコードするデータ
points(オプション)検出されたQRコードの四角形の頂点座標が格納されたnumpy配列が入力されます。戻り値として取得するものと同じです。
straight_code(オプション)検出されたQRコードの各セルの白黒情報(0: 黒、1: 白)が格納されたnumpy配列が入力されます。各要素は、QRコードのバージョンに応じたサイズ(21x21、25x25、…) のベクトルです。戻り値として取得するものと同じです。

戻り値

下のデータが入ったタプルが戻り値となります。

名称説明
retvalQRコードを読み取ることができた場合、Trueを返し、できなかった場合、Falseを返します。
decoded_infoデコードされたQRコードのデータ(UTF-8エンコード)が格納されたnumpy配列
points検出されたQRコードの四角形の頂点座標が格納されたnumpy配列
straight_code検出されたQRコードの各セルの白黒情報(0: 黒、1: 白)を格納するベクトル。各要素は、QRコードのバージョンに応じたサイズ(21x21、25x25、…) のベクトルです。戻り値として取得するものと同じです。
(広告) OpenCV関連書籍をAmazonで探す

使い方

読み取るQRコードの画像データを準備します。
今回は、下の画像を用いました。
回転した6個のQRコードの画像です。

QR Codes

QRCodeDetectorクラス

QRCodeDetectorクラスのサンプルコードを下に示します。
上のQRコードを読みとるプログラムとなります。

import cv2
from matplotlib import pyplot as plt

# QRコードの画像を読み込む
image = cv2.imread("qrcodes.png")

# QRCodeDetectorオブジェクトを作成する
detector = cv2.QRCodeDetector()

# 画像内のすべてのQRコードを検出
retval, decoded_info, points, straight_qrcode = detector.detectAndDecodeMulti(image)

# 検出したQRコードの座標のデータ型を"int"に変換
points = points.astype(int)

# 描画設定
is_closed = True                    # QRコードを囲う四角形を閉包図形とする
font = cv2.FONT_HERSHEY_SIMPLEX     # フォントの種類
font_color = (255, 0, 255)          # フォントの色をマゼンタに設定
font_line_type = cv2.LINE_AA        # フォントをアンチエイリアスで描画

# 検出したQRコードのそれぞれを四角で囲い、デコードしたデータを描画する
if len(decoded_info) > 0:
    for i in range(len(decoded_info)):
        # QRコードの左上の座標を取得
        x = points[i][0][0]
        y = points[i][0][1]

        # QRコードの周囲に矩形を描画
        cv2.polylines(image, [points[i]], is_closed, (0, 255, 0), thickness=2)

        # QRコードをデコードしたデータを表示
        cv2.putText(image, decoded_info[i], (x, y-10), font, 0.8, font_color, thickness=2, lineType=font_line_type)

    # 結果の可視化
    title = "cv2.QRCodeEncoder: codevace.com"
    plt.figure(title)                                       # ウィンドウタイトルを設定
    plt.axis("off")                                         # 軸目盛、軸ラベルを消す
    plt.imshow(image)                                       # ウィンドウ上に画像を配置
    plt.show()                                              # ウィンドウを表示する。

読み取り結果は下の様になりました。
読み取ることができたQRコードは緑色の四角形で囲い、読み取ったコードをマゼンタで表示しましたが、回転角度が30°以上のQRコードを読み取ることができませんでした。

QRCodeEncoder results

QRCodeDetectorArucoクラス

QRCodeDetectorArucoクラスのサンプルコードを下に示します。
QRCodeDetectorクラスの場合と同様に、上のQRコードを読みとるプログラムとなります。

import cv2
from matplotlib import pyplot as plt

# RQコードの画像を読み込む
image = cv2.imread("qrcodes.png")

# QRCodeDetectorArucoオブジェクトを作成する
detector = cv2.QRCodeDetectorAruco() # OpenCVのバージョン4.8以上

# 画像内のすべてのQRコードを検出
retval, decoded_info, points, straight_qrcode = detector.detectAndDecodeMulti(image)

# 検出したQRコードの座標のデータ型を"int"に変換
points = points.astype(int)

# 描画設定
is_closed = True                    # QRコードを囲う四角形を閉包図形とする
font = cv2.FONT_HERSHEY_SIMPLEX     # フォントの種類
font_color = (255, 0, 255)          # フォントの色をマゼンタに設定
font_line_type = cv2.LINE_AA        # フォントをアンチエイリアスで描画

# 検出したQRコードのそれぞれを四角で囲い、デコードしたデータを描画する
if len(decoded_info) > 0:
    for i in range(len(decoded_info)):
        # QRコードの左上の座標を取得
        x = points[i][0][0]
        y = points[i][0][1]

        # QRコードの周囲に矩形を描画
        cv2.polylines(image, [points[i]], is_closed, (0, 255, 0), thickness=2)

        # QRコードをデコードしたデータを表示
        cv2.putText(image, decoded_info[i], (x, y-10), font, 0.8, font_color, thickness=2, lineType=font_line_type)

    # 結果の可視化
    title = "cv2.QRCodeDetectorAruco: codevace.com"
    plt.figure(title)                                       # ウィンドウタイトルを設定
    plt.axis("off")                                         # 軸目盛、軸ラベルを消す
    plt.imshow(image)                                       # ウィンドウ上に画像を配置
    plt.show()                                              # ウィンドウを表示する。

読み取り結果は下の様になりました。
QRCodeDetectorArucoクラスでは全てのQRコードを読み取ることができました。
QRコードを含む画像の解像度や、歪みなどの影響の検証が十分とは言えませんが、QRCodeDetectorクラスよりQRCodeDetectorArucoクラスの方が検出精度が高いことが確認できました。

QRCodeDetectorAruco results

読み取り速度の比較

QRCodeDetectorクラスとQRCodeDetectorArucoクラスの読み取り速度を比較しました。
それぞれのクラスで、これまでと同じQRコードの画像を500回読み取る時間を計測しました。
処理時間の計測は、下記の記事で紹介しています。

import cv2

# RQコードの画像を読み込む
image = cv2.imread("qrcodes.png")

# QRCodeDetectorオブジェクトを作成する
detector = cv2.QRCodeDetector()

# QRCodeDetectorを使った、QRコードの検出とデコード
start_time = cv2.getTickCount()
for i in range(500):
    retval, decoded_info, points, straight_qrcode = detector.detectAndDecodeMulti(image)

end_time = cv2.getTickCount()
elapsed_time = (end_time - start_time) / cv2.getTickFrequency()
print("QRCodeDetector")
print("Elapsed time:", elapsed_time, "seconds")
print('\n')

# QRCodeDetectorArucoオブジェクトを作成する
detector = cv2.QRCodeDetectorAruco() # OpenCVのバージョン4.8以上

# QRCodeDetectorArucoを使った、QRコードの検出とデコード
start_time = cv2.getTickCount()
for i in range(500):
    retval, decoded_info, points, straight_qrcode = detector.detectAndDecodeMulti(image)

end_time = cv2.getTickCount()
elapsed_time = (end_time - start_time) / cv2.getTickFrequency()
print("QRCodeDetectorAruco")
print("Elapsed time:", elapsed_time, "seconds")

今回、実行環境はこちらです。

ソフトウェア:

  • OpenCV 4.9.0
  • Python 3.11.1

ハードウェア:

  • MacBook Pro 13-inch, M1, 2020

結果はQRCodeDetectorArucoクラスの方が倍近い速さで終了しました。

QRCodeDetector
Elapsed time: 15.781673167 seconds

QRCodeDetectorAruco
Elapsed time: 8.464325167 seconds

おまけ

読み取りに使ったQRコードの画像は下のコードで作成しました。
ただし、QRコードの回転角度をランダムに選択しているので、上のQRコードと全く同じにはならないと思います。

import random
import cv2
import numpy as np
from matplotlib import pyplot as plt

def warpAffine_rotaion(src, angle, scale):
    """
    指定された回転角度に反時計回りに画像を回転し、回転の画像サイズに合わせてサイズを変化した画像を返す。

    src: 入力画像
    angle: 回転角度
    scale: 画像の拡大・縮小を指定する倍率

    戻り値: 回転した画像
    """
    # 入力画像のサイズを取得
    height, width = src.shape[:2]
    # 回転の中心座標を指定
    center = (width // 2, height // 2)

    # 変換行列(回転行列)を計算
    M = cv2.getRotationMatrix2D(center, angle, scale)

    # 回転後の画像のサイズを計算
    cos_theta = np.abs(M[0, 0])
    sin_theta = np.abs(M[0, 1])
    new_width = int(width * cos_theta + height * sin_theta)
    new_height = int(width * sin_theta + height * cos_theta)

    # 回転の中心のズレを修正
    M[0,2] += (new_width - width)/2.0
    M[1,2] += (new_height - height)/2.0

    # 画像を反時計回りにangle°回転する
    return cv2.warpAffine(src, M, (new_width, new_height), flags=cv2.INTER_NEAREST, borderValue=(255,255,255))

# QRコードにする文字列のリスト
encoded_info = ["QR Code #1",
                "QR Code #2",
                "QR Code #3",
                "QR Code #4",
                "QR Code #5",
                "QR Code #6"]

# QRコード エンコーダーの設定
qr_params = cv2.QRCodeEncoder_Params()
qr_params.correction_level = cv2.QRCODE_ENCODER_CORRECT_LEVEL_Q
qr_params.mode = cv2.QRCODE_ENCODER_MODE_AUTO
qr_params.version = 3
qr_params.structure_number = 1

# QRCodeEncoderクラスのインスタンスを作成
QRencoder = cv2.QRCodeEncoder.create(qr_params)

qrcodes = []

for item in range(len(encoded_info)):
    # QRコードを作成
    qrcode = QRencoder.encode(encoded_info[item])

    # サイズを取得
    height, width = qrcode.shape[:]
    # 回転の中心を指定
    center = (int(width/2), int(height/2))
    # 回転角を指定
    angle = random.randrange(-45, 45, 5)
    # スケールを指定
    scale = 5.0
    # getRotationMatrix2D関数を使用
    trans = cv2.getRotationMatrix2D(center, angle , scale)
    # アフィン変換
    qrcode = warpAffine_rotaion(qrcode, angle, scale)
    # QRコードをリストに追加
    qrcodes.append(qrcode)

# 結果の可視化
plt.rcParams["figure.figsize"] = [8,5.5]                            # 表示領域のアスペクト比を設定
title = "QR Codes: codevace.com"
plt.figure(title)                                                   # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.9)    # 余白を設定

for code in range(len(qrcodes)):
    pos = 231 + code
    plt.subplot(pos)                                                # 2行3列のcode番目の領域にプロットを設定
    plt.imshow(qrcodes[code], cmap='gray')                          # QRコードを表示
    plt.title(encoded_info[code])                                             # 画像タイトル設定
    plt.axis("off")                                                 # 軸目盛、軸ラベルを消す

plt.savefig("qrcodes.png")                                          # QRコードを画像として保存
plt.show()                                                          # ウィンドウを表示する。

おわりに

本記事の検証で、QRCodeDetectorクラスよりQRCodeDetectorArucoクラスの方が高精度かつ高速にQRコードの読み取りを行うことができると言えそうです。

ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。

参考リンク

■(広告)Pythonのオススメ書籍■

(広告)あなたに合ったプログラミング スクールが見つかるかも

プログラミングの学習が、新しい世界を開きます。副業からキャリアの転換まで、目標に向かって一歩を踏み出しましょう。

これらのプログラミング スクールは、あなたの目的に合わせて選択できるさまざまなプログラムを提供しています。どのスクールを選ぶにせよ、プログラミングは未来への扉を開き、新しい機会を切り開く手段として素晴らしい選択です。あなたの夢や目標を実現する第一歩を踏み出す準備はできていますか?

-Python, プログラミング, 画像処理
-