Python识别花卉种类并自动整理分类:迁移学习实战
简介这份资源面向具备Python基础、希望入门图像识别与AI应用开发的开发者围绕「识别花卉种类并自动整理分类」这一完整小项目展开。核心脚本通过调用百度AI开放平台的图像识别接口对本地花卉照片进行类别预测再借助os、shutil等模块按识别结果自动移动或重命名文件实现照片的批量归类。包内共21个文件以15个png和5个jpg图片为主构成用于测试与演示的花卉合集另有1个py源码文件承载接口鉴权、图像上传、结果处理与文件操作等逻辑压缩包约10.47MB。项目串联了Python编程、第三方API调用、图像识别、数据预处理与文件操作等知识点读者可据此理解从读取图片、请求云端模型到落地分类的完整链路并在此基础上尝试模型微调或部署扩展。目前已有1756人学习下载适合作为AI应用开发的练手案例。1. 从一堆杂乱照片到按品种归档python识别花卉种类并自动整理分类到底在做什么手机相册里躺着两千多张植物照片文件名全是 IMG_20240512_093421.jpg 这种想找某一次拍的月季得翻半小时——这是我去年帮一个做园艺电商的朋友处理素材时的真实场景。他每天拍上百张花卉图要按品种分到不同商品目录下人工做一天只能处理三四百张还经常把相似品种搞混。python识别花卉种类并自动整理分类这件事本质上就是让程序替人完成「看图 → 判断品种 → 移动到对应文件夹」这条流水线把重复劳动压到几秒钟一张。它解决的不是「识别得多准」这种学术问题而是「我有一堆图怎么自动归位」这种工程问题。适合三类人手里有大量花卉照片需要归档的从业者、想入门图像分类但被各种框架劝退的 python 新手、以及需要给现有业务加一个自动打标环节的开发者。整套方案不需要 GPU 也能跑起来一台普通笔记本就能完成从训练到批量整理的全流程。下面我按自己实际落地的顺序把选型、训练、推理、整理脚本和踩过的坑一次讲清楚。2. 选型与数据准备为什么用迁移学习而不是从零训练2.1 花卉分类任务的三个现实约束先想清楚这件事的边界才不会在选型上翻车。第一花卉类别之间的差异往往非常细微比如不同品种的月季花瓣形状几乎一样只有颜色深浅和花蕊结构有区别这对模型的特征提取能力要求不低。第二绝大多数人手里的数据量根本不够从零训练一个卷积网络几百张到几千张是常态从零训练必然过拟合。第三整理分类是批量任务推理速度比单张精度更重要一个准确率 95% 但每张要 3 秒的模型处理两千张图要一个多小时体验很差。这三个约束直接指向同一个结论用预训练模型做迁移学习。常见做法是拿在 ImageNet 上训练好的网络冻结前面的卷积层只重新训练最后的分类头。这样几百张图就能得到一个可用的模型训练时间从几小时压到几分钟。2.2 环境搭建与依赖安装我一般用 Python 3.10 配 PyTorchCPU 版本足够。如果你还在纠结 python安装教程或者 vscode python环境配置先把解释器和编辑器跑通再往下看。下面是完整的环境准备命令逐条执行即可。# 创建独立虚拟环境避免污染系统 python python -m venv flower_env # 激活环境Windows 用 flower_env\Scripts\activate source flower_env/bin/activate # 安装核心依赖torch 选 CPU 版本即可 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install pillow pandas scikit-learn tqdm这里几个包的分工要说清楚torch 和 torchvision 负责模型和图像变换pillow 读图pandas 用来生成整理报告scikit-learn 做训练集划分tqdm 显示进度条。装 torch 时如果网络慢可以换国内镜像源但注意镜像源要和你系统匹配。装完用python -c import torch; print(torch.__version__)验证一下能打印版本号就说明环境没问题。2.3 数据集目录结构与划分迁移学习对数据组织有固定要求torchvision 的 ImageFolder 会按文件夹名自动生成类别标签。所以第一步是把你的照片按品种分好文件夹哪怕每个品种只有几十张。目录结构长这样flower_data/ ├── train/ │ ├── rose/ │ │ ├── img001.jpg │ │ └── ... │ ├── tulip/ │ └── sunflower/ └── val/ ├── rose/ ├── tulip/ └── sunflower/如果原始数据全堆在一起没分类得先手工分一遍这是整个流程里唯一没法自动化的环节。分完之后按 8:2 切训练集和验证集下面这个脚本帮你自动完成划分避免手动复制粘贴出错。import os import shutil import random from pathlib import Path def split_dataset(src_dir, dst_dir, val_ratio0.2, seed42): 把按类别分好的原始数据切分成 train/val 两个目录 random.seed(seed) # 固定随机种子保证每次划分结果一致 src Path(src_dir) for class_dir in src.iterdir(): if not class_dir.is_dir(): continue images list(class_dir.glob(*.jpg)) list(class_dir.glob(*.png)) random.shuffle(images) n_val max(1, int(len(images) * val_ratio)) # 每类至少留一张验证图 splits {val: images[:n_val], train: images[n_val:]} for split, files in splits.items(): out Path(dst_dir) / split / class_dir.name out.mkdir(parentsTrue, exist_okTrue) for f in files: shutil.copy2(f, out / f.name) print(f{class_dir.name}: train{len(splits[train])}, val{len(splits[val])}) split_dataset(raw_flowers, flower_data)参数说明val_ratio控制验证集比例数据量少于 200 张时建议调到 0.3让验证结果更可靠seed固定后每次划分一致方便复现实验。脚本跑完会打印每个类别的划分数量如果某个类别验证集只有 1 张说明这个品种样本太少需要考虑补数据或者合并相似品种。3. 训练一个能用的花卉分类模型迁移学习的最小实现3.1 为什么选 ResNet18 而不是更大的网络ResNet18 是个很务实的起点。它只有 1100 万参数CPU 上单张推理 50 毫秒左右在花卉这种类内差异大、类间差异小的任务上配合数据增强能到 90% 以上的准确率。更大的 ResNet50 或 EfficientNet 精度提升有限但训练和推理时间翻倍对整理分类这种批量任务不划算。等你把流程跑通、发现某些品种总是混淆再换更大的骨干网络针对性优化。3.2 训练脚本与关键参数下面这个训练脚本是我反复用过的版本去掉了花哨的日志和回调只保留必要部分。核心思路是冻结 ResNet18 的卷积层把最后的全连接层换成你的类别数。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, models, transforms from tqdm import tqdm # 图像预处理训练集加增强验证集只做标准化 train_tf transforms.Compose([ transforms.Resize(256), transforms.RandomResizedCrop(224), # 随机裁剪提升泛化 transforms.RandomHorizontalFlip(), # 水平翻转花卉左右对称合理 transforms.ColorJitter(0.2, 0.2, 0.2), # 颜色抖动应对不同光线 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) train_ds datasets.ImageFolder(flower_data/train, train_tf) val_ds datasets.ImageFolder(flower_data/val, val_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers2) device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) for param in model.parameters(): param.requires_grad False # 冻结预训练权重 model.fc nn.Linear(model.fc.in_features, len(train_ds.classes)) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.fc.parameters(), lr1e-3) # 只优化分类头 best_acc 0.0 for epoch in range(15): model.train() for imgs, labels in tqdm(train_loader, descfepoch {epoch}): imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) acc correct / total print(fepoch {epoch} val_acc{acc:.4f}) if acc best_acc: best_acc acc torch.save({model: model.state_dict(), classes: train_ds.classes}, flower_model.pth)几个参数值得单独说。lr1e-3是只训练分类头时的常用值如果你解冻了部分卷积层要降到 1e-4 量级否则预训练权重会被破坏。batch_size32在 CPU 上内存占用可控显存够可以加到 64。epoch15对几百张图的数据集通常够收敛如果验证准确率还在涨就加到 30。保存时把classes一起存进去很关键推理时类别顺序必须和训练时一致否则标签全错。3.3 训练过程怎么判断有没有问题训练日志里最该盯的是验证准确率曲线。正常情况是前 3 个 epoch 快速上升之后缓慢爬升并趋于平稳。如果训练准确率一直上不去大概率是学习率太小或者数据增强太狠如果训练准确率很高但验证准确率明显低一截就是过拟合需要加数据或者加强增强。还有一种玄学情况验证准确率在 0.5 附近震荡这通常意味着类别标签乱了检查一下 ImageFolder 读到的classes顺序和你预期是否一致。4. 批量识别与自动整理把模型接进文件系统4.1 推理脚本单张图到类别标签训练完拿到flower_model.pth接下来写推理脚本。注意推理时的预处理必须和验证集完全一致不能加任何随机增强否则结果不稳定。import torch from torchvision import models, transforms from PIL import Image def load_model(pathflower_model.pth): ckpt torch.load(path, map_locationcpu) model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, len(ckpt[classes])) model.load_state_dict(ckpt[model]) model.eval() return model, ckpt[classes] infer_tf transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def predict(image_path, model, classes): img Image.open(image_path).convert(RGB) # 统一转 RGB防止灰度图报错 tensor infer_tf(img).unsqueeze(0) with torch.no_grad(): probs torch.softmax(model(tensor), dim1)[0] idx probs.argmax().item() return classes[idx], probs[idx].item()convert(RGB)这行别省手机拍的图有时带透明通道或者灰度模式不转直接喂给模型会报维度错误。返回的置信度分数很有用低于 0.6 的可以单独挑出来人工复核避免错分。4.2 自动整理脚本按品种建文件夹并移动整理逻辑要考虑几个现实问题目标文件夹已存在怎么办、重名文件怎么处理、置信度低的图放哪。下面这个脚本把这些都处理了。import shutil from pathlib import Path from tqdm import tqdm def organize(src_dir, dst_dir, model, classes, threshold0.6): src Path(src_dir) dst Path(dst_dir) low_conf dst / _待复核 low_conf.mkdir(parentsTrue, exist_okTrue) report [] exts {.jpg, .jpeg, .png, .bmp, .webp} files [f for f in src.rglob(*) if f.suffix.lower() in exts] for f in tqdm(files, desc整理中): label, conf predict(f, model, classes) if conf threshold: target low_conf / f.name status 低置信度 else: target dst / label / f.name target.parent.mkdir(parentsTrue, exist_okTrue) status 已分类 # 重名时加序号避免覆盖 if target.exists(): target target.with_stem(f{target.stem}_{abs(hash(f.name)) % 10000}) shutil.move(str(f), str(target)) report.append({file: f.name, label: label, conf: round(conf, 3), status: status}) import pandas as pd pd.DataFrame(report).to_csv(dst / 整理报告.csv, indexFalse, encodingutf-8-sig) print(f完成共处理 {len(files)} 张低置信度 {sum(1 for r in report if r[status]低置信度)} 张) model, classes load_model() organize(待整理照片, 已分类, model, classes)threshold0.6是经验值宁可多挑一些出来人工看也别让错分混进正式目录。with_stem加哈希后缀解决重名比单纯加数字更不容易冲突。最后导出 CSV 报告方便回溯哪张图被分到了哪类出问题能查。这个报告在批量处理几千张图时特别重要相当于给整个流程留了后悔药。4.3 处理速度和资源占用CPU 上单张推理约 50 到 80 毫秒两千张图大概两到三分钟加上文件移动时间总共五分钟以内。如果图特别多可以把DataLoader用上做批量推理吞吐能再提升两三倍。内存占用主要看图片分辨率手机原图 4000x3000 读进来解码会占几十 MB如果内存吃紧可以在预处理里先Resize(512)再走后续流程。5. 避坑与排查那些让我返工三次的问题5.1 验证准确率虚高实际整理一塌糊涂现象训练时验证准确率 0.95但拿新照片整理时错得离谱。原因训练集和验证集来自同一批照片的随机划分同一朵花的不同角度被分到了两边模型其实记住了背景而不是花本身。解决划分数据时按拍摄批次分同一批次要么全在训练集要么全在验证集这样验证结果才反映真实泛化能力。5.2 某些品种总是被分到同一类现象三种月季全被识别成同一种。原因这几个品种视觉差异太小ResNet18 的特征区分不开加上每类样本只有二三十张模型学不到细粒度特征。解决先把这几个易混品种合并成一个大类保证整理不出错如果必须区分换 EfficientNet 或者加一个专门针对花瓣区域的裁剪预处理把注意力集中到关键部位。5.3 中文路径导致读图失败现象Image.open报 FileNotFoundError但文件明明存在。原因Windows 下 PIL 对中文路径支持不稳定尤其是路径里有空格和特殊字符时。解决读图前用Path对象处理路径或者临时把文件复制到纯英文目录再处理。我在整理脚本里统一用pathlib比字符串拼接稳得多。5.4 整理到一半程序崩了文件状态混乱现象处理到第 800 张时程序异常退出已经移动的文件和没移动的混在一起重跑会重复处理。原因脚本没有断点续传机制shutil.move执行到一半失败会留下不完整状态。解决先复制到目标位置再删除源文件并且每处理 100 张就把进度写到一个临时文件重跑时跳过已处理的文件。这个改动让我后来再没遇到过整理到一半崩掉的情况。5.5 置信度阈值设太高大量图进待复核现象阈值设 0.9结果一半以上的图都被判为低置信度。原因softmax 输出的置信度受类别数量和训练充分程度影响类别多的时候单类概率天然偏低。解决阈值按实际分布调先跑一批看置信度直方图取能覆盖 80% 正确分类的那个值通常 0.5 到 0.7 之间。别迷信固定值。6. 让分类更准的两个进阶技巧测试时增强与主动补数据把流程跑通只是第一步真正让这套方案在生产里稳定用起来还得解决「边界样本」的问题。我后来加了两件事错分率明显下降。第一件是测试时增强TTA。推理时对同一张图做多次变换——原图、水平翻转、轻微裁剪——分别预测后把概率平均。这个技巧不增加训练成本只多花一点推理时间对花卉这种左右对称的物体特别有效。实现上就是把predict函数改成对多个变换结果取平均tta_tf [ transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224)]), transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224), transforms.RandomHorizontalFlip(p1.0)]), transforms.Compose([transforms.Resize(288), transforms.CenterCrop(224)]), ] def predict_tta(image_path, model, classes): img Image.open(image_path).convert(RGB) probs torch.zeros(len(classes)) for tf in tta_tf: tensor transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])(transforms.ToTensor()(tf(img))).unsqueeze(0) with torch.no_grad(): probs torch.softmax(model(tensor), dim1)[0] probs / len(tta_tf) idx probs.argmax().item() return classes[idx], probs[idx].item()第二件是主动补数据。整理报告里那些低置信度的图其实是最有价值的训练素材。我每周会把_待复核文件夹里的图人工标一遍追加到训练集里重新训练一轮。这样跑上三四轮模型对你自己数据的适应度会明显提升原本容易混的品种慢慢就分开了。这个循环不需要一次投入很多时间每周花二十分钟标几十张一个月后效果比一开始就追求大模型好得多。最后说个我自己的习惯每次重新训练前先把上一版模型在固定测试集上的表现记下来新模型只有明显更好才替换。这样能避免某次训练数据没整理好导致模型退化毕竟整理分类这种任务稳定比偶尔的高准确率重要得多。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →