news 2026/9/29 12:00:09

Python识别花卉种类并自动整理分类:迁移学习实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 配 PyTorch,CPU 版本足够。如果你还在纠结 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_ratio=0.2, seed=42): """把按类别分好的原始数据切分成 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(parents=True, exist_ok=True) 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_size=32, shuffle=True, num_workers=2) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=2) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = models.resnet18(weights=models.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(), lr=1e-3) # 只优化分类头 best_acc = 0.0 for epoch in range(15): model.train() for imgs, labels in tqdm(train_loader, desc=f"epoch {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(f"epoch {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")

几个参数值得单独说。lr=1e-3是只训练分类头时的常用值,如果你解冻了部分卷积层,要降到 1e-4 量级,否则预训练权重会被破坏。batch_size=32在 CPU 上内存占用可控,显存够可以加到 64。epoch=15对几百张图的数据集通常够收敛,如果验证准确率还在涨就加到 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(path="flower_model.pth"): ckpt = torch.load(path, map_location="cpu") model = models.resnet18(weights=None) 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), dim=1)[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, threshold=0.6): src = Path(src_dir) dst = Path(dst_dir) low_conf = dst / "_待复核" low_conf.mkdir(parents=True, exist_ok=True) 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(parents=True, exist_ok=True) 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", index=False, encoding="utf-8-sig") print(f"完成,共处理 {len(files)} 张,低置信度 {sum(1 for r in report if r['status']=='低置信度')} 张") model, classes = load_model() organize("待整理照片", "已分类", model, classes)

threshold=0.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(p=1.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), dim=1)[0] probs /= len(tta_tf) idx = probs.argmax().item() return classes[idx], probs[idx].item()

第二件是主动补数据。整理报告里那些低置信度的图,其实是最有价值的训练素材。我每周会把_待复核文件夹里的图人工标一遍,追加到训练集里重新训练一轮。这样跑上三四轮,模型对你自己数据的适应度会明显提升,原本容易混的品种慢慢就分开了。这个循环不需要一次投入很多时间,每周花二十分钟标几十张,一个月后效果比一开始就追求大模型好得多。

最后说个我自己的习惯:每次重新训练前,先把上一版模型在固定测试集上的表现记下来,新模型只有明显更好才替换。这样能避免某次训练数据没整理好导致模型退化,毕竟整理分类这种任务,稳定比偶尔的高准确率重要得多。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 9:05:35

ROS2机器人定位核心:robot_localization多传感器融合实战指南

1. 为什么这个融合不是“配个参数就能跑”&#xff0c;而是机器人定位的生死线你刚在ROS2里跑通小乌龟&#xff0c;连上IMU和轮式编码器&#xff0c;兴冲冲写了个robot_localization的launch文件&#xff0c;rviz2里一刷新——机器人原地打转、轨迹呈螺旋状发散、甚至突然瞬移到…

作者头像 李华
网站建设 2026/9/28 9:05:32

浙江响应式网站建设制作避坑指南:3步看懂报价单实战案例

浙江响应式网站建设制作避坑指南:3步看懂报价单实战案例 找浙江的建站公司,最怕的不是技术不行,而是报价单像天书,一开口就是几万块,还说不清钱花在哪。很多甲方对接人手里拿着三份报价单,看着“响应式开发”、“SEO优化”这些词,心里直打鼓:这钱到底该不该花?是不是被当韭菜割了?别急,今天咱们不整虚的,直…

作者头像 李华
网站建设 2026/9/28 9:05:29

群晖可以做网站服务器吗?3步图解步骤,省下一万块

群晖可以做网站服务器吗?3步图解步骤,省下一万块 找建站公司报价五万八,还要每年续费服务器和域名?别急着掏钱。很多独立站长其实不知道,家里那台吃灰的群晖NAS,完全能扛下企业官网甚至小型商城的服务器角色。这篇图解步骤,不扯虚的,直接告诉你怎么把群晖变成你的私有云服务器,避开那些被高价收割的坑。…

作者头像 李华
网站建设 2026/9/28 9:05:20

叫人做网站要注意这5点,性能优化才是获客命脉

叫人做网站要注意这5点,性能优化才是获客命脉 网站上线三个月,后台数据惨不忍睹。你盯着那寥寥无几的UV(独立访客),心里直打鼓:是不是SEO没做好?还是内容太水?别急着甩锅给SEO专员,很多时候问题出在源头——你叫人做网站时,根本没把“性能优化”当回事。…

作者头像 李华
网站建设 2026/9/28 9:04:46

3步搞定科讯网站发布文章,性能优化避坑指南

3步搞定科讯网站发布文章,性能优化避坑指南 自己不会代码想做网站,最怕的不是写不出来,而是发出去的内容没人看、加载慢如蜗牛。很多中小企业老板找我们做 科讯网站发布文章 ,第一句话就是:“我要快,我要稳,我还要排名好。”这背后其实是两个核心诉求:内容分发的效率,以及底层的 性能优化…

作者头像 李华
网站建设 2026/9/28 9:04:31

用dw做的网站怎么上线?新手入门避坑指南

用dw做的网站怎么上线?新手入门避坑指南 备案号卡在半路,服务器配置全白搭?这是无数用Dreamweaver(DW)手搓网页的新手最崩溃的瞬间。很多人以为写完HTML代码就算完事,结果面对备案流程一头雾水,根本不知道服务器和域名怎么挂钩。其实,用DW做的静态站上线门槛极低,但细节里的坑能把人埋活。…

作者头像 李华