项目背景与目的

在本文中,我们将通过一个实战项目介绍使用Keras构建一个简单的卷积神经网络(CNN),以实现对猫和狗图像的识别任务。

数据来源

本项目使用的数据集来源于kaggle网站里已经分类好的猫狗图片,分为训练集和测试集。

下载地址:Cat and Dog (kaggle.com)

1.猫狗识别数据集整理与划分

在构建深度学习模型时,数据集的准备与划分是至关重要的一步。在本项目中,我们将针对猫狗识别任务,对原始数据集进行整理,并划分为训练集、验证集和测试集。

import os, shutil 
# 原始目录所在的路径
original_dataset_dir = '猫狗识别/train'

# 数据集分类后的目录
base_dir = '猫狗识别/test1'

# # 训练、验证、测试数据集的目录
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')

# 猫训练图片所在目录
train_cats_dir = os.path.join(train_dir, 'cats')

# 狗训练图片所在目录
train_dogs_dir = os.path.join(train_dir, 'dogs')

# 猫验证图片所在目录
validation_cats_dir = os.path.join(validation_dir, 'cats')

# 狗验证数据集所在目录
validation_dogs_dir = os.path.join(validation_dir, 'dogs')

# 猫测试数据集所在目录
test_cats_dir = os.path.join(test_dir, 'cats')

# 狗测试数据集所在目录
test_dogs_dir = os.path.join(test_dir, 'dogs')

# 将前1000张猫图像复制到train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    shutil.copyfile(src, dst)

# 将下500张猫图像复制到validation_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    shutil.copyfile(src, dst)
    
# 将下500张猫图像复制到test_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_cats_dir, fname)
    shutil.copyfile(src, dst)
    
# 将前1000张狗图像复制到train_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    shutil.copyfile(src, dst)
    
# 将下500张狗图像复制到validation_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    shutil.copyfile(src, dst)
    
# 将下500张狗图像复制到test_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    shutil.copyfile(src, dst)

分类后如下图所示:


现在我们已经完成了数据集的整理和划分。接下来,我们可以开始构建和训练深度学习模型了。在下一部分中,我们将介绍如何使用Keras等深度学习框架来构建和训练一个用于猫狗识别的卷积神经网络(CNN)模型,并评估其在测试集上的性能。

2.网络模型构建

在完成了数据集的整理与划分后,我们现在将转向构建深度学习模型来执行猫狗识别的任务。在本节中,我们将使用Keras框架来定义一个卷积神经网络(CNN)模型

首先,我们需要从Keras中导入所需的层和模型类。然后,我们可以开始定义我们的序贯模型(Sequential model)

#网络模型构建:
from keras import layers
from keras import models
#keras的序贯模型
model = models.Sequential()
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核2*2,激活函数relu
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#flatten层,用于将多维的输入一维化,用于卷积层和全连接层的过渡
model.add(layers.Flatten())
#退出层
model.add(layers.Dropout(0.5))
#全连接,激活函数relu
model.add(layers.Dense(512, activation='relu'))
#全连接,激活函数sigmoid
model.add(layers.Dense(1, activation='sigmoid'))

#输出模型各层的参数状况
model.summary()

结果如下图所示:

3.配置优化器和图像数据增强

3.1配置优化器

在训练神经网络之前,我们需要配置模型的损失函数和优化器。损失函数用于量化模型预测与实际值之间的差距,而优化器则用于根据损失函数的梯度更新模型的权重。

from keras import optimizers  
  
# 配置模型,使用二元交叉熵损失(binary_crossentropy)作为损失函数  
# RMSprop优化器,学习率(lr)设置为1e-4  
# 并指定精度(acc)作为评估指标  
model.compile(loss='binary_crossentropy',  
              optimizer=optimizers.RMSprop(lr=1e-4),  
              metrics=['acc'])

这里,我们选择了二元交叉熵损失函数,因为它适用于二分类问题。RMSprop优化器是一个自适应学习率方法,它通过除以最近梯度平方的指数衰减平均值来调节每个参数的学习率。

3.2图像数据生成器增强数据

数据增强是一种用于增加模型泛化能力的技术,它通过随机变换输入图像来生成新的训练样本。在图像分类任务中,数据增强特别有用,因为它可以模拟图像的各种变换,如旋转、平移、缩放等,从而帮助模型学习这些变换下的不变性。

from keras.preprocessing.image import ImageDataGenerator  
  
# 创建一个ImageDataGenerator对象,并指定各种图像变换的参数  
datagen = ImageDataGenerator(  
    rotation_range=40,  # 随机旋转的角度范围  
    width_shift_range=0.2,  # 随机水平平移的范围(相对于总宽度的比例)  
    height_shift_range=0.2,  # 随机垂直平移的范围(相对于总高度的比例)  
    shear_range=0.2,  # 随机错切变换的角度  
    zoom_range=0.2,  # 随机缩放的范围  
    horizontal_flip=True,  # 随机水平翻转  
    fill_mode='nearest'  # 当进行变换时,超出原图像边界的像素的填充方式  
)
3.3数据效果可视化

为了查看数据增强的效果,我们可以随机选取一张图像,并使用ImageDataGeneratorflow方法生成变换后的图像批次。然后,我们可以使用matplotlib来可视化这些变换后的图像。

import matplotlib.pyplot as plt  
from keras.preprocessing import image  
  
# 假设train_cats_dir是训练集中猫的图片目录的路径  
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]  
img_path = fnames[3]  # 选择第四张图片  
  
# 加载并预处理图像  
img = image.load_img(img_path, target_size=(150, 150))  
x = image.img_to_array(img)  
x = x.reshape((1,) + x.shape)  # 添加批次维度  
  
# 使用数据生成器生成变换后的图像批次,并可视化其中的几张  
i = 0  
for batch in datagen.flow(x, batch_size=1):  
    plt.figure(i)  
    imgplot = plt.imshow(image.array_to_img(batch[0]))  
    i += 1  
    if i % 4 == 0:  # 每4张图显示一次  
        break  
plt.show()

这段代码会显示原始图像以及经过四种不同随机变换后的图像,从而直观地展示了数据增强的效果。

4.图片格式转化与模型训练

4.1图片格式转化

在深度学习的图像处理任务中,数据预处理是一个至关重要的步骤。它涉及到数据的清洗、增强、归一化等多个方面。在本项目中,我们将专注于图片格式转化和数据增强,并使用Keras框架的ImageDataGenerator来实现这一功能。同时,我们还将使用这些处理过的数据来训练一个深度学习模型,并将训练好的模型保存以便后续使用。

# 图片数据增强设置  
train_datagen = ImageDataGenerator(  
    rescale=1./255,  # 将像素值缩放到0-1之间  
    rotation_range=40,  # 随机旋转图片(0-40度)  
    width_shift_range=0.2,  # 宽度随机平移(最大平移20%的宽度)  
    height_shift_range=0.2,  # 高度随机平移(最大平移20%的高度)  
    shear_range=0.2,  # 随机剪切变换(0-0.2弧度)  
    zoom_range=0.2,  # 随机缩放(1-10%的比例)  
    horizontal_flip=True,  # 随机水平翻转  
)  
  
# 注意:验证数据不应被增强!  
test_datagen = ImageDataGenerator(rescale=1./255)  # 仅对验证数据进行归一化  
  
# 使用flow_from_directory方法从目录中读取数据  
train_generator = train_datagen.flow_from_directory(  
    train_dir,  # 训练数据目录  
    target_size=(150, 150),  # 所有图片将被缩放到150x150  
    batch_size=32,  # 批处理大小  
    class_mode='binary'  # 二分类问题,标签为0或1  
)  
  
validation_generator = test_datagen.flow_from_directory(  
    validation_dir,  # 验证数据目录  
    target_size=(150, 150),  # 所有图片将被缩放到150x150  
    batch_size=32,  # 批处理大小  
    class_mode='binary'  # 二分类问题,标签为0或1  
)
4.2开始训练并保存结果

在准备好训练数据和验证数据后,我们就可以开始训练模型了。在本项目中,我们使用model.fit_generator方法来训练模型。该方法会按照指定的批次大小从数据生成器中读取数据,并在每个epoch结束后计算验证集上的性能。

# 假设model是已经定义好的深度学习模型  
history = model.fit_generator(  
    train_generator,  
    steps_per_epoch=100,  # 每个epoch的迭代次数  
    epochs=100,  # 训练的总轮数  
    validation_data=validation_generator,  
    validation_steps=50  # 验证集的迭代次数  
)  
  
# 保存模型  
model.save('D:\\python文件\\4-3\\10\\log1.h5')

运行结果:

经过训练模型可以看出模型精度在85%左右。

训练完成后,我们使用model.save方法将训练好的模型保存为一个HDF5文件('.h5')。这样,我们就可以在需要的时候加载这个模型,而无需重新训练。

5.结果可视化

# 结果可视化:
import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

由于数据量的增加,对比基准模型,可以很明显的观察到曲线没有过度拟合了,训练曲线紧密地的踪验证曲线。

6.训练模型总结

通过以上步骤,我们成功创建并训练了一个猫狗图像分类器,模型在测试集上达到了较高的准确率,显示了良好的泛化能力。为后面的猫狗图像识别应用实现提高了准确率。

7.构建基于PyQt5与TensorFlow的猫狗图像识别应用实现

我们将介绍如何结合PyQt5(一个用于创建桌面应用程序的Python GUI框架)和TensorFlow(一个开源机器学习库)来创建一个简单的图像浏览与分类应用。这个应用将允许用户通过图形用户界面(GUI)选择图像文件,然后使用预训练的TensorFlow模型对图像进行分类。

7.1环境准备

在开始之前,请确保您的Python环境中安装了以下库:

  • PyQt5
  • TensorFlow
  • numpy

您可以使用pip来安装这些库(如果尚未安装)

pip install PyQt5 tensorflow numpy
import sys
import os
import threading
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QPushButton, QFileDialog, QHBoxLayout, QScrollArea, QMessageBox
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, pyqtSignal, QObject
import tensorflow as tf
import numpy as np
from tensorflow.keras.preprocessing import image
  • 7.2 模型加载

  • 预训练模型:利用tf.keras.models.load_model加载一个预先训练好的猫狗分类模型(.h5文件)
# 加载预先训练的猫狗分类模型
model = tf.keras.models.load_model("预先训练的猫狗分类模型")
  • 7.3图像处理与分类逻辑

  • 现在,将介绍如何定义一个名为ImageProcessorQObject子类,用于处理图像分类任务。
# 定义一个QObject子类,用于处理图像分类任务
class ImageProcessor(QObject):
    # 定义一个信号,用于发送文件名和分类结果到UI进行更新
    result_signal = pyqtSignal(str, str)  # 发送文件名和识别结果的信号

    def __init__(self, filename):
        super().__init__()
        self.filename = filename

7.4处理流程

加载图像、调整尺寸至模型所需输入尺寸、归一化、模型预测,并根据预测概率判断类别

# 实现图像处理逻辑,包括加载图像、预处理和模型预测
    def process_image(self):
        try:
            img = image.load_img(self.filename, target_size=(150, 150))
            img_array = image.img_to_array(img)
            img_array = np.expand_dims(img_array, axis=0) / 255.0
            prediction = model.predict(img_array)
            result = "狗" if prediction[0][0] > 0.5 else "猫"
            self.result_signal.emit(self.filename, result)
        except Exception as e:
            print(f"图像处理异常:{e}")


最后,如果图像处理过程中出现任何异常,我们将捕获异常并打印其信息。这有助于在调试过程中发现问题并进行修复。 

  • 7.5用户界面设计

  • 现在,我们将创建一个名为 CatDogClassifierApp 的 QWidget 子类,作为我们的主应用窗口。这个类将包含应用窗口的初始化逻辑,以及一个用于存储 ImageProcessor 实例的列表。


class CatDogClassifierApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("猫狗识别系统")
        self.initUI()
        self.image_processors = []

完善主应用窗口的UI:设置窗口标题,初始化布局,包括图像预览区、上传按钮、批量处理按钮。

   def initUI(self):
        layout = QVBoxLayout()

        self.preview_area = QScrollArea()
        self.preview_area.setWidgetResizable(True)
        layout.addWidget(self.preview_area)

        self.preview_widget = QWidget()
        self.preview_layout = QHBoxLayout()
        self.preview_widget.setLayout(self.preview_layout)
        self.preview_area.setWidget(self.preview_widget)

        self.upload_button = QPushButton("上传图像")
        self.upload_button.clicked.connect(self.uploadImage)
        layout.addWidget(self.upload_button)

        self.batch_process_button = QPushButton("批量处理")
        self.batch_process_button.clicked.connect(self.batchProcess)
        layout.addWidget(self.batch_process_button)

        self.setLayout(layout)

7.6图像上传与展示

现在,我们将实现这两个按钮对应的槽函数:uploadImage 和 batchProcess。通过uploadImage方法实现单图上传,batchProcess方法实现多图批量处理。

  def uploadImage(self):
        filename, _ = QFileDialog.getOpenFileName(self, "选择图像", "", "图像文件 (*.png *.jpg *.jpeg)")
        if filename:
            self.displayImage(filename)

    def batchProcess(self):
        filenames, _ = QFileDialog.getOpenFileNames(self, "选择图像", "", "图像文件 (*.png *.jpg *.jpeg)")
        if filenames:
            for filename in filenames:
                self.displayImage(filename)

7.7结果显示

现在,我们将实现 displayImage 方法,还动态添加每个图片的容器,包含图片、结果标签及删除按钮。同时,启动一个新的ImageProcessor线程处理图像,并监听处理结果更新UI


    def displayImage(self, filename):
        if not os.path.isfile(filename):
            QMessageBox.warning(self, "警告", "文件路径不安全或文件不存在")
            return

        for i in reversed(range(self.preview_layout.count())):
            item = self.preview_layout.itemAt(i)
            if item.widget() and item.widget().objectName().startswith(f"container_{filename}"):
                widget_to_remove = item.widget()
                self.preview_layout.removeWidget(widget_to_remove)
                widget_to_remove.deleteLater()

        container = QWidget()
        container.setObjectName(f"container_{filename}")
        container_layout = QVBoxLayout(container)
        container_layout.setContentsMargins(0, 0, 0, 0)

        pixmap = QPixmap(filename)
        preview_label = QLabel(container)
        preview_label.setPixmap(pixmap)
        preview_label.setAlignment(Qt.AlignCenter)
        container_layout.addWidget(preview_label)

        delete_button = QPushButton("删除", container)
        delete_button.setObjectName(f"button_{filename}")
        delete_button.clicked.connect(lambda _, fn=filename: self.deleteImage(fn))
        container_layout.addWidget(delete_button)

        result_label = QLabel("", container)
        result_label.setObjectName(f"result_{filename}")
        result_label.setAlignment(Qt.AlignCenter)
        container_layout.addWidget(result_label)

        self.preview_layout.addWidget(container)

        processor = ImageProcessor(filename)
        processor.result_signal.connect(self.updateUIWithResult)
        threading.Thread(target=processor.process_image).start()
        self.image_processors.append(processor)

        if self.preview_layout.count() > 20:
            QMessageBox.warning(self, "警告", "最多只能同时处理20张图像")
            self.image_processors.clear()

7.8结果更新与错误处理

通过updateUIWithResult方法更新图像下方的结果标签,展示预测结果。

    def deleteImage(self, filename):
        container_name = f"container_{filename}"
        container = self.findChild(QWidget, container_name)
        if container:
            self.preview_layout.removeWidget(container)
            container.deleteLater()

    def updateUIWithResult(self, filename, result):
        container = self.findChild(QWidget, f"container_{filename}")
        if container:
            result_label = container.findChild(QLabel, f"result_{filename}")
            if result_label:
                result_label.setText(f"这是{result}!")
                result_label.setVisible(True)
  • 7.9程序入口点

  • 最后是使用PyQt5应用启动的应用程序的入口点。它设置了应用程序的环境,创建了一个主窗口对象(在这里是CatDogClassifierApp的实例),然后显示了该窗口并开始的事件循环。

  • # 程序入口点
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = CatDogClassifierApp()
        window.show()
        sys.exit(app.exec_())

    最后运行出来的程序图片:

8.实现总结

根据上面的项目实现,我们成功构建了一款集用户友好界面与高效图像识别功能于一体的应用程序。该程序为用户提供了直观的操作体验,使他们能够便捷地上传单张或多张猫狗图片。应用后端则采用异步处理机制,确保图片的高效分类,并实时反馈分类结果给用户。这一项目的亮点在于将深度学习模型无缝地整合到了桌面应用中,不仅极大地提升了用户体验,也充分展现了人工智能技术在现实生活中的广泛应用潜力和价值。通过这一实践,我们证明了AI技术与传统桌面应用结合能够为用户带来更加智能、便捷的服务。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐