基于深度学习CNN算法的蔬菜识别系统01--带数据集-pyqt5UI界面-全套源码

该文章已生成可运行项目,

各位同学大家好,本次给大家分享的项目为:

基于深度学习CNN算法的蔬菜识别系统01–带数据集-pyqt5UI界面-全套源码

项目文件获取地址:

百度网盘链接: https://pan.baidu.com/s/12paOMPj8mTq9-XGs7pUCJw?pwd=pfwv 
提取码:  pfwv 

一、项目摘要

随着人工智能技术的快速发展,深度学习在图像识别领域取得了显著的进步。本文设计并实现了一种基于MobileNet深度学习网络的蔬菜识别系统。该系统通过从网络上采集的15类蔬菜图像数据集(共计21000张)进行训练和验证,利用MobileNet模型在资源受限的环境中实现高效、准确的图像分类。数据集按照8:2的比例划分为训练集和验证集,并通过随机裁剪、水平翻转等数据增强技术提高模型的泛化能力。经过30个训练周期,系统在验证集上的准确率达到了99%,并通过混淆矩阵显示出在各类蔬菜分类任务中的优异表现。最终,利用PyQt5库设计了用户交互界面,实现了蔬菜图片上传与分类结果展示的功能。该系统表现出高效的分类性能,为农业自动化领域的蔬菜识别任务提供了一种可行的解决方案。

二、项目运行效果

运行效果视频:
https://www.bilibili.com/video/BV1WrCRY1Erq

运行效果截图:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、项目文件介绍

在这里插入图片描述

在这里插入图片描述

四、项目环境配置

1、项目环境库

python=3.8 pytorch pyqt5 opencv matplotlib 等

2、环境配置视频教程

1)anaconda下载安装教程
2)pycharm下载安装教程
3)项目环境库安装步骤教程

五、项目系统架构

蔬菜识别系统分为两大模块:后端深度学习模型和前端用户界面。

后端模块:MobileNet模型是系统的核心,负责处理用户上传的蔬菜图片,并对其进行分类。模型通过PyTorch加载,并利用训练好的参数进行推理。在用户上传图片后,系统将调用该模型对图片进行处理,并输出预测的蔬菜种类。

前端模块:前端界面是基于PyQt5库开发的。用户可以通过界面上传蔬菜图像,点击“识别”按钮后,系统会自动调用后端的深度学习模型对图像进行分类,最终结果会在界面

系统的整体工作流程如下:首先,用户通过用户界面选择要识别的蔬菜图片,随后系统对蔬菜进行预处理,接着将处理后的图像输入到深度学习模型中进行分类,最后在用户界面上显示识别结果及蔬菜相关的简介。

六、项目构建流程

1、数据集

数据集文件夹:all_data

在这里插入图片描述

概述:

本文使用的数据集由15类不同蔬菜的图像组成,总计21000张

在这里插入图片描述

数据集格式及命令统一代码:to_rgb.py
(对数据集中的图像统一成rgb格式并进行统一规范命名)

在这里插入图片描述

2、算法网络Mobilenet

概述:
Mobilenet是专为移动设备和嵌入式系统设计的轻量化卷积神经网络。其主要特点在于采用了深度可分离卷积(Depthwise Separable Convolution)来减少计算量和参数数量,从而在资源受限的环境下实现高效的图像分类和识别。
在这里插入图片描述

算法代码为:models文件夹下的mobilenet.py

在这里插入图片描述

"""mobilenet in pytorch

[1] Andrew G. Howard, Menglong Zhu, Bo Chen, Dmitry Kalenichenko, Weijun Wang, Tobias Weyand, Marco Andreetto, Hartwig Adam

    MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
    https://arxiv.org/abs/1704.04861
"""

import torch
import torch.nn as nn


class DepthSeperabelConv2d(nn.Module):

    def __init__(self, input_channels, output_channels, kernel_size, **kwargs):
        super().__init__()
        self.depthwise = nn.Sequential(
            nn.Conv2d(
                input_channels,
                input_channels,
                kernel_size,
                groups=input_channels,
                **kwargs),
            nn.BatchNorm2d(input_channels),
            nn.ReLU(inplace=True)
        )

        self.pointwise = nn.Sequential(
            nn.Conv2d(input_channels, output_channels, 1),
            nn.BatchNorm2d(output_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        x = self.depthwise(x)
        x = self.pointwise(x)

        return x


class BasicConv2d(nn.Module):

    def __init__(self, input_channels, output_channels, kernel_size, **kwargs):

        super().__init__()
        self.conv = nn.Conv2d(
            input_channels, output_channels, kernel_size, **kwargs)
        self.bn = nn.BatchNorm2d(output_channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)

        return x


class MobileNet(nn.Module):

    """
    Args:
        width multipler: The role of the width multiplier α is to thin
                         a network uniformly at each layer. For a given
                         layer and width multiplier α, the number of
                         input channels M becomes αM and the number of
                         output channels N becomes αN.
    """

    def __init__(self, width_multiplier=1, class_num=100):
       super().__init__()

       alpha = width_multiplier
       self.stem = nn.Sequential(
           BasicConv2d(3, int(32 * alpha), 3, padding=1, bias=False),
           DepthSeperabelConv2d(
               int(32 * alpha),
               int(64 * alpha),
               3,
               padding=1,
               bias=False
           )
       )

       #downsample
       self.conv1 = nn.Sequential(
           DepthSeperabelConv2d(
               int(64 * alpha),
               int(128 * alpha),
               3,
               stride=2,
               padding=1,
               bias=False
           ),
           DepthSeperabelConv2d(
               int(128 * alpha),
               int(128 * alpha),
               3,
               padding=1,
               bias=False
           )
       )

       #downsample
       self.conv2 = nn.Sequential(
           DepthSeperabelConv2d(
               int(128 * alpha),
               int(256 * alpha),
               3,
               stride=2,
               padding=1,
               bias=False
           ),
           DepthSeperabelConv2d(
               int(256 * alpha),
               int(256 * alpha),
               3,
               padding=1,
               bias=False
           )
       )

       #downsample
       self.conv3 = nn.Sequential(
           DepthSeperabelConv2d(
               int(256 * alpha),
               int(512 * alpha),
               3,
               stride=2,
               padding=1,
               bias=False
           ),

           DepthSeperabelConv2d(
               int(512 * alpha),
               int(512 * alpha),
               3,
               padding=1,
               bias=False
           ),
           DepthSeperabelConv2d(
               int(512 * alpha),
               int(512 * alpha),
               3,
               padding=1,
               bias=False
           ),
           DepthSeperabelConv2d(
               int(512 * alpha),
               int(512 * alpha),
               3,
               padding=1,
               bias=False
           ),
           DepthSeperabelConv2d(
               int(512 * alpha),
               int(512 * alpha),
               3,
               padding=1,
               bias=False
           ),
           DepthSeperabelConv2d(
               int(512 * alpha),
               int(512 * alpha),
               3,
               padding=1,
               bias=False
           )
       )

       #downsample
       self.conv4 = nn.Sequential(
           DepthSeperabelConv2d(
               int(512 * alpha),
               int(1024 * alpha),
               3,
               stride=2,
               padding=1,
               bias=False
           ),
           DepthSeperabelConv2d(
               int(1024 * alpha),
               int(1024 * alpha),
               3,
               padding=1,
               bias=False
           )
       )

       self.fc = nn.Linear(int(1024 * alpha), class_num)
       self.avg = nn.AdaptiveAvgPool2d(1)

    def forward(self, x):
        x = self.stem(x)

        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)

        x = self.avg(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x


def mobilenet(alpha=1, class_num=10):
    return MobileNet(alpha, class_num)


3、网络模型训练

训练代码为:train.py
训练参数设置:为了优化MobileNet模型的训练,我们设置了以下超参数:

批次大小(Batch Size):16
学习率(Learning Rate):初始学习率设置为0.0001,使用Adamw优化器。
训练周期(Epoch):训练过程持续30个周期,以确保模型的充分训练。

在这里插入图片描述

import os
import argparse

import torch
import torch.optim as optim
import matplotlib.pyplot as plt
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

from my_dataset import MyDataSet
from models.mobilenet import mobilenet as create_model
from utils import read_split_data, train_one_epoch, evaluate


def draw(train, val, ca):
    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
    plt.rcParams['axes.unicode_minus'] = False
    plt.cla() # 清空之前绘图数据
    plt.title('精确度曲线图' if ca == "acc" else '损失值曲线图')
    plt.plot(train, label='train_{}'.format(ca))
    plt.plot(val, label='val_{}'.format(ca))
    plt.legend()
    plt.grid()
    plt.savefig('精确度曲线图' if ca == "acc" else '损失值曲线图')
    # plt.show()



def main(args):
    device = torch.device(args.device if torch.cuda.is_available() else "cpu")

    if os.path.exists("./weights") is False:
        os.makedirs("./weights")

    tb_writer = SummaryWriter()

    train_images_path, train_images_label, val_images_path, val_images_label = read_split_data(args.data_path)

    img_size = 224
    data_transform = {
        "train": transforms.Compose([transforms.RandomResizedCrop(img_size),
                                     transforms.RandomHorizontalFlip(),
                                     transforms.ToTensor(),
                                     transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),
        "val": transforms.Compose([transforms.Resize(int(img_size * 1.143)),
                                   transforms.CenterCrop(img_size),
                                   transforms.ToTensor(),
                                   transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])}

    # 实例化训练数据集
    train_dataset = MyDataSet(images_path=train_images_path,
                              images_class=train_images_label,
                              transform=data_transform["train"])

    # 实例化验证数据集
    val_dataset = MyDataSet(images_path=val_images_path,
                            images_class=val_images_label,
                            transform=data_transform["val"])

    batch_size = args.batch_size
    nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workers
    print('Using {} dataloader workers every process'.format(nw))
    train_loader = torch.utils.data.DataLoader(train_dataset,
                                               batch_size=batch_size,
                                               shuffle=True,
                                               pin_memory=True,
                                               num_workers=nw,
                                               collate_fn=train_dataset.collate_fn)

    val_loader = torch.utils.data.DataLoader(val_dataset,
                                             batch_size=batch_size,
                                             shuffle=False,
                                             pin_memory=True,
                                             num_workers=nw,
                                             collate_fn=val_dataset.collate_fn)

    model = create_model(class_num=args.num_classes).to(device)

    if args.weights != "":
        assert os.path.exists(args.weights), "weights file: '{}' not exist.".format(args.weights)

        model.load_state_dict(torch.load(args.weights, map_location=device))


    if args.freeze_layers:
        for name, para in model.named_parameters():
            # 除head外,其他权重全部冻结
            if "head" not in name:
                para.requires_grad_(False)
            else:
                print("training {}".format(name))

    pg = [p for p in model.parameters() if p.requires_grad]
    optimizer = optim.AdamW(pg, lr=args.lr, weight_decay=5E-2)

    for epoch in range(args.epochs):
        # train
        train_loss, train_acc = train_one_epoch(model=model,
                                                optimizer=optimizer,
                                                data_loader=train_loader,
                                                device=device,
                                                epoch=epoch)

        # validate
        val_loss, val_acc = evaluate(model=model,
                                     data_loader=val_loader,
                                     device=device,
                                     epoch=epoch)

        train_acc_list.append(train_acc)
        train_loss_list.append(train_loss)

        val_acc_list.append(val_acc)
        val_loss_list.append(val_loss)


        tags = ["train_loss", "train_acc", "val_loss", "val_acc", "learning_rate"]
        tb_writer.add_scalar(tags[0], train_loss, epoch)
        tb_writer.add_scalar(tags[1], train_acc, epoch)
        tb_writer.add_scalar(tags[2], val_loss, epoch)
        tb_writer.add_scalar(tags[3], val_acc, epoch)
        tb_writer.add_scalar(tags[4], optimizer.param_groups[0]["lr"], epoch)

        if val_loss == min(val_loss_list):
            print('vegetable-save-best-epoch:{}'.format(epoch))
            with open('loss.txt', 'w') as fb:
                fb.write(str(train_loss) + ',' + str(train_acc) + ',' + str(val_loss) + ',' + str(val_acc))
            torch.save(model.state_dict(), "./weights/best-epoch.pth")



if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--num_classes', type=int, default=15)
    parser.add_argument('--epochs', type=int, default=30)
    parser.add_argument('--batch-size', type=int, default=16)
    parser.add_argument('--lr', type=float, default=0.0001)

    # 数据集所在根目录
    parser.add_argument('--data-path', type=str,
                        default="all_data")

    # 预训练权重路径,如果不想载入就设置为空字符
    parser.add_argument('--weights', type=str, default='',
                        help='initial weights path')
    # 是否冻结权重
    parser.add_argument('--freeze-layers', type=bool, default=False)
    parser.add_argument('--device', default='cuda:0', help='device id (i.e. 0 or 0,1 or cpu)')

    opt = parser.parse_args()

    train_loss_list = []
    train_acc_list = []
    val_loss_list = []
    val_acc_list = []

    main(opt)

    draw(train_acc_list, val_acc_list, 'acc')
    draw(train_loss_list, val_loss_list, 'loss')


开始训练

在all_data中准备好数据集,并设置好超参数后,即可开始运行train.py

成功运行效果展示

1)会生成dataset.png数据集分布柱状图

2)pycharm下方实时显示相关训练日志

在这里插入图片描述

等待所有epoch训练完成后代码会自动停止,并在weights文件夹下生成训练好的模型pth文件,并生成准确率和损失值曲线图。

在这里插入图片描述
在这里插入图片描述

4、训练好的模型预测

无界面预测代码为:predict.py

在这里插入图片描述

import os
import json

import torch
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt

from models.mobilenet import mobilenet  as create_model


def main(img_path):
    import os
    os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    img_size = 224
    data_transform = transforms.Compose(
        [transforms.Resize(int(img_size * 1.143)),
         transforms.CenterCrop(img_size),
         transforms.ToTensor(),
         transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])

    assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
    img = Image.open(img_path)
    plt.imshow(img)
    # [N, C, H, W]
    img = data_transform(img)
    # expand batch dimension
    img = torch.unsqueeze(img, dim=0)

    # read class_indict
    json_path = './class_indices.json'
    assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)

    json_file = open(json_path, "r")
    class_indict = json.load(json_file)

    # create model  创建模型网络
    model = create_model(class_num=15).to(device)
    # load model weights  加载模型
    model_weight_path = "weights/vegetable-best-epoch.pth"
    model.load_state_dict(torch.load(model_weight_path, map_location=device))
    model.eval()
    #调用模型进行检测
    with torch.no_grad():
        # predict class
        output = torch.squeeze(model(img.to(device))).cpu()
        predict = torch.softmax(output, dim=0)
        predict_cla = torch.argmax(predict).numpy()


    for i in range(len(predict)):
        print("class: {:10}   prob: {:.3}".format(class_indict[str(i)],
                                                  predict[i].numpy()))
    # 返回检测结果和准确率
    res = class_indict[str(list(predict.numpy()).index(max(predict.numpy())))]
    num= "%.2f" % (max(predict.numpy()) * 100) + "%"
    print(res,num)
    return res,num


if __name__ == '__main__':
    img_path = r"all_data\土豆\0002.jpg"
    main(img_path)

使用方法
1)设置好训练好的模型权重路径
2)设置好要预测的图像的路径
直接右键运行即可,成功运行后会在pycharm下方生成预测结果数据

在这里插入图片描述

5、UI界面设计-pyqt5

两款UI样式界面,自由选择

样式1纯色背景界面,可自由更改界面背景颜色

在这里插入图片描述

样式2:背景图像界面

在这里插入图片描述

对应代码文件
1)ui.py 和 ui2.py
用于设置界面中控件的属性样式和显示的文本内容,可自行修改界面背景色及界面文本内容

在这里插入图片描述

在这里插入图片描述

2)主界面.py 和 主界面2.py
用于设置界面中的相关按钮及动态的交互功能

在这里插入图片描述

3)inf.py 相关介绍及展示信息文本,可自行修改介绍信息

在这里插入图片描述

6、项目相关评价指标

1、准确率曲线图(训练后自动生成)
在这里插入图片描述

2、损失值曲线图(训练后自动生成)
在这里插入图片描述

3、混淆矩阵图

在这里插入图片描述

生成方式:训练完模型后,运行confusion_matrix.py文件,设置好使用的模型权重文件后,直接右键运行即可,等待模型进行预测生成

在这里插入图片描述

以上为本项目完整的构建实现流程步骤,更加详细的项目讲解视频如下
https://www.bilibili.com/video/BV18bCRY9EyK
(对程序使用,项目中各个文件作用,算法网络结构,所有程序代码等进行的细致讲解,时长1小时)

七、项目论文报告

本项目有配套的论文报告(1w字左右),部分截图如下
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值