【图像处理基石】图像压缩有哪些经典算法?

图像压缩是减少图像数据量的技术,主要分为无损压缩 (不丢失信息)和有损压缩(允许一定信息损失以换取更高压缩率)。以下是几种经典算法及其Python实现:

一、经典图像压缩算法简介

  1. 行程长度编码(RLE)

    无损压缩,适用于存在连续重复像素的图像(如线条图、图标)。原理是记录"像素值+连续出现次数"替代重复像素。

  2. 哈夫曼编码(Huffman Coding)

    无损压缩,基于像素值出现频率构建变长编码(高频值用短码),广泛用于JPEG、PNG等格式。

  3. 离散余弦变换(DCT)

    有损压缩,JPEG核心算法。将图像分块后转换到频率域,保留低频分量(视觉重要),丢弃高频分量(细节)。

  4. 小波变换(Wavelet Transform)

    有损/无损均可,JPEG 2000采用。相比DCT能更好保留边缘信息,压缩率更高。

二、Python实现示例

1. 行程长度编码(RLE)

适用于简单图像(如二值图),实现简单且压缩效果直观。

python 复制代码
import numpy as np
from PIL import Image

def rle_compress(image):
    """将灰度图像压缩为RLE编码(列表形式:[(值, 长度), ...])"""
    # 将二维图像转为一维数组
    flat = image.flatten()
    if len(flat) == 0:
        return []
    
    compressed = []
    current_val = flat[0]
    count = 1
    
    for val in flat[1:]:
        if val == current_val:
            count += 1
        else:
            compressed.append((current_val, count))
            current_val = val
            count = 1
    # 处理最后一组
    compressed.append((current_val, count))
    return compressed

def 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)

基于频率的变长编码,适合像素值分布不均匀的图像。

python 复制代码
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 = None
        self.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 = left
        merged.right = right
        heapq.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 += bit
        if 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核心算法,有损压缩,通过丢弃高频分量减少数据量。

python 复制代码
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) * 8
    w_padded = ((w + 7) // 8) * 8
    padded = 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_block
    
    return compressed, (h, w)  # 保留原始尺寸用于解压

def dct_decompress(compressed, original_shape, quant_matrix=QUANTIZATION_MATRIX):
    """DCT解压:反量化->IDCT->拼接"""
    h_padded, w_padded = compressed.shape
    h, w = original_shape
    decompressed = 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+哈夫曼编码)并优化细节(如色彩空间转换、熵编码)。

相关推荐
chxin140163 小时前
openCV3.0 C++ 学习笔记补充(自用 代码+注释)---持续更新 四(91-)
c++·opencv·计算机视觉
茜茜西西CeCe3 小时前
数字图像处理-巴特沃斯高通滤波、低通滤波
图像处理·opencv·计算机视觉·matlab·巴特沃斯高通滤波·巴特沃斯低通滤波
IT古董4 小时前
【第五章:计算机视觉】1.计算机视觉基础-(3)卷积神经网络核心层与架构分析:卷积层、池化层、归一化层、激活层
人工智能·计算机视觉·cnn
AI 嗯啦7 小时前
计算机视觉----图像投影(透视)变换(小案例)
人工智能·opencv·计算机视觉
不枯石9 小时前
Python实现点云法向量各种方向设定
python·计算机视觉·numpy
FisherYu11 小时前
AI环境搭建pytorch+yolo8搭建
前端·计算机视觉
范男11 小时前
YOLO11目标检测运行推理简约GUI界面
图像处理·人工智能·yolo·计算机视觉·视觉检测
sali-tec12 小时前
C# 基于halcon的视觉工作流-章33-矩状测量
开发语言·人工智能·算法·计算机视觉·c#
格林威13 小时前
短波红外相机在机器视觉检测方向的应用
运维·人工智能·深度学习·数码相机·计算机视觉·视觉检测