如果需要源码,请猛戳我: github fork me
一、设计题目
对一幅BMP格式的灰度图像(个人证件照片)进行二元霍夫曼编码和译码
二、算法设计
(1)二元霍夫曼编码:
①:图像灰度处理:
利用python的PIL自带的灰度图像转换函数,首先将彩色图片转为灰度的bmp图像,此时每个像素点可以用单个像素点来表示。
②:二元霍夫曼编码:
程序流程图:
详细设计:
1. 统计像素点频率,首先通过python自带的PIL库的图像像素点读取函数read()获取灰度图像的所有像素点,通过循环遍历每个像素点,将每个出现的像素点值以及其次数以键值对的形式放入到python的字典中。
2. ①:首先构造用以表示节点的类,其中每个节点包括一下成员属性:
self.left = left
self.right = right
self.parent = parent
self.weight = weight
self.code = code
②:遍历已经保存好的像素点频率字典,将图片中出现的像素点全部定义为叶子节点,通过类的code以及weight来表示对应的像素点值和该像素点出现的次数。
③:此时叶子结点中的权值为乱序,此时依据每个叶子结点的权重所有的叶子结点进行从小到大的排序;
④:每次取权值最小的两个节点最为要被替换的节点,将这两个节点的权值进行相加,然后生成新的节点,同时将这两个节点从叶子结点列表中去除,同时将新生成的节点放入叶子节点列表,同时对列表进行排序;
⑤:重复步骤④,直到列表中还剩下一个节点,此时这个节点便为头节点。
3. ①:根据已经构造好的二元霍夫曼编码树,由叶子节点开始遍历整棵树,左边赋予码字1,右边赋予码字0,每次向下遍历一次,若节点为非根节点,则依次将码符号放在码字左边,若遍历到根节点,则将该叶子结点所表示的像素值以及对应编成的码字放到码字字典中;
②:重复步骤①,直到所有的叶子节点都有其对应的二元霍夫曼码字。
③:经过步骤②,此时像素的码字字典已经生成,此时回归到原图片,根据遍历原图片的像素点,依次在码字列表中查找其对应的码字,将所有像素点对应的码字拼接在一起。
4. 此时由于为二元霍夫曼编码,则编码结果为01字符串,此时为了对信息量进行压缩,采用将8个string类似的值转为一个byte,首先填充编码结果使其长度为8的倍数,并增加冗余位数保存原使编码结果最后多余位数的值以及其长度。填充完毕后,只需每次取编码结果的八位转为一个byte存入到txt中即可。
(2)二元霍夫曼译码:
详细设计:
1.每次读取txt中的一个字节,将其还原为字符串,直到txt中的所有字节被读取结束 ,则所得到的字符串则为霍夫曼编码的结果。
2.①遍历霍夫曼编码的结果,根据霍夫曼编码已经生成的码字列表,对原始像素点进行还原,并根据还原后的像素点生成原始bmp图片。
②:对于每一个被遍历到的字符均在码字列表中进行查找,若未找到则加上后续一个字符,继续查找;
③:重复步骤③,直到在码字列表中找到该码字对应的像素点,将其码字对应的像素值放入到像素点列表中,重复以上查找还原像素值的步骤直到所有字符串均被遍历完。
三、模块划分
(1)二元霍夫曼编码部分:
①:类:
class node:
"""
构造用以表示节点的类
"""
def __init__(self, right=None, left=None, parent=None, weight=0,
code=None): # 节点构造方法
self.left = left
self.right = right
self.parent = parent
self.weight = weight
self.code = code
②:函数:
def picture_convert():
"""
此函数完成将bmp图片转换为灰值图
:return:
"""
picture = Image.open('./original_photo.jpg')
# print '====', picture.mode # RGBA
pic = picture.convert('L')
# L = R * 299/1000 + G * 587/1000+ B * 114/1000 依据此公式将RGBA图像转换为L图像
pic.save('gray_photo.bmp')
return pic # 返回转换后的图片对象
③:函数:
def pixel_frequency_statistics(list):
"""
统计每个像素出现的次数
:param list:
:return:
"""
global xiang_su_pin_lv
for i in list:
if i not in xiang_su_pin_lv.keys():
xiang_su_pin_lv[i] = 1 # 若此像素点不在字符频率字典里则直接添加
else:
xiang_su_pin_lv[i] += 1 # 若存在在字符频率字典里则对应值加一
return xiang_su_pin_lv
④:函数
def construct_leaf_nodes(xiang_su_zhi):
"""
构造叶子节点,分别赋予其像素点的值和像素点的权重
code = 像素点的值
weight = 像素点的权重
:param xiang_su_zhi:
:return:
"""
nodes_list = [node(weight=xiang_su_zhi[i][1], code=str(xiang_su_zhi[i][0]))
for i in range(len(xiang_su_zhi))]
# print node_list.__len__() # 256
# print node_list[0].code, node_list[0].weight # 255 26
return nodes_list
⑤:函数
def sort_by_weight(list_node):
"""
根据每个叶子结点的权重对叶子结点列表进行排序
:param list_node:
:return:
"""
list_node = sorted(list_node, key=lambda node: node.weight)
return list_node
⑥:函数
def huffman_tree(listnode):
"""
根据叶子结点列表,生成对应的霍夫曼编码树
:param listnode:
:return:
"""
listnode = sort_by_weight(listnode)
# x = []
# for i in listnode:
# x.append(i.weight)
# print '我就想看看---', x # return [26, 57, 58, 62, 63, 64, 64, 65, ...]
# print '我就想看看---', sum(x) # return 252000
while len(listnode) != 1:
low_node0, low_node1 = listnode[0], listnode[1] # 每次取最小权值的两个像素点进行合并
new_change_node = node()
new_change_node.weight = low_node0.weight + low_node1.weight
new_change_node.left = low_node0
new_change_node.right = low_node1
low_node0.parent = new_change_node
low_node1.parent = new_change_node
# remove() 函数用于移除列表中某个值的第一个匹配项
listnode.remove(low_node0)
listnode.remove(low_node1)
listnode.append(new_change_node)
listnode = sort_by_weight(listnode)
return listnode # 返回头结点
⑦:函数
def binary_huffman_encode(picture):
"""
编码函数,返回编码表以及编码结果
:param picture:
:return:
"""
# print '==size===', picture.size # 420(width) * 600(height) = 252000
width = picture.size[0]
height = picture.size[1]
im = picture.load() # 为图像分配内存并从文件中加载它
print "像素点个数为:{0}(width) * {1}(height) = {2}".format(width, height,
width * height)
# 将像素点保存在列表中进行频率统计; im[i, j]表示为像素点的灰色值
list = [im[i, j] for i in range(width) for j in range(height)]
# 统计每个像素出现的次数
xiang_su = pixel_frequency_statistics(list)
# 以频数从小到大将像素进行排序,频数作为节点的权重 return [(像素, 频数), ()]
# ########其实此处已经根据权重排序了, sort_by_weight()函数也是做这个事#####
xiang_su = sorted(xiang_su.items(), key=lambda item: item[1])
# 构造叶子节点
leaf_nodes_list = construct_leaf_nodes(xiang_su)
# 根据叶子结点列表,生成对应的霍夫曼编码树
head = huffman_tree(leaf_nodes_list)[0] # 保存编码树的头结点return
# TODO:huffman 编码规则的正确性,不同角度去理解
global bian_ma_biao
# 遍历所有的叶子节点,卓一为huffman 树进行编码
for e in leaf_nodes_list: # 构造编码表
new_change_node = e
bian_ma_biao.setdefault(e.code, "")
while new_change_node != head:
if new_change_node.parent.left == new_change_node:
bian_ma_biao[e.code] = "1" + bian_ma_biao[e.code]
else:
bian_ma_biao[e.code] = "0" + bian_ma_biao[e.code]
new_change_node = new_change_node.parent
for key in bian_ma_biao.keys():
print "信源像素点 {0} Huffman编码后的码字为: {1}".format(key, bian_ma_biao[key])
result = '' # 编码结果,对每个像素点进行霍夫曼编码
for i in range(width):
for j in range(height):
for key, values in bian_ma_biao.iteritems():
if str(im[i, j]) == key:
result = result + values
with open('result.txt', 'w') as f:
f.write(result)
print "您的编码表为:", bian_ma_biao
⑧:函数
def switch_byte(erjinzhi, char=00000000):
for j in range(8):
if erjinzhi[j] == '1':
if j == 7:
char += 1
else:
char += 1
char = char << 1
if erjinzhi[j] == "0":
if j == 7:
pass
else:
char = char << 1
return char
⑧:函数
def byte_xie_ru():
"""
由于霍夫曼编码结果为string类型,此时应将其转为byte保存,此函数完成将编码结果的字节存入
:return:
"""
with open('result.txt', 'r') as f:
# 读取文件第一行数据并删除其中的换行符
p = f.readlines()[0].strip('\n') # da= f.readlines()返回的是列表;
str = p
# print '还想看看===', p.__len__()
yu_shu = 8 - p.__len__() % 8
with open("huffman_compress.txt", "wb") as f:
for i in range(0, str.__len__(), 8):
if i + 8 > str.__len__():
erjinzhi = str[i:]
# print erjinzhi # return 10010
# print yu_shu # return 3
buling = ''
for i in range(yu_shu):
buling = buling + '0'
erjinzhi = buling + erjinzhi # return 00010010
char = switch_byte(erjinzhi)
f.write(pack("B", char))
break
erjinzhi = str[i:i + 8]
char = switch_byte(erjinzhi)
f.write(pack("B", char))
f.write(pack('B', yu_shu))
print "您的编码已经完成:二元霍夫曼编码结果已经存到huffman_compress.txt中"
(2)二元霍夫曼译码部分:
①:函数:
def zi_jie_du_qu(qqqq):
"""
根据霍夫曼编码生成的txt文件读取其中的字节恢复为字符串形式的编码结果
:param qqqq:
:return:
"""
with open('result.txt', 'r') as p:
pppp = p.readlines()[0].strip('\n')
with open('huffman_decomressed.txt', 'w') as fff:
l = ((pppp.__len__() - pppp.__len__() % 8) / 8) + 2
list = []
for i in range(l):
with open(str(qqqq), "rb") as file:
file.seek(i) #
(a,) = unpack("B", file.read(1))
list.append(a)
result = ''
for i in range(len(list) - 2):
buling = ''
for j in range(8 - len(bin(list[i])[2:])):
buling = buling + '0'
erjinzhi = buling + bin(list[i])[2:]
result = result + erjinzhi
yu_shu = 8 - list[-1]
last = bin(list[-2])[2:]
if last.__len__() != yu_shu:
buling = ''
for j in range(8 - yu_shu - last.__len__()):
buling = buling + '0'
erjinzhi = buling + bin(list[-2])[2:]
result = result + erjinzhi
if last.__len__() == yu_shu:
result = result + bin(list[-2])[2:]
fff.write(result)
②函数:
def binary_huffman_decode(kuan, gao):
"""
二元霍夫曼译码的主函数,通过调用其他函数来还原原始的bmp图像
:param kuan:
:param gao:
:return:
"""
with open('huffman_decomressed.txt', 'r') as f:
zifuchuan = f.readlines()[0].strip('\n')
i = 0
sao_miao = ''
huan_yuan_xiang_su = []
while i != zifuchuan.__len__(): # 利用编码表进行译码
sao_miao = sao_miao + zifuchuan[i]
for key in bian_ma_biao.keys():
if sao_miao == bian_ma_biao[key]:
huan_yuan_xiang_su.append(key)
sao_miao = ''
break
i += 1
x = kuan
y = gao
c = Image.new('L', (x, y))
k = 0
for i in range(x):
for j in range(y):
c.putpixel((i, j), (int(huan_yuan_xiang_su[k])))
k += 1
c.save('huffman_restore_photo' + '.bmp')
print "您的译码已经完成:" + "图片存储为huffman_restore_photo.bmp"