news 2026/10/1 9:02:10

深度学习农作物病虫害识别:从数据到部署的全流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度学习农作物病虫害识别:从数据到部署的全流程实战

简介:一份基于深度学习的农作物病虫害识别项目完整资料包,面向计算机相关专业需完成毕业设计、课程设计或期末大作业的学生,也适合有项目实战练习需求的学习者。内容涵盖可运行的Python源码、Vue前端页面、配套论文及查重修改版docx文档、技术路线图,以及大量jpg/png标注图片和txt说明文件,可帮助快速复现病虫害识别流程,理解数据预处理、模型训练、评估与部署的关键环节。压缩包共281个文件,以txt、jpg/png图片、py脚本、vue/js前端文件、pdf/docx文档为主,总大小522.67MB,目录结构清晰便于按模块查阅。项目源于导师指导并获评审98分的高分毕设,方案完整、写作规范,能大幅节省从零搭建与整理资料的时间。目前已有186人学习下载,适合用作毕业设计参考或深度学习图像识别实战练习。

1. 用深度学习做农作物病虫害识别:先搞清楚这套方案能解决什么问题

像“水稻稻瘟病识别”“玉米叶斑病分类”这类课题,几乎是每年 Python 课程设计和毕业设计里出现频率最高的方向之一。背靠的是深度学习图像分类那一套成熟流程:准备带标签的作物叶片图片,训练一个 CNN 分类模型,最后把模型封装成能输入图片输出病害类别的服务。所谓“源码+论文资料+使用教程”,本质就是把这条链路做成一份可复现的工程包——你拿到手能做实验、出图表、写论文,而不是只看懂概念。

很多第一次做这个方向的人会把精力全压在模型结构上,调参调到半夜,最后发现准确率上不去,问题却出在数据预处理和标签整理上。这套方案里的模型选型反而是最不需要纠结的部分,在公开数据集上,ResNet 级别的模型配合迁移学习已经能把常见病害的识别准确率做到很高。真正拉开差距的,是数据怎么整理、训练参数怎么定、模型怎么部署这三个环节。

适合谁?准备做课程设计或毕业设计的学生,以及想快速验证图像分类技术能否用于农业场景的开发者。如果你手头没有 GPU,也能跑,只是训练时间会变长,后面会讲没有 GPU 时怎么调整方案。

2. 数据是病虫害识别的命门:数据集选择、预处理与增强策略

病虫害识别和通用物体分类有个关键差异:同一病害在不同作物、不同生长阶段、不同拍摄角度下,叶片表观差异非常大。模型学到的不是“病害本质”,而是训练集里的像素分布规律。因此这一步作图省事,后面训练阶段会成倍还回来。

2.1 公开数据集怎么挑:类别覆盖与样本均衡

常见做法是先找公开数据集起步。PlantVillage 这一类数据集收集了多种作物叶片的病害图像,背景干净、类别标注齐全,适合用来验证模型结构。但它的图像大多是单叶、均匀背景,和田间拍摄的复杂背景差异明显。如果你的应用场景是田间拍照识别,就要额外补充真实场景图片做微调,否则模型在演示时会“见光死”。

选数据集时重点看三个指标。第一是类别覆盖,是否包含你论文里要写的目标病害;第二是每个类别的样本数量是否均衡,很多公开数据集的健康叶片样本远多于病害样本,直接训练会让模型偏向多数类;第三是图像分辨率,如果原始图小于 224×224,做 resize 时会损失细节,叶片病斑这种细小特征很容易被抹掉。

数据集类型背景特点类别覆盖主要坑点
公开实验室图均匀白底或单色底覆盖常见病害与田间场景差异大,泛化差
田间实拍图复杂背景、遮挡多覆盖不完整标注成本高、样本不均衡
自建补充集贴近真实场景按需补充数量少,需配合强增强

2.2 预处理参数别照搬:Resize、归一化与保存格式

预处理阶段最容易翻车的地方是直接照搬 ImageNet 的统计参数却不理解含义。torchvision 里默认的 mean 和 std 是 [0.485, 0.456, 0.406] 和 [0.229, 0.224, 0.225],这套参数适用于自然图像,对叶片图像也基本适用,因为迁移学习用的预训练权重就是在这个分布上训练的。如果你的数据本身是另类分布,比加红外图或高度统一的扫描图,就要重算 mean 和 std,否则第一轮 loss 会异常高。

下面是最小可用的数据加载脚本,目录结构按data/train/类别名/图片.jpg组织:

# dataloader.py from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomRotation(15), # 随机旋转,模拟叶片摆放角度差异 transforms.RandomHorizontalFlip(p=0.5), # 水平翻转,增强方向不变性 transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) 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]) ]) train_set = datasets.ImageFolder('data/train', transform=train_transform) val_set = datasets.ImageFolder('data/val', transform=val_transform) train_loader = DataLoader(train_set, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_set, batch_size=32, shuffle=False, num_workers=4) print('train classes:', train_set.classes) print('train samples:', len(train_set.samples))

这段代码里,Resize 统一到 224×224 是为了匹配 ImageNet 预训练模型的输入尺寸。RandomRotation 设置 15 度而不是更大度数,是因为叶片照片本身不会出现 180 度翻转的极端情况,旋转过大反而会让模型学到无关的旋转不变性。ColorJitter 的三个参数控制在 0.2,能模拟不同光照条件,但过大会改变叶片本身的颜色特征——注意,有些病害就是靠叶片颜色变化来判断的,色彩抖动过度会让模型丢失这个关键信号。

验证集不要做数据增强,只做 resize 和归一化,因为评估时必须保证结果稳定。如果验证集也旋转翻转,你无法判断准确率波动是模型问题还是增强引入的随机性。

2.3 数据增强的度:先旋转翻转,再考虑 MixUp

很多教程上来就推荐 MixUp、CutMix 这类高级增强,但对病虫害识别来说,这是有风险的。MixUp 会把两张不同病害的叶片线性混合,生成一张“看起来既像 A 又像 B”的图,如果原图本身病害特征就很细微,混合后的特征会更加模糊,反而让模型学不到清晰的判别边界。我的建议是第一批实验只用几何增强加轻度色彩增强,把 baseline 跑出来,再考虑高级增强。

如果样本确实少,比如每类只有几百张,优先做的是针对性的过采样,而不是盲目堆增强策略。另外要检查增强后的图片是否保存了正确的标签映射关系。用 ImageFolder 这种按目录名取标签的方式,目录名一旦和类别对应错位,整个训练集就等于标错了数据,这种错误在训练时几乎无法从 loss 曲线上发现。建议训练前打印train_set.classes确认索引顺序,并且从增强后的数据里抽一张图人工检查。

到这里,数据准备基本完成。接下来进入模型部分——选择什么结构,以及为什么迁移学习是唯一合理起点。

3. 模型选型与训练流程:ResNet、MobileNet 与迁移学习的取舍

模型结构是这个方案里最不需要创新的部分。病虫害识别本质是图像分类任务,CNN 在图像分类上已经被验证了很多年,深度学习课程里讲的卷积、池化、全连接这些概念,落到这个项目上就是一套标准的“卷积网络提取特征 + 全连接层分类”流程。不需要重新设计网络结构,选一个成熟 backbone 然后做迁移学习即可。

3.1 为什么默认选 ResNet 而不是 Vision Transformer

目前这个任务最稳妥的选择是 ResNet18 或 ResNet50。ResNet 的残差连接解决了深层网络梯度消失问题,在中小规模数据集上表现稳定,而且 torchvision 里有成熟的预训练权重。Vision Transformer 虽然在大数据集上表现更好,但在几万张甚至几千张的农业图像上,需要更复杂的训练策略才能收敛,对小数据集不友好。

MobileNetV3 是另一个值得考虑的选择,特别适合后面要部署到 CPU 或无 GPU 环境的情况。它的参数量远小于 ResNet,推理速度快,代价是准确率略有下降。如果你的论文需要对比实验,用 ResNet18 做主力模型、MobileNetV3 做轻量级对比,是成本最低的组合。

模型参数量级推理速度典型场景
ResNet18千万级快默认主力,准确率与速度均衡
ResNet50千万级偏大中等追求更高准确率,需 GPU
MobileNetV3百万级极快CPU 部署、实时识别

3.2 迁移学习是唯一合理起点:加载预训练权重

不要从零训练一个 CNN。病虫害图片数量通常只有几千到几万张,从零训练会严重过拟合。ImageNet 上预训练过的模型已经学会了边缘、纹理、颜色分布这些通用视觉特征,把这些特征迁移到叶片图像上,只需训练最后的分类层和微调网络的高层参数。

需要注意 torchvision 的版本差异。新版推荐用weights=参数,旧版用pretrained=True:

# model.py import torch import torch.nn as nn from torchvision import models def build_model(num_classes, backbone='resnet18'): if backbone == 'resnet18': model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes) elif backbone == 'mobilenet_v3': model = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.IMAGENET1K_V1) in_features = model.classifier[-1].in_features model.classifier[-1] = nn.Linear(in_features, num_classes) return model

替换最后一层时要保留in_features,它从预训练模型的最后一层拿输入维度,再映射到你的类别数。ResNet 的分类头是fc,MobileNetV3 的分类头是classifier列表里的最后一层,两者结构不同,不要写错替换对象。

一种可选的做法是冻住 backbone,只训练分类头,先用这种模式跑 10 个 epoch 得到一个快速 baseline,再解冻全部参数做整体微调。这个方法能减少训练时间,但最终效果通常比全量微调差,适合用来快速验证数据是否有问题。

提示:训练时保存的 state_dict 只含权重不含结构,加载时必须先用build_model创建相同结构的模型。这个顺序颠倒会导致未知的 key 错误。

3.3 训练参数怎么定:学习率、batch size 与 epoch

训练参数的选择直接决定你是顺风局还是逆风局。学习率是最核心的超参数,迁移学习场景下,全量微调的初始学习率我一般设为 1e-4,这个值既能有效更新参数,又不会破坏预训练权重。如果使用 SGD 优化器,学习率可以设为 1e-3 加 momentum=0.9;如果使用 AdamW,1e-4 是更稳的起点。

batch size 受限于显存,图像 224×224 的条件下,ResNet18 可以用 32,ResNet50 建议降到 16。epoch 数量不要超过 50,配合 cosine 学习率衰减,30 个 epoch 左右就能观察到收敛趋势。以下是完整训练循环的骨架:

# train.py import torch import torch.nn as nn import torch.optim as optim from model import build_model device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = build_model(num_classes=len(train_set.classes)).to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) best_acc = 0.0 for epoch in range(30): model.train() running_loss = 0.0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) model.eval() correct = total = 0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) _, preds = torch.max(outputs, 1) correct += (preds == labels).sum().item() total += labels.size(0) acc = correct / total print(f'epoch {epoch+1}: loss={running_loss/len(train_set):.4f}, val_acc={acc:.4f}') if acc > best_acc: best_acc = acc torch.save(model.state_dict(), 'best_model.pth')

AdamW 里的weight_decay=1e-4是 L2 正则,对防止过拟合有实际帮助。CosineAnnealingLR 的T_max=30与 epoch 数一致,学习率会在 30 个 epoch 内从初始值余弦下降到接近 0,到后半段自动进行精细收敛。保存模型时只保存state_dict而不是整个模型,这样加载时依赖关系更清晰,后面部署接口时也方便。

如果只用 CPU 训练,ResNet18 跑一个 epoch 在几千张图上可能要几十分钟。这时候把 epoch 数降到 15、batch size 降到 16,并用 SGD + momentum 代替 AdamW,一般也能得到可用的结果。训练时观察打印的 loss 和 val_acc——loss 应该持续下降,val_acc 在后期有小幅波动属于正常,但如果从头到尾不涨,就是参数或数据出了问题,这个在下一章专门讲。

4. 训练翻车排查:loss 不降、过拟合与类别不均衡的 4 个真实场景

训练阶段最磨人的不是写代码,而是面对一个“看起来没问题但就是不收敛”的模型。以下四个场景是我在做这个方向时踩过的真实坑,按现象、原因、解决的顺序讲。

4.1 现象:loss 在前几个 epoch 纹丝不动

loss 从第一个 epoch 开始就保持在 2.3 左右,训练十几个 epoch 几乎不下降。2.3 这个数字对 10 个类别的多分类任务来说约等于随机猜的概率,因为随机猜测的交叉熵损失就是 ln(10)≈2.3。

原因通常有两个。一是学习率太小,迁移学习场景下如果用了 1e-5 这种过小的值,梯度更新幅度微乎其微,loss 变化肉眼不可见;二是预训练权重没加载成功,代码里写了weights=但模型某个层因为 key 不匹配被跳过了,实际是从随机初始化开始训练,这种情况下数据量又不够,几个 epoch 根本看不出收敛迹象。

解决思路是先用一个快速实验排除参数问题。把学习率调到 3e-4,只训练分类头 3 个 epoch,如果 loss 明显下降,说明数据管道和模型结构没问题,再恢复全量微调。如果调大学习率后 loss 还是不降,打印模型第一层卷积权重的mean和std,和预训练权重的数值范围对比,就能确认权重是否真的加载进去了。

4.2 现象:训练集准确率 99%,验证集只有 70%

这个是最典型的过拟合。验证集准确率在训练初期正常上升,到第 10 个 epoch 左右开始停滞甚至回落,而训练集准确率一路逼近满分。本质上就是模型把训练集的图像背景、噪声和病害特征一起记住了,换了一批新图片就失灵。

主要原因有三层:训练样本太少、数据增强不够强、模型参数量过大。ResNet50 比 ResNet18 更容易过拟合,尤其是在几千张图的小数据集上。

解决方法按优先级操作。第一步增强数据,把 RandomRotation 从 15 度加到 20 度,加上 RandomResizedCrop 模拟不同视角。第二步加正则,在全连接层前插入 Dropout(0.3),或者把 weight_decay 从 1e-4 提高到 1e-3。第三步换模型,如果以上两步做完验证集准确率还是上不去,就把 ResNet50 换成 ResNet18,减少模型容量。注意别把 Dropout 加在 backbone 层,那样会破坏预训练特征的稳定性。

4.3 现象:某种病害几乎不识别

训练结束后,模型在多数类别上表现不错,唯独对某一种病害的识别准确率接近 0,预测结果总是偏向其他类别。去查看该类别的训练样本数量,会发现它只占全部样本的不到 5%。

这是典型的类别不均衡问题。交叉熵损失对所有样本一视同仁,样本多的类梯度更新幅度大,模型自然偏向多数类,少数类的特征几乎没有机会被充分学习。

直接换用带权重的交叉熵损失是最简单的办法。torch.nn.CrossEntropyLoss(weight=class_weights)里class_weights按每个类别样本数量的倒数设置,让少数类的 loss 贡献更大。想更精细可以做 focal loss,它会额外降低已正确分类样本的权重,把训练重点推向难分的少数类。另外也可以对少数类图片做过采样,在 DataLoader 里用 WeightedRandomSampler 按类别倒数的概率采样,这种方法不改变 loss 函数,实现起来更直观。

4.4 现象:验证集准确率震荡剧烈

验证集准确率不收敛,在 40% 到 80% 之间来回跳,loss 曲线同时出现周期性抖动。这个情况在 Adam 系列优化器下容易遇到,尤其是学习率偏高时,参数在最优解附近来回振荡。

首先要排除验证集本身的问题——如果验证集样本太少,比如每类只有十几张,单次评估的方差就会很大,这种情况加多验证集样本即可。排除了数据问题后,把优化器从 AdamW 换成 SGD 加 momentum=0.9,SGD 对学习率的敏感度相对低,震荡幅度会明显收敛。也可以搭配 cosine 学习率调度,它会在训练后半段自动降低学习率,让参数逐步稳定下来。

如果震荡出现在 epoch 后半段而前半段正常,说明初始学习率偏高,把 1e-4 降到 3e-5 再跑一轮对比。

5. 把模型变成可调用接口:Flask 推理服务与置信度阈值设计

训练完成之后,模型的形态还只是一个best_model.pth文件。要让它能在论文演示或实际使用中被调用,需要写一层推理服务。常见做法是用 Flask 包一个 HTTP 接口,接收图片、返回类别和置信度,这也是源码包里“使用教程”部分应该覆盖的内容。

5.1 模型保存与加载:状态字典和完整模型

训练时保存的是model.state_dict(),对应加载时先实例化模型结构再载入参数。要注意的是,保存和加载时的类别定义必须一致,如果你在训练脚本和推理脚本里分别定义了类别列表,顺序错一个字母,预测结果就完全错位。我习惯把类别列表存成一个labels.txt,和权重文件放在同一目录。

# load_model.py import torch from model import build_model labels = [line.strip() for line in open('labels.txt')] model = build_model(num_classes=len(labels), backbone='resnet18') model.load_state_dict(torch.load('best_model.pth', map_location='cpu')) model.eval()

map_location='cpu'保证在没有 GPU 的机器上也能加载训练好的模型。model.eval()必须调用,它会关闭 Dropout 和 BatchNorm 的训练行为,否则同一个输入在不同批次下的预测结果会不一致。

5.2 用 Flask 包一个 POST 接口

接口设计遵循一个简单原则:输入是图片,输出是 JSON。Flask 读取上传文件以后,需要走一遍和训练时完全一致的预处理流程,包括 resize、归一化、mean 和 std。注意 PIL 打开图片后要转成 RGB,否则遇到 RGBA 格式的 PNG 图会报错。

# app.py from flask import Flask, request, jsonify from PIL import Image from io import BytesIO import torch from torchvision import transforms from load_model import model, labels app = Flask(__name__) 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]) ]) @app.route('/predict', methods=['POST']) def predict(): file = request.files.get('image') if file is None: return jsonify({'error': 'no image uploaded'}), 400 img = Image.open(BytesIO(file.read())).convert('RGB') tensor = transform(img).unsqueeze(0) with torch.no_grad(): outputs = model(tensor) probs = torch.softmax(outputs, dim=1) conf, idx = probs.max(1) label = labels[idx.item()] conf = round(conf.item(), 4) return jsonify({'label': label, 'confidence': conf}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

推理过程用torch.no_grad()包起来,关闭梯度计算,显存占用和推理时间都有明显改善。返回的置信度用round保留四位小数,避免 JSON 序列化时出现一长串浮点数。接口的错误处理只覆盖了“没上传图片”这种情况,实际项目里还要考虑上传文件不是图片、图片损坏、推理异常等分支,这些都会在论文的测试部分成为可写的内容。

5.3 置信度阈值与错误输出设计

模型对任何输入都会输出一个置信度最高的类别,哪怕这张图根本不是农作物叶片。生产环境里必须加一道置信度阈值判断,低于阈值就返回“无法识别”,而不是硬给一个病害标签。这个阈值是论文里可以量化的指标。

阈值怎么定?收集一部分真实场景下的负样本图片,比如杂草、土壤、人手,跑一遍模型统计置信度分布,选取能覆盖绝大多数负样本的阈值。我一般先用 0.5 起步,如果发现真实的病叶误判率偏高就降到 0.4,如果负样本被误识别就往上调。也可以在返回结果里同时返回排名前 3 的类别及置信度,让调用方自己判断——对论文分析来说,top-2 的置信度差异很有参考价值。

6. 论文资料与使用教程怎么配套:从结果复现到实验记录

这部分是源码包价值最容易被低估的地方。代码能跑只是第一步,论文需要的实验数据、图表和复现说明,才是让这个方向能写成论文的关键。

6.1 实验结果怎么记录才够写论文

我自己的习惯是每次实验建立一个独立目录,命名规则是日期_模型_优化器_lr,比如20250115_resnet18_adamw_1e-4。目录内存放训练日志、最佳权重、验证结果汇总。训练日志里除了 loss 和 acc,还要记录随机种子、数据增强配置、训练耗时,这些细节在写论文的实验设置部分都要用到。

保存预测错误的样例图同样是重要习惯。训练结束后,从验证集里挑出预测错误和置信度低的样本,按“错误类型+真实类别+预测类别”命名保存。论文里做错误分析时,这些图片就是最直接的证据,能说明模型在什么场景下失效,比单纯堆准确率数字更有说服力。

实验复现需要固定随机种子。PyTorch 里torch.manual_seed(42)和random.seed(42)都要设置,数据加载器里如果用了随机采样,还要设置DataLoader的generator参数,否则每次运行的增强结果不同,实验无法复现。

6.2 使用教程和源码组织:别人拿到手能跑通

源码的组织方式直接影响使用教程好不好写。常见做法是把训练、预测、接口服务分成三个独立脚本,数据目录、权重目录、日志目录分开。教程部分应该从环境准备开始写,Python 安装之后装依赖,然后给出一条能完整复现实验的命令链路。依赖安装保持最小化,避免把与项目无关的包全装进去:

pip install torch torchvision flask pillow

在 VSCode 里打开项目,安装 Python 扩展后直接运行脚本。使用教程里一定要写明数据目录的组织格式,并提供一个类别数量最少的快速测试命令,让使用者先确认环境通、再跑完整训练。论文资料部分通常包含数据集说明、实验记录、模型结构图、结果分析。写论文时有一组基线数据非常重要:同模型在公开数据集和自采数据上的准确率对比,以及不同模型、不同优化器的消融实验。这组数据如果在一开始训练时就有意识地记录,写论文时直接整理成表格即可。运行完一轮实验后回头补记录,基本都会漏掉关键参数。

我的习惯是每跑完一轮实验,当场把损失曲线截图和参数配置写进记录文件,训练完顺手保存错误样例。这样论文初稿的实验部分基本上是从记录文件里搬出来的。这个习惯帮我少走了很多回头路,希望帮到你。

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

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

MIPI HS TX物理层调试:驱动强度、预加重与阻抗校准实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 9:01:34

舌苔识别深度学习项目:本地GUI+论文全链路落地

简介:本资源是一套完整的基于深度学习的舌苔识别检测鉴定系统实现方案,面向计算机视觉初学者、医学图像处理方向本科生及毕业设计选题者,聚焦中医舌诊数字化这一典型应用场景。包内含110个文件,涵盖26个核心Python源码&#xff08…

作者头像 李华
网站建设 2026/10/1 8:59:57

微信小程序拍照识别手写汉字与书写评分实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 8:59:56

Substance Painter三维纹理绘制核心技术与PBR工作流实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 8:58:18

航拍小目标人体检测:YOLO在校园操场场景的实战方法论

1. 项目概述:为什么这个航拍人体检测数据集值得单独拎出来讲你有没有试过在校园操场用无人机拍一段视频,想自动数清有多少学生在跑步、做操、打篮球,结果YOLO模型一跑,人影糊成一片、小目标全漏检、俯视角度下人体比例严重失真&am…

作者头像 李华
网站建设 2026/10/1 8:58:07

PY1.939传世引擎源码实测编译与运行全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华