【图像处理基石】图像压缩有哪些经典算法?
图像压缩是减少图像数据量的技术,主要分为无损压缩(不丢失信息)和有损压缩(允许一定信息损失以换取更高压缩率)。以下是几种经典算法及其Python实现:
一、经典图像压缩算法简介
-
行程长度编码(RLE)
无损压缩,适用于存在连续重复像素的图像(如线条图、图标)。原理是记录"像素值+连续出现次数"替代重复像素。 -
哈夫曼编码(Huffman Coding)
无损压缩,基于像素值出现频率构建变长编码(高频值用短码),广泛用于JPEG、PNG等格式。 -
离散余弦变换(DCT)
有损压缩,JPEG核心算法。将图像分块后转换到频率域,保留低频分量(视觉重要),丢弃高频分量(细节)。 -
小波变换(Wavelet Transform)
有损/无损均可,JPEG 2000采用。相比DCT能更好保留边缘信息,压缩率更高。
二、Python实现示例
1. 行程长度编码(RLE)
适用于简单图像(如二值图),实现简单且压缩效果直观。
import numpy as np
from PIL import Imagedef rle_compress(image):"""将灰度图像压缩为RLE编码(列表形式:[(值, 长度), ...])"""# 将二维图像转为一维数组flat = image.flatten()if len(flat) == 0:return []compressed = []current_val = flat[0]count = 1for val in flat[1:]:if val == current_val:count += 1else:compressed.append((current_val, count))current_val = valcount = 1# 处理最后一组compressed.append((current_val, count))return compresseddef rle_decompress(compressed, shape):"""将RLE编码解压为图像"""flat = []for val, count in compressed:flat.extend([val] * count)# 恢复为原始形状return np.array(flat).reshape(shape)# 测试
if __name__ == "__main__":# 读取图像并转为灰度图img = Image.open("test_image.png").convert("L") # 替换为你的图像路径img_array = np.array(img)print("原始图像形状:", img_array.shape)print("原始数据量:", img_array.size, "像素")# 压缩compressed = rle_compress(img_array)print("RLE压缩后数据量:", len(compressed), "组")# 解压decompressed = rle_decompress(compressed, img_array.shape)# 验证是否完全恢复(无损压缩)print("解压后与原始是否一致:", np.array_equal(img_array, decompressed))# 保存解压后的图像Image.fromarray(decompressed).save("rle_decompressed.png")
2. 哈夫曼编码(Huffman Coding)
基于频率的变长编码,适合像素值分布不均匀的图像。
import numpy as np
from PIL import Image
import heapq
from collections import defaultdict# 构建哈夫曼树节点
class HuffmanNode:def __init__(self, value, freq):self.value = value # 像素值self.freq = freq # 频率self.left = Noneself.right = None# 用于优先队列排序def __lt__(self, other):return self.freq < other.freq# 构建哈夫曼树
def build_huffman_tree(freq_dict):heap = [HuffmanNode(val, freq) for val, freq in freq_dict.items()]heapq.heapify(heap)while len(heap) > 1:left = heapq.heappop(heap)right = heapq.heappop(heap)# 合并节点(值为None表示内部节点)merged = HuffmanNode(None, left.freq + right.freq)merged.left = leftmerged.right = rightheapq.heappush(heap, merged)return heap[0] if heap else None# 生成哈夫曼编码表
def generate_code_table(root):code_table = {}def traverse(node, current_code=""):if node is None:return# 叶子节点(有像素值)if node.value is not None:code_table[node.value] = current_code if current_code else "0"return# 左子树加"0",右子树加"1"traverse(node.left, current_code + "0")traverse(node.right, current_code + "1")traverse(root)return code_table# 哈夫曼压缩
def huffman_compress(image):flat = image.flatten()# 统计频率freq_dict = defaultdict(int)for val in flat:freq_dict[val] += 1# 构建树和编码表root = build_huffman_tree(freq_dict)code_table = generate_code_table(root)# 编码数据(转为二进制字符串)encoded_bits = "".join([code_table[val] for val in flat])return encoded_bits, code_table, image.shape# 哈夫曼解压
def huffman_decompress(encoded_bits, code_table, shape):# 反转编码表(编码->值)reverse_table = {code: val for val, code in code_table.items()}current_code = ""flat = []for bit in encoded_bits:current_code += bitif current_code in reverse_table:flat.append(reverse_table[current_code])current_code = ""return np.array(flat).reshape(shape)# 测试
if __name__ == "__main__":img = Image.open("test_image.png").convert("L")img_array = np.array(img)print("原始图像形状:", img_array.shape)print("原始数据量:", img_array.size * 8, "比特(假设8位灰度)")# 压缩encoded_bits, code_table, shape = huffman_compress(img_array)print("哈夫曼压缩后数据量:", len(encoded_bits), "比特")print("压缩率:", len(encoded_bits)/(img_array.size * 8))# 解压decompressed = huffman_decompress(encoded_bits, code_table, shape)# 验证无损性print("解压后与原始是否一致:", np.array_equal(img_array, decompressed))Image.fromarray(decompressed).save("huffman_decompressed.png")
3. 离散余弦变换(DCT)
JPEG核心算法,有损压缩,通过丢弃高频分量减少数据量。
import numpy as np
from PIL import Image
from scipy.fftpack import dct, idct# JPEG标准亮度量化矩阵(用于有损压缩)
QUANTIZATION_MATRIX = np.array([[16, 11, 10, 16, 24, 40, 51, 61],[12, 12, 14, 19, 26, 58, 60, 55],[14, 13, 16, 24, 40, 57, 69, 56],[14, 17, 22, 29, 51, 87, 80, 62],[18, 22, 37, 56, 68, 109, 103, 77],[24, 35, 55, 64, 81, 104, 113, 92],[49, 64, 78, 87, 103, 121, 120, 101],[72, 92, 95, 98, 112, 100, 103, 99]
], dtype=np.float32)def dct_compress(image, quant_matrix=QUANTIZATION_MATRIX):"""DCT压缩:分块->DCT->量化"""h, w = image.shape# 确保尺寸是8的倍数(JPEG分块大小为8x8)h_padded = ((h + 7) // 8) * 8w_padded = ((w + 7) // 8) * 8padded = np.pad(image, ((0, h_padded - h), (0, w_padded - w)), mode='constant')compressed = np.zeros_like(padded, dtype=np.int32)# 对每个8x8块进行处理for i in range(0, h_padded, 8):for j in range(0, w_padded, 8):block = padded[i:i+8, j:j+8]# 转换为[-128, 127](DCT通常基于零均值)block_shifted = block - 128# 2D DCT变换dct_block = dct(dct(block_shifted, axis=0, norm='ortho'), axis=1, norm='ortho')# 量化(除以量化矩阵并取整,核心压缩步骤)quant_block = np.round(dct_block / quant_matrix).astype(np.int32)compressed[i:i+8, j:j+8] = quant_blockreturn compressed, (h, w) # 保留原始尺寸用于解压def dct_decompress(compressed, original_shape, quant_matrix=QUANTIZATION_MATRIX):"""DCT解压:反量化->IDCT->拼接"""h_padded, w_padded = compressed.shapeh, w = original_shapedecompressed = np.zeros_like(compressed, dtype=np.float32)# 对每个8x8块处理for i in range(0, h_padded, 8):for j in range(0, w_padded, 8):quant_block = compressed[i:i+8, j:j+8]# 反量化dct_block = quant_block * quant_matrix# 逆DCT变换block_shifted = idct(idct(dct_block, axis=0, norm='ortho'), axis=1, norm='ortho')# 恢复到[0, 255]block = np.round(block_shifted + 128).clip(0, 255).astype(np.uint8)decompressed[i:i+8, j:j+8] = block# 裁剪回原始尺寸return decompressed[:h, :w].astype(np.uint8)# 测试
if __name__ == "__main__":img = Image.open("test_image.png").convert("L")img_array = np.array(img)print("原始图像形状:", img_array.shape)# 压缩compressed, original_shape = dct_compress(img_array)# 解压(会有损失)decompressed = dct_decompress(compressed, original_shape)# 保存结果Image.fromarray(decompressed).save("dct_decompressed.png")print("DCT压缩解压完成(有损压缩,可能有画质损失)")
三、算法对比与应用场景
算法 | 类型 | 压缩率 | 适用场景 | 典型应用 |
---|---|---|---|---|
RLE | 无损 | 低 | 连续重复像素多的图像(图标) | BMP、Fax格式 |
哈夫曼编码 | 无损 | 中 | 像素分布不均匀的图像 | JPEG、PNG、ZIP |
DCT | 有损 | 高 | 自然图像(人眼对细节不敏感) | JPEG |
小波变换 | 有损/无损 | 高 | 需要高压缩率+保留细节的场景 | JPEG 2000、医学图像 |
以上实现均为简化版本,实际工业级压缩(如JPEG)会结合多种算法(如DCT+哈夫曼编码)并优化细节(如色彩空间转换、熵编码)。