备注
本项目为hhu_21级_2023年秋季_多媒体技术概论的课设作业
基于互联网的开源精神在此分享,希望能给大家一些参考
有不妥的地方还请大家指出
一、项目概述
1.1 图像压缩技术的背景与重要性
在数字图像广泛运用的今天,无论是在数字摄影、电子商务还是远程医疗领域,图像数据的高效存储与传输都显得尤为重要。由于高质量的图像占用大量存储空间和传输带宽,图像压缩技术因此成为减少这一需求的关键手段。图像压缩技术能够在保证图像质量的同时,降低文件大小,使得图像传输更加迅速,极大地提升了用户体验。
1.2 现有图像压缩算法的局限性
1.2.1无损压缩算法
(1)RLE算法
RLE算法是一种基于重复字符的无损压缩算法,通过对连续的重复数据进行编码来实现数据压缩。该算法具有简单、高效的特点,适用于对连续性较强的数据进行压缩。但对于数据分布较为分散的情况,该算法效果不佳。
(2)LZW算法
LZW算法是一种基于字典的无损压缩算法,通过采用动态建立字典和编码方式,将图像数据进行压缩。该算法具有压缩比高、适用于各种数据分布的特点,但需要额外建立字典表,处理时需要耗费较多的计算资源。
1.2.2有损压缩算法
(1)JPEG算法
JPEG算法是一种基于离散余弦变换的有损压缩算法,通过将图像分为若干个8×8大小的块,对每块图像进行离散余弦变换和量化,并采用哈夫曼编码进行压缩,实现数据压缩。该算法具有压缩比高、色彩表现良好的特点,但会造成图像质量损失,适用于对图像数据压缩要求较高、对质量要求较低的场景。
(2)JPEG2000算法
JPEG2000算法是一种基于小波变换的有损压缩算法,通过对图像进行小波变换和量化,并采用算术编码进行压缩,实现数据压缩。该算法具有良好的压缩比和图像质量表现,适用于对图像质量要求较高的场景。但该算法处理过程较为复杂,计算量较大。
(3)PNG算法
PNG算法是一种基于可逆压缩的有损压缩算法,通过对图像数据进行差分编码和基于LZ77算法的压缩实现数据压缩。该算法具有良好的图像质量表现、压缩比适中、无损压缩的特点,适用于对图像质量要求较高、对压缩比要求适中的场景。
1.3 基于神经网络的图像压缩新途径
神经网络在图像和视频领域具有强大的特征提取和表达能力,它可以通过学习数据中的复杂模式和结构来进行更精细的压缩。与传统算法相比,基于深度学习的方法可以更好地保留细节、减少失真,并提供更好的视觉体验。此外,神经网络还可以根据不同场景和内容自动调整压缩参数,从而在不同情况下获得更优的压缩效果。这种自适应性将为未来网络传输、存储和流媒体等领域带来更高效的解决方案。
1.4 本研究的目标与意义
本项目使用基于卷积神经网络自编码器对图像进行压缩和解压缩,构建一个自编码器神经网络模型,训练它来学习输入图像的压缩表示,然后使用该模型来压缩和解压缩图像。通过不断的训练和调整各类参数来获得最佳的压缩比和最优的重构图像。该研究对提升图像压缩技术具有重要意义。
二、理论基础与研究方法
2.1 神经网络简介
2.1.1 神经网络发展历程
从早期的M-P模型、感知器(单层神经网络)、多层神经网络到现代的多层前馈神经网络,神经网络的核心思想一直在于模拟生物神经系统的信息处理过程。特别是在引入激活函数、权重和偏置以及多层结构后,神经网络的表达能力得到了质的飞跃。
2.1.2 神经网络分类
全连接神经网络 | 卷积神经网络 | 循环神经网络 | |
---|---|---|---|
特性 | 每个神经元与前一层的所有神经元相连,网络结构简单。 | 利用卷积层保持空间关系,参数共享和局部连接减少计算量。 | 能处理任意长度的序列数据,具有内部状态用于捕捉时间序列信息。 |
应用 | 固定大小的输入数据处理,如表格数据、向量化的文本数据。 | 图像识别、计算机视觉等处理图像数据的任务。 | 语言模型、文本生成、时间序列分析等序列数据处理任务。 |
训练过程 | FCNN由多层全连接层构成,每一层的每个神经元与前一层的所有神经元相连。 训练过程通常包括前向传播和反向传播。 前向传播是输入数据在网络中依次通过各层,最终输出预测结果。 反向传播是根据预测结果与真实标签的差距,计算损失函数的梯度,并将梯度传递回网络,更新每一层的权重。 训练时使用梯度下降算法或其变种进行权重更新。 | CNN专门用于处理具有明显网格结构的数据,如图像。 CNN训练过程包括数据输入层处理、卷积计算层、池化层和全连接层,其中卷积层和池化层对图像进行特征提取[6]。 训练过程同样涉及前向传播和反向传播,但是卷积层和池化层有特定的操作,如滤波器卷积和下采样。 权重更新同样采用梯度下降算法或其变种。 | RNN设计用于处理序列数据,具有记忆前面信息的能力,常用于文本或时间序列分析。 训练过程涉及前向传播,其中状态在序列的每个时间点传递。 反向传播时,误差会通过时间反向传播,这称为Backpropagation Through Time (BPTT)。 RNN常见的变种有LSTM和GRU,它们通过特殊的门控机制改善了长期依赖问题[5]。 同样使用梯度下降算法或其变种进行权 |
模型评估方法 | 使用交叉验证来评估模型的泛化能力。 通过计算测试集上的损失函数值或准确率等评估指标来评估模型性能。 | 训练过程同样涉及前向传播和反向传播,但是卷积层和池化层有特定的操作,如滤波器卷积和下采样。 权重更新同样采用梯度下降算法或其变种。 | 过特殊的门控机制改善了长期依赖问题[5]。 同样使用梯度下降算法或其变种进行权重更新。 |
2.2 卷积神经网络
卷积神经网络通过卷积层、激活函数和池化层的多层结构对图像进行特征提取和降维,极大地减少了模型参数并提高了计算效率。
相关概念:
(1)输入层:一个或者多个二维数组
(2)偏置:增加偏置单元可以给网络分类增加平移的能力
(3)激活函数:将原始数据从线性空间映射到非线性空间。为了得到复杂的函数关系,如果不使用激活函数,最后得到的只是线性关系,并不是我们所需要的。
(4)隐藏层:多个隐藏层来实现特征的提取
(5)卷积:提取特征值
(6)池化:减小卷积层得到的特征值的规模,避免过拟合
(7)输出:全连接层实现输出
2.3 自编码器
自编码器是一种特殊的神经网络架构,用于无监督学习有效的特征编码。它通过编码器将输入数据转换为低维表示,并通过解码器重建数据,目的是最小化原始输入与重建输出之间的差异。在本项目中Encoder和Decoder均用卷积神经网络实现。
三、实验过程及操作步骤
3.1 实验环境准备
软件:
Anaconda环境下的
Python ==3.8,
TensorFlow ==2.4.0,
Keras ==2.4.3,
NumPy ==1.19.5,
Matplotlib ==3.4.2
硬件:
NVIDIA GeForce MX450
3.2 数据准备与处理
训练集由约一万张图像组成,测试集包含约一百张图像,每张图像大小为224*224。
利用定义好的load_images(path)函数读取和归一化图像数据。
数据集地址:
https://aistudio.baidu.com/datasetoverview
3.3 自编码器模型构建
采用create_autoencoder()函数构建自编码器模型,通过卷积、激活、池化等操作层构建网络结构。
(理论上为1/3,但实际压缩比为1/5~1/6【笑哭】不知道为什么)
3.4 模型训练
使用train_module(path)函数进行模型训练,设置适当的epochs和batch_size以适应硬件条件。(epochs=10,batch_size=32)
3.5 模型测试
通过test_module(autoencoder, path)函数测试模型性能,展示压缩前后的图像大小以及原始图像与重建图像的对比。
3.6 项目运行实例
四、研究成果与展示
4.1 压缩效果对比
展示了使用自编码器压缩前后的图像文件大小,以及原始图像与由模型重建的图像的视觉对比。
五、项目分析与总结
本项目实现了基于卷积神经网络而组成的自编码器,以此来实现图像的压缩与重构。
卷积神经网络的底层就是用多个卷积核来提取出一个特征网络,再通过一个池化层来降低特征的维度避免过拟合,多层嵌套以实现对于目标特征的有效提取,提取后的值与理想值存在差异,用损失函数Loss值刻画,采用反向传播的梯度下降算法来更新参数,如此循环,随着训练轮数的增加,参数更新到了一定的水平就可以非常有效地提取出目标的特征。本质可以理解为一个拟合非线性函数的过程。但底层就是乘与加的操作(可以理解为计算机非常擅长的矩阵运算),但参数更新的过程确实较为抽象,但keras库已经封装好了,直接调参调用就行了,但配置anaconda环境和GPU环境确实比较繁琐。
本项目在本地计算机训练与测试,因为硬件的限制,autoencoder.fit(x_train, x_train, epochs=10, batch_size=32,shuffle=True, validation_split=0.1, callbacks=[early_stopping])训练函数中训练轮次epochs设为10轮,batch_size设为32(再多会报错:显卡内存不足)。并且训练集有一万张224*224的图像,传入更多的图像电脑内存无法导入。如果有更好的硬件条件,或许能训练出更好的模型,能让重构的图像更加接近原始的图像。
六、项目源代码
# compress.py
import os
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D
from tensorflow.keras.models import Model
from tensorflow.keras.preprocessing.image import img_to_array, load_img
from tensorflow.keras.callbacks import EarlyStopping
from PIL import Image
'''
anaconda环境:
python==3.8
tensorflow==2.4.0
keras==2.4.3
numpy==1.19.5
matplotlib==3.4.2
硬件:
NVIDIA GeForce MX450
'''
# 创建自编码器模型结构
def create_autoencoder():
input_img = Input(shape=(224, 224, 3)) # 输入层,224x224x3
# 编码器
# 这个卷积层有32个过滤器,每个过滤器的大小为3x3,使用ReLU激活函数,并采用"same"填充方式。
x = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img)
# 添加一个最大池化层,大小为2x2,采用"same"填充方式。最大池化层用于降低特征图的尺寸,并提取主要特征。
x = MaxPooling2D((2, 2), padding='same')(x)
# 这个卷积层有16个过滤器,每个过滤器的大小为3x3,使用ReLU激活函数,并采用"same"填充方式。
x = Conv2D(16, (3, 3), activation='relu', padding='same')(x)
encoded = MaxPooling2D((2, 2), padding='same')(x)
# 解码器
# 添加一个卷积层作为解码器网络的第一层,具有16个过滤器,大小为3x3,使用ReLU激活函数,并采用"same"填充方式。
x = Conv2D(16, (3, 3), activation='relu', padding='same')(encoded)
# 添加一个上采样层,将特征图的尺寸扩大两倍。
x = UpSampling2D((2, 2))(x)
# 添加另一个卷积层作为解码器网络的第二层,具有32个过滤器,大小为3x3,使用ReLU激活函数,并采用"same"填充方式。
x = Conv2D(32, (3, 3), activation='relu', padding='same')(x)
# 添加第二个上采样层,将特征图的尺寸再次扩大两倍。
x = UpSampling2D((2, 2))(x)
# 添加输出层,这是解码器网络的最后一层。它具有3个过滤器,大小为3x3,使用Sigmoid激活函数,并采用"same"填充方式。输出层的目标是重构输入图像。
decoded = Conv2D(3, (3, 3), activation='sigmoid', padding='same')(x)
autoencoder = Model(input_img, decoded)
# 编译模型,指定优化器为Adam,损失函数为二元交叉熵(binary_crossentropy)。自动编码器的目标是通过最小化重构误差来学习数据的有效表示。
autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
# 打印模型的概要信息,包括每一层的名称、输出形状和参数数量等。概要信息提供了对模型结构的整体概览。
# autoencoder.summary()
return autoencoder
# 加载图像数据
def load_images(path):
images = [] # 图像信息:类型为数组,并已进行归一化
sizes = [] # 图像大小
# 遍历路径下的所有文件夹和文件
for root, _, filenames in os.walk(path):
for img_name in filenames:
# 构造每个文件的完整路径
img_path = os.path.join(root, img_name)
try:
# 加载并处理图像
sizes.append(os.path.getsize(img_path)) # 获取原始文件大小
image = load_img(img_path, target_size=(224, 224))
image = img_to_array(image) # 图像-->数组
images.append(image)
except Exception as e:
# 如果出现任何错误,打印错误并跳过这个图像
print(f"Error loading image {img_path}: {e}")
# 将图像数据转换为浮点数,并归一化到0-1之间
images = np.array(images) / 255.0
return images, sizes
# 展示原始和重建的图像
def show_images(original, decoded, n=3):
plt.figure(figsize=(10, 4))
for i in range(n):
# 展示原始图像
ax = plt.subplot(2, n, i + 1)
plt.imshow(original[i])
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# 展示重建图像
ax = plt.subplot(2, n, i + 1 + n)
plt.imshow(decoded[i])
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
# 保存压缩后的图像,并返回文件大小
def save_and_get_compressed_size(image, path):
image *= 255.0 # 反归一化
image = image.astype('uint8')
img = Image.fromarray(image)
img.save(path, format='JPEG', quality=25) # 假设使用25的质量因子压缩
return os.path.getsize(path)
# 训练模型
def train_module(path):
# 创建自编码器模型
autoencoder = create_autoencoder()
autoencoder.summary()
'''
当你调用 .summary() 方法时,它会给出以下信息:
Layer (type):显示层的名称和类型,例如 Conv2D 是二维卷积层。
Output Shape:显示该层输出的维度。对于图像数据,这通常是四维的,形式如 (None, height, width, channels),其中 None 代表批处理维度,它在模型定义时是不确定的。
Param #:显示该层的参数数量。这包括权重和偏置。
Connected to:如果你的模型比较复杂,这一列会显示该层的输入是如何连接的,即它接收哪些层的输出作为输入。
总结信息的底部还会给出总参数数量、可训练参数数量和非训练参数数量。
可训练参数是指在训练过程中会更新的参数,而非训练参数通常来自于像BatchNormalization层这样的层,在训练过程中不会更新。
这个功能在设计和调试模型时特别有用,因为它可以让你快速检查模型结构和参数计数是否符合预期。
'''
'''
loss: 训练误差,表示模型在每个训练批次中的平均损失值.
它是模型在训练数据上的表现,用于衡量模型对训练数据的拟合程度。
训练误差的目标是尽量减小,使模型能够更好地拟合训练数据。
val_loss: 验证误差,表示模型在每个训练轮次结束时,在验证数据上的平均损失值。
它是模型在未参与训练的数据上的表现,用于衡量模型的泛化能力。
验证误差的目标是尽量减小,使模型能够在未见过的数据上有良好的表现。
'''
# 加载训练集
# "D:\\基于神经网络的图像压缩算法\\train"
train_path = path
x_train, train_sizes = load_images(train_path)
# 训练模型
early_stopping = EarlyStopping(monitor='val_loss', patience=2)
autoencoder.fit(x_train, x_train, epochs=10, batch_size=32,
shuffle=True, validation_split=0.1, callbacks=[early_stopping])
'''
EarlyStopping: 这是一个回调函数,它可以在训练过程中提前停止,以防止过拟合。如果在连续几个时期(这里是2个时期)内监测的指标(这里是验证集上的损失val_loss)没有改进,则会停止训练。
autoencoder.fit: 这是Keras中的函数,用于在给定的输入数据上训练模型。在这个例子中,自编码器使用x_train作为输入数据,并尝试重建相同的x_train数据,这就是为什么输入和目标数据是相同的。
epochs=10: 这表示模型将遍历整个数据集总共10次。
batch_size=256: 这表示在更新模型权重之前,每次训练将考虑256个样本。这是一个超参数,可以根据计算资源和数据集大小进行调整。
shuffle=True: 在每个时期开始之前,数据会被打乱,这有助于提高模型的泛化能力并减少过拟合的风险。
validation_split=0.1: 这表示从训练数据中保留10%作为验证数据,用于监测训练过程中模型的性能。这部分数据不会用于权重的更新。
callbacks=[early_stopping]: 这表示将早停法作为回调函数传递给训练过程,以便在训练时使用。
'''
return autoencoder
# 测试模型
def test_module(autoencoder, path):
# 加载测试集,并选择3张图片进行展示
test_path = path
test_images, test_sizes = load_images(test_path)
indices = np.random.choice(len(test_images), 5, replace=False)
selected_images = test_images[indices]
selected_sizes = [test_sizes[i] for i in indices]
# 对选定的测试图像进行编码和解码
decoded_images = autoencoder.predict(selected_images)
# 展示原始和重建的图像
show_images(selected_images, decoded_images)
# 计算并输出压缩前后的文件大小
compressed_sizes = []
for i, decoded_image in enumerate(decoded_images):
# 假设压缩后的图像保存在以下路径
compressed_path = f"D:\\基于神经网络的图像压缩\\compressed\\image_{i}.jpg"
compressed_size = save_and_get_compressed_size(decoded_image, compressed_path)
compressed_sizes.append(compressed_size)
print(f"Image {i}: Original size = {selected_sizes[i]} bytes, Compressed size = {compressed_size} bytes")
from compress import train_module, test_module
if __name__ == "__main__":
# 训练模型
autoencoder = train_module("D:\\基于神经网络的图像压缩\\train")
# 对测试集进行压缩,并输出压缩前后的文件大小
test_module(autoencoder, "D:\\基于神经网络的图像压缩\\test")