python-3.x - Python 图像压缩
问题描述
我正在使用 Python 的 Pillow 库来读取图像文件。如何使用 Huffman 编码进行压缩和解压缩?这是一条指令:
您已经获得了一组示例图像,您的目标是尽可能地压缩它们而不会丢失任何可感知的信息——在解压缩时它们应该与原始图像相同。图像本质上存储为一系列颜色点,其中每个点都表示为红色、绿色和蓝色 (rgb) 的组合。rgb 值的每个分量都在 0-255 之间,例如:(100, 0, 200) 表示紫色阴影。使用固定长度编码,rgb 值的每个分量都需要 8 位进行编码(28=256),这意味着整个 rgb 值需要 24 位进行编码。您可以使用像霍夫曼编码这样的压缩算法来减少更常见值所需的位数,从而减少对图像进行编码所需的总位数。
# For my current code I just read the image, get all the rgb and build the tree
from PIL import Image
import sys, string
import copy
codes = {}
def sortFreq(freqs):
letters = freqs.keys()
tuples = []
for let in letters:
tuples.append (freqs[let],let)
tuples.sort()
return tuples
def buildTree(tuples):
while len (tuples) > 1:
leastTwo = tuple (tuples[0:2]) # get the 2 to combine
theRest = tuples[2:] # all the others
combFreq = leastTwo[0][0] + leastTwo[1][0] # the branch points freq
tuples = theRest + [(combFreq, leastTwo)] # add branch point to the end
tuples.sort() # sort it into place
return tuples[0] # Return the single tree inside the list
def trimTree(tree):
# Trim the freq counters off, leaving just the letters
p = tree[1] # ignore freq count in [0]
if type (p) == type (""):
return p # if just a leaf, return it
else:
return (trimTree (p[0]), trimTree (p[1]) # trim left then right and recombine
def assignCodes(node, pat=''):
global codes
if type (node) == type (""):
codes[node] = pat # A leaf. Set its code
else:
assignCodes(node[0], pat+"0") # Branch point. Do the left branch
assignCodes(node[1], pat+"1") # then do the right branch.
dictionary = {}
table = {}
image = Image.open('fall.bmp')
#image.show()
width, height = image.size
px = image.load()
totalpixel = width*height
print ("Total pixel: "+ str(totalpixel))
for x in range (width):
for y in range (height):
# print (px[x, y])
for i in range (3):
if dictionary.get(str(px[x, y][i])) is None:
dictionary[str(px[x, y][i])] = 1
else:
dictionary[str(px[x, y][i])] = dictionary[str(px[x, y][i])] +1
table = copy.deepcopy(dictionary)
#combination = len(dictionary)
#for value in table:
# table[value] = table[value] / (totalpixel * combination) * 100
#print(table)
print(dictionary)
sortdic = sortFreq(dictionary)
tree = buildTree(sortdic)
trim = trimTree(tree)
print(trim)
assignCodes(trim)
print(codes)
解决方案
HuffmanCoding 类将要压缩的文本文件的完整路径作为参数。(因为它的数据成员存储特定于输入文件的数据)。
compress() 函数返回输出压缩文件的路径。
函数 decompress() 需要解压文件的路径。(并且 decompress() 将从为压缩创建的同一对象中调用,以便从其数据成员中获取代码映射)
import heapq
import os
class HeapNode:
def __init__(self, char, freq):
self.char = char
self.freq = freq
self.left = None
self.right = None
def __cmp__(self, other):
if(other == None):
return -1
if(not isinstance(other, HeapNode)):
return -1
return self.freq > other.freq
class HuffmanCoding:
def __init__(self, path):
self.path = path
self.heap = []
self.codes = {}
self.reverse_mapping = {}
# functions for compression:
def make_frequency_dict(self, text):
frequency = {}
for character in text:
if not character in frequency:
frequency[character] = 0
frequency[character] += 1
return frequency
def make_heap(self, frequency):
for key in frequency:
node = HeapNode(key, frequency[key])
heapq.heappush(self.heap, node)
def merge_nodes(self):
while(len(self.heap)>1):
node1 = heapq.heappop(self.heap)
node2 = heapq.heappop(self.heap)
merged = HeapNode(None, node1.freq + node2.freq)
merged.left = node1
merged.right = node2
heapq.heappush(self.heap, merged)
def make_codes_helper(self, root, current_code):
if(root == None):
return
if(root.char != None):
self.codes[root.char] = current_code
self.reverse_mapping[current_code] = root.char
return
self.make_codes_helper(root.left, current_code + "0")
self.make_codes_helper(root.right, current_code + "1")
def make_codes(self):
root = heapq.heappop(self.heap)
current_code = ""
self.make_codes_helper(root, current_code)
def get_encoded_text(self, text):
encoded_text = ""
for character in text:
encoded_text += self.codes[character]
return encoded_text
def pad_encoded_text(self, encoded_text):
extra_padding = 8 - len(encoded_text) % 8
for i in range(extra_padding):
encoded_text += "0"
padded_info = "{0:08b}".format(extra_padding)
encoded_text = padded_info + encoded_text
return encoded_text
def get_byte_array(self, padded_encoded_text):
if(len(padded_encoded_text) % 8 != 0):
print("Encoded text not padded properly")
exit(0)
b = bytearray()
for i in range(0, len(padded_encoded_text), 8):
byte = padded_encoded_text[i:i+8]
b.append(int(byte, 2))
return b
def compress(self):
filename, file_extension = os.path.splitext(self.path)
output_path = filename + ".bin"
with open(self.path, 'r+') as file, open(output_path, 'wb') as output:
text = file.read()
text = text.rstrip()
frequency = self.make_frequency_dict(text)
self.make_heap(frequency)
self.merge_nodes()
self.make_codes()
encoded_text = self.get_encoded_text(text)
padded_encoded_text = self.pad_encoded_text(encoded_text)
b = self.get_byte_array(padded_encoded_text)
output.write(bytes(b))
print("Compressed")
return output_path
""" functions for decompression: """
def remove_padding(self, padded_encoded_text):
padded_info = padded_encoded_text[:8]
extra_padding = int(padded_info, 2)
padded_encoded_text = padded_encoded_text[8:]
encoded_text = padded_encoded_text[:-1*extra_padding]
return encoded_text
def decode_text(self, encoded_text):
current_code = ""
decoded_text = ""
for bit in encoded_text:
current_code += bit
if(current_code in self.reverse_mapping):
character = self.reverse_mapping[current_code]
decoded_text += character
current_code = ""
return decoded_text
def decompress(self, input_path):
filename, file_extension = os.path.splitext(self.path)
output_path = filename + "_decompressed" + ".txt"
with open(input_path, 'rb') as file, open(output_path, 'w') as output:
bit_string = ""
byte = file.read(1)
while(byte != ""):
byte = ord(byte)
bits = bin(byte)[2:].rjust(8, '0')
bit_string += bits
byte = file.read(1)
encoded_text = self.remove_padding(bit_string)
decompressed_text = self.decode_text(encoded_text)
output.write(decompressed_text)
print("Decompressed")
return output_path
运行程序:将上述代码保存在文件 huffman.py 中。
创建一个示例文本文件。或者从 sample.txt 下载一个示例文件(右击,另存为)
将下面的代码保存在与上面代码相同的目录下,并运行这段python代码(运行前编辑下面的路径变量。将其初始化为文本文件路径)
使用霍夫曼.py
from huffman import HuffmanCoding
#input file path
path = "/home/ubuntu/Downloads/sample.txt"
h = HuffmanCoding(path)
output_path = h.compress()
h.decompress(output_path)
The compressed .bin file and the decompressed file are both saved in the same directory as of the input file.
结果在上面链接的示例文本文件上运行:
初始大小:715.3 kB 压缩文件大小:394.0 kB 另外,解压后的文件与原始文件完全相同,没有任何数据丢失。
这就是 Huffman Coding 实现的全部内容,包括压缩和解压缩。编写代码很有趣。
上述程序要求使用创建压缩文件的同一对象运行解压缩函数(因为代码映射存储在其数据成员中)。我们还可以使压缩和解压缩功能独立运行,如果在压缩过程中我们也将映射信息存储在压缩文件中(在开始时)。然后,在解压过程中,我们将首先从文件中读取映射信息,然后使用该映射信息解压其余文件。
推荐阅读
- python - 使用模型作为特征提取器时,使用深度学习模型 (DenseNet-121) 的哪一层作为输出
- php - Laravel 8 - 无法从数据库中获取数据
- pine-script - Pine 脚本:无法使用参数调用“plotshape”
- javascript - 从浅复制对象中删除属性
- c++ - 为什么程序在 main 中看不到我的名为“geneticLibrary/geneticProgram.hpp”的库?
- c# - 如何计算对象列表中的项目?
- c++ - 当派生类和基类具有不同类型的参数时,我在继承方面遇到了一些严重的问题,如下所示:
- django - 我应该在我的 django 应用程序中使用 select_for_update 和 postgresql 数据库吗?
- javascript - 如何使用基于 id 的分组数据从另一个数组创建一个数组?
- python - 使用 django 在 Qrcode 中的用户名和密码