PyTorch实战:用Transformer搞定无人机影像中的农作物识别(附完整代码)
PyTorch实战:用Transformer搞定无人机影像中的农作物识别(附完整代码)
去年夏天,我在一个农业科技公司的项目里遇到了一个棘手的问题:他们用无人机拍摄了上千亩的玉米田影像,希望我们能自动识别出不同生长阶段的玉米植株,并估算出每块区域的植株密度。最初我们尝试了传统的CNN模型,比如ResNet和U-Net,但在处理那些边缘模糊、光照变化剧烈的航拍图像时,效果总是不尽如人意。模型要么把田埂误判成作物,要么对幼苗期的稀疏植株视而不见。直到我们把目光转向了Transformer架构,局面才真正打开。
你可能已经听说过Transformer在自然语言处理领域的统治地位,但它在计算机视觉,特别是遥感影像分析上的潜力,可能比你想象的还要大。传统的卷积神经网络(CNN)依靠局部感受野来提取特征,这在处理纹理规则的自然图像时很有效,但面对无人机拍摄的农田影像——那些作物排列看似随机、尺度变化极大、背景复杂的场景——CNN的局部归纳偏置反而成了限制。Transformer的自注意力机制能够捕捉图像中任意两个像素之间的长程依赖关系,这让它特别擅长理解整片农田的全局布局和上下文信息。
这篇文章就是为你准备的,如果你是一位智慧农业领域的算法工程师、农业科技公司的技术负责人,或者是对AI+农业交叉应用感兴趣的研究者,想要一个即拿即用、可落地的解决方案,而不仅仅是理论探讨。我会带你从零开始,构建一个基于PyTorch和Transformer的轻量化农作物识别模型,重点解决小样本训练、数据增强策略、模型部署优化这些实际工程中真正卡脖子的问题。文末会提供完整的、可运行的代码仓库链接。
1. 为什么是Transformer?重新思考无人机影像的识别难题
在深入代码之前,我们有必要先厘清一个根本问题:为什么在农作物识别这个特定任务上,Transformer可能比CNN更合适?这得从无人机影像的特点说起。
无人机通常在低空飞行(几十米到几百米),拍摄的影像分辨率很高,但随之而来的是一系列挑战:
- 尺度多样性极大:同一张图片里,近处的作物叶片纹理清晰,远处的则可能只是几个像素点。
- 光照与阴影影响显著:不同时间、不同天气下拍摄,颜色和对比度差异巨大。
- 背景复杂:农田中混杂着土壤、田埂、灌溉设施、杂草,甚至还有农机具的影子。
- 类内差异大,类间差异小:不同健康状态的同种作物(如受旱与正常)可能看起来差别很大,而某些杂草在苗期可能与作物幼苗极为相似。
CNN的卷积核在固定尺寸的局部窗口内操作,虽然通过堆叠层数可以扩大感受野,但这种对长距离依赖的建模是间接且低效的。而Transformer的核心——自注意力机制,允许模型直接计算图像中所有位置(patch)之间的关系权重。这意味着,模型可以同时“看到”图像左上角的一株玉米和右下角的一株玉米,并判断它们是否属于同一种类别、处于何种生长阶段,这对于理解作物的行距、种植密度、病害传播模式至关重要。
注意:这里说的Transformer通常指Vision Transformer (ViT)及其变种。ViT将图像分割成固定大小的图像块(patch),将每个块线性投影为序列向量,然后送入标准的Transformer编码器进行处理。这种处理方式完全放弃了卷积,纯粹依赖注意力机制。
为了更直观地对比,我们来看一个简单的特性对照表:
| 特性维度 | 卷积神经网络 (CNN) | Vision Transformer (ViT) |
|---|---|---|
| 核心操作 | 局部卷积、池化 | 全局自注意力 |
| 归纳偏置 | 平移不变性、局部性 | 较少,更依赖数据 |
| 感受野 | 随网络深度增大 | 从一开始就是全局的 |
| 数据需求 | 相对较少,可利用预训练 | 通常需要更多数据,或更强的数据增强 |
| 计算复杂度 | 与图像尺寸呈线性或平方关系 | 与序列长度(patch数)的平方成正比 |
| 对遮挡/噪声 | 相对鲁棒 | 可能更敏感,但上下文建模能力强 |
| 在无人机影像上的优势 | 纹理特征提取强,部署成熟 | 长程依赖建模强,对不规则排列、尺度变化适应更好 |
从表格可以看出,Transformer并非全面碾压CNN,它的数据饥渴性和计算开销是众所周知的挑战。这也引出了我们实战中的第一个核心策略:如何为Transformer设计一个轻量化的、适合小样本农业数据集的架构? 我们不会直接使用原始的、参数庞大的ViT-Base或ViT-Large,而是会采用一种混合架构或更小巧的变体。
2. 实战准备:数据、环境与一个轻量化的Transformer模型
我们的目标是识别无人机影像中的农作物,假设任务是一个语义分割任务——即为图像中的每一个像素点分类,比如分为“玉米”、“大豆”、“土壤”、“杂草”、“背景”等。我们选择PyTorch作为框架,因为它动态图的特点非常适合研究和快速迭代。
2.1 数据准备与增强:针对农业影像的“特效药”
农业数据集往往规模有限,标注成本高昂。因此,数据增强不是可选项,而是必选项。除了通用的旋转、翻转、裁剪,我们需要针对农业影像的特点设计增强策略。
-
多光谱/颜色增强:无人机影像常常包含近红外等波段。即使我们只用RGB,模拟不同光照条件也至关重要。我们可以使用
albumentations库,它比torchvision的transform更强大。import albumentations as A from albumentations.pytorch import ToTensorV2 def get_train_transform(): return A.Compose([ A.RandomRotate90(p=0.5), A.Flip(p=0.5), # 模拟光照变化:随机调整亮度、对比度、饱和度 A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.75), A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=10, p=0.5), # 模拟云雾或噪声 A.RandomGamma(gamma_limit=(80, 120), p=0.3), A.GaussNoise(var_limit=(10.0, 50.0), p=0.2), # 针对农田:随机模拟作物缺失(模拟病害或稀疏区域) A.CoarseDropout(max_holes=8, max_height=32, max_width=32, fill_value=0, p=0.3), # 归一化并转换Tensor A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2(), ]) -
标签自适应:在进行空间变换(如旋转、弹性形变)时,必须同步处理标注的掩码(mask)。
albumentations可以很好地处理这一点。 -
构建DataLoader:我们将创建一个继承自
torch.utils.data.Dataset的类,来读取图像-掩码对,并应用上述增强。
2.2 模型选择:SegFormer——为语义分割而生的高效Transformer
直接使用ViT做密集预测(如分割)需要处理高分辨率特征图,计算量巨大。因此,我们选择SegFormer这个模型。它由MIT的华人研究者提出,核心创新在于:
- 分层Transformer编码器:像CNN一样输出多尺度特征(1/4, 1/8, 1/16, 1/32分辨率),这对分割任务至关重要。
- 轻量化的注意力机制:使用了混合注意力,在计算效率和性能间取得了很好的平衡。
- 简单的MLP解码器:无需复杂的FPN或U-Net式跳跃连接,仅通过MLP层融合多尺度特征,简单高效。
我们将采用segformer的轻量级版本MiT-B0作为编码器。首先安装必要的库:
pip install torch torchvision
pip install mmsegmentation # 或者使用timm库中的segformer实现
如果使用timm库(推荐,更轻便):
pip install timm
下面是我们构建SegFormer模型的核心代码块:
import torch
import torch.nn as nn
import torch.nn.functional as F
from timm.models.vision_transformer import VisionTransformer
# 注意:timm可能没有直接导出SegFormer,这里我们简化实现其核心思想
# 实际项目中,可以考虑使用开源实现,如 https://github.com/NVlabs/SegFormer
class MixFFN(nn.Module):
""" SegFormer中使用的混合FFN,包含深度卷积 """
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
super().__init__()
out_features = out_features or in_features
hidden_features = hidden_features or in_features
self.fc1 = nn.Linear(in_features, hidden_features)
self.dwconv = nn.Conv2d(hidden_features, hidden_features, 3, 1, 1, groups=hidden_features) # 深度卷积
self.act = act_layer()
self.fc2 = nn.Linear(hidden_features, out_features)
self.drop = nn.Dropout(drop)
def forward(self, x, H, W):
# x: (B, N, C)
B, N, C = x.shape
x = self.fc1(x)
x = x.transpose(1, 2).view(B, C, H, W) # 重塑为2D进行卷积
x = self.dwconv(x)
x = x.flatten(2).transpose(1, 2) # 恢复序列
x = self.act(x)
x = self.drop(x)
x = self.fc2(x)
x = self.drop(x)
return x
class EfficientSelfAttention(nn.Module):
""" 一种简化版的高效自注意力,通过降维减少计算量 """
def __init__(self, dim, num_heads=8, qkv_bias=False, attn_drop=0., proj_drop=0., sr_ratio=1):
super().__init__()
# 省略了序列缩减(SR)的实现以保持简洁
self.num_heads = num_heads
self.scale = (dim // num_heads) ** -0.5
self.q = nn.Linear(dim, dim, bias=qkv_bias)
self.kv = nn.Linear(dim, dim * 2, bias=qkv_bias)
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(dim, dim)
self.proj_drop = nn.Dropout(proj_drop)
def forward(self, x, H, W):
B, N, C = x.shape
q = self.q(x).reshape(B, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
# 简化处理,实际SegFormer的KV有序列缩减
kv = self.kv(x).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
k, v = kv[0], kv[1]
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
attn = self.attn_drop(attn)
x = (attn @ v).transpose(1, 2).reshape(B, N, C)
x = self.proj(x)
x = self.proj_drop(x)
return x
class TransformerBlock(nn.Module):
""" SegFormer编码器的基础块 """
def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, drop=0., attn_drop=0.,
act_layer=nn.GELU, norm_layer=nn.LayerNorm):
super().__init__()
self.norm1 = norm_layer(dim)
self.attn = EfficientSelfAttention(dim, num_heads=num_heads, qkv_bias=qkv_bias,
attn_drop=attn_drop, proj_drop=drop)
self.norm2 = norm_layer(dim)
mlp_hidden_dim = int(dim * mlp_ratio)
self.mlp = MixFFN(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
def forward(self, x, H, W):
x = x + self.attn(self.norm1(x), H, W)
x = x + self.mlp(self.norm2(x), H, W)
return x
# 由于完整实现SegFormer较复杂,在实际项目中,我们可以直接使用timm中已有的模型或引用开源实现。
# 以下是一个使用预训练模型构建分割头的示意性流程:
import timm
class SegFormerForAgriculture(nn.Module):
def __init__(self, num_classes=5, pretrained=True):
super().__init__()
# 加载timm中的预训练MixTransformer (MiT) backbone
self.backbone = timm.create_model('mit_b0', pretrained=pretrained, features_only=True)
# 获取backbone各阶段的通道数
feature_channels = self.backbone.feature_info.channels() # 例如 [32, 64, 160, 256]
# 构建一个简单的全MLP解码器(仿SegFormer)
self.linear_fuse = nn.Conv2d(sum(feature_channels), 256, kernel_size=1)
self.linear_pred = nn.Conv2d(256, num_classes, kernel_size=1)
# 上采样到原图尺寸的卷积
self.up_sample = nn.Upsample(scale_factor=4, mode='bilinear', align_corners=False)
def forward(self, x):
# 提取多尺度特征
features = self.backbone(x) # 返回一个list,包含4个不同尺度的特征图
# 将特征图统一上采样到1/4尺寸并拼接
H, W = x.shape[2] // 4, x.shape[3] // 4
features = [F.interpolate(f, size=(H, W), mode='bilinear', align_corners=False) for f in features]
x = torch.cat(features, dim=1)
x = self.linear_fuse(x)
x = self.linear_pred(x)
x = self.up_sample(x) # 上采样回原图尺寸
return x
这个SegFormerForAgriculture类定义了我们用于农作物分割的模型主干。它利用timm库中预训练的mit_b0模型作为编码器,这是一个非常轻量化的Transformer架构,参数约3.7M,非常适合在资源受限的边缘设备或需要快速迭代的实验中使用。
3. 训练策略:让小样本数据喂饱Transformer
Transformer模型通常需要海量数据,但我们的农业数据集可能只有几百或几千张标注图像。如何克服这个矛盾?这里分享三个经过验证的策略。
策略一:利用大规模自然图像预训练权重
这是最关键的一步。我们使用的mit_b0模型已经在ImageNet-1K或更大的数据集上预训练过。这相当于让模型先学会了识别边缘、纹理、形状等通用视觉特征,我们只需要在其基础上进行“微调”,让它适应农作物这个特定领域。在PyTorch中,通过pretrained=True参数即可轻松加载。
策略二:渐进式微调与分层学习率 不要一上来就更新所有参数。我们可以采用分层设置学习率的策略:Transformer编码器的深层(靠近输入)参数,学习率设置得小一些,因为这些层提取的是通用特征;而解码器和最后的分类层,学习率可以设置得大一些,因为它们需要快速适应新任务。
from torch.optim import AdamW
model = SegFormerForAgriculture(num_classes=5, pretrained=True)
param_groups = [
{'params': model.backbone.parameters(), 'lr': 1e-5}, # backbone学习率小
{'params': model.linear_fuse.parameters(), 'lr': 1e-4},
{'params': model.linear_pred.parameters(), 'lr': 1e-4},
]
optimizer = AdamW(param_groups, weight_decay=1e-4)
策略三:强正则化与早停 为了防止在小数据集上过拟合,我们必须加强正则化。
- 权重衰减:如上代码中的
weight_decay。 - Dropout:在Transformer块和MLP中使用Dropout。
- 标签平滑:在计算损失时使用标签平滑,防止模型对训练数据过于自信。
- 早停:密切监控验证集损失,当其在连续多个epoch不再下降时,停止训练。
损失函数我们选择在分割任务中表现稳健的交叉熵损失与Dice损失的组合。Dice损失特别适用于处理前景(作物)和背景(土壤)像素数量不平衡的情况。
class CombinedLoss(nn.Module):
def __init__(self, alpha=0.5):
super().__init__()
self.alpha = alpha
self.ce_loss = nn.CrossEntropyLoss(ignore_index=255, label_smoothing=0.1) # label_smoothing是标签平滑
# Dice Loss实现
def dice_loss(self, pred, target):
smooth = 1.
pred = F.softmax(pred, dim=1)
target_one_hot = F.one_hot(target, num_classes=pred.shape[1]).permute(0, 3, 1, 2).float()
intersection = (pred * target_one_hot).sum(dim=(2,3))
union = pred.sum(dim=(2,3)) + target_one_hot.sum(dim=(2,3))
dice = (2. * intersection + smooth) / (union + smooth)
return 1 - dice.mean()
def forward(self, pred, target):
ce = self.ce_loss(pred, target)
dice = self.dice_loss(pred, target)
return self.alpha * ce + (1 - self.alpha) * dice
4. 部署优化:让模型在田间地头跑起来
模型训练好了,精度也不错,但怎么把它部署到实际的农业巡检系统里?无人机机载计算机或边缘计算盒子的计算资源通常有限。这里有几个关键的优化方向。
4.1 模型量化 将模型参数从32位浮点数(FP32)转换为8位整数(INT8),可以显著减少模型体积和内存占用,并提升推理速度,而精度损失通常很小。PyTorch提供了方便的量化API。
# 动态量化(后训练量化,最简单)
import torch.quantization
quantized_model = torch.quantization.quantize_dynamic(
model, {torch.nn.Linear, torch.nn.Conv2d}, dtype=torch.qint8
)
# 保存量化后的模型
torch.jit.save(torch.jit.script(quantized_model), 'quantized_agriculture_model.pth')
4.2 使用TensorRT或ONNX Runtime加速 对于NVIDIA的硬件,可以将PyTorch模型导出为ONNX格式,然后用TensorRT进行优化和推理,能获得数倍的加速比。对于其他硬件,ONNX Runtime也是一个高性能的推理引擎。
# 导出为ONNX格式(简化示例)
dummy_input = torch.randn(1, 3, 512, 512).to(device)
torch.onnx.export(model, dummy_input, "agriculture_model.onnx",
input_names=['input'], output_names=['output'],
dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}})
4.3 设计高效的推理流水线 无人机影像通常很大(如4000x3000像素),直接输入网络不现实。我们需要采用滑动窗口或分块推理的策略。
- 将大图分割成重叠的小块(如512x512)。
- 对每个小块进行推理。
- 将小块的预测结果拼接回原图尺寸,重叠区域可以采用加权平均来平滑边缘。
这个过程需要注意内存管理和并行计算,以提升吞吐量。下面是一个简单的分块推理函数框架:
def inference_large_image(model, large_img, patch_size=512, overlap=64):
"""
对大图像进行分块推理
Args:
model: 训练好的模型
large_img: 输入大图像,形状为 (C, H, W)
patch_size: 分块大小
overlap: 块之间的重叠像素,用于减少拼接缝隙
Returns:
整张图的预测结果 (H, W)
"""
C, H, W = large_img.shape
output = torch.zeros((model.num_classes, H, W), device=large_img.device)
count = torch.zeros((H, W), device=large_img.device)
stride = patch_size - overlap
for y in range(0, H, stride):
for x in range(0, W, stride):
# 计算当前块的坐标,防止越界
y1, y2 = y, min(y + patch_size, H)
x1, x2 = x, min(x + patch_size, W)
patch = large_img[:, y1:y2, x1:x2]
# 如果块大小不足,进行填充
if patch.shape[1] < patch_size or patch.shape[2] < patch_size:
pad_h = patch_size - patch.shape[1]
pad_w = patch_size - patch.shape[2]
patch = F.pad(patch, (0, pad_w, 0, pad_h), mode='constant', value=0)
# 推理
with torch.no_grad():
pred_patch = model(patch.unsqueeze(0)).squeeze(0) # (C, patch_size, patch_size)
# 将预测结果放回对应位置,并累加计数(用于平均)
output[:, y1:y1+patch.shape[1], x1:x1+patch.shape[2]] += pred_patch[:, :y2-y1, :x2-x1]
count[y1:y2, x1:x2] += 1
# 平均重叠区域的预测
output = output / count.unsqueeze(0).clamp(min=1)
final_pred = output.argmax(dim=0) # 得到最终的类别索引图
return final_pred.cpu().numpy()
4.4 模型轻量化备选方案
如果MiT-B0在目标设备上仍然运行缓慢,可以考虑更极致的轻量化方案:
- 知识蒸馏:用一个大的“教师模型”指导一个小的“学生模型”训练,让学生模型模仿教师模型的输出。
- 神经架构搜索:自动搜索适合特定硬件和任务的最优小模型。
- 使用MobileViT等移动端Transformer:专门为移动设备设计的Transformer变体,在精度和速度间取得了更好的平衡。
5. 超越识别:从像素到业务洞察
准确识别出农作物只是第一步。在智慧农业的实际应用中,我们需要从识别结果中提炼出有价值的业务指标。这里提供几个思路和代码片段。
5.1 植株计数与密度估算 对于条播作物(如玉米、小麦),我们可以通过连通域分析来统计植株数量。
import cv2
import numpy as np
def count_plants(mask, class_id=1, min_area=10):
"""
从分割掩码中统计特定类别(如玉米)的连通域数量,用于植株计数。
Args:
mask: 预测得到的单通道类别索引图,值域为[0, num_classes-1]
class_id: 需要统计的作物类别ID
min_area: 最小连通域面积,用于过滤噪声
Returns:
count: 植株数量
labeled_mask: 标记了连通域的图像(用于可视化)
"""
# 创建二值图像:作物区域为255,其他为0
binary = (mask == class_id).astype(np.uint8) * 255
# 形态学操作,去除小噪声,连接断裂部分
kernel = np.ones((3,3), np.uint8)
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# 连通域分析
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary, connectivity=8)
# 过滤掉面积太小的连通域(可能是噪声)
plant_count = 0
for i in range(1, num_labels): # 跳过背景(标签0)
if stats[i, cv2.CC_STAT_AREA] >= min_area:
plant_count += 1
return plant_count, labels
5.2 长势评估与异常检测 通过分析作物区域的像素特征(如颜色、纹理),可以对其长势进行初步评估。例如,计算玉米区域的归一化植被指数(NDVI,如果有多光谱数据)或简单计算绿色像素的平均强度。
def assess_growth_health(rgb_img, plant_mask):
"""
一个简单的长势评估示例:通过作物区域的绿色通道强度来粗略判断健康状况。
Args:
rgb_img: 原始RGB图像,形状(H,W,3),值域[0,255]
plant_mask: 作物区域的二值掩码,形状(H,W)
Returns:
avg_green_intensity: 平均绿色强度
health_score: 一个简单的健康评分(示例)
"""
# 将RGB转换为HSV,更容易提取绿色信息
hsv = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2HSV)
# 定义绿色的HSV范围(粗略估计)
lower_green = np.array([35, 50, 50])
upper_green = np.array([85, 255, 255])
green_mask = cv2.inRange(hsv, lower_green, upper_green)
# 只关注作物区域内的绿色
green_in_plants = cv2.bitwise_and(green_mask, green_mask, mask=plant_mask)
# 计算绿色像素的比例和平均强度
plant_pixel_count = np.sum(plant_mask > 0)
if plant_pixel_count > 0:
green_ratio = np.sum(green_in_plants > 0) / plant_pixel_count
# 也可以计算绿色通道的平均值
green_channel = rgb_img[:, :, 1] # G通道
avg_green_intensity = np.mean(green_channel[plant_mask > 0])
# 一个简单的健康评分(可根据实际数据校准)
health_score = min(green_ratio * avg_green_intensity / 100.0, 1.0)
return avg_green_intensity, health_score
else:
return 0.0, 0.0
5.3 生成处方图 将识别和评估结果与地理坐标(无人机影像通常带有GPS信息)结合,可以生成农田的“处方图”。例如,一张显示植株密度过低或疑似病害区域的地图,可以直接导入到变量施肥或喷药农机中,实现精准作业。
# 假设我们有每个像素的经纬度坐标(从无人机影像元数据获得)
def generate_prescription_map(mask, health_scores, gps_coords):
"""
生成简单的处方图GeoJSON(示例)
Args:
mask: 分类掩码
health_scores: 每个评估单元的健康评分网格
gps_coords: 图像四个角点的GPS坐标 (lat, lon)
Returns:
prescription_geojson: 包含多边形和属性的GeoJSON字典
"""
# 1. 将健康评分网格二值化,找出需要处理的区域(如健康评分<0.5)
need_treatment = health_scores < 0.5
# 2. 找到这些区域的轮廓
contours, _ = cv2.findContours(need_treatment.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 3. 将轮廓像素坐标转换为地理坐标(需要根据GPS坐标进行仿射变换,此处简化)
features = []
for contour in contours:
if cv2.contourArea(contour) > 100: # 忽略太小的区域
# 简化轮廓
epsilon = 0.01 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
# 将像素坐标转换为经纬度(这里需要实际的坐标转换函数)
# lon_lat_coords = pixel_to_lonlat(approx, gps_coords, mask.shape)
# 构建GeoJSON特征
feature = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [lon_lat_coords.tolist()] # 假设已转换
},
"properties": {
"treatment": "fertilizer", # 处方类型
"intensity": "high" # 强度
}
}
features.append(feature)
prescription_geojson = {"type": "FeatureCollection", "features": features}
return prescription_geojson
走到这一步,你已经拥有了一个从数据准备、模型构建、训练优化到部署应用和结果分析的完整技术闭环。这个方案的核心优势在于其轻量化和实用性,它没有追求极致的学术指标,而是在精度、速度和工程可行性之间找到了一个平衡点。在实际项目中,我遇到过因为模型太大而无法在边缘设备上实时运行的情况,也遇到过因为数据增强不够而导致模型在阴天图片上完全失效的坑。最终让我们项目成功上线的,正是这套结合了先进Transformer架构和大量工程实践经验的方案。代码仓库里包含了文中提到的所有关键代码模块和数据预处理脚本,你可以直接克隆下来,用自己的数据试试看。记住,最好的模型永远是那个能在实际场景中稳定运行的模型。
更多推荐
所有评论(0)