【中小学AI人工智能教育】智慧农业——多种病虫害分析
Ai创想实验室是专门为中小学AI教育开发的教学平台包含了值计算、图像分类、音频分类、文本分类、数值回归、图像回归、图像分类回归、平衡杆、手写数字生成、文本生成等中小学人工智能学习类项目。无需编程基础、无需添加硬件、无需购买算力、无隐私担忧、无需师资培训即可进行教学实践。本文介绍如何使用AiEduLab.tech中的图像分类项目类型来做病虫害分类——Ai创想实验室是以数据为驱动的你可以任意选择一个图像分类项目来完成例如手写数字识别、猫狗分类……模型构建、数据清洗、模型训练、预测、导出模型并部署本文模型较大导出时请耐心等待2-3分钟。整体流程如下1、训练模型达到满意程度2、导出模型TF Lite .H ONNX3、通过人工、高清摄像头、行走机器人等采样4、模型预测5、病虫害警告或自动给喷淋加料本文主要介绍模型训练、导出相关部分。至于在移动端、嵌入硬件、其他框架下使用之后再进行介绍。一、数据获取和清洗在百度星河、阿里天池上有很多数据集在下载和使用时我们应遵守使用协议。本文使用的数据集下载自百度星河具体地址为农业病虫害_数据集-飞桨AI Studio星河社区其协议为CC0这个数据集有3G多解压缩之后运行以下脚本将json解析并分类存储在organized_dataset文件夹中import json import os import shutil from PIL import Image def process_dataset(json_path, images_dir, output_dir, dataset_name): 处理单个数据集训练集或验证集 Args: json_path: annotations JSON文件路径 images_dir: 原始图片所在文件夹 output_dir: 输出根目录 dataset_name: 数据集名称用于显示 print(f\n{*60}) print(f正在处理: {dataset_name}) print(f{*60}) # 加载JSON文件 print(正在加载JSON文件...) with open(json_path, r, encodingutf-8) as f: data json.load(f) # 检查JSON结构 if not isinstance(data, list): print(错误期望JSON是列表格式) return print(f检测到JSON列表长度: {len(data)}) # 从列表中提取标注信息 # 每个元素格式: {disease_class: xxx, image_id: filename.jpg} img_to_label {} for item in data: if isinstance(item, dict) and image_id in item and disease_class in item: filename item[image_id] img_to_label[filename] item[disease_class] print(f提取到 {len(img_to_label)} 条标注记录) if not img_to_label: print(错误未能提取到有效的标注信息) return # 使用预定义的61分类映射表 class_dict { 0: 苹果_健康, 1: 苹果_黑星病_一般, 2: 苹果_黑星病_严重, 3: 苹果_灰斑病_一般, 4: 苹果_灰斑病_严重, 5: 苹果_锈病_一般, 6: 苹果_锈病_严重, 7: 樱桃_健康, 8: 樱桃_白粉病_一般, 9: 樱桃_白粉病_严重, 10: 玉米_健康, 11: 玉米_灰斑病_一般, 12: 玉米_灰斑病_严重, 13: 玉米_锈病_一般, 14: 玉米_锈病_严重, 15: 玉米_大斑病_一般, 16: 玉米_大斑病_严重, 17: 葡萄_健康, 18: 葡萄_黑腐病_一般, 19: 葡萄_黑腐病_严重, 20: 葡萄_轮斑病_一般, 21: 葡萄_轮斑病_严重, 22: 葡萄_叶枯病_一般, 23: 葡萄_叶枯病_严重, 24: 柑桔_健康, 25: 柑桔_溃疡病_一般, 26: 柑桔_溃疡病_严重, 27: 桃_健康, 28: 桃_流胶病_一般, 29: 桃_流胶病_严重, 30: 葡萄_健康, 31: 葡萄_黑腐病_一般, 32: 葡萄_黑腐病_严重, 33: 柑桔_健康, 34: 柑桔_溃疡病_一般, 35: 柑桔_溃疡病_严重, 36: 桃_健康, 37: 桃_流胶病_一般, 38: 桃_流胶病_严重, 39: 草莓_健康, 40: 草莓_叶枯病_一般, 41: 草莓_叶枯病_严重, 42: 番茄_健康, 43: 番茄_白粉病_一般, 44: 番茄_白粉病_严重, 45: 番茄_早疫病_一般, 46: 番茄_早疫病_严重, 47: 番茄_晚疫病_一般, 48: 番茄_晚疫病_严重, 49: 辣椒_健康, 50: 辣椒_疮痂病_一般, 51: 辣椒_疮痂病_严重, 52: 马铃薯_健康, 53: 马铃薯_早疫病_一般, 54: 马铃薯_早疫病_严重, 55: 马铃薯_晚疫病_一般, 56: 马铃薯_晚疫病_严重, 57: 番茄_黄化曲叶病_一般, 58: 番茄_黄化曲叶病_严重, 59: 番茄_花叶病_一般, 60: 番茄_花叶病_严重 } # 创建输出目录 os.makedirs(output_dir, exist_okTrue) # 统计信息 total_images len(img_to_label) success_count 0 error_count 0 min_width float(inf) min_height float(inf) max_width 0 max_height 0 error_files [] print(f需要处理 {total_images} 张图片) print(开始按类别整理图片并统计尺寸...\n) processed 0 for filename, label_id in img_to_label.items(): processed 1 # 进度输出 if processed % 1000 0 or processed total_images: print(f进度: {processed}/{total_images} ({processed*100/total_images:.1f}%)) # 源文件路径 src_path os.path.join(images_dir, filename) # 检查源文件是否存在 if not os.path.exists(src_path): error_count 1 error_files.append(f文件不存在: {filename}) continue # 获取图片尺寸 try: with Image.open(src_path) as img: width, height img.size if width min_width: min_width width if height min_height: min_height height if width max_width: max_width width if height max_height: max_height height except Exception as e: error_files.append(f读取尺寸失败: {filename}, {e}) # 获取类别名称 class_name class_dict.get(label_id, f未知类别_{label_id}) # 解析植物和病害去掉程度 parts class_name.split(_) if len(parts) 2: plant parts[0] disease parts[1] # 只取病害名称忽略程度 else: plant 未知 disease 未知 # 构建目标路径植物/病害/去掉程度层 dest_dir os.path.join(output_dir, plant, disease) os.makedirs(dest_dir, exist_okTrue) dest_path os.path.join(dest_dir, filename) # 复制文件 try: shutil.copy2(src_path, dest_path) success_count 1 except Exception as e: error_count 1 error_files.append(f复制失败: {filename}, {e}) # 输出统计结果 print(\n -*60) print(f{dataset_name} 处理完成) print(f总图片数: {total_images}) print(f成功复制: {success_count}) print(f失败数量: {error_count}) print(-*60) print(图片尺寸统计) if min_width ! float(inf): print(f 最小宽度: {min_width} 像素) print(f 最小高度: {min_height} 像素) print(f 最大宽度: {max_width} 像素) print(f 最大高度: {max_height} 像素) else: print( 未能获取任何图片的尺寸信息) if error_files: print(-*60) print(f错误详情前10条:) for err in error_files[:10]: print(f {err}) if len(error_files) 10: print(f ... 还有 {len(error_files)-10} 条错误) print(f图片已整理到: {output_dir}) def main(): # 配置路径 # 训练集 train_json AgriculturalDisease_trainingset/AgriculturalDisease_train_annotations.json train_images AgriculturalDisease_trainingset/images train_output organized_dataset/train # 验证集 val_json AgriculturalDisease_validationset/AgriculturalDisease_val_annotations.json val_images AgriculturalDisease_validationset/images val_output organized_dataset/val # 检查路径 datasets_to_process [] # 检查训练集 if os.path.exists(train_json) and os.path.exists(train_images): datasets_to_process.append((train_json, train_images, train_output, 训练集)) else: if not os.path.exists(train_json): print(f警告找不到训练集JSON文件 {train_json}) if not os.path.exists(train_images): print(f警告找不到训练集图片目录 {train_images}) # 检查验证集 if os.path.exists(val_json) and os.path.exists(val_images): datasets_to_process.append((val_json, val_images, val_output, 验证集)) else: if not os.path.exists(val_json): print(f警告找不到验证集JSON文件 {val_json}) if not os.path.exists(val_images): print(f警告找不到验证集图片目录 {val_images}) if not datasets_to_process: print(\n错误没有找到任何可用的数据集) print(请确认路径配置是否正确) return print(f\n找到 {len(datasets_to_process)} 个数据集待处理) # 处理所有数据集 for json_path, images_dir, output_dir, dataset_name in datasets_to_process: process_dataset(json_path, images_dir, output_dir, dataset_name) print(\n *60) print(所有数据集处理完成) print(*60) if __name__ __main__: main()本文仅使用训练集中的番茄部分的数据训练一个能够辨别番茄病虫害的模型。为了得到归一化的图像使用通用图像处理工具对图像进行缩放和归一化注意不点击“清空”将会在当前包中添加数据。新类别数据应先清空之前的数据。为了导入数据时能够比较均衡和知道我们训练时用了哪些数据——预测时避开它们给文件重命名一下据对不是写第一个脚本时忘了- -!!!import os from pathlib import Path def rename_png_in_subfolders(root_dir, dry_runFalse): 将每个子文件夹内的PNG图片从1开始重新编号 Args: root_dir: 根目录路径 dry_run: True时只预览不实际重命名 root_path Path(root_dir) if not root_path.exists(): print(f错误目录不存在 - {root_dir}) return # 统计信息 total_folders 0 total_files 0 renamed_files 0 skipped_files 0 # 遍历所有子文件夹包括根目录本身 for folder in sorted([root_path] [d for d in root_path.iterdir() if d.is_dir()]): # 获取该文件夹下所有PNG文件按文件名排序 png_files sorted([f for f in folder.glob(*.png) if f.is_file()]) if not png_files: continue total_folders 1 total_files len(png_files) print(f\n 处理文件夹: {folder.relative_to(root_path)}) print(f 找到 {len(png_files)} 张PNG图片) # 计算编号位数自动补零 digits len(str(len(png_files))) if digits 2: digits 2 for idx, file_path in enumerate(png_files, start1): # 生成新文件名如01.png, 02.png, ... new_name f{idx:0{digits}d}.png new_path folder / new_name # 检查是否冲突如果新文件名已存在且不是当前文件 if new_path.exists() and new_path ! file_path: print(f ⚠️ 冲突: {file_path.name} - {new_name} (目标文件已存在跳过)) skipped_files 1 continue if not dry_run: # 执行重命名 try: file_path.rename(new_path) print(f ✅ {file_path.name} - {new_name}) renamed_files 1 except Exception as e: print(f ❌ 重命名失败: {file_path.name} - {new_name}, 错误: {e}) skipped_files 1 else: # 预览模式 print(f {file_path.name} - {new_name}) renamed_files 1 # 输出统计信息 print(\n *50) print( 统计信息:) print(f - 处理的文件夹数量: {total_folders}) print(f - 总PNG文件数量: {total_files}) print(f - 成功重命名: {renamed_files}) print(f - 跳过/失败: {skipped_files}) if dry_run: print(\n⚠️ 这是预览模式未实际执行重命名操作) print( 如需执行请将 dry_runFalse) else: print(f\n✅ 重命名完成共处理 {total_folders} 个文件夹{renamed_files} 张图片) print(*50) def main(): # 配置 # 要处理的根目录 root_dir . # 当前目录可以修改为你的具体路径 # 是否预览True只预览不执行False实际执行 dry_run False # 改为 True 先预览确认无误后改为 False 执行 # 执行 print(*50) print(️ PNG图片批量重命名工具) print(*50) print(f 目标目录: {os.path.abspath(root_dir)}) print(f 模式: {预览模式 if dry_run else 执行模式}) print(*50) if not dry_run: confirm input(\n⚠️ 即将重命名所有PNG文件是否继续(y/n): ) if confirm.lower() ! y: print(已取消操作) return rename_png_in_subfolders(root_dir, dry_rundry_run) if __name__ __main__: main()此时我们就可以构建训练数据。对于手写数字来说32*32单通道足够对于猫狗分类64*64三通道即可本文我们做一个稍大的模型使用128*128三通道二、模型构建只需修改手写数字识别的模型的输入层为none,128,128,3输出层为6类别即可。其他我们不进行修改。三、训练打开全部数据增强、使用默认参数进行训练这个模型有点大但在核显上训练依然没问题由于模型和数据都比较大所以训练稍慢约3分钟一轮。从日志可以看出模型很快就学会了这些分类。四、预测把模型训练到参数表现比较好的时候停止训练如果不满意可以点击继续训练来再次训练。以下是训练10轮之后的模型的表现测试更多图片或使用原数据集中validationset的数据进行测试实际准确率。使用更多一些的数据训练更多轮次会有效提升置信度和准确率。当模型满意之后在训练器中“导出移动嵌入通用模型”如果训练器中没有模型了可以导入模型。五、部署通过“导出移动嵌入通用模型”会得到3个模型其中.h模型和tflite模型都是经过量化的。onnx模型可以用于多种框架多个国内模型平台都可以使用。包括华为(Ascend)、华为(HarmonyOS、阿里巴巴、百度、国芯科技、龙芯中科等等它们要么具有原生支持、要么有框架或工具链支持可以在这些平台上进行使用。.h模型的使用在之前的文章中有详细的全流程讲解后续会有文章介绍其他模型的部署——会自己写一个Demo。在AI创想实验室中我们无需编程基础不用学习框架不用配置环境无需购买费用高昂的显卡更不用为云端算力付费使用当前已有的各种硬件仅有核显的个人、办公、机房电脑希沃白板等都能达到理想的教学效果。操作简单但AI核心知识样样俱全无需师资培训就可以进行教学且能取得理想的教学效果。如果加入试点或合作方那么只需要一台局域网服务器无需显卡、服务器不用供算力即可一次投入永久使用全部项目和功能通过后台管理一分钟即可创建一个本地化、校本化的项目实例。演示版本地址www.AiEduLab.tech有任何问题欢迎留言或发送邮件至helloAiEduLab.tech