AIによって芸術的なQRコードを作る方法
自分の記事一覧リンクです。よろしくお願いします。
AIによって芸術的なQRコードを作る方法
最近はStable Diffusionが画像生成で注目を集めていますが、昔の手法も便利なものがあります。Neural Style Transferは元の画像からスタイルを変換する方法として2016年に出た手法です。CVPR2021ではその手法を用いて芸術的なQRコードを作成できる手法が提案されました。今回はその論文の手法を用いてQRコードを生成したいと思います。
今回用いる手法の論文はこちら!
ArtCoder: An End-to-end Method for Generating Scanning-robust Stylized QR Codes
1. ArtCoderの概要
ArtCoderをすべて説明すると長くなってしまうため簡単に概要だけ説明します。
ArtCoderはNeural Style Transferと同様にStyle画像とContent画像からそれらを混同させた画像を生成します。その時にQRコードを読み取る関数によってその生成画像がQRコードとして読み取れるかをチェックします。そして、QRコードを読み取れる度合を損失関数にすることで学習可能にし、芸術的なQRコードを生成することができます。
Neural Style Transferは損失関数による制御が簡単なため、論理的な仕組みを組み込むことができます。そういった点ではStable Diffusionよりも思い通りの画像を生成できますね。
2. コード解説
今回の論文はgithubで公開されていたのでgoogle colabで使用できるようにしました。
ぜひ試してみてください!
google colabへのリンク
2.1 ライブラリインポート
PyQRCode,pypng
はQRコードを作成するときに使うライブラリです。
!git clone https://github.com/SwordHolderSH/ArtCoder
!pip install PyQRCode
!pip install pypng
2.2 実装コードの修正
githubの実装コードでは画像が正しく読み込めない場合があるため、それを修正するコードです。RGBモードで画像を読み取るという処理を追加しただけです。
#バグが残っているため修正
with open("/content/ArtCoder/utils.py", mode="r") as f:
utils_py = f.readlines()
utils_py.insert(15, " img = img.convert(\"RGB\")\n")
with open("/content/ArtCoder/utils.py", mode="w") as f:
utils_py = f.write("".join(utils_py))
2.3 ライブラリインポート
必要なライブラリをインポートしています。
import os
import sys
import pyqrcode
sys.path.append("ArtCoder")
from Artcoder import artcoder
2.4 QRコード作成
text
にQRコードに埋め込みたいテキストを入力してください。日本語は対応していますが、読み取る機種によっては文字化けが発生します。
#絶対パスで入力してください
QR_code_path = '/content/QRcode.png'
text = "ここにテキストを入力してください"
#日本語の場合
# text = text.encode('shift-jis')
# code = pyqrcode.create(text, error='L', version=5, encoding='shift-jis')
# QRコード作成
code = pyqrcode.create(text, error='L', version=5)
code.png(QR_code_path, scale=16, module_color=[0, 0, 0], background=[255, 255, 255], quiet_zone=0)
2.5 ArtQRコード生成
style_img_path
にはスタイル画像を、content_img_path
にはコンテンツ画像を入れてください。どちらも絶対パスにしてください。
その他、パラメータを変更することができます。
LEARNING_RATE
は学習率です。上げれば画像生成の速度は上昇しますが、安定性がなくなります。いじらなくてもいいと思います。
CONTENT_WEIGHT
はコンテンツの重要度を示しています。元画像をあまり変えたくない場合は高い値に設定しましょう。
STYLE_WEIGHT
はスタイルの重要度を示しています。元画像をスタイル画像に似せたい場合は高い値に設定しましょう。
CODE_WEIGHT
はQRコードの可読性の重要度を示しています。QRコードとして読み込みができないときはこの値をあげるといいかもしれません。
EPOCHS
は学習回数です。1000回ほどで綺麗な画像は生成できるためそこまで多くしなくても大丈夫です。
#絶対パスで入力してください
#スタイル画像のパスを設定してください。
style_img_path = "/content/ArtCoder/style/candy.jpg"
#コンテンツ画像のパスを設定してください。
content_img_path = "/content/ArtCoder/content/panda.jpg"
base_name_qrcode = os.path.splitext(os.path.basename(QR_code_path))[0]
base_name_style = os.path.splitext(os.path.basename(style_img_path))[0]
base_name_content = os.path.splitext(os.path.basename(content_img_path))[0]
output_dir = f"{base_name_qrcode}_{base_name_style}_{base_name_content}"
if not os.path.isdir(output_dir):
os.makedirs(output_dir)
artcoder(style_img_path, content_img_path, QR_code_path, output_dir,
LEARNING_RATE=0.01, # 学習率
CONTENT_WEIGHT=1e8, # 元画像の体裁を重視するか。高いと重視
STYLE_WEIGHT=1e15, # スタイル画像を重視するか。高いと重視
CODE_WEIGHT=1e12, # QRコードの可読性を重視するか。高いと重視
EPOCHS=2000, # デフォルト50000、2000で十分です。
Dis_b=80, Dis_w=180, Correct_b=40, Correct_w=220, USE_ACTIVATION_MECHANISM=1)
出力値のCode Lossは重要な値でQRコードとして読み取りができるかという指標になります。この値が小さい時の画像を使いましょう。
Epoch 420: Style Loss : 178332656.000000. Content Loss: 498704192.000000. Code Loss: 107.776451. Activated module number: 241.00. Discriminate_b:80.00. Discriminate_w:180.00.
Epoch 440: Style Loss : 170649280.000000. Content Loss: 496724608.000000. Code Loss: 98.646469. Activated module number: 236.00. Discriminate_b:80.00. Discriminate_w:180.00.
Epoch 460: Style Loss : 163712528.000000. Content Loss: 494811392.000000. Code Loss: 90.956947. Activated module number: 233.00. Discriminate_b:80.00. Discriminate_w:180.00.
Epoch 480: Style Loss : 157676256.000000. Content Loss: 492953728.000000. Code Loss: 79.331490. Activated module number: 227.00. Discriminate_b:80.00. Discriminate_w:180.00.
2.6 画像表示
こちらのコードで画像を表示できます。表示された画像は学習回数順に並んでいます。
import re
import numpy as np
from glob import glob
import cv2
from PIL import Image
from IPython.display import display
def sort(img_paths):
pattern = r'epoch=(\d+)'
epoch = [int(re.search(pattern, path).group(1)) for path in img_paths]
sorted_indices = np.argsort(epoch)
return [img_paths[idx] for idx in sorted_indices]
def tile(images, n=2):
images = np.array_split(np.array(images), n)
images = [[img for img in img_list] for img_list in images]
for _ in range(len(images[0]) - len(images[-1])):
images[-1].append(np.zeros_like(images[0][0]))
return images
img_paths = glob(os.path.join(output_dir, "*.jpg"))
#学習順に並び替え
img_paths = sort(img_paths)
#画像読み込み1/4のサイズに変更
images = [cv2.resize(cv2.imread(path), [148, 148])[:,:,[2,1,0]] for path in img_paths]
#余白がないとQRコードとして読み込まれない
images = [cv2.copyMakeBorder(img, 8, 8, 8, 8, cv2.BORDER_CONSTANT, value=(255,255,255)) for img in images]
# 画像を並べる
images = tile(images)
images = cv2.vconcat([cv2.hconcat(im_list_h) for im_list_h in images])
images = Image.fromarray(images)
display(images)
3. 注意事項
生成した画像は画像が大きすぎるとQRコードとして認識してくれません。ある程度画像サイズを小さくして表示し、余白を生成することで正しく認識するようになります。
また、コンテンツ画像とスタイル画像の色彩は似ているほうが生成がうまく行きます。生成がうまくいかない場合はstyle画像の色彩をコンテンツ画像に合わせてみてはいかかでしょうか?
下の画像は冨嶽三十六景をスタイル画像として自分のアイコンをQRコードにしてみた画像です。くすんだ部分と似ていないため変な風になっていますね。
以上になります。良き生成ライフを!
Discussion