差错控制-reed-solomon编码

笔者最近在研究二维码相关的东西,正好有涉及到Reed-Solomon码,这里简单写一下笔记。有不当之处请评论区指正,欢迎理性讨论。
【参考资料】
Reed–Solomon codes for coders - Wikiversity
网址:https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders


意义、优势、应用等暂时略过。
基础部分可以看我之前写的 基础知识。这里只在它的基础上加以补充(虽然前一篇也没写多少东西)

热身例子

假设B有个字典,里面有三个单词:this that 和 corn。A向B发送的信息只能是字典中的一个单词。

某次当A向B发送信息时,某些位被破坏,变成了星号,所以B只收到一个词 co**,那么根据字典,B可以知道,corn与co**最相似,所以原来的词是corn。

但是如果B收到的词是th**,则B无法确定是this还是that。因此,这个字典应该选取彼此不一样的词,以便于信息损坏后能更准确地恢复。

那么,字典中任意两个词之间差异的位数,我们称为汉明距离【Hmming distance】,这其中的最小值,称为字典的最大汉明距离【maximum Hamming distance】。

确保字典中任意两个词都只在几个(值为最大汉明距离)位置上不同,称为最大分离度【maximum separability】。

对于现在的字典,为了增加最大汉明距离,可以增加所有字条的长度,如改为:

 t h i s a b c d
 t h a t b c d e
 c o r n c d e f

那么,当 1)有4个损坏到来时,我们能知道原码是 t h i s a b c d。

t * * * a b * d

当 2)有两个错码到来时,我们能知道原码是 t h a t b c d e。

t h o s b c d e

Reed-Solomon编码的核心思想也与此相似:在伽罗华域【Galois Field】的数据后,加入一些冗余码。解码的原始思想也是在一个集合里查找最相似的数据,不过为了能编解码更长的信息,在算法方面做了优化。

二维码基础

二维码有自己的结构,如下图所示,很多部分是跟版本(尺寸)绑定不变的。
在这里插入图片描述
编码步骤是:1)数据分析;2)数据编码;3)纠错码编制;4)数据交错和补充;5)填充矩阵;6)掩码;7)填充格式和版本信息。其中掩码是为了让黑块和白块分布地更均衡,尽量避免某些图案的出现。

如下图所示,右侧是掩码前的原码。红色区域是编码格式信息的,浅灰色是format information和timing patterns,也就是已经固定的信息,不是实际要传输的数据信息。实际要传的、已经编码好了的,是黑色区域。白色表示0,黑色表示1。

而掩码本身,就是拿一个相同尺寸的矩阵,去做异或。掩码有很多种模式,需要选择效果最好的。
在这里插入图片描述
由于二维码被扫描时受光线、印刷质量等的影响,所以难免有损失。为了解码时能更可靠地解码,我们先引入BCH译码器,再细化到Reed-Solomon译码器。

BCH检错

如果确定二维码编码的结果是对的呢?假设生成二维码的generator是101 0011 0111,编码的结果是000 1111 0101 1001,那么用编码结果除以generator(长除法,做异或而非【减】),余数应该是0的。(为什么应该是0,参考下面有限域知识)下面展示的是bch的解码能力。

def qr_check_format(fmt):
    g = 0x537
    for i in range(4, -1, -1):
        if fmt & (1 << (i+10)):
            fmt ^= (g << i)
    return fmt

# 实际是计算码里有多少1    
def hamming_weight(x):
    weight = 0
    while x > 0:
        weight += x & 1
        x >>= 1
    return weight
    
def qr_decode_format(fmt):
    best_fmt = -1
    best_dist = 15
    for test_fmt in range(0,32):
        test_code = (test_fmt<<10)^qr_check_format(test_fmt<<10)
        test_dist = hamming_weight(fmt^test_code)
        if test_dist < best_dist:
            best_dist = test_dist
            best_fmt = test_fmt
        elif test_dist == best_dist:
            best_fmt = -1
        #print("best_fmt " + str(best_fmt))
    return best_fmt
    
print(qr_decode_format(int("000111101011001",2))) # 3
# 分别错1, 2, 3位, min(Hamming distance)都是3
print(qr_decode_format(int("100111101011001",2))) # 3
print(qr_decode_format(int("010101101011001",2))) # 3
print(qr_decode_format(int("111111101011001",2))) # 3
# 错4位时有至少两个是等最小距离的,所以无法单一解码
print(qr_decode_format(int("111011101011001",2))) # -1

有限域知识

阿贝尔群(交换群):满足结合律、交换律;群有单位元;所有元素有逆元。

环:1.加法上是阿贝尔群;2.乘法满足结合律;3.对加法和乘法有分配律。

域:能进行加、减、乘、除的代数结构。计算结果不会超出域的集合。其中乘法应该满足交换律,否则则称为【体】。

有限域:域的元素个数有限,元素的个数称为【阶】。

**伽罗华域:**每个有限域的阶必须为素数的幂,可表示为 p ^ n,这样的有限域称为Galois域,记作GF(p ^ n)。其上做完加法和乘法后,要对p^n取模,以便结果仍是域中的元素。

对于GF(2^n),用多项式表示的话,系数只能用0或1,因此在计算机领域很方便。举例:GF (2 ^ 8)中,170用二进制表示是10101010,那么多项式为:

0101010 = 1*x^7 + 0*x^6 + 1*x^5 + 0*x^4 + 1*x^3 + 0*x^2 + 1*x + 0 
= x^7 + x^5 + x^3 + x

同时,GF(2 ^ n)上,加和减是一样的,都是按位做异或。

def gf_add(x, y):
    return x ^ y
def gf_sub(x, y):
    return x ^ y

乘法则是多项式乘法,或者直接移位异或,如下图所示:
在这里插入图片描述

def cl_mul(x, y):
    z = 0
    i = 0
    while (y>>i) > 0:
        if y & (1<<i):
            z ^= x<<i
        i += 1
    return z
# 0b1010001111010
print(bin(cl_mul(0b10001001, 0b00101010)))

那么,既然要放到有限域,太长的话是不行的,要取模。这里的除数称为生成式,为啥选它后面会说,现在只告诉你要选一个 1 0001 1101 这样的东西取模。完整的计算:

def gf_multi_noLUT(x, y, prim=0):
    def cl_mul(x, y):
        z = 0
        i = 0
        while (y>>i) > 0:
            if y & (1<<i):
                z ^= x<<i
            i += 1
        return z
        
    # 类似 int(4).bit_length(),就是算这个数字用几个二进制位可以表示
    def bit_length(n):
        bits = 0
        while n >> bits: bits += 1
        return bits
       
    # 这里模拟的是长除法
    def cl_div(dividend, divisor=None):
        dl1 = bit_length(dividend)
        dl2 = bit_length(divisor)
        if dl1 < dl2:
            return dividend
        for i in range(dl1-dl2, -1, -1):
            if dividend & (1<<(i+dl2-1)):
                dividend ^= divisor << i
        return dividend
        
    result = cl_mul(x, y)
    if prim > 0:
        result = cl_div(result, prim)
    return result
    
a = 0b10001001
b = 0b00101010
# 跟上面一样
print(bin(gf_multi_noLUT(a, b, 0))) # 0b1010001111010
# 0x11d就是1 0001 1101
print(bin(gf_multi_noLUT(a, b, 0x11d))) # 0b11000011

【未完待续】

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值