☰
VGG图像分类实战:植物生长阶段识别全流程解析
2026/10/5 7:15:33 网站建设 项目流程

简介:面向PyTorch初学者的植物生长阶段分类实战代码包,基于VGG模型实现图像分类,适合深度学习入门、课程设计或毕业设计参考。代码仅3个Python脚本,分别负责数据集TXT生成、CNN训练和PyQt界面展示,每行均含中文注释,配合说明文档.docx可快速理解整体流程。压缩包共9个文件,包含4张类别示例JPG、3个脚本、1个requirements.txt依赖清单和1个说明文档,整体仅227KB,轻量易用。资源不含数据集图片,目录下每个类别文件夹均附提示图,用户按提示自行放入收集的图片即可扩展分类类别并训练模型;完成训练后也可通过PyQt界面加载模型进行图片识别,便于演示和验证效果。目前已有128人学习下载,说明文档还给出了Anaconda、Python3.7/3.8及PyTorch1.7.1/1.8.1环境安装指引,能有效降低环境配置门槛,帮助读者从零跑通VGG训练与识别流程。

1. VGG 识别植物生长阶段:这个资源真正帮你省掉的时间

植物生长阶段的识别这事,看起来只是把图片分成种子、幼苗、开花、结果四类,但真用深度学习跑一遍,你会发现卡点全在那些“看似简单”的环节:环境装不上、文件夹结构没设计好、数据集标签对不上、训练完的模型不知道怎么拿去做预测。这套基于 PyTorch 的 VGG 分类资源,正是冲着这几个卡点来的——三个 py 文件把「生成标注 txt → 训练 CNN → PyQt 界面推理」整条链路串完,代码逐行带中文注释,而且刻意不打包数据集图片,逼你按自己的场景去建立类别文件夹、搜集图片。适合两类人:一是刚学完 PyTorch 基础、想完整跑通一个图像分类项目的新手;二是农学、园艺背景的研究者,手里有植物各阶段的实拍图,需要快速出一个可用的分类模型。它不是黑匣子,每个环节都能打开看、能改。

2. VGG 选型与三个 py 文件的架构:为什么不用 ResNet

很多人在选模型时会纠结:现在 ResNet、EfficientNet 满天飞,VGG 是不是太老了?这个项目用 VGG 其实是有道理的,而且恰恰是这种“老模型”最适合作为学习深度学习和图像分类的起点。

2.1 VGG 在小数据集上的选型理由

VGG 的核心思路很朴素:用连续的小卷积核(3×3)堆叠出足够深的网络,把卷积层从 AlexNet 的 5 层加深到 16 层或 19 层。它没有残差连接,没有注意力机制,就是老老实实的卷积、池化、全连接。也正是因为结构规整,它成了理解 CNN 特征提取过程的最佳教学模型——每一步卷积后特征图怎么变化,在代码里一眼就能看明白。

在你只有几百张到上千张植物图片、类别只有四个的情况下,VGG16 的优势比 ResNet 更明显:

  • 迁移学习效果好:VGG 在 ImageNet 上学到的边缘、纹理、颜色等底层特征非常通用。植物叶片的边缘、果实的形状纹理,正好能直接复用这些特征,只需要替换最后的全连接层,花很少的训练轮次就能达到不错的准确率。
  • 显存和训练时间可控:虽然 VGG 参数量有 1.38 亿,但那是全连接层占大头。在这个项目里会替换掉最后的分类层,用冻结主干 + 训练分类头的方式,显存占用并不夸张,一张 6G 显存的 GTX 1660 就能跑起来。
  • 调试方便:结构简单意味着出问题时容易定位。训练 loss 不下降,你能很快判断是学习率问题还是数据问题,而不是在复杂的模型结构里排查。

用 ResNet 当然也可以,但新手在第一次跑项目时面对残差块的跳跃连接、BatchNorm 的统计量更新,很容易被细节带偏。VGG 的价值在于把练手的难度降下来,让注意力集中在数据准备和训练流程上。

2.2 三个文件的职责与整体数据流

这个资源一共三个 py 文件,整个流程是这样的:

文件职责输入输出
01生成txt.py扫描类别文件夹,生成训练列表数据集/各分类文件夹train.txt(每行:图片路径 + 类别索引)
02CNN训练数据集.py读取 txt 和图片,训练 VGG 分类模型train.txt + 图片文件夹训练好的模型权重(.pth)
03pyqt界面.py加载权重,提供 PyQt 图形界面做推理待预测图片界面显示类别和置信度

这个设计的巧妙之处在于:txt 文件是数据层和训练层之间的解耦点。你换自己的数据集,只需要保证文件夹命名正确、图片放对位置,01 文件会自动扫描生成对应的标签文件;02 训练文件完全不用动。如果你要增加类别,比如加一个“成熟”阶段,只需要在数据集文件夹下新建一个成熟文件夹,放好图片,脚本会自动把新类别纳入训练。后面我会细讲每个文件的实现逻辑。

3. 数据准备与 01生成txt.py:类别文件夹与标注文件怎么配合

整个项目的命门在数据准备这一步。由于资源本身不含数据集图片,你需要自己搜集图片放到对应文件夹。这个环节做得好不好,直接决定训练出来的模型能不能用。

3.1 建立类别文件夹与图片搜集策略

解压后你会看到数据集文件夹下已经建好了种子、幼苗、开花、结果四个子文件夹,每个里面有一张提示图,示意你该往这个文件夹放什么类型的图片。第一步就是替换掉提示图,放入你自己搜集的实拍图。

搜集图片时有几个策略值得注意:

  • 每类图片数量尽量均衡:四个文件夹各放 100 到 300 张,比一个文件夹 500 张、另一个只有 50 张要可靠得多。类别不平衡时,模型会倾向于把模糊样本预测为样本多的那一类。
  • 图片分辨率不用太高:VGG 的输入固定是 224×224,你搜集 1000×1000 的大图在加载时会被强制缩放。反而 300×300 左右的图更合适,缩放损失的信息少,加载也快。
  • 多样性优先于数量:尽量让图片覆盖不同角度、光照、背景条件。如果所有图片都是同一个角度、同一个背景拍的,模型很快会偷懒——它可能在学背景而不是学植物本身。

图片放好后,完整目录结构是这样的:

项目根目录/ ├── 01生成txt.py ├── 02CNN训练数据集.py ├── 03pyqt界面.py ├── requirement.txt ├── 说明文档.docx └── 数据集/ ├── 种子/ # 粘贴种子阶段图片 ├── 幼苗/ # 粘贴幼苗阶段图片 ├── 开花/ # 粘贴开花阶段图片 └── 结果/ # 粘贴结果阶段图片

注意文件夹命名不要带空格和中文标点,因为后续代码会直接拼接路径,空格容易在命令行和文件读取时引入隐患。

3.2 01生成txt.py 的逐段逻辑

这个文件做的事情很简单:遍历数据集文件夹下的每个子文件夹,把所有图片的绝对路径和对应的类别索引写进一个 txt 文件。核心代码逻辑如下:

import os # 数据集根目录,改成你实际解压的位置 dataset_path = './数据集' # 输出的 txt 文件名 output_file = 'train.txt' # 获取所有类别文件夹,并按名称排序 classes = os.listdir(dataset_path) classes.sort() print(f'发现 {len(classes)} 个类别: {classes}') # 建立一个类别名称到索引的映射字典 class_to_idx = {cls_name: idx for idx, cls_name in enumerate(classes)} with open(output_file, 'w', encoding='utf-8') as f: for cls_name in classes: cls_folder = os.path.join(dataset_path, cls_name) # 跳过非文件夹(比如隐藏文件) if not os.path.isdir(cls_folder): continue # 遍历该类别文件夹下的所有图片 for img_name in os.listdir(cls_folder): # 过滤掉提示图和非图片文件 if img_name.startswith('.'): continue if not img_name.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')): continue img_path = os.path.join(cls_folder, img_name) # 写入格式:图片绝对路径 + 空格 + 类别索引 f.write(f'{img_path} {class_to_idx[cls_name]}\n') print(f'已生成 {output_file},共 {sum(1 for _ in open(output_file))} 条记录')

逻辑说明:这段代码先扫描数据集根目录下的每个子文件夹,每个子文件夹的名字就是一个类别;然后为每个类别分配一个从 0 开始的索引,种子是 0、幼苗是 1、开花是 2、结果是 3;最后遍历每个文件夹里的图片,把「图片路径 + 空格 + 索引」写到 train.txt 的一行。

参数说明:dataset_path 是你数据集的实际路径,注意 Python 字符串里的反斜杠要转义,或者像我这样直接用正斜杠;img_name.lower() 用于统一后缀判断,避免 .JPG 和 .jpg 被当成两种文件;编码声明 encoding='utf-8' 是为了防止 Windows 下默认 GBK 编码导致的中文路径乱码问题。

运行方式是在终端里执行:

python 01生成txt.py

运行完后你应该能在项目根目录看到生成的 train.txt。打开看一眼,每行格式类似:

D:/plant_project/数据集/种子/seed_001.jpg 0 D:/plant_project/数据集/开花/flower_010.jpg 2

4. 训练核心 02CNN训练数据集.py:从 DataLoader 到迁移学习调参

02 文件是这个资源的核心,承担了数据加载、模型构建、训练和保存的全流程。它加载上一步生成的 train.txt,配合图片文件夹完成训练。这一章的代码逻辑理解了,你就能自己改参数适配不同的数据集。

4.1 数据预处理与 DataLoader 关键参数

训练前需要对图片做统一的预处理,让它们变成固定尺寸、数值范围合理、分布接近标准正态的张量。VGG 官方训练时用的预处理方式是:缩放至 256×256、随机裁剪出 224×224、按 ImageNet 的均值方差做标准化。

from torchvision import transforms # 训练集的预处理:加随机翻转增强,减少过拟合 train_transform = transforms.Compose([ transforms.Resize((256, 256)), # 先缩放到 256 transforms.RandomCrop(224), # 随机裁剪到 224,相当于数据增强 transforms.RandomHorizontalFlip(), # 随机水平翻转,增加样本多样性 transforms.ToTensor(), # 转为 Tensor,像素值归一化到 0~1 transforms.Normalize( mean=[0.485, 0.456, 0.406], # ImageNet 三个通道的均值 std=[0.229, 0.224, 0.225] # ImageNet 三个通道的标准差 ) ]) # 验证集用纯 resize + center crop,不做随机增强 val_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

参数说明:Resize((256, 256)) 和 RandomCrop(224) 的组合是 VGG 官方推荐的训练策略——先放大再随机裁剪,等于每轮训练看到的是同一张图的不同局部,变相扩大了数据集;Normalize 的参数 0.485、0.456、0.406 是 ImageNet 全量数据的统计值,不要改成别的数字,迁移学习要求输入分布尽量接近预训练时的分布;RandomHorizontalFlip 对植物图片是安全的增强操作,因为植物的左右翻转不改变类别语义。

接着用 PyTorch 的 Dataset 和 DataLoader 把 train.txt 的内容变成可迭代的训练批次:

from torch.utils.data import Dataset, DataLoader from PIL import Image class PlantDataset(Dataset): """读取 train.txt,按行解析图片路径和标签""" def __init__(self, txt_path, transform=None): self.samples = [] self.transform = transform with open(txt_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line: continue img_path, label = line.rsplit(' ', 1) self.samples.append((img_path, int(label))) def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label = self.samples[idx] img = Image.open(img_path).convert('RGB') # 统一转成 RGB 三通道 if self.transform: img = self.transform(img) return img, label train_dataset = PlantDataset('train.txt', transform=train_transform) # batch_size 按显存调整,4G 显存建议 16,8G 显存可以用 32 train_loader = DataLoader( train_dataset, batch_size=32, shuffle=True, # 每个 epoch 打乱数据顺序 num_workers=2, # Windows 下建议设 0 或 2,过高会报错 drop_last=True # 最后一批不足 batch_size 时丢弃,防止 BN 层统计异常 )

逻辑说明:PlantDataset 类把 train.txt 每一行的「路径 + 标签」拆开,getitem按索引读取图片并做预处理。DataLoader 负责把这些样本拼成 batch。这里有个细节:shuffle=True 在训练时是必须的,否则模型每个 epoch 看到的样本顺序完全一样,训练容易震荡。

参数说明:num_workers 在 Windows 上经常出问题,如果报 DataLoader worker 相关的错误,直接改成 0——它表示用主进程加载数据,慢一点但绝对稳定;drop_last=True 踩过坑的人才知道为什么要加,当最后一个 batch 不足 32 张时,BatchNorm 层会因为统计量偏差报错或产生异常训练行为。

4.2 迁移学习与训练循环

模型的构建采用标准的迁移学习方案:加载 ImageNet 预训练的 VGG16 权重,把最后一层全连接从 1000 类换成 4 类,只训练分类头和分类头之前的部分参数。这里给出训练循环的完整代码:

import torch import torch.nn as nn import torch.optim as optim from torchvision import models # 检测 GPU,没有 GPU 就回退到 CPU device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f'使用设备: {device}') # 加载预训练 VGG16 模型 model = models.vgg16(weights=models.VGG16_Weights.IMAGENET1K_V1) # 替换最后一层全连接,4 对应你的类别数 num_classes = 4 model.classifier[6] = nn.Linear(4096, num_classes) # 冻结前面所有层,只训练替换后的全连接层 for param in model.features.parameters(): param.requires_grad = False model = model.to(device) # 多分类用交叉熵损失,自带 Softmax criterion = nn.CrossEntropyLoss() # 只把需要训练的参数的梯度传给优化器 optimizer = optim.SGD( filter(lambda p: p.requires_grad, model.parameters()), lr=0.001, momentum=0.9 ) # 学习率调度:每 10 个 epoch 学习率乘 0.1 scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) epochs = 30 for epoch in range(epochs): model.train() running_loss = 0.0 correct = 0 total = 0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() * images.size(0) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() epoch_loss = running_loss / total epoch_acc = correct / total print(f'Epoch [{epoch+1}/{epochs}] Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}') scheduler.step() # 保存整个模型(含结构),后续推理直接 load 即可 torch.save(model.state_dict(), 'plant_vgg16.pth') print('训练完成,模型已保存为 plant_vgg16.pth')

逻辑说明:models.vgg16 会先下载预训练权重到本地缓存,然后通过 model.classifier[6] 把原本输出 1000 类的全连接层替换成输出 4 类的线性层。冻结 features 层意味着训练时卷积特征提取部分完全不更新参数,只在原有特征基础上训练新的分类器——这样训练速度快,且小数据集不容易过拟合。optimizer 只用 filter 筛选出的 requires_grad=True 的参数,就是那一个全连接层。

参数说明:学习率 0.001 搭配 SGD momentum 0.9 是 VGG 迁移学习的经典组合,不要上来就换 Adam。Adam 在这类任务上收敛快但最终精度经常不如调好学习率的 SGD;StepLR 每 10 个 epoch 把学习率降一个量级,作用是后期用小步长精细收敛;epochs=30 够用了,如果训练集很小(每类 100 张以下),20 个 epoch 就应该停,再往后 loss 下降不明显甚至回升。

训练结束后,项目根目录会出现一个 plant_vgg16.pth 文件。你可以用下面的命令快速验证训练曲线是否有问题:

python -c "import torch; sd = torch.load('plant_vgg16.pth', map_location='cpu'); print([(k, v.shape) for k, v in sd.items()])"

如果列出的键里 classifier.6.weight 的形状是 [4, 4096],说明保存正确。

5. 复现避坑:五条能让你少熬两个通宵的报错记录

这个项目我在自己电脑上完整复现过,也帮朋友排查过问题。下面这几条是出现频率最高的坑,每一条都是实际遇到过的血泪经验。

5.1 界面报“No module named‘PyQt5’”

现象:运行 03pyqt界面.py 时,终端直接报 ModuleNotFoundError: No module named 'PyQt5',程序退出。

原因:PyQt5 很容易装,但很多人在装依赖时只用了一行 pip install torch。requirement.txt 里虽然列出了所有依赖,但环境没装全。还有一个隐蔽原因:PyQt5 对 Python 版本有要求,Python 3.9 之后某些老版本 PyQt5 安装会失败。

解决:在项目根目录执行:

pip install -r requirement.txt

如果你用的是 Anaconda,建议先创建一个 Python 3.8 的环境再安装:

conda create -n plant python=3.8 conda activate plant pip install -r requirement.txt

如果 PyQt5 还是装不上,单独装一次:

pip install PyQt5 PyQt5-sip

5.2 运行 02 时报 FileNotFoundError,路径带“数据集”三个字就出错

现象:01 生成 train.txt 一切正常,但 02 训练时一读图片,Python 报错说找不到 D:/…/数据集/种子/seed_001.jpg,但你明明看到文件就在那里。

原因:这几乎是 Windows 中文路径的经典 bug。某些版本的 PyTorch 在 Windows 下处理中文路径时,底层 C++ 的编码转换会出问题,导致中文目录无法正常读取。我自己排查时发现,换成全英文路径后问题立刻消失。

解决:把项目根目录改在纯英文路径下,比如 D:/plant_project/,并且确保文件夹里的图片文件名也不含中文。如果你实在需要在中文路径下跑,一个绕行方案是把代码里的中文路径改成短路径别名,但最省事的方式永远是英文路径。

5.3 训练时 loss 一直是 1.3 左右,怎么都不下降

现象:前几个 epoch loss 在 1.3 附近徘徊(四分类随机猜测的理论 loss 是 ln(4)≈1.386),几十个 epoch 后还是没明显变化,准确率在 25% 附近。

原因:这是典型的标签错位问题。01 生成 txt 时是遍历顺序给类别编号的,如果数据集文件夹下的类别顺序变了,或者你手动编辑过 train.txt,标签和图片对不上号,模型学不到任何有效规律。

解决:删除 train.txt,重新运行 01生成txt.py,再确认生成的文件里每一行最后一个数字是不是 0 到 3 之间。同时打开一两个路径,肉眼核对一下:种子 文件夹下的图片路径,后面跟的标签必须是 0。如果类别的顺序被打乱,最保险的办法是固定数据集文件夹里子文件夹的排列顺序,不要随意重命名。

5.4 训练过程中显存溢出(CUDA out of memory)

现象:训练在跑到第 10 个 epoch 附近时突然报 RuntimeError: CUDA out of memory,前面一切正常。

原因:VGG16 在 224×224 分辨率下每个 batch 的显存占用确实不低。如果你按 32 的 batch_size 能跑是因为某些高分辨率图片在缩放后内容复杂、激活值大,或者后台有其他程序占了显存。还有一个常见原因:训练中途验证时忘记关梯度,导致显存被验证图占住。

解决:先降 batch_size 到 16 试试,一般就能跑通。如果还不行,在训练循环里把验证部分的代码包上 torch.no_grad()。另外养成好习惯,每次训练前用 nvidia-smi 看一下显存占用,把无关进程清掉。

5.5 界面能跑但预测一个都不对,所有图片都分成同一类

现象:03 界面打开正常,选择图片也能显示结果,但不管选种子还是开花,预测结果清一色是“种子”。

原因:这种情况十有八九是类别映射表的问题。03 文件里有一个类别索引到中文名称的映射列表,比如 class_names = ['种子', '幼苗', '开花', '结果']。如果这个列表的顺序和 01 生成 txt 时的遍历顺序不一致,模型输出的索引 0 会被错误地翻译成中文名。也有可能是模型的 fc 输出层和类别数不匹配,导致最终输出全部偏向某类。

解决:把数据集文件夹下的类别按名称排序后,对照一下 01 生成 txt 打印出的类别顺序,跟 03 里的 class_names 列表逐项比对。正确顺序是:种子=0、幼苗=1、开花=2、结果=3。改完后重新运行 01,再训练一次。

# 快速检查类别顺序是否正确 python -c " import os classes = os.listdir('./数据集') classes.sort() print(classes) "

6. 进阶部署:把 PyQt 界面改成命令行推理脚本

03pyqt界面.py 提供了一个图形界面用于单张图片预测,但实际使用中你会发现命令行方式往往更灵活,尤其是你要批量验证模型效果或者把它接进自动化流程时。把图形界面里的推理逻辑抽出来,不到 30 行就能做成一个独立的推理脚本。

核心推理代码:

import torch from torchvision import models, transforms from PIL import Image # 类别顺序必须和训练时一致 class_names = ['种子', '幼苗', '开花', '结果'] device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 构建模型并加载权重 model = models.vgg16() model.classifier[6] = torch.nn.Linear(4096, len(class_names)) model.load_state_dict(torch.load('plant_vgg16.pth', map_location=device)) model = model.to(device) model.eval() # 切换为评估模式,关闭 Dropout 和 BN 的统计更新 # 推理时的预处理:固定缩放,不做随机增强 transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) def predict(img_path): img = Image.open(img_path).convert('RGB') tensor = transform(img).unsqueeze(0).to(device) # 推理阶段不计算梯度,省显存且速度更快 with torch.no_grad(): outputs = model(tensor) probs = torch.softmax(outputs, dim=1) # 把输出转成概率 conf, pred_idx = torch.max(probs, dim=1) pred_class = class_names[pred_idx.item()] conf_value = conf.item() * 100 return pred_class, conf_value if __name__ == '__main__': result = predict('test_seed.jpg') print(f'预测类别: {result[0]},置信度: {result[1]:.2f}%')

逻辑说明:models.vgg16() 不带预训练权重时,不会触发下载;然后手动替换分类层并加载之前保存的 plant_vgg16.pth。torch.no_grad() 是推理的标准写法,它告诉 PyTorch 不需要构建计算图,显存占用降低一个量级,批量预测时速度提升明显。softmax 的作用是把全连接层输出的 logits 转成加和为 1 的概率分布,方便解读置信度。

验证效果时有一个值得坚持的习惯:不要只测一张图就下结论。从那以后我每次做完分类项目,都会强制自己跑一遍全部类别的抽查——从每一个类别文件夹里抽出几张图,分别走一遍上面的预测代码,把预测结果和真实标签对照。特别是看那些错误的样本,到底是因为光照、角度还是类别本身太相似。这一步能帮你快速判断是数据问题还是模型问题,也能让你对新数据集上模型的可用边界心里有数。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询