途中からの備忘録なのでざっくり前置き(気が向いたら前置きに追加していく)
QRcodeのジェネレータ、リーダを
作成し、
VRchatと家のPCとで
やり取りをする試みです。
今回のQRcodeジェネレータ、
リーダー共に、
とりあえず次のように固定する。
- モデル2
- バージョン2
- エラー訂正レベルH
この条件は、
QRcodeの国際規格的に
2-H型
と呼ぶみたい。
今後は 2-H型 専門として
組み立てていくこととしよう。
2-Hでの決まり事は
次の通り(一部(全部書けし!
Masarinaっ!))
データコード数: 16
16コードワード
(1コードワード = 8ビット)が
データとして使用されます。
エラー訂正コード数: 28
28コードワード
(1コードワード = 8ビット)が
誤り訂正に使われます。
つまり、QRコード全体としては、
28コードワード分の
誤り訂正用のコードが含まれます。
RSブロック数: 1
この型ではRSブロックは1つだけです。
データとエラー訂正が
1つのブロックにまとめられています。
=== QRコード 2-H型の仕様まとめ ===
☆ 問題提起:
モデル2,
バージョン2,
エラーレベルH として
実装を進めてたら、
「あなたのRSブロックはこちらです」と、
タプル形式で次のようなものを
ChatGPTがわたしてきた。
『
RSブロックの詳細: (44, 16, 14)
』
...何この3つの文字!?
ーーーー
☆ 問題解消:
※ キーワード:
【コードワード】
ここでは、8bitの塊を
1コードワードと
呼んでいます。
RSブロックの詳細: (44, 16, 14)
44コードワードが
1つのブロックに含まれます。
これは、データとエラー訂正の
合計数です。
16コードワードはデータ用に使われます。
14コードワードはRS符号(誤り訂正用)に使われます。
ここで、エラー訂正コード数が
28コードワードなのに、
RSブロックで使われる
誤り訂正のコードワード数は14です。
これは
1つのRSブロック内で
2つの誤り訂正コードが存在する
ということを意味しています。
...自己流にすることにした...国際規格完全無視する...いつか再挑戦したい
=== QRコード 2-H型の仕様まとめ END ===
前置き終わり!
プロジェクト場所↓
https://github.com/masarina/DaijinAi
2024-09-26
- 備忘録開始
- リーダーを作成中。
- リーダー作成中に、ジェネレータのバグも出てきたので調整中
- ジェネレータのデバッグ、完了
2024-09-30
- リーダーを作成中。
- 新しいプレイヤーを作る。
- プレイヤーの機能
- 次の箇所を-11に置き換える
- 位置検出
- タイミング
- ダークモジュール
- 次の箇所を-11に置き換える
- とりあえずGPTに作成させたので、正しいか確認する(とくにパターン座標)
- 確認ですが、ジェネレータでは、{-1:黒, -2:白(非データ用), 0:白(データ用)}としています。
- リーダーでは、データ以外のピクセルは全て-11とします。({-11:データ部以外のピクセル})
- 位置検出、タイミング、ダークモジュール、それぞれ-11が代入され、フォーマット情報も前回-10として代入しているので、データを読み込む処理に移ります。
- プレイヤーの機能
2024-10-01
- データの読み込み方の考案
- 右から2列ずつ取り込む
- 新しいshape(25*n, 2)のマトリックスを作成する。
- 取り込み時、取り込み回数n回目の取り出した行列は、上下180°回転 if (n % 2 != 0) else そのままとする。
2024-10-03
- n*2の2次元リストを読み込むプレイヤーの追加完了
- l_RSXor....の、でコードバージョンのプレイヤーの作成開始。
- そのまえにジェネレーターの方の各プレイヤーのプレイヤーからプレイヤーへの受け渡しの実装が未実装だったため、全てを訂正する作業を開始する(マジでだるい)
2024-10-08
- ジェネレーターのプレイヤー同士のデータの受け渡しの修正と、デバッグの続き
2024-10-10
- 次は RSBlockAndPolynomialPlayer の受け渡しの調整。
2024-10-12
- 続き。
- だが、足りない実装を見つけた。
- PolynomialDivisionPlayer
- このメインの最初に、bitデータを10進数に変換するメソッドが必要。
- この10進数は、体が256の10進数にするべきであり、、、、(持ち越し)
mode + 文字数情報 + データ + パディング(4bit基準) + パディング(8bit基準)
2024-10-16
- PolynomialDivisionPlayer のデバッグ → 完了
- RSFirstCoefficientDivisionPlayer のデバッグ
- ここでmainメソッドで入力として扱っている変数は次の通り。
- f_x
→ ここで言うf(x)とは、今まで作ってきた、
- f_x
- ここでmainメソッドで入力として扱っている変数は次の通り。
mode + 文字数情報 + データ + パディング(4bit基準) + パディング(8bit基準)
の8bit群
[00101111,11000111,.....(テキトー)]
を、
10進数(256新数的に)に直した
[16,57,17、、、(テキトー)]
というリストのことを言うんだってさ。
このリストをひとつの関数と見るのです。
リストを"関数"として観るという感覚は、新鮮だが、
ゼロから作るDeepLearning1(...か2 ...多分1...というか齋藤さん!...斎藤...さん、、?さいとうさん。ごめんなさい。)
では、ベクトル、2次元データ、3次元データ(テンソル)以上のデータは、ドット積やハダマード積や、足し算のように使うことがある。つまり、
"なにかの値を別の値へ変化させるモノ"
として捉えることが出来るため、ベクトルやテンソルは、1つの関数として数学では扱われることがある。
などと話されていた。
恐らくこのQRcodeにおけるガロア体(モジュロ値256)において、ベクトルのデータをf(x)
と、呼んでいるのは、そういうことなのかもしれない。
- g_x_polynomial
→ 生成多項式g(x)
(ここでもg(x)という関数として捉えるようですね。)
- alpha_exp_table → aのべき乗テーブル
えっと、f(x)は、これまで作ってきたデータの10進数(256進数)。
alpha_exp_tableは、aのべき乗テーブル。
あれ、するとg(x)は、、なんだっけ、、はは
(・・・復習)
g(x)は、エラー訂正コードの生成に必要な、"生成多項式"と呼ばれるもの。
- リードソロモンブロック
- 生成多項式
- g(x)
これらは同じモノを指しているのね。
rs_brocksはpolynomialDivisionPlayerで用意したので、今回は次の様な感じで呼び出しです。
f_x = self.one_time_world_instance.polynomialDivisionPlayer.mode_charNumInfo_data_pad4_pad8_Decimal_list # (mode+文字数情報+データ+4bitパディング+8bitパディング のガロア基準の10進数リスト
g_x = self.one_time_world_instance.polynomialDivisionPlayer.rs_blocks # 生成多項式g(x)
alpha_exp_table = self.one_time_world_instance.galoisFieldPlayer.exponent_table # αのべき乗テーブル
- 次のプレイヤーは
RSXorCalculationPlayer
というプレイヤーだ。
このプレイヤーに対応できるように
RSFirstCoefficientDivisionPlayer
を調節しよう。- まずは、RSXorCalculationPlayer が何をしているプレイヤーなのか再確認をしよう(というか全く覚えていない)
ので、リードソロモン暗号を求める流れを今一度確認する。リード・ソロモン符号エンコードの手順
リード・ソロモン符号を使ってエラー訂正データを生成する流れは、次のようなステップで進むわ:ステップ1: データの準備
f(x) として扱うQRコードのデータ(モード + 文字数情報 + データ + パディング)を準備する。
これは10進数リストとして表現されていて、QRコードの基本データ部分。
→ りな、ここはもうできているね! f(x) のデータは準備完了!ステップ2: 生成多項式(g(x))の取得
g(x)、つまりリード・ソロモン符号用の生成多項式を取得する。これがエラー訂正コードを生成するために使われる多項式だよ。
バージョンやエラー訂正レベルに応じた生成多項式を使う。
→ ここもOK! 生成多項式 g(x) はすでに取得してあるね。ステップ3: f(x)の最初の項とg(x)の演算
f(x)の最初の項をαのべき乗に変換して、それをg(x) に掛ける。
これで、f(x) の最初の項と生成多項式 g(x) の係数を使って新しい多項式を計算する。
→ これもOK! RSFirstCoefficientDivisionPlayerで実行済み!ステップ4: XOR演算(排他的論理和)
f(x) と g(x) をXORで処理し、新しいf(x)'を生成する。
XORは、対応する係数同士を排他的論理和で計算して、多項式を更新する作業よ。
→ これが次に実行しようとしているRSXorCalculationPlayerの役割ね。f(x) と g(x) を使ってXOR演算を行い、新しいf(x)' を作ることで、次のステップに進む準備をしているわ。ステップ5: 次のステップに進む(ループ処理)
このXOR演算を行った後、新しいf(x)' が次のステップでまたg(x)と演算され、これを繰り返していく。
最終的には、エラー訂正コード(リード・ソロモン符号)が生成される。
→ ここが次の段階!XOR演算を何度も繰り返すことで、エラー訂正コードが最終的に完成するのよ。今、りなができていることまとめ
- f(x)(データ多項式)は作成済み。
-
- g(x)(生成多項式)は取得済み。
-
- f(x)の最初の項とg(x)の演算は完了。
-
- XOR演算をする準備が整っている(これが次のステップ)。
-
RSXorCalculationPlayerの役割
- このプレイヤーは、f(x)とg(x)をXORで演算するプレイヤーだよ。具体的には、リード・ソロモン符号の中で、データ多項式f(x)と生成多項式g(x)の対応する係数をXORで処理して、新しいデータ多項式f(x)'を作るんだ。
-
リード・ソロモン符号の完成にはもう少し!
- 今の段階でリード・ソロモン符号のエラー訂正コードはまだ完成していないけれど、もうすぐよ!XORの演算を繰り返すことで、最終的にエラー訂正用のコードワードが完成するわ。
- まずは、RSXorCalculationPlayer が何をしているプレイヤーなのか再確認をしよう(というか全く覚えていない)
2024/10/17
- 一旦、頭をリセットする。色々、理解を改める。間違っていることもある。
まずは、エラー訂正コードの発行の、
f(x)
g(x)
aのべき乗
この3つを用いた、エラー訂正コードの算出についてをあらためて、仮の例を上げて、次のように確認をした。
f_x = [1, 2, 3]
g_x = [4, 5, 6]
a_f = [2, 4, 8] # αのべき乗テーブル
new_g_xs_list2d = [] # 新しいg(x)を保存するリスト
new_f_xs_list2d = [] # 新しいf(x)を保存するリスト
XOR = functions.XOR # XOR演算を行う仮想関数
i = 0 # iをループの外で定義して初期化
while len(f_x) > 0 and len(f_x) >= len(g_x): # f(x)がg(x)より短くなるか、要素がなくなるまで繰り返す
""" 新しいg(x)を作る """
new_g_x = []
for j in range(len(g_x)): # g(x)分ループ
# αのべき乗をg(x)の各項に掛ける (ガロア体の乗算)
new_g_x.append((g_x[j] + a_f[i]) % 255) # i番目のαのべき乗を使う(例: α^1 = a_f[i])
""" 新しいg(x)' が完成 """
""" 新しいf(x)' を作成 (XOR演算) """
new_f_x = []
for j in range(len(f_x)): # f(x)分ループ
# f(x) と 新しいg(x)' を XOR で処理
new_f_x.append(XOR(f_x[j], new_g_x[j])) # j,j
""" 新しいf(x)' が完成 """
""" f(x)の最高次項が0なら、その項を削除してf(x)を短くする """
if new_f_x[0] == 0:
new_f_x.pop(0) # 最高次項が0なら、削除して次に進む
""" 保存 (新しいf(x)とg(x)を保存して、次のループで使用) """
new_f_xs_list2d.append(new_f_x)
new_g_xs_list2d.append(new_g_x)
""" f(x)を次の新しいf(x)に更新してループを続ける """
f_x = new_f_x # 更新して次の繰り返しで使う
""" インデックスを更新 """
i += 1 # 次のべき乗を使うためにiをインクリメント
2024-10-18
- もう嫌だ…リードソロモン暗号いやだ…
- Checksum を実装することにする。
mode
文字数情報
データ
チェックサム
この4つでオリジナルのQRcodeとして実装を進めていく。
-
リードソロモン暗号のエラー訂正コード関連のプレイヤーである、
g, h, i, j, k, l
この6つのプレイヤーは使用しないこととする。
尚、jのみ、10進数に変換するメソッドの利用をもとめ、ライブラリとして使用中(2024-10-18) -
パディングもやめることにする
そのため、プレイヤーe, fも実行しないこととする。 -
使用しないプレイヤー
使用しないプレイヤーの代わりに、
プレイヤーefghijklを用意する。 -
a ~ l を、bitジェネレータモードとしよう。
2024-10-20
-
何していたかを思い出す
QRcodeやめたんだ。使わないプレイヤーをマークして、ほかの代わりのプレイヤーを用意したんだった -
プレイヤー同士の変数の連携を確認する。
- ChecksumPlayer
▶︎ QRCodeMapInitializerPlayer
この受け渡し箇所を改良する - QRCodeMapInitializerPlayer
▶︎ QRCodePositionDetectionPlayer
この受け渡しを改良する - QRCodePositionDetectionPlayer
▶︎ QRCodeTimingPatternPlayer
この受け渡しを改良する - QRCodeTimingPatternPlayer
▶︎ QRCodeAlignmentPatternPlayer
この受け渡しを改良する - QRCodeAlignmentPatternPlayer
▶︎ QRCodeMarkingPlayer
この受け渡しを改良する - QRCodeMarkingPlayer
▶︎ WriteMainDataPatternPlayer(このプレイヤーで最後!)
この受け渡しを改良する - WriteMainDataPatternPlayer
▶︎ QRCodeMaskApplicatorPlayer(いらないぷれいやーじゃんっ(下参考))
この受け渡しを改良、、あっ、マスクパターンもやめよう。ダミーとして新しいプレイヤーと置き換えなくちゃ。
- ChecksumPlayer
-
1度まとめてみる、、
- データの形式
- モード、文字数情報、データ
- マスクパターン
- なし
- エラー補正、15ビット情報
- なし
なので、プレイヤーs(マスク)もt,u(15ビット情報)も不要。
つまり、rが最後のプレイヤーとなる(生成において)のですね。 - データの形式
-
リーダーの作成に移ろう。
すこし進めてあるから、、1度状態確認しよう。- ジェネレータはプレイヤーrが最後。
- あっ、、リーダーは、過去の自分は『デコード』と読んでいるみたいなので、以降デコードと呼ぶことにする。
- デコードプレイヤーaは15ビット部分に-10をマーキングするプレイヤーなので、不要。
- デコードプレイヤーbは位置検出パターン、 タイミングパターン、ダークモジュールに-11としてマーキングするプレイヤーなので必要(全てのプレイヤー完成後、好きな模様に改造予定)
あれっ、アライメントパターンは? - c,dは読み取るプレイヤーなので必要。
- eはリードソロモンの逆元を利用して戻すプレイヤーなので、不要。
なので、必要なのはbのみ。
bの次に、デコードするチェックサムプレイヤー実装しなきゃ。
aに、画像スキャン結果を取ってくるプレイヤーの戻値を渡すコードもいれないと。
2024-10-21
- 受け渡しの実装の続き
-
プレイヤーCentralSquareReaderPlayerに、プレイヤーr(WriteMainDataPatternPlayer)の戻り値を受け取るコードを書、、
間違えた、いらないや。 -
プレイヤーcentralSquareReaderPlayer
▶︎ TrapezoidCorrectionPlaye
の受け渡しの実装 -
プレイヤーTrapezoidCorrectionPlayer
▶︎ PatternDetectionPlayer
の受け渡しの実装 -
TrapezoidCorrectionPlayer
▶︎ PatternDetectionPlayer
の受け渡しの実装 -
Decode_a_FormatInfoCatcherPlayerDir() は今回使用しない。
-
PatternDetectionPlayer(スキャンの最後のプレイヤー)
▶︎ replacePatternPlayer(デコードのプレイヤーb。aは15ビット読み取り専用のなのでパス(今回ジェネレータ時に15ビット部分の実装していないので。)。このプレイヤーでは各パターン部分を-11でマーキングしています。)
の受け渡しの実装 -
ReplacePatternPlayer
▶︎ ColumnSplitterConcatPlayer (データ部分を n*2 の2次元リストとして抜き取るプレイヤー)
の受け渡しの実装 -
ColumnSplitterConcatPlayer
▶︎ RightBottomReaderPlayer
の受け渡しの実装
…と、行きたいのだが
RightBottomReaderPlayer で過去の自分が何やら
次のようなメッセージを残している
-
import os, sys
sys.path.append("../..")
from Players_CommonPlayers.SuperPlayerDir.SuperPlayer import SuperPlayer
""" ATTENTION
ただのテンプレートです!
"""
class RightBottomReaderPlayer(SuperPlayer):
def init(self):
super().init() # スーパークラスの初期化メソッドを呼び出す
self.my_name = None
なので、このプレイヤーをしっかり確認してから進めよう。
1つ前のプレイヤーでは、(25,25)のコードから、データ部分のみを抜き取り、 (n,2) の新しい2次元リストとした。
このRightBottomReaderPlayerプレイヤーでは、この新しい (n,2) の2次元リストを右下から読み込み、ことが求められるプレイヤーになるはずです。
面倒ですので、GPTに投げてこのプレイヤーにテキトーな入力を入れて、出力をしてもらおう。
2024-10-22
- RightBottomReaderPlayer の確認の続き。GPTに検証してもらった結果、出力が思っている値の逆値を出した。コード中の比較演算子が真逆でしたので、修正。
Before
""" 右, 左の順に格納。負の数である場合は格納しないでパスする。 """
# 右から見る。
if 0 > pair[1]: # 0以上であれば
read_list.append(pair[1]) # 登録
else:
pass
# 次に左を見る。
if 0 > pair[0]: # 0以上であれば
read_list.append(pair[0]) # 登録
else:
pass
After
""" 右, 左の順に格納。負の数である場合は格納しないでパスする。 """
# 右から見る。
if 0 <= pair[1]: # 0以上であれば
read_list.append(pair[1]) # 登録
else:
pass
# 次に左を見る。
if 0 <= pair[0]: # 0以上であれば
read_list.append(pair[0]) # 登録
else:
pass
- ColumnSplitterConcatPlayer
▶︎ RightBottomReaderPlayer
の変数の受け渡しの修正
- この時点で、RightBottomReaderPlayer
の次に用意されているプレイヤーは
RSXorCalculationPlayer
だけれども。
リードソロモンの実装はジェネレータの方で実装していないので、このプレイヤーは不要となる。
そしてこのプレイヤーRSXorCalculationPlayerが、デコーダプログラム組み込み途中で、ジェネレータのデバッグに移行した時の、、その時のプレイヤーなので、これ以降のプレイヤーはここから作っていくことになる。ようやくジェネレータのデバッグに移行した地点に追いついた。 - RightBottomReaderPlayer のつぎのプレイヤーを作ろう。
つぎのプレイヤーは、8ビットごとに区切るプレイヤーとしよう。- 例
入力:[1000110000001111]
出力:[1000, 1100, 0000, 1111]
- BitDataProcessorPlayer のつぎのプレイヤーを作ろう
- つぎのプレイヤーはチェックサムの値をテストするプレイヤーにしよう。 - 1要素8ビットのリストを10進数に戻す。
- いいえ、一旦手を止める
画像から摘出した25*25の行列を、n*2にする所から、コードを確認しよう。なんだか間違っている気がしてしょうがないので。 - ColumnSplitterConcatPlayer のかくにん。
- 例
2024-10-23
- 続き。
- ColumnSplitterConcatPlayer のかくにん。
インデックスが反対だったり、様々な修正。- RightBottomReaderPlayer の確認。
- ここで1度、スキャンプレイヤーa,b,cについて。
- プレイヤーの確認に戻る。
- BitDataProcessorPlayer
あ、昨日作ったやつですね。OKです。
そしたら、チェックサムの確認をするプレイヤーが、次にくるべきでしょうか。
- BitDataProcessorPlayer
- チェックサムの確認をするプレイヤーの作成。
ジェネレータ時のチェックサムのプレイヤーは次のようでした。流れとしては、import os, sys sys.path.append("../..") from Players_CommonPlayers.SuperPlayerDir.SuperPlayer import SuperPlayer class ChecksumPlayer(SuperPlayer): def __init__(self): super().__init__() # スーパークラスの初期化メソッドを呼び出す self.my_name = None # 必ずNoneで初期化 self.self.mode_charNumInfo_checksum_bitlist = None self.loop11101100and00010001pad_only_list = None def return_my_name(self): return "ChecksumPlayer" def calculate_checksum(self, data_list): """ データリストからChecksumを計算する :param data_list: 数値のリスト :return: Checksum値 """ return sum(data_list) % 256 def append_checksum(self, data_list, checksum): """ データリストにChecksumを追加する :param data_list: 数値のリスト :param checksum: 計算されたChecksum :return: Checksum付きのデータリスト """ return data_list + [checksum] def print_data(self, data_list, checksum, data_list_with_checksum): """ データリスト、Checksum、Checksum付きデータリストを出力する """ print("元のデータリスト:", data_list) print("Checksum:", checksum) print("Checksum付きデータリスト:", data_list_with_checksum) def main(self): """ mainメソッドで全てのメソッドを実行 """ """ 入力 """ woP = self.one_time_world_instance.qRCodeBitConversionPlayer self.data_str = woP.data_bits # データのみ self.mode_charNumInfo_bitlist = woP.mode_and_countinfo_bit + self.data_str to_decimal = self.one_time_world_instance.polynomialDivisionPlayer.bit_list_to_decimal_list # to10bitメソッド """ メイン """ # 任意のデータリスト mode_charNumInfo_decimallist = to_decimal(self.self.mode_charNumInfo_bitlist) # Checksumの計算 checksum = self.calculate_checksum(mode_charNumInfo_decimallist) # Checksumをデータに追加 self.mode_charNumInfo_checksum_bitlist = self.append_checksum(self.mode_charNumInfo_bitlist , checksum) """ 出力 """ self.mode_charNumInfo_checksum_bitlist """ プレイヤー自身を更新 """ self.one_time_world_instance.checksumPlayer = self return "Completed"
- 10進数化
- チェックサムの計算
- チェックサムをデータに追加
でした。
今回は、
- 10進数化
- チェックサムの計算(== A)
- A と、コード中のチェックサムが等しいか確認する
とすることで、1/255の確率で外れてしまいますが、254/255%の確率で、正しく読み取れているとします。
今回プレイヤーのコードは次の通りです。(余談:U#に持っていく時、大量のインスタンスを作成するだけのオブジェクトが必要になりそうですね。)import os, sys sys.path.append("../..") from Players_CommonPlayers.SuperPlayerDir.SuperPlayer import SuperPlayer class ChecksumCheekPlayer(SuperPlayer): def __init__(self): super().__init__() # スーパークラスの初期化メソッドを呼び出す self.my_name = None # 必ずNoneで初期化 self.png_file_path = None # 写真のパス self.processed_data = None # 8bit毎にスプリットしたデータ encode_time_check_code = None # encode時のチェックサム def return_my_name(self): return "ChecksumCheekPlayer" def main(self): """ このメソッド実行直前に、 one_time_world_instanceに、最新のworldインスタンスを代入しています。 """ """ 入力 """ woP = self.one_time_world_instance.bitDataProcessorPlayer self.png_file_path = woP.png_file_path # 写真のパス self.processed_data = woP.processed_data # 8bit毎にスプリットしたデータ encode_time_check_code = self.processed_data.pop() # encode時のチェックサムを取り除く。 woC = self.one_time_world_instance.checksumPlayer """ メイン """ # 任意のデータリスト mode_charNumInfo_decimallist = woC.to_decimal(self.processed_data) # Checksumの計算 decode_time_checksum = woC.calculate_checksum(mode_charNumInfo_decimallist) # チェックサムが正しいか確認 if encode_time_checksum != decode_time_checksum: # もしチェックサムが正しくない場合、commonプレイヤー以外の実行を拒むフラグを立てる。 # ↪︎ KBprojectの方で実装したので、その方法をここでも使用しよう。 """ 出力 """ self.png_file_path # 写真のパス self.processed_data # 8bit毎にスプリットしたデータ # 自身のインスタンスを更新 self.one_time_world_instance.checksumCheekPlayer = self return "Completed"
- ColumnSplitterConcatPlayer のかくにん。