算法的设计说明
目标
- 对彩色图像进行压缩,使用霍夫曼编码方法对图像的每个像素进行编码,从而减少其存储空间。
- 解码时,能够恢复图像的原始像素数据,确保图像在经过压缩和解压后与原图像一致。
输入
- 原始图像(以RGB格式存储)
- 霍夫曼编码的输入是图像的像素数据(RGB元组),每个像素表示为一个
(R, G, B)
的三元组
输出
- 霍夫曼编码后的图像数据(以二进制字符串形式存储)
- 解码后的图像(还原为原始的RGB图像)
算法设计
1. 图像数据表示:
- 首先,读取图像并将其转换为RGB数据格式。图像的每个像素由三个数字表示,分别表示红、绿、蓝颜色的强度。
- 图像数据被展平成一个二维数组,每一行包含一个像素的
(R, G, B)
元组。
2. 霍夫曼编码过程:
2.1 统计频率:
- 使用
collections.Counter
来计算图像中每个颜色(RGB元组)出现的频率。由于图像中可能有大量相同的像素颜色,这些颜色会有较高的频率。
2.2 构建霍夫曼树:
- 根据频率生成霍夫曼树,具体步骤是:
- 将每个RGB元组和其对应的频率放入最小堆(
heapq
)。最小堆的作用是快速取出频率最小的元素。
- 通过不断合并最小频率的元素(叶子节点),直到堆中只剩下一个元素。每次合并时,生成一个新的内部节点,该节点的频率是合并的两个节点的频率之和。
- 在合并过程中,为每个节点的左子树和右子树分配不同的二进制数字(‘0’ 和 ‘1’),从而为每个RGB元组分配唯一的霍夫曼编码。
2.3 生成霍夫曼编码字典:
- 遍历霍夫曼树,基于树的结构为每个RGB元组分配霍夫曼编码。
- 将每个RGB元组与其对应的编码保存在字典中
huffman_code
中,键是RGB元组,值是该RGB元组的霍夫曼编码。
2.4 编码图像数据:
- 使用生成的霍夫曼编码字典,将每个像素的RGB元组转换为对应的二进制编码。通过遍历图像的每一个像素,将每个RGB元组替换为其对应的霍夫曼编码,最终形成一个长的二进制字符串(
encoded_data
)。
3. 霍夫曼解码过程:
3.1 反转编码字典:
- 由于霍夫曼编码是可逆的,解码时需要通过编码字典的反向映射来恢复原始的RGB元组。
- 通过交换字典中的键和值,创建一个反向编码字典
reverse_code
,其中每个二进制编码映射回相应的RGB元组。
3.2 逐位解码:
- 解码过程从编码字符串的开头开始,逐个读取二进制位。通过将这些位拼接成一个二进制字符串,不断与反向字典匹配,直到匹配到一个完整的RGB元组。
- 一旦匹配成功,恢复出RGB元组并将其添加到解码后的数据中,然后继续处理剩余的编码。
3.3 重建图像数据:
- 解码后的像素数据被存储为一个列表
decoded_data
,最终通过 np.array(decoded_data)
将其转换为NumPy数组,并恢复图像的原始形状(根据图像的宽、高、颜色通道数)。
4. 文件保存与恢复:
4.1 保存编码数据**:**
- 编码后的数据(霍夫曼编码后的二进制字符串)被保存到一个文本文件中
霍夫曼编码后的文件.txt
,以便后续使用。
4.2 保存解码后的图像:
- 解码后的图像数据(恢复的RGB图像)被保存为一个新图像文件
霍夫曼解码后的图片.bmp
。
复杂度分析
- 时间复杂度:
- 统计频率:
O(n)
,其中 n
是图像中像素的总数。
- 构建霍夫曼树:
O(m log m)
,其中 m
是图像中唯一RGB颜色的数量。最坏情况下,m
接近 n
,因此复杂度大约为 O(n log n)
。
- 编码:
O(n)
,遍历每个像素并通过字典获取霍夫曼编码。
- 解码:
O(n)
,遍历编码数据并通过反向字典恢复像素数据。
- 空间复杂度:
- 霍夫曼编码字典:
O(m)
,其中 m
是图像中的唯一RGB颜色数。
- 图像数据:
O(n)
,存储图像的像素数据。
总结
- 压缩过程: 使用霍夫曼编码通过统计图像中颜色的频率为每个颜色分配一个二进制编码,常见颜色的编码较短,不常见的颜色的编码较长,从而压缩图像的数据量。
- 解压过程: 通过反向映射的霍夫曼编码字典,逐步将二进制编码恢复为原始的RGB颜色值,重建图像数据。
代码
import numpy as np
from collections import Counter
import heapq
import os
from PIL import Image
image_path = "图像原图.bmp"
image = Image.open(image_path)
rgb_image_data = np.array(image)
def huffman_encoding_rgb(data):
data_flat = data.reshape(-1, data.shape[2])
freq = Counter(tuple(pixel) for pixel in data_flat)
heap = [[weight, [symbol, ""]] for symbol, weight in freq.items()]
heapq.heapify(heap)
while len(heap) > 1:
lo = heapq.heappop(heap)
hi = heapq.heappop(heap)
for pair in lo[1:]:
pair[1] = '0' + pair[1]
for pair in hi[1:]:
pair[1] = '1' + pair[1]
heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])
huffman_code = dict(sorted(heap[0][1:], key=lambda p: p[0]))
encoded_data = ''.join([huffman_code[tuple(pixel)] for pixel in data_flat])
return huffman_code, encoded_data
def decode_huffman_rgb(encoded_data, huffman_code, image_shape):
reverse_code = {v: k for k, v in huffman_code.items()}
decoded_data = []
temp_code = ''
for bit in encoded_data:
temp_code += bit
if temp_code in reverse_code:
decoded_data.append(reverse_code[temp_code])
temp_code = ''
return np.array(decoded_data).reshape(image_shape)
def save_encoded_data(filename, encoded_data):
with open(filename, 'w') as f:
f.write(encoded_data)
output_dir = os.path.join(os.path.dirname(image_path), "compressed_files")
os.makedirs(output_dir, exist_ok=True)
huffman_code_rgb, encoded_data_rgb = huffman_encoding_rgb(rgb_image_data)
huffman_encoded_filename = os.path.join(output_dir, "霍夫曼编码后的文件.txt")
save_encoded_data(huffman_encoded_filename, encoded_data_rgb)
decoded_huffman_rgb_image_data = decode_huffman_rgb(encoded_data_rgb, huffman_code_rgb, rgb_image_data.shape)
decoded_huffman_rgb_image = Image.fromarray(decoded_huffman_rgb_image_data.astype(np.uint8))
huffman_decoded_rgb_image_filename = os.path.join(output_dir, "霍夫曼解码后的图片.bmp")
decoded_huffman_rgb_image.save(huffman_decoded_rgb_image_filename)
original_size_bits = rgb_image_data.size * 8
huffman_encoded_size_bits = len(encoded_data_rgb)
compression_ratio = original_size_bits / huffman_encoded_size_bits
bit_rate = huffman_encoded_size_bits / rgb_image_data.size
mse = np.mean((rgb_image_data - decoded_huffman_rgb_image_data) ** 2)
signal_variance = np.var(rgb_image_data)
snr = 10 * np.log10(signal_variance / mse)
print("压缩比:", compression_ratio)
print("比特率:", bit_rate)
print("信噪比 (SNR):", snr)
huffman_encoded_filename, huffman_decoded_rgb_image_filename
相关指标计算
表格统计
压缩比 |
比特率 (比特/像素) |
信噪比 (SNR) |
13.58265 |
0.588987 |
inf |
综合分析
- 压缩效果:
- 压缩比为 13.58,说明压缩算法在保持图像质量的同时显著减少了文件的大小,这意味着该压缩方法压缩效率较好。
- 比特率:
- 0.589 比特/像素 表示每个像素仅需不到 1 比特的存储空间,这也表明压缩方法比较高效,能够以非常小的存储空间表示每个像素。
- 图像质量:
- 由于 SNR 为无限,表示该压缩方法是 无损的。无损压缩意味着图像的所有细节和信息都被完全保留,恢复后的图像与原始图像没有任何差异。