QRコードを解読する:エラーはどうやって直る?リード・ソロモン符号の基本 【第1回】

コンビニのレジや街中のポスターなど、私たちの生活に欠かせないQRコード。一部が汚れていたり、シールが貼られていたりしても、スマホのカメラで問題なく読み取れますよね。
実はQRコードの中身は8bit(1 byte 0~255)を1単位として情報を埋め込んでいます。

なぜ、失われたデータを「元通りに復元」できるのでしょうか?

今シリーズでは、その裏側で活躍している最強のエラー訂正アルゴリズム「リード・ソロモン符号」の仕組みを理解していきましょう。

QRコードを理解する。CRCと比較しながら、リードソロモン符号の操作の概要を理解しよう

1. エラーを「見つける」と「直す」の大きな違い

まず、データのエラー処理には大きく分けて2つのフェーズがあります。

  1. エラー検出(見つける): データが書き換わっていないかチェックする
  2. エラー訂正(直す): 壊れたデータを元の正しい姿に復元する

いきなりリード・ソロモン符号から話してもよいのですが、ガロア体(2^nを1つの単位とするちょっと特殊な体)など先に説明すべき概念が多いため、ここで少し寄り道をして、処理の手順が似ていて理解しやすい技術と比較してみましょう。

コラム:通信の番人「CRC」とQRコードの「リード・ソロモン」

ネットワーク通信やファイルの圧縮(ZIPやpngなど)では、CRC(巡回冗長検査)という技術がよく使われます。CRCは「データが壊れているか、いないか」を非常に高速に見つけるのが得意です。

CRCの仕組みは単純で、あらかじめ決めた生成多項式(これはIEEEとかの規格でいくつかよく使われるものが決まっています)を用いて割り算を行いその余りを計算します。
この余りを元のデータにくっつけて送信し、受け取った側で同じ生成多項式で割ります。すると、もともとの数+余りなので正確に届いていれば余りは0になるため、この余りを見て誤りかどうかを判定します。

CRCの計算の例

ここでは、単純な具体例を使って実際にCRCの計算を追ってみましょう。

※CRCの計算(Mod 2演算)では、足し算・引き算の代わりにすべてXOR(排他的論理和:同じなら0、違うなら1)を使います。

1. 送信データと生成多項式の定義

まず、送りたい元のデータと、割り算の基準となる「生成多項式」を定義します。今回は計算をシンプルにするため、3次の生成多項式(4ビット)を使用します。

  • 送るデータ \(M(x)\): 1011
    \[M(x) = x^3 + x + 1\]
  • 生成多項式 \(G(x)\): 1101
    \[G(x) = x^3 + x^2 + 1\]

※送るデータは二進数の値として、多項式計算したいので、\(n\)桁目に\(x^n\)がかかった多項式として扱います。

2. CRC(余り)を生成してみる

データを多項式で割る前に、あらかじめ生成多項式の次数(今回は3次)の分だけ、データに「0」を付け足して桁シフトさせます。

  • シフト後のデータ: 1011000
    \[M(x) \cdot x^3 = x^6 + x^4 + x^3\]

このシフトしたデータを、生成多項式 1101 で割り算(XOR)して余りを求めます。

    1110  <- 商
  --------
1101 | 1011000  <- シフト後のデータ
       1101
       ----
        1100
        1101
        ----
         0010
         0000
         ----
           1000
           1101
           ----
            101 <- 余り(これがCRC!)

数式で表すと、余り \(R(x)\) は以下のようになります。
\[R(x) = x^2 + 1 \quad (\text{バイナリ表現で } 101)\]

3. 送るデータ(送信フレーム)の作成

元のデータの末尾に、先ほど計算した余り(CRC)をそのままくっつけます。これが最終的にネットワーク等へ送信されるデータ \(T(x)\) になります。

  • 送信データ \(T(x)\): 1011 + 101 = 1011100
    \[T(x) = x^6 + x^4 + x^3 + x^2\]

4. 受信時のチェック(正常な場合)

受信側は、受け取ったデータ 1011100 を、送信側と同じ生成多項式 1101 で割ります。途中でエラーが起きていなければ、必ず「余りは 0」になります。

      1111  <- 商
  --------
1101 | 1011100  <- 受信データ
       1101
       ----
        1101
        1101
        ----
         0000
         0000
         ----
            000
            000
            ----
              0 <- 余り 0!(誤りなし)

余りが 0 になったため、受信側は「このデータは壊れていない」と判定できます。

5. 受信時のチェック(誤りがある場合)

では、通信の途中でノイズが乗り、データが1ビット化けてしまったとします。(例:1011100 の左から4番目が反転して 1010100 になった場合)

  • 誤りのある受信データ \(T'(x)\): 1010100
    \[T'(x) = x^6 + x^4 + x^2\]

これを同じように 1101 で割ってみます。

      1101  <- 商
  --------
1101 | 1010100  <- 誤りのある受信データ
       1101
       ----
        1111
        1101
        ----
         0100
         0000
         ----
          1000
          1101
          ----
           101 <- 余りが 0 ではない!(エラー検知)

数式上の余りは以下のようになります。
\[R'(x) = x^2 + 1 \neq 0\]

このように、割り切れない(余りが 0 ではない)という結果が出ることで、受信側は「データがどこかで破損している」と確実に見抜くことができるのです。

一方、QRコードは印刷物なので「もう一度送って」ができません。そこで、自力で傷や汚れを修復できる「リード・ソロモン符号」が採用されているのです。実は、この2つは「メッセージを数式に変換して、ある式で割り算した『余り』をデータにくっつける」という計算の基本アイデアが非常によく似ています。

2. CRCの「進化版」:リード・ソロモン符号の大まかな概念

先ほどのコラムで、CRCは「データを数式(多項式)にして割り算し、その『余り』をくっつけて送る。受け取った側でもう一度割り算して、余りが0ならエラーなし」という仕組みだと説明しました。

実は、QRコードで使われる「リード・ソロモン符号」も、根本的なアプローチはCRCと全く同じです。

では何が違うのか?
CRCは、高速に且つ正確にエラーを見つけられる一方、復元することはできません。しかし、リードソロモン符号では、誤っている部分を見つけて復元できます。

リードソロモン符号でのエンコードとデコードの流れを見ていきましょう。

  • CRC:エラーがあることを正確に検知する
  • リードソロモン符号:エラーを見つけ復元できるなら復元する。

① データを「多項式」に変身させる(CRCと同じ)

CRCでは 1011 のようなビットの並びを多項式にしましたが、リード・ソロモン符号ではもう少し大きなデータの塊(例えば「5, 2, 9」といった数値の羅列)を多項式の「係数」として扱います。

送りたいデータが [ 5, 2, 9 ] なら、次のように \(x\) を使った数式を作ります。

\[f(x) = 5x^2 + 2x + 9\]

② 魔法の割り算で「パリティ(余り)」を作る(CRCと同じ)

CRCと同じように、あらかじめ決めておいた特定の数式「生成多項式」で、元のデータを割り算します。そして、計算して出てきた「余り」を、エラー訂正用のデータ(通称:パリティ)として元のデータの後ろにくっつけます。

  • 送信するデータ = [元のデータ] + [パリティ(余り)]

③ 受信側のチェックと「シンドローム」の計算

受信側(スマホのカメラ)は、届いたデータ全体を再び同じ生成多項式で割り算します。

  • 汚れや欠損がなければ、CRCと同じく「きれいに割り切れて余りは 0」になります。
  • しかし、QRコードに汚れ(エラー)があると、割り切れずに「0ではない何か別の余り」が出てきます。

この「0ではない余り」から抽出される、エラーの兆候を表す特別な値のことを「シンドローム(Syndrome:症候群)」と呼びます。

④ 余りから「エラーの場所と値」を特定する

CRCはここで諦めますが、リード・ソロモン符号はここからが本番です。

出てきた「シンドローム」を解析することで、以下の2つを特定する数式を作り出します。

  1. 誤り位置多項式: 「データの何番目が壊れているか?」を突き止める
  2. 誤り評価多項式: 「そこが元の正しい数値から、どれくらいズレているか?」を突き止める

この2つが分かれば、壊れた箇所にズレた分を足し引きするだけで、データは完全に元通り(復元)になります!

3.シンプルシミュレーター

余り計算シミュレータです。

情報(数字)を入れて”割り算を実行”を押すと(1, -2) g(x) = x – 2を生成多項式として余りを実数空間で計算できます。

※情報を大きい値(例:データ1を‘9’にする)にすると余りが\(2^8 = 255\)を超えることを確認してみてください

多項式変換とパリティ生成シミュレーター
(普通の数学バージョン)

5つの数字を入力して、生成多項式 g(x) = x – 2 で割り算し、パリティ(余り)を求めてみましょう。

① データを多項式にして1つシフト(xを掛ける)

② 係数の割り算(筆算): ÷ (1, -2)



    

3.1 復元例 正しく届いたとき [1, 5, 2, 7, 4 とパリティ 164

初期値の1, 5, 2, 7, 4 とパリティ 164 が正しく届いたとします。生成多項式はg(x) = x-2です。

受信側は以下の計算を行います。生成多項式の解は(x =2)です。

  1. データを多項式にして1つシフトする(送信側と同じ式を作る)
    \[1x^5 + 5x^4 + 2x^3 + 7x^2 + 4x\]
  2. \(x = 2\) を代入して計算する
    \[1(32) + 5(16) + 2(8) + 7(4) + 4(2)= 32 + 80 + 16 + 28 + 8 = \mathbf{164}\]

自分で計算した結果(164)と、送られてきたパリティ(164)を引き算します。
\[164 – 164 = 0\]
この引き算の結果をシンドローム(Syndrome)と呼びます。シンドロームが 0 になったため、受信側は「通信途中でデータは壊れていない!」と確信できます。

3.2 もし破損していたら? [ 1, 5, 8, 7, 4 ] とパリティ 164

では、通信の途中でノイズが乗り、3番目のデータ 28 に化けてしまったとしましょう。

受信側には [ 1, 5, 8, 7, 4 ] とパリティ 164 が届きます。

受信側が同じように \(x = 2\) を代入して計算するとどうなるでしょうか?

  • 届いたデータでの計算結果:\(1(32) + 5(16) + \mathbf{8(8)} + 7(4) + 4(2) = \mathbf{212}\)

自分で計算したパリティは 212 になりました。送られてきたパリティは 164 です。
\[シンドローム = 212 – 164 = 48\]

シンドロームが 0 ではない(今回は 48 ズレている)ため、受信側は「どこかにエラーがある!」と瞬時に見抜くことができます。

3. どうやってデータを直すのか?(エラー訂正)

ここからがリード・ソロモン符号の肝となる部分です。ズレの量であるシンドローム 48 から、元の正しい数字を逆算します。
しかし今回は1しか訂正符号がないので位置と誤った値を復元することはできません。n個の未知数を求めるためには、少なくともn個の方程式が必要だからです。

今回のエラーを直すには、以下の2つの未知数を特定しなければなりません。

  1. どこが間違っているか(エラーの位置)
  2. どれくらいズレているか(エラーの大きさ)

しかし、今回送ったパリティは 1641つだけです。ヒントが1つしかないので、「どこが」「どれくらい」の両方を当てることは数学的に不可能です。(※そのため、実際のQRコードではパリティを複数個生成します)

「汚れの場所」が分かっていれば、1つでも直せる!(消失訂正)

もし、「QRコードの3番目のマスに泥の汚れがついている」など、エラーの位置があらかじめ分かっている場合はどうでしょうか?

未知数が「どれくらいズレているか」の1つだけになるため、パリティが1つでも復元できます!

  1. エラーの位置の重みを確認する今回は \(x^3\) の位置(8の重みを持つ場所)が壊れていると分かっています。
  2. ズレの量を重みで割る全体のズレ(シンドローム)が 48 で、そこの重みが \(2^3 = 8\) です。\(48 \div 8 = 6\)つまり、「その場所の数字が、本来より 6 大きくなっている」ことが判明します。
  3. データを復元する受け取った間違ったデータ 8 から 6 を引きます。
    \[8 – 6 = 2\]

見事に元のデータ 2 が復元されました!

実際のQRコードでは、
1. 画像処理的に消失訂正(消失している位置の推定)を推定
2. 誤り訂正レベルに応じた位置と誤り訂正のための符号

これらを複合的に利用して破損したデータを復元しています。

次回:シンドロームとガロア体

さて、上のシミュレーターを触ってみて、鋭い方はある「致命的な問題」に気がついたかもしれません。

「普通の数式で掛け算や割り算を繰り返すと、数字(余り)がどんどん巨大になってしまうのでは?」

冒頭で触れたように、QRコードの中身は「8ビット(0〜255)」を1単位としています。 限られた白黒マスの中にデータを収めなければならないのに、計算途中で 100050000 といった巨大な数字が出てきては枠に収まりきらず、情報に合わせてQRコードのサイズを巨大にしないといけなくなってしまいます。これでは、urlとか長い情報を埋め込もうとしたら200~*200~サイズとかを超える巨大なサイズの二次元コードを用意しないといけなくなり実用的ではありません。

そこで、リード・ソロモン符号では普通の足し算や掛け算を捨て、「どんなに計算しても、絶対に0〜255の範囲に収まる特殊な数学の世界」を使います。

それが「ガロア体(Galois Field)」です。

次回(第2回)は、プログラミングでQRコードを理解する上で最大の壁となる「ガロア体」について解説します。「足し算と引き算が、なぜかXOR(排他的論理和)で全く同じになる」という不思議な計算ルールから、対数表を使った掛け算の魔法まで、図解たっぷりで分かりやすく紐解いていきます。

シリーズを読み終える頃には、あなたもQRコードの仕組みを完全にマスターしているはずです。

次回:ガロア体を少し理解する。
関連記事は、2026年6月15日に公開予定 (あと1時間)

ここまで読んでいただきありがとうございます。

では、次の記事で。 lumenHero