数字图像处理 -- 霍夫曼编码(无损压缩)练习

算法的设计说明

目标

  • 对彩色图像进行压缩,使用霍夫曼编码方法对图像的每个像素进行编码,从而减少其存储空间。
  • 解码时,能够恢复图像的原始像素数据,确保图像在经过压缩和解压后与原图像一致。

输入

  1. 原始图像(以RGB格式存储)
  2. 霍夫曼编码的输入是图像的像素数据(RGB元组),每个像素表示为一个 (R, G, B) 的三元组

输出

  1. 霍夫曼编码后的图像数据(以二进制字符串形式存储)
  2. 解码后的图像(还原为原始的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

复杂度分析

  1. 时间复杂度:
    • 统计频率:O(n),其中 n 是图像中像素的总数。
    • 构建霍夫曼树:O(m log m),其中 m 是图像中唯一RGB颜色的数量。最坏情况下,m 接近 n,因此复杂度大约为 O(n log n)
    • 编码:O(n),遍历每个像素并通过字典获取霍夫曼编码。
    • 解码:O(n),遍历编码数据并通过反向字典恢复像素数据。
  2. 空间复杂度:
    • 霍夫曼编码字典:O(m),其中 m 是图像中的唯一RGB颜色数。
    • 图像数据:O(n),存储图像的像素数据。

总结

  • 压缩过程: 使用霍夫曼编码通过统计图像中颜色的频率为每个颜色分配一个二进制编码,常见颜色的编码较短,不常见的颜色的编码较长,从而压缩图像的数据量。
  • 解压过程: 通过反向映射的霍夫曼编码字典,逐步将二进制编码恢复为原始的RGB颜色值,重建图像数据。

代码

python 复制代码
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(以保留颜色信息)
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

# 解码霍夫曼编码后的RGB图像数据
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  # 每个像素3个通道,每个通道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  # 每个像素的比特数

# 计算信噪比(SNR)
# 计算均方误差(MSE)
mse = np.mean((rgb_image_data - decoded_huffman_rgb_image_data) ** 2)
# 计算信号的方差(原始图像的方差)
signal_variance = np.var(rgb_image_data)
# 计算SNR
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
综合分析
  1. 压缩效果:
    • 压缩比为 13.58,说明压缩算法在保持图像质量的同时显著减少了文件的大小,这意味着该压缩方法压缩效率较好。
  2. 比特率:
    • 0.589 比特/像素 表示每个像素仅需不到 1 比特的存储空间,这也表明压缩方法比较高效,能够以非常小的存储空间表示每个像素。
  3. 图像质量:
    • 由于 SNR 为无限 ,表示该压缩方法是 无损的。无损压缩意味着图像的所有细节和信息都被完全保留,恢复后的图像与原始图像没有任何差异。
相关推荐
struggle20255 分钟前
AWS Bedrock 多代理蓝图存储库使用 CDK、Streamlit 和 LangFuse 运行 AWS Bedrock 多代理 AI 协作的蓝图
运维·人工智能·自动化·云计算·aws
xiangzhihong828 分钟前
Google 决定终止开源 Android项目
人工智能·android-studio
Matrix_1134 分钟前
论文阅读:GS-Blur: A 3D Scene-Based Dataset for Realistic Image Deblurring
论文阅读·人工智能·计算摄影
Fuction.38 分钟前
聚类注意点
人工智能·python·机器学习·kmeans·聚类
xc10111 小时前
树莓派5从零开发至脱机脚本运行教程——6.实战篇
人工智能·opencv·计算机视觉
WhyNot?1 小时前
深度学习入门(二):从感知机到神经网络
人工智能·深度学习·神经网络
腾讯安全应急响应中心1 小时前
命悬生死线:当游戏遭遇DDoS围剿,如何用AI破局?
网络·人工智能·游戏·ddos
jamison_11 小时前
文心一言与 DeepSeek 的竞争分析:技术先发优势为何未能转化为市场主导地位?
人工智能·ai·chatgpt·gpt-3·1024程序员节
一只小闪闪1 小时前
langchain4j搭建失物招领系统(四)---实现更新功能
java·人工智能·后端
CJH(本人账号)1 小时前
Trae-中国首款免费AI原生IDE
人工智能