基于图像识别的作物病虫害智能诊断与防治系统
实际应用场景描述
在现代农业生产中,病虫害是影响作物产量和品质的主要因素之一。传统病虫害识别依赖农技专家现场观察,效率低、成本高,且易受专家经验水平影响。许多农民由于缺乏专业知识,往往无法及时准确识别病虫害,导致错失最佳防治时机,造成严重经济损失。
痛点分析
- 识别难度大:病虫害种类繁多,症状相似,非专业人士难以准确识别
- 响应不及时:从发现症状到专家诊断周期长,病虫害可能已扩散
- 防治不精准:盲目用药导致成本增加、环境污染和农药残留
- 知识获取难:防治知识分散,农民难以获取权威指导
核心逻辑
- 使用深度学习模型对作物图像进行特征提取
- 基于提取特征进行病虫害分类
- 根据病虫害类型匹配防治知识库
- 输出诊断结果和个性化防治建议
系统架构
├── app.py # 主应用程序 ├── config.py # 配置文件 ├── models/ │ ├── disease_detector.py # 病虫害检测模型 │ └── classifier.py # 分类器 ├── utils/ │ ├── image_processor.py # 图像处理工具 │ └── knowledge_base.py # 知识库模块 ├── static/ # 静态资源 ├── templates/ # HTML模板 └── README.md # 说明文档
核心代码实现
- 配置文件 (config.py)
""" 配置文件 - 系统参数和路径配置 """ import os from pathlib import Path
class Config: """系统配置类"""
# 基础配置
BASE_DIR = Path(__file__).parent
UPLOAD_FOLDER = BASE_DIR / 'static' / 'uploads'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'bmp'}
# 模型配置
MODEL_PATH = BASE_DIR / 'models' / 'pretrained' / 'plant_disease_model.h5'
CLASS_NAMES = [
'healthy', # 健康
'powdery_mildew', # 白粉病
'leaf_spot', # 叶斑病
'aphid_infestation', # 蚜虫侵害
'caterpillar_damage', # 毛虫损害
'fungal_infection', # 真菌感染
'nutrient_deficiency' # 营养缺乏
]
# 图像处理参数
IMG_SIZE = (224, 224) # 模型输入尺寸
# 知识库配置
KNOWLEDGE_BASE_FILE = BASE_DIR / 'data' / 'disease_knowledge.json'
@classmethod
def init_directories(cls):
"""初始化必要的目录"""
directories = [cls.UPLOAD_FOLDER]
for directory in directories:
directory.mkdir(parents=True, exist_ok=True)
2. 图像处理器 (utils/image_processor.py)
""" 图像处理模块 - 负责图像预处理和增强 """ import cv2 import numpy as np from PIL import Image import io
class ImageProcessor: """图像处理工具类"""
def __init__(self, img_size=(224, 224)):
"""
初始化图像处理器
参数:
img_size: 目标图像尺寸 (宽, 高)
"""
self.img_size = img_size
def load_image(self, image_path):
"""
加载并预处理图像
参数:
image_path: 图像路径或图像字节
返回:
processed_image: 预处理后的图像
"""
# 判断输入类型
if isinstance(image_path, (bytes, bytearray)):
# 从字节流加载
image = Image.open(io.BytesIO(image_path))
image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
else:
# 从文件路径加载
image = cv2.imread(str(image_path))
if image is None:
raise ValueError("无法加载图像")
return image
def preprocess(self, image):
"""
图像预处理流程
参数:
image: 输入图像
返回:
processed: 预处理后的图像数组
"""
# 1. 调整尺寸
image = cv2.resize(image, self.img_size)
# 2. 颜色空间转换 (BGR -> RGB)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 3. 归一化
image = image.astype('float32') / 255.0
# 4. 数据增强(可选)
# 这里可以添加旋转、翻转等增强操作
return image
def extract_features(self, image):
"""
提取图像特征(替代深度学习模型的简化版本)
实际应用中应该使用CNN模型提取特征
参数:
image: 预处理后的图像
返回:
features: 特征向量
"""
# 颜色特征
mean_color = np.mean(image, axis=(0, 1))
# 纹理特征(使用灰度图的LBP简化版)
gray = cv2.cvtColor((image * 255).astype('uint8'), cv2.COLOR_RGB2GRAY)
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
hist = hist.flatten() / hist.sum() # 归一化
# 形状特征(边缘检测)
edges = cv2.Canny(gray, 100, 200)
edge_density = np.sum(edges > 0) / edges.size
# 组合特征
features = np.concatenate([
mean_color, # 3维
hist[:10], # 取前10个直方图bin
[edge_density] # 1维
])
return features
def diagnose_plant_health(self, image):
"""
基于传统图像处理的植物健康诊断(辅助方法)
参数:
image: 输入图像
返回:
health_metrics: 健康指标
"""
# 转换为HSV颜色空间
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 定义健康叶片的颜色范围(绿色)
lower_green = np.array([25, 40, 40])
upper_green = np.array([90, 255, 255])
# 创建掩码
mask = cv2.inRange(hsv, lower_green, upper_green)
# 计算绿色区域比例
green_ratio = np.sum(mask > 0) / mask.size
# 计算颜色变化(病害通常导致颜色变化)
std_color = np.std(image, axis=(0, 1)).mean()
return {
'green_ratio': green_ratio,
'color_variation': std_color,
'is_likely_healthy': green_ratio > 0.3
}
3. 知识库模块 (utils/knowledge_base.py)
""" 知识库模块 - 存储和管理病虫害防治知识 """ import json from pathlib import Path from typing import Dict, List, Optional
class DiseaseKnowledgeBase: """病虫害知识库管理类"""
def __init__(self, knowledge_base_file: Path):
"""
初始化知识库
参数:
knowledge_base_file: 知识库文件路径
"""
self.knowledge_base_file = knowledge_base_file
self.knowledge = self._load_knowledge_base()
def _load_knowledge_base(self) -> Dict:
"""
加载知识库数据
返回:
知识库字典
"""
if not self.knowledge_base_file.exists():
# 如果文件不存在,创建默认知识库
return self._create_default_knowledge_base()
with open(self.knowledge_base_file, 'r', encoding='utf-8') as f:
return json.load(f)
def _create_default_knowledge_base(self) -> Dict:
"""
创建默认病虫害知识库
返回:
默认知识库数据
"""
knowledge_base = {
"diseases": {
"healthy": {
"name": "健康植株",
"description": "作物生长状况良好,无明显病虫害症状。",
"symptoms": [
"叶片颜色鲜绿均匀",
"植株生长健壮",
"无异常斑点或虫害痕迹"
],
"prevention": [
"保持合理种植密度",
"定期检查植株健康状况",
"保持良好通风透光"
],
"treatment": "无需特别处理,维持正常管理即可。"
},
"powdery_mildew": {
"name": "白粉病",
"description": "由白粉菌引起的真菌性病害,在叶片表面形成白色粉末状霉层。",
"symptoms": [
"叶片表面白色粉末状霉层",
"叶片褪绿、黄化",
"严重时叶片卷曲、干枯"
],
"prevention": [
"选择抗病品种",
"合理密植,保持通风透光",
"避免过量施用氮肥"
],
"treatment": "使用三唑酮、甲基托布津等药剂喷雾防治,7-10天一次,连续2-3次。"
},
"leaf_spot": {
"name": "叶斑病",
"description": "由真菌或细菌引起的叶片斑点类病害。",
"symptoms": [
"叶片出现圆形或不规则形病斑",
"病斑中心灰白色,边缘褐色",
"严重时病斑连片,叶片枯死"
],
"prevention": [
"及时清除病残体",
"实行轮作制度",
"避免田间积水"
],
"treatment": "使用代森锰锌、多菌灵等药剂防治,注意交替用药。"
},
"aphid_infestation": {
"name": "蚜虫侵害",
"description": "蚜虫吸食植物汁液,传播病毒病,分泌蜜露诱发煤污病。",
"symptoms": [
"叶片卷曲、畸形",
"植株生长迟缓",
"叶片有蜜露分泌物"
],
"prevention": [
"设置黄色粘虫板",
"保护利用天敌(瓢虫、草蛉)",
"及时清除杂草"
],
"treatment": "使用吡虫啉、噻虫嗪等内吸性药剂喷雾防治。"
},
"caterpillar_damage": {
"name": "毛虫损害",
"description": "鳞翅目幼虫取食叶片,造成缺刻或孔洞。",
"symptoms": [
"叶片有缺刻、孔洞",
"叶缘被取食",
"有虫粪残留"
],
"prevention": [
"人工捕捉幼虫",
"使用杀虫灯诱杀成虫",
"清洁田园,减少虫源"
],
"treatment": "使用高效氯氟氰菊酯、阿维菌素等药剂防治。"
}
},
"general_advice": {
"monitoring": "定期检查植株,早发现早防治。",
"prevention": "以预防为主,综合防治。",
"environment": "保持适宜的生长环境,增强植株抗性。"
}
}
# 保存默认知识库
self.save_knowledge_base(knowledge_base)
return knowledge_base
def save_knowledge_base(self, knowledge_data: Dict = None):
"""
保存知识库到文件
参数:
knowledge_data: 知识库数据,为None时保存当前知识库
"""
if knowledge_data is None:
knowledge_data = self.knowledge
with open(self.knowledge_base_file, 'w', encoding='utf-8') as f:
json.dump(knowledge_data, f, ensure_ascii=False, indent=2)
def get_disease_info(self, disease_id: str) -> Optional[Dict]:
"""
获取特定病虫害的详细信息
参数:
disease_id: 病虫害ID
返回:
病虫害信息字典,如果不存在则返回None
"""
return self.knowledge["diseases"].get(disease_id)
def get_prevention_advice(self, disease_id: str) -> List[str]:
"""
获取病虫害预防建议
参数:
disease_id: 病虫害ID
返回:
预防建议列表
"""
disease_info = self.get_disease_info(disease_id)
if disease_info:
return disease_info.get("prevention", [])
return []
def get_treatment_plan(self, disease_id: str) -> str:
"""
获取病虫害治疗方案
参数:
disease_id: 病虫害ID
返回:
治疗方案文本
"""
disease_info = self.get_disease_info(disease_id)
if disease_info:
return disease_info.get("treatment", "暂无具体治疗方案")
return "未知病虫害类型"
def search_by_symptom(self, symptom: str) -> List[Dict]:
"""
根据症状搜索可能的病虫害
参数:
symptom: 症状描述
返回:
匹配的病虫害列表
"""
results = []
for disease_id, info in self.knowledge["diseases"].items():
symptoms = info.get("symptoms", [])
# 简单关键词匹配
if any(symptom.lower() in s.lower() for s in symptoms):
results.append({
"disease_id": disease_id,
"name": info["name"],
"match_symptoms": [s for s in symptoms if symptom.lower() in s.lower()]
})
return results
4. 病害检测模型 (models/disease_detector.py)
""" 病害检测模型 - 基于机器学习的病虫害分类器 """ import numpy as np from typing import Dict, Tuple, List from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import StandardScaler import joblib import os
class PlantDiseaseDetector: """植物病害检测器"""
def __init__(self, model_path: str = None):
"""
初始化病害检测器
参数:
model_path: 预训练模型路径
"""
self.model = None
self.scaler = StandardScaler()
self.classes = None
self.model_path = model_path
if model_path and os.path.exists(model_path):
self.load_model(model_path)
else:
# 初始化一个简单的随机森林模型
self.model = RandomForestClassifier(
n_estimators=100,
max_depth=10,
random_state=42
)
def train(self, X_train: np.ndarray, y_train: np.ndarray):
"""
训练模型
参数:
X_train: 训练特征
y_train: 训练标签
"""
# 数据标准化
X_train_scaled = self.scaler.fit_transform(X_train)
# 训练模型
self.model.fit(X_train_scaled, y_train)
self.classes = self.model.classes_
# 计算训练准确率
train_accuracy = self.model.score(X_train_scaled, y_train)
print(f"模型训练完成,训练准确率: {train_accuracy:.2f}")
# 保存模型
if self.model_path:
self.save_model(self.model_path)
def predict(self, features: np.ndarray) -> Tuple[str, float]:
"""
预测病虫害类型
参数:
features: 图像特征向量
返回:
tuple: (预测类别, 置信度)
"""
if self.model is None:
raise ValueError("模型未训练或加载")
# 数据标准化
features_scaled = self.scaler.transform(features.reshape(1, -1))
# 预测
probabilities = self.model.predict_proba(features_scaled)[0]
predicted_class_idx = np.argmax(probabilities)
confidence = probabilities[predicted_class_idx]
if self.classes is not None:
predicted_class = self.classes[predicted_class_idx]
else:
predicted_class = str(predicted_class_idx)
return predicted_class, float(confidence)
def predict_with_analysis(self, features: np.ndarray, image_metrics: Dict) -> Dict:
"""
带分析的预测方法
参数:
features: 图像特征
image_metrics: 图像健康指标
返回:
包含详细分析的预测结果
"""
# 基础预测
disease_class, confidence = self.predict(features)
# 基于图像指标进行后处理
if image_metrics.get('is_likely_healthy', False) and confidence < 0.7:
# 如果图像看起来很健康且置信度不高,倾向于判断为健康
if disease_class != 'healthy':
disease_class = 'healthy'
confidence = 0.8 # 调整置信度
return {
'disease_class': disease_class,
'confidence': confidence,
'health_metrics': image_metrics,
'features_used': len(features)
}
def save_model(self, path: str):
"""
保存模型
参数:
path: 模型保存路径
"""
model_data = {
'model': self.model,
'scaler': self.scaler,
'classes': self.classes
}
joblib.dump(model_data, path)
print(f"模型已保存到: {path}")
def load_model(self, path: str):
"""
加载模型
参数:
path: 模型路径
"""
model_data = joblib.load(path)
self.model = model_data['model']
self.scaler = model_data['scaler']
self.classes = model_data['classes']
print(f"模型已从 {path} 加载")
def get_feature_importance(self) -> np.ndarray:
"""
获取特征重要性
返回:
特征重要性数组
"""
if self.model is None:
raise ValueError("模型未训练或加载")
return self.model.feature_importances_
5. 主应用程序 (app.py)
""" 主应用程序 - 病虫害识别系统Web界面 """ from flask import Flask, render_template, request, jsonify, send_file import os from datetime import datetime import base64 from PIL import Image import io
from config import Config from utils.image_processor import ImageProcessor from models.disease_detector import PlantDiseaseDetector from utils.knowledge_base import DiseaseKnowledgeBase
初始化配置
Config.init_directories()
创建Flask应用
app = Flask(name) app.config['UPLOAD_FOLDER'] = Config.UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB限制
初始化各模块
image_processor = ImageProcessor(img_size=Config.IMG_SIZE) knowledge_base = DiseaseKnowledgeBase(Config.KNOWLEDGE_BASE_FILE)
初始化/加载模型
detector = PlantDiseaseDetector()
模拟训练数据(实际应用中应该从数据集加载)
def create_training_data(): """创建模拟训练数据(仅用于演示)""" # 在实际应用中,这里应该从真实数据集中加载 # 这里使用随机数据模拟 import numpy as np np.random.seed(42)
n_samples = 100
n_features = 14 # 与ImageProcessor中提取的特征数量一致
# 生成模拟特征
X_train = np.random.randn(n_samples, n_features)
# 生成模拟标签
n_classes = len(Config.CLASS_NAMES)
y_train = np.random.randint(0, n_classes, n_samples)
return X_train, y_train
训练模型(实际应用中应该使用预训练模型)
X_train, y_train = create_training_data() detector.train(X_train, y_train)
@app.route('/') def index(): """首页""" return render_template('index.html')
@app.route('/upload', methods=['POST']) def upload_image(): """处理图像上传""" if 'file' not in request.files: return jsonify({'error': '没有文件上传'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': '未选择文件'}), 400
if file and allowed_file(file.filename):
# 生成唯一文件名
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"{timestamp}_{file.filename}"
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
# 保存文件
file.save(filepath)
# 处理图像
try:
result = process_image(filepath)
return jsonify(result)
except Exception as e:
return jsonify({'error': f'图像处理失败: {str(e)}'}), 500
return jsonify({'error': '不支持的文件类型'}), 400
@app.route('/analyze_base64', methods=['POST']) def analyze_base64(): """处理base64图像数据""" data = request.json if not data or 'image' not in data: return jsonify({'error': '没有图像数据'}), 400
try:
# 解码base64图像
image_data = base64.b64decode(data['image'].split(',')[1])
# 处理图像
result = process_image_bytes(image_data)
return jsonify(result)
except Exception as e:
return jsonify({'error': f'图像分析失败: {str(e)}'}), 500
def allowed_file(filename):
"""检查文件类型是否允许"""
return '.' in filename and
filename.rsplit('.', 1)[1].lower() in Config.ALLOWED_EXTENSIONS
def process_image(image_path): """处理图像文件""" # 1. 加载和处理图像 image = image_processor.load_image(image_path)
# 2. 预处理图像
processed_image = image_processor.preprocess(image)
# 3. 提取特征
features = image_processor.extract_features(processed_image)
# 4. 获取健康指标
health_metrics = image_processor.diagnose_plant_health(image)
# 5. 预测病虫害
prediction_result = detector.predict_with_analysis(features, health_metrics)
# 6. 获取防治建议
disease_info = knowledge_base.get_disease_info(prediction_result['disease_class'])
if not disease_info:
disease_info = {
'name': '未知病虫害',
'description': '无法识别该病虫害类型',
'treatment': '请咨询专业农技人员'
}
# 准备结果
result = {
'status': 'success',
'prediction': {
'disease_class': prediction_result['disease_class'],
'disease_name': disease_info['name'],
'confidence': prediction_result['confidence'],
'description': disease_info['description']
},
'recommendations': {
'treatment': disease_info.get('treatment', ''),
'prevention': disease_info.get('prevention', []),
'symptoms': disease_info.get('symptoms', [])
},
'health_metrics': prediction_result['health_metrics'],
'image_info': {
'path': image_path,
'size': f"{image.shape[1]}x{image.shape[0]}"
}
}
return result
def process_image_bytes(image_bytes): """处理字节流图像""" # 直接从字节流处理 image = image_processor.load_image(image_bytes) processed_image = image_processor.preprocess(image) features = image_processor.extract_features(processed_image) health_metrics = image_processor.diagnose_plant_health(image)
prediction_result = detector.predict_with_analysis(features, health_metrics)
disease_info = knowledge_base.get_disease_info(prediction_result['disease_class'])
如果你觉得这个工具好用,欢迎关注我!