21
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CoconeAdvent Calendar 2023

Day 5

QRコードを活用しよう

Last updated at Posted at 2023-12-04

title.gif

この記事は、Cocone Advent Calendar 2023の5日目の記事です。

はじめに

みなさん、QRコード、活用してますか?
最近はQRコード決済なんかで利用頻度も上がっていますが、あんまり業務上活用されているのを見ていない気がします。
というわけでQRコードの活用方法について考えてみます。

QRコードの利点

ネットワークに依存しない情報伝達

QRコードの利点はずばり、ネットワークに依存せずに情報伝達が可能なこと、ではないかと思います。

現代のインターネット社会において、スマホやPCなど、ほとんどのIT端末がなんらかのネットワークによって接続されています。
しかしネットワークを辿れば情報伝達は可能でも、辿るための情報がわからなければ伝達できません。
このような電子的な接続とは違い、QRコードはいわば光学的な接続であるため、カメラ等の光学機器とQRコードとが向かい合っていれば情報伝達が可能なのです。
カメラが標準搭載されているスマホやPCにはもってこいの伝達手段といえます。

時系列的に不変

電子的ではない情報伝達といえば他にも
・光通信
 赤外線等
・音通信
 音声、モールス等
なんかもありますが、いずれも時系列的に変化させることで伝達する情報を変化させています。
QRコードやバーコードは時系列で変化しない情報伝達だといえます。
この特徴のため、QRコードの表示側は変化させる機構がいらない、つまり紙に印刷するだけ、画像として画面に表示するだけでも十分なのです。
思えば人類最高の発明とも言われる「文字」と同じなわけですね。

QRコードの活用例

WiFi接続情報

街中のFreeWiFiで見る機会も多いですが、WiFiの接続情報とQRコードは相性がいいです。
WiFiのSSIDとパスワード、あれ文字で見て入力すると大体間違えますよね。
わかりにくい文字を入力する手段でQRは最も適しているといえます。

WiFiの接続情報となる文字列は下記のようにします。
WIFI:S:(接続するSSID);T:(接続方式);P:(パスワード);;
例) SSID: abcde パスワード: 12345 接続方式: WPA/WPA2 の場合
WIFI:S:abcde;T:WPA;P:12345;
わかりやすくWiFiアイコンをつけとくといいですね(後述)。
wifiqr.png

iOSだと標準のカメラアプリでもWiFi接続可能なんですが、手持ちのAndroid(14)だとQRコード読み込みアプリでも接続できませんでした。
Android端末の場合はWiFiの接続先リストの下部にある「+ネットワークを追加」からQRコードアイコンをタップするとカメラが起動して読み込めます。

URLの共有

WiFi接続情報と同じく、URLも手で入力するには面倒な文字列です。
HPなんかのURLをQRコードで見る機会は多いですね。
業務利用する場合、テスト端末へのアプリDL先URLの共有なんかに役立ちます。
slackなどで共有してもテスト端末すべてで見れるようにするには面倒ですしね。
iOS版、android版とわかりやすくすると間違えにくくなります。
testandroidios.png

どういうところに使えるか?

URLをQRコードに埋め込み、スマホからアクセスさせることでなんらかのサービスを行う、というのが基本的な利用になるかと思います。
Webページであればボタンを押すところをカメラを向けさせることで代用する感じですね。
QRコードを使う方が利便性が高くなるケースは、下記が挙げられるでしょうか。

  • 似たようなたくさんあるものの管理
    ・機材
    ・本
    ・座席
    ・部屋/場所
    など、大量にある物の中から目の前にある一つを選ぶ場合に向いているかと思います。
  • 消耗品の管理
    シールなどに印刷して貼るだけで読み込み可能で容易に捨てられるという、コストの安さもQRコードやバーコードの利点ですね。
    例えばQRコードシールを食材に貼っておいて冷蔵庫で読み込めれば、食品の管理やレシピの推奨なんかができると思うんですが、そういう冷蔵庫ってないんですかね?
    処分時に読み込ませることで補充が可能になるとか、オフィス利用も便利になりそうです。

 

QRコードをわかりやすくしよう

QRコードの欠点として、読み込んでみなければ何のコードかがわからない点があります。
画像の外側に説明を書けばいいだけの話ではありますが、QRコード自体で説明できないか試してみます。

アイコン画像を埋め込む

iconqr.png

先の例でも使っている、QRコードに別画像を入れる方法です。
QRコードは工場等でも使われることが前提で設計されていて、多少の汚れやノイズでも読めるようになっています。
誤り訂正能力をHIGHに設定したQRコードの場合、画像の真ん中であれば一辺がQRコードサイズの40%くらいの画像が埋め込めます。

アイコン画像を埋め込むQR作成コード
makeQR.py
import qrcode
import sys
from PIL import Image

#############
# 引数読み込み
#############
if(len(sys.argv) < 3):
	print('usage: $ python3 ' + __file__ + ' <text> <outputFile> <color=black> <iconfile=null> <iconrate=0.2>')
	print('   text: QRコードに変換する文字列')
	print('   outputFile: QRコード出力画像ファイル')
	print('   color: QRコードの普通黒い部分の色(デフォルトで黒)')
	print('   iconfile: QRコードに埋め込みたい画像ファイル(デフォルトで画像なし)')
	print('   iconrate: QRコードサイズに対するiconfileの大きさの割合 0.0〜0.5(デフォルトで0.2)')
	exit()
	
text = sys.argv[1]
outputFile = sys.argv[2]
color = sys.argv[3] if len(sys.argv) >= 4 else "black"
icon = Image.open(sys.argv[4]) if len(sys.argv) >= 5 else None
iconrate = float(sys.argv[5]) if len(sys.argv) >= 6 else 0.2

#################
# QRコード画像作成
#################
qr = qrcode.QRCode(
	version=1,
	error_correction=qrcode.constants.ERROR_CORRECT_H,
	box_size=4,
	border=1
)
qr.add_data(text)
qr.make()
img_qr = qr.make_image(fill_color=color, back_color="white")


#############
# 画像埋め込み
#############
if icon != None:
	icon = icon.resize((int(img_qr.size[0] * float(iconrate)), int(img_qr.size[1] * float(iconrate))))
	pos = ((img_qr.size[0] - icon.size[0]) // 2, (img_qr.size[1] - icon.size[1]) // 2)
	img_qr.paste(icon, pos, icon.split()[3])


#############
# 画像書き出し
#############
img_qr.save(outputFile)

色指定でblackにすると埋め込んだ画像までモノクロになっちゃいました。
カラーで埋め込みたい場合は"#010101"とかを指定してください。

大きめの画像を埋め込む

pngqr.png

QRコードを隠すように手前に画像を置いてしまうとどうしても小さくなります。
QRコードの白い部分を透過して後ろに画像を置ける気もしますが、そのままだと画像の濃い部分も黒ドットと判定されてしまいます。
ただし白も黒も「ドットの真ん中数ピクセル」だけでも読み込んでくれるので、そこだけ白と黒、それ以外は透過することができます。
ただしコードの角にある大きな四角「ファインダパターン」、それらを繋ぐ「タイミングパターン」、ところどころにある小さい四角「アライメントパターン」は加工すると読み取り精度が下がるのでそのままにしておきます。
(個人的にはこれがないとQRコードっぽくないのでできるだけ残したい)
notext.png

背景画像を埋め込むQR作成コード
makeQRTransImage.py
import qrcode
from PIL import Image
from PIL import ImageDraw
import numpy as np
from enum import Enum
import sys
import os

# QRドット
DOT_T = 0  # 透明
DOT_B = 1  # 黒
DOT_W = 2  # 白
    

# ドットを反転
def inverseDot(img):
    size = img.shape[0]
    inv_img = np.full((size, size),False)
    for y in range(size):
        for x in range(size):
            inv_img[x,y] = not img[x,y]
    return inv_img



# QRコード情報取得
def getQRInfo(qrImg):
    size = qrImg.shape[0]
    
    ver = (int)((size-17)/4)
    
    if(ver == 1):
        ali = 0
    else:
        ali = (int)(ver / 7)+2
        
    return ver, size, ali


# x,yがファインダパターンまたはタイミングパターンかチェック
def checkMarker(qrInfo, x, y):
    ver = qrInfo[0]
    size = qrInfo[1]
    aliNum = qrInfo[2]
    
    markerSize = 7
    
    if x < markerSize+1 and y < markerSize+1:
        return True
    elif x < markerSize+1 and y >= size-markerSize-1:
        return True
    elif x >= size-markerSize-1 and y < markerSize+1:
        return True
    elif x == markerSize-1 or y == markerSize-1:
        return True
        
    return False


# x,yを中心にdotSizeで塗りつぶす
def fillCell(img, x, y, orgSize, dotSize, dot):
    cellSize = (int)(img.shape[0] / orgSize)
    offset=(int)((cellSize-dotSize)/2)
    for dy in range(dotSize):
        for dx in range(dotSize):
            img[x*cellSize + dx + offset, y*cellSize + dy + offset] = dot

    return img


# アライメントマーカーをdotSizeにする
def boldAlignment(img1, img2, dotSize):
    img3 = np.copy(img2)
    size = img1.shape[0]
    
	# アライメントマーカーっぽい四角を探す力技・・
    for y in range(6, size-6):
        for x in range(6, size-6):
            if img1[x-2, y-2] and img1[x-1, y-2] and img1[x, y-2] and img1[x+1, y-2] and img1[x+2, y-2] and img1[x-2, y-1] and img1[x+2, y-1] and img1[x-2, y] and img1[x+2, y] and img1[x-2, y+1] and img1[x+2, y+1] and img1[x-2, y+2] and img1[x-1, y+2] and img1[x, y+2] and img1[x+1, y+2] and img1[x+2, y+2]:
                if not img1[x-1, y-1] and not img1[x, y-1] and not img1[x+1, y-1] and not img1[x-1, y] and not img1[x+1, y] and not img1[x-1, y+1] and not img1[x, y+1] and not img1[x+1, y+1]:
                    dot = DOT_W            
                    if img1[x,y] == True:
                        dot = DOT_B
                    img3 = fillCell(img3, x-2, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x  , y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+2, y-2, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y-1, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+1, y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y-1, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y+1, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+1, y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y+1, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x  , y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+2, y+2, size, dotSize, DOT_B)
                        
    return img3



# QRコード周りのマージン追加
def addQRMargin(img, dotSize):
    size = img.shape[0]
    marginSize = dotSize*3
    img2 = np.full((size + marginSize*2, size + marginSize*2), DOT_W)
    
    for y in range(size):
        for x in range(size):
            img2[x+marginSize, y+marginSize] = img[x,y]

    return img2



# 1dotのQR作成
def makeOrgQR(message, minVer, ec):
    qr = qrcode.QRCode(
        version=minVer,
        error_correction=ec,
        box_size=1,
        border=0
    )
    qr.add_data(message)
    qr.make()
    qrimg = qr.make_image()

    img = np.array(qrimg)

    # 黒をTrueにする
    img1 = inverseDot(img)
   
    return img1


# マーカー部分をLargeDotSizeで塗りつぶしそれ以外をSmallDotSizeで塗りつぶす
def fillMarkerDot(img1, LargeDotSize, SmallDotSize):
    qrInfo = getQRInfo(img1)

    QRSize = qrInfo[1]
    offset=(int)((LargeDotSize-SmallDotSize)/2)

    img2 = np.full((QRSize * LargeDotSize, QRSize * LargeDotSize), DOT_T)
    
    for y in range(QRSize):
        for x in range(QRSize):
            x2 = x * LargeDotSize
            y2 = y * LargeDotSize

            dotSize = SmallDotSize
            if checkMarker(qrInfo, x, y):
                dotSize = LargeDotSize
            dot = DOT_W
            if img1[x,y]==True:
                dot = DOT_B

            img2 = fillCell(img2, x, y, QRSize, dotSize, dot)
                
                    
    img3 = boldAlignment(img1, img2, LargeDotSize)
    return img3


# RGBAのイメージに変換
def makeTransImage(img):
    size = img.shape[0]
    transImg = np.empty((size, size, 4), dtype=np.uint8)

    for y in range(size):
        for x in range(size):
            if img[x,y] == DOT_W:
                transImg[x,y,0] = 255
                transImg[x,y,1] = 255
                transImg[x,y,2] = 255
                transImg[x,y,3] = 255
            elif img[x,y] == DOT_B:
                transImg[x,y,0] = 0
                transImg[x,y,1] = 0
                transImg[x,y,2] = 0
                transImg[x,y,3] = 255
            else:
                transImg[x,y,0] = 0
                transImg[x,y,1] = 0
                transImg[x,y,2] = 0
                transImg[x,y,3] = 0

                
    return transImg

def makeTransQR():
    # 1dotのQR
    img1 = makeOrgQR(message, 2, qrcode.constants.ERROR_CORRECT_H)

    # マーカーだけ大きく、それ以外小さくする
    img2 = fillMarkerDot(img1, LargeDotSize, SmallDotSize)

    # マージンを追加
    img3 = addQRMargin(img2, LargeDotSize)

    # 透過イメージに変換
    img4 = makeTransImage(img3)

    return img4




# 透過したQRイメージarrayの背景にpngPathの画像をつける
def makeQRImagePng(qrarray):
    width = qrarray.shape[0]
    
	# QR画像
    qrimg = Image.fromarray(qrarray, 'RGBA')
    
	# 背景画像
    im = Image.open(pngPath)
    im = im.resize((width, width))

	# 結合画像
    compositeImg = Image.alpha_composite(im, qrimg)
    
    return compositeImg



if __name__ == '__main__':

	args = sys.argv

	if len(args) < 3:
		print("args error!")
		print("usage: $ python3 %s [message] [pngPath] [outputPath]" % (os.path.basename(__file__)))
		exit(0)
		
	
	message = args[1]
	pngPath = args[2]
	outputPath = args[3]

	# マーカー用ドットサイズ
	LargeDotSize = 7
	# マーカー以外のドットサイズ
	SmallDotSize = 2

	bgColor = (255, 255, 255, 255) # 背景色
	


    # 透過QR作成
	transQRImg = makeTransQR()
	# 背景画像と結合
	imageQR = makeQRImagePng(transQRImg);
	# ファイル保存
	imageQR.save(outputPath);

ドットが小さくなるので長い文字列でQRコード画像が大きくなると読み込みづらくなります。
ほどほどのサイズ、折り曲げたりしない、安定した場所で読み込む、というあたりが条件かもしれません。

背景画像を動かす

qranim.gif

利点の時系列的に不変という点を無視してますが、せっかくなんで動かしてみましょう。
画像を埋め込んだQRコード画像をアニメーションGIFにするだけです。

動く背景画像を埋め込むQR作成コード
makeQRTransImage.py
import qrcode
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import numpy as np
from enum import Enum
import sys
import os
import glob

# QRドット
DOT_T = 0  # 透明
DOT_B = 1  # 黒
DOT_W = 2  # 白
    

# ドットを反転
def inverseDot(img):
    size = img.shape[0]
    inv_img = np.full((size, size),False)
    for y in range(size):
        for x in range(size):
            inv_img[x,y] = not img[x,y]
    return inv_img



# QRコード情報取得
def getQRInfo(qrImg):
    size = qrImg.shape[0]
    
    ver = (int)((size-17)/4)
    
    if(ver == 1):
        ali = 0
    else:
        ali = (int)(ver / 7)+2
        
    return ver, size, ali


# x,yがファインダパターンまたはタイミングパターンかチェック
def checkMarker(qrInfo, x, y):
    ver = qrInfo[0]
    size = qrInfo[1]
    aliNum = qrInfo[2]
    
    markerSize = 7
    
    if x < markerSize+1 and y < markerSize+1:
        return True
    elif x < markerSize+1 and y >= size-markerSize-1:
        return True
    elif x >= size-markerSize-1 and y < markerSize+1:
        return True
    elif x == markerSize-1 or y == markerSize-1:
        return True
        
    return False


# x,yを中心にdotSizeで塗りつぶす
def fillCell(img, x, y, orgSize, dotSize, dot):
    cellSize = (int)(img.shape[0] / orgSize)
    offset=(int)((cellSize-dotSize)/2)
    for dy in range(dotSize):
        for dx in range(dotSize):
            img[x*cellSize + dx + offset, y*cellSize + dy + offset] = dot

    return img


# アライメントマーカーをdotSizeにする
def boldAlignment(img1, img2, dotSize):
    img3 = np.copy(img2)
    size = img1.shape[0]
    
	# アライメントマーカーっぽい四角を探す力技・・
    for y in range(6, size-6):
        for x in range(6, size-6):
            if img1[x-2, y-2] and img1[x-1, y-2] and img1[x, y-2] and img1[x+1, y-2] and img1[x+2, y-2] and img1[x-2, y-1] and img1[x+2, y-1] and img1[x-2, y] and img1[x+2, y] and img1[x-2, y+1] and img1[x+2, y+1] and img1[x-2, y+2] and img1[x-1, y+2] and img1[x, y+2] and img1[x+1, y+2] and img1[x+2, y+2]:
                if not img1[x-1, y-1] and not img1[x, y-1] and not img1[x+1, y-1] and not img1[x-1, y] and not img1[x+1, y] and not img1[x-1, y+1] and not img1[x, y+1] and not img1[x+1, y+1]:
                    dot = DOT_W            
                    if img1[x,y] == True:
                        dot = DOT_B
                    img3 = fillCell(img3, x-2, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x  , y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+2, y-2, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y-1, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+1, y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y-1, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y+1, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+1, y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y+1, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x  , y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+2, y+2, size, dotSize, DOT_B)
                        
    return img3



# QRコード周りのマージン追加
def addQRMargin(img, dotSize):
    size = img.shape[0]
    marginSize = dotSize*3
    img2 = np.full((size + marginSize*2, size + marginSize*2), DOT_W)
    
    for y in range(size):
        for x in range(size):
            img2[x+marginSize, y+marginSize] = img[x,y]

    return img2



# 1dotのQR作成
def makeOrgQR(message, minVer, ec):
    qr = qrcode.QRCode(
        version=minVer,
        error_correction=ec,
        box_size=1,
        border=0
    )
    qr.add_data(message)
    qr.make()
    qrimg = qr.make_image()

    img = np.array(qrimg)

    # 黒をTrueにする
    img1 = inverseDot(img)
   
    return img1


# マーカー部分をLargeDotSizeで塗りつぶしそれ以外をSmallDotSizeで塗りつぶす
def fillMarkerDot(img1, LargeDotSize, SmallDotSize):
    qrInfo = getQRInfo(img1)

    QRSize = qrInfo[1]
    offset=(int)((LargeDotSize-SmallDotSize)/2)

    img2 = np.full((QRSize * LargeDotSize, QRSize * LargeDotSize), DOT_T)
    
    for y in range(QRSize):
        for x in range(QRSize):
            x2 = x * LargeDotSize
            y2 = y * LargeDotSize

            dotSize = SmallDotSize
            if checkMarker(qrInfo, x, y):
                dotSize = LargeDotSize
            dot = DOT_W
            if img1[x,y]==True:
                dot = DOT_B

            img2 = fillCell(img2, x, y, QRSize, dotSize, dot)
                
                    
    img3 = boldAlignment(img1, img2, LargeDotSize)
    return img3


# RGBAのイメージに変換
def makeTransImage(img):
    size = img.shape[0]
    transImg = np.empty((size, size, 4), dtype=np.uint8)

    for y in range(size):
        for x in range(size):
            if img[x,y] == DOT_W:
                transImg[x,y,0] = 255
                transImg[x,y,1] = 255
                transImg[x,y,2] = 255
                transImg[x,y,3] = 255
            elif img[x,y] == DOT_B:
                transImg[x,y,0] = 0
                transImg[x,y,1] = 0
                transImg[x,y,2] = 0
                transImg[x,y,3] = 255
            else:
                transImg[x,y,0] = 0
                transImg[x,y,1] = 0
                transImg[x,y,2] = 0
                transImg[x,y,3] = 0

                
    return transImg

def makeTransQR():
    # 1dotのQR
    img1 = makeOrgQR(message, 2, qrcode.constants.ERROR_CORRECT_H)

    # マーカーだけ大きく、それ以外小さくする
    img2 = fillMarkerDot(img1, LargeDotSize, SmallDotSize)

    # マージンを追加
    img3 = addQRMargin(img2, LargeDotSize)

    # 透過イメージに変換
    img4 = makeTransImage(img3)

    return img4




# 連番pngを読み込み、背景色とQRをつけて配列にする
def makeAnimGif(qrarray):
    images = []
    width = qrarray.shape[0]
    
    # QR画像
    qrimg = Image.fromarray(qrarray, 'RGBA')
    
    extension=".png"
    # 指定したフォルダ内のファイルを取得し、指定した拡張子のファイルのみをフィルタリング
    files = sorted(glob.glob(os.path.join(pngDir, f"*{extension}")))

    for idx, file_path in enumerate(files):
        print(f"{idx + 1}: {os.path.basename(file_path)}")

        # ベース画像
        baseImage = Image.new('RGBA', (width, width), bgColor)
        
        # 背景画像
        im = Image.open(file_path)
        im = im.resize((width, width))

        # ベースに背景画像をつける
        baseImage.paste(im, (0,0), im)

        # ベースにQRイメージを結合
        compositeImg = Image.alpha_composite(baseImage, qrimg)

		# リストに追加
        images.append(compositeImg)
    
    return images



if __name__ == '__main__':

	args = sys.argv

	if len(args) < 3:
		print("args error!")
		print("usage: $ python3 %s [message] [pngDir] [outputPath]" % (os.path.basename(__file__)))
		exit(0)
		
	
	message = args[1]
	pngDir = args[2]
	outputPath = args[3]
	
	# マーカー用ドットサイズ
	LargeDotSize = 7
	# マーカー以外のドットサイズ
	SmallDotSize = 2

	bgColor = (255, 255, 255, 255) # 背景色
	


	# 透過QR作成
	transQRImg = makeTransQR()
	
	# アニメーションGIF作成
	gifimages = makeAnimGif(transQRImg)

	# ファイル保存
	gifimages[0].save(outputPath,
                   save_all=True, append_images=gifimages[1:], optimize=False, duration=40, loop=0)
				   

テキストを表示する

textqr.gif
textqroutline.gif

人にも読めるQRコードを作ってみました。
人に読みやすいのと機械に読みやすいの、どっちがいいでしょうね。

テキストを表示するQR作成コード
makeQRTextGif.py
import sys
import qrcode
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import numpy as np
from enum import Enum
import os


# QRドット
DOT_T = 0  # 透明
DOT_B = 1  # 黒
DOT_W = 2  # 白
    

# ドットを反転
def inverseDot(img):
    size = img.shape[0]
    inv_img = np.full((size, size),False)
    for y in range(size):
        for x in range(size):
            inv_img[x,y] = not img[x,y]
    return inv_img



# QRコード情報取得
def getQRInfo(qrImg):
    size = qrImg.shape[0]
    
    ver = (int)((size-17)/4)
    
    if(ver == 1):
        ali = 0
    else:
        ali = (int)(ver / 7)+2
        
    return ver, size, ali


# x,yがファインダパターンまたはタイミングパターンかチェック
def checkMarker(qrInfo, x, y):
    ver = qrInfo[0]
    size = qrInfo[1]
    aliNum = qrInfo[2]
    
    markerSize = 7
    
    if x < markerSize+1 and y < markerSize+1:
        return True
    elif x < markerSize+1 and y >= size-markerSize-1:
        return True
    elif x >= size-markerSize-1 and y < markerSize+1:
        return True
    elif x == markerSize-1 or y == markerSize-1:
        return True
        
    return False


# x,yを中心にdotSizeで塗りつぶす
def fillCell(img, x, y, orgSize, dotSize, dot):
    cellSize = (int)(img.shape[0] / orgSize)
    offset=(int)((cellSize-dotSize)/2)
    for dy in range(dotSize):
        for dx in range(dotSize):
            img[x*cellSize + dx + offset, y*cellSize + dy + offset] = dot

    return img


# アライメントマーカーをdotSizeにする
def boldAlignment(img1, img2, dotSize):
    img3 = np.copy(img2)
    size = img1.shape[0]
    
    for y in range(6, size-6):
        for x in range(6, size-6):
            if img1[x-2, y-2] and img1[x-1, y-2] and img1[x, y-2] and img1[x+1, y-2] and img1[x+2, y-2] and img1[x-2, y-1] and img1[x+2, y-1] and img1[x-2, y] and img1[x+2, y] and img1[x-2, y+1] and img1[x+2, y+1] and img1[x-2, y+2] and img1[x-1, y+2] and img1[x, y+2] and img1[x+1, y+2] and img1[x+2, y+2]:
                if not img1[x-1, y-1] and not img1[x, y-1] and not img1[x+1, y-1] and not img1[x-1, y] and not img1[x+1, y] and not img1[x-1, y+1] and not img1[x, y+1] and not img1[x+1, y+1]:
                    dot = DOT_W            
                    if img1[x,y] == True:
                        dot = DOT_B
                    img3 = fillCell(img3, x-2, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x  , y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y-2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+2, y-2, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y-1, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+1, y-1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y-1, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y+1, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x  , y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+1, y+1, size, dotSize, DOT_W)
                    img3 = fillCell(img3, x+2, y+1, size, dotSize, DOT_B)

                    img3 = fillCell(img3, x-2, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x-1, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x  , y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+1, y+2, size, dotSize, DOT_B)
                    img3 = fillCell(img3, x+2, y+2, size, dotSize, DOT_B)
                        
    return img3



# QRコード周りのマージン追加
def addQRMargin(img, dotSize):
    size = img.shape[0]
    marginSize = dotSize*3
    img2 = np.full((size + marginSize*2, size + marginSize*2), DOT_W)
    
    for y in range(size):
        for x in range(size):
            img2[x+marginSize, y+marginSize] = img[x,y]

    return img2



# 1dotのQR作成
def makeOrgQR(message, minVer, ec):
    qr = qrcode.QRCode(
        version=minVer,
        error_correction=ec,
        box_size=1,
        border=0
    )
    qr.add_data(message)
    qr.make()
    qrimg = qr.make_image()

    img = np.array(qrimg)
#    qrimg.save('images/org_qr.png')

    # 黒をTrueにする
    img1 = inverseDot(img)
   
    return img1


# マーカー部分をLargeDotSizeで塗りつぶしそれ以外をSmallDotSizeで塗りつぶす
def fillMarkerDot(img1, LargeDotSize, SmallDotSize):
    qrInfo = getQRInfo(img1)

    QRSize = qrInfo[1]
    offset=(int)((LargeDotSize-SmallDotSize)/2)

    img2 = np.full((QRSize * LargeDotSize, QRSize * LargeDotSize), DOT_T)
    
    for y in range(QRSize):
        for x in range(QRSize):
            x2 = x * LargeDotSize
            y2 = y * LargeDotSize

            dotSize = SmallDotSize
            if checkMarker(qrInfo, x, y):
                dotSize = LargeDotSize
            dot = DOT_W
            if img1[x,y]==True:
                dot = DOT_B

            img2 = fillCell(img2, x, y, QRSize, dotSize, dot)
                
                    
    img3 = boldAlignment(img1, img2, LargeDotSize)
    return img3


# RGBAのイメージに変換
def makeTransImage(img):
    size = img.shape[0]
    transImg = np.empty((size, size, 4), dtype=np.uint8)

    for y in range(size):
        for x in range(size):
            if img[x,y] == DOT_W:
                transImg[x,y,0] = 255
                transImg[x,y,1] = 255
                transImg[x,y,2] = 255
                transImg[x,y,3] = 255
            elif img[x,y] == DOT_B:
                transImg[x,y,0] = 0
                transImg[x,y,1] = 0
                transImg[x,y,2] = 0
                transImg[x,y,3] = 255
            else:
                transImg[x,y,0] = 0
                transImg[x,y,1] = 0
                transImg[x,y,2] = 0
                transImg[x,y,3] = 0

                
    return transImg

def makeTransQR(largeDotSize, smallDotSize):
    # 1dotのQR
    img1 = makeOrgQR(message, 2, qrcode.constants.ERROR_CORRECT_H)
    
    # マーカーだけ大きく、それ以外小さくする
    img2 = fillMarkerDot(img1, largeDotSize, smallDotSize)

    # マージンを追加
    img3 = addQRMargin(img2, largeDotSize)

    # 透過イメージに変換
    img4 = makeTransImage(img3)

    return img4


# テキスト付きアニメーションGIF作成
def makeTextGif(transQRArray, normalQRArray):
    images = []

    width = transQRArray.shape[0]


    textsize = (int)(width/3) # 描画するテキストの大きさ

    font = ImageFont.truetype("/Library/Fonts/Arial Unicode.ttf", size=textsize)    # mac用
#    font = ImageFont.truetype("C:\Windows\Fonts\BIZ-UDGothicB.ttc", size=textsize) # windows用


    qrimg = Image.fromarray(transQRArray, 'RGBA')
    normalQRImg = Image.fromarray(normalQRArray, 'RGBA')

    testim = Image.new('RGB', (width, width), bgColor)
    testdraw = ImageDraw.Draw(testim) # 矩形の描画の準備
    len = testdraw.textlength(text, font=font)

    txw = (int)(len)
    txh = (int)(textsize)
    
    textmode = 2    # 1の場合見やすいモード、2の場合通常サイズのQRコードにアウトライン文字を表示

    for i in range(0, txw+width, textSpeed):

        if textmode == 1 :
            im = Image.new('RGBA', (width, width), bgColor)
        else :
            im = normalQRImg.copy()
    
        draw = ImageDraw.Draw(im) # 矩形の描画の準備
        left, top = (width - i, width/2 -textsize/2 - LargeDotSize*3)
        txpos = (left, top) # テキストの描画を開始する座標

        # テキストをtextcolorで描画
        if textmode == 1 :
            draw.text(txpos, text, font=font, fill=textcolor, stroke_width=10, stroke_fill='white')
        else :
            draw.text(txpos, text, font=font, fill='white', stroke_width=10, stroke_fill=textcolor)

        compositeImg = Image.alpha_composite(im, qrimg)

        images.append(compositeImg)

    return images


if __name__ == '__main__':

    args = sys.argv

    if len(args) < 3:
        print("args error!")
        print("usage: $ python3 %s [message] [text] [outputPath]" % (os.path.basename(__file__)))
        exit(0)
        
    
    message = args[1]
    text = args[2]
    outputPath = args[3]
    
    # マーカー用ドットサイズ
    LargeDotSize = 7
    # マーカー以外のドットサイズ
    SmallDotSize = 2

    textcolor = (0, 0, 0) # テキストの色(RGB)
    bgColor = (255, 255, 255) # 背景色
    textSpeed = 20 # テキスト速度


    # 透過QR作成
    transQRImg = makeTransQR(LargeDotSize, SmallDotSize)

    # 通常QR作成
    normalQRImg = makeTransQR(LargeDotSize, LargeDotSize)

    # テキスト付きアニメーションGIF作成
    gifimages = makeTextGif(transQRImg, normalQRImg)
    
    # ファイル保存
    gifimages[0].save(outputPath,
                   save_all=True, append_images=gifimages[1:], optimize=False, duration=40, loop=0)

おわりに

個人的に、今後QRコードはどんどん身近に、街中に溢れるようになると思っています。
ARが一般的になれば情報を含んだマーカーに。
IoT機器との連携。
商品パッケージに当たり前についているバーコードがQRに置き換わることは難しいとしても、一緒に印刷されることになる可能性はありそうです。
未来のサンタさん(自動配達ドローン)がプレゼントを持ってくるのに必要なのは、煙突ではなく自宅の玄関先にあるQRコードマーカーかもしれませんね。

というわけで皆様、メリークリスマス。

21
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?