合集:AI案例-CV-农业
数据集:25名农民身份图片序列
数据集价值:优化农场工作分配、考勤记录或绩效评估。
解决方案:Pytorch框架、ResNet18模型
一、赛题介绍
机器虽然被大量用到农业生产中,但人还是不可或缺的因素。通过农民身份识别,可以真实客观地记录农民的状态,为农场管理和农产品追溯提供真实的客观数据;较之直接存储视频,可以有效地降低存储空间;自动识别也比人工监管,大幅度提高效率,减少人工成本。农民身份识别需要对农民进行分类,本次大赛提供了中国农业大学实验室视频制成的图像序列。参赛选手先对图像进行预处理,并制作样本,对图像中的农民进行识别。选手需要自行训练模型,并上传自己训练好的模型和权重。本模型依据提交的结果文件,采用Macro-F1进行评价。
二、数据集内容
数据结构
本次比赛为参赛选手提供了25名农民身份,分类标签/label取值范围为0~24,每个身份包含10段视频制成的图像序列,选手需要对图像序列进行预处理,打标签,并对农民进行身份识别。
标签数据样例:train.csv
文件名/name | 分类标签/label |
---|---|
001fgve41s.jpg | 17 |
0042Hux54q.jpg | 5 |
005pLHKsI3.jpg | 15 |
00CJTMEaBU.jpg | 1 |
00GKYy51Fb.jpg | 0 |
00QZaC92Jl.jpg | 5 |
00Y33CBSgd.jpg | 0 |
00wErT1bgx.jpg | 11 |
训练集图片文件个数:22,912。测试集图片文件个数:6,165。
图片样例:

数据集版权许可协议
署名—非商业性使用—相同方式共享 4.0
三、解题思路
本题的任务是对图像进行预处理,并制作样本,对图像中的农民进行识别,是典型的图像分类问题。
- 输入:10段视频制成的图像序列,
- 输出:图像类别
图像分类是计算机视觉领域的一个重要任务,旨在将输入的图像分为不同的预定义类别。图像分类在很多领域都有广泛的应用,包括图像识别、人脸识别、自动驾驶、医学图像分析等。 这项任务的目标是让计算机能够理解和识别图像的内容,并将其归类到已知的类别中。图像分类一般的处理流程为:
数据收集和准备:在数据竞赛中,赛题设计方已经准备好数据,大家只需下载即可。
- 需要收集具有不同类别标签的图像数据集,这些图像可以手动标记或通过自动化方法获得。
- 数据集应包含足够的样本来代表每个类别,并且需要进行预处理和标准化,以便在模型训练过程中获得良好的性能。
特征提取:在图像分类中,常用的特征提取方法是图像特征统计 或 使用卷积神经网络(CNN)。
- 图像特征统计是指对图像中的像素或图像区域进行统计分析,以提取和描述图像的特征信息
- CNN是一种专门设计用于处理图像数据的神经网络架构,能够有效地从原始像素中学习和提取特征。
- 通过一系列的卷积层、池化层和全连接层,CNN能够自动学习具有区分性的图像特征。
模型训练:在模型训练阶段,使用标记好的图像数据集来训练图像分类模型。
- 通过将图像输入到模型中,并根据预定义的类别标签进行监督学习,模型逐渐学会从图像中提取有用的特征,并预测图像的类别。
- 常用的训练算法包括梯度下降和反向传播,用于调整模型的权重和偏置,以最小化预测错误。
模型评估和优化:训练完成后,需要对模型进行评估和优化。
- 通常会将一部分未在训练中使用过的图像数据作为测试集,用于评估模型在新数据上的性能。
- 评估指标可以包括准确率(Accuracy)、精确率(Precision)、召回率(Recall)和F1值等。
- 如果模型性能不理想,可以通过调整模型架构、增加训练数据、调整超参数等方法进行优化。
四、Baseline实践
源码:农民身份识别挑战赛_baseline.ipynb
安装
选择合适的CUDA版本进行安装,例如pytorch==2.4.1:
conda create -n pytorch241-gpu python=3.10
conda activate pytorch241-gpu
conda install pytorch==2.4.1 torchvision==0.19.1 torchaudio==2.4.1 pytorch-cuda=12.1 -c pytorch -c nvidia
其他库的安装:
conda install albumentations
导入开发包
import os, sys, glob, argparse
import pandas as pd
import numpy as np
from tqdm import tqdm
%pylab inline
import cv2
from PIL import Image
from sklearn.model_selection import train_test_split, StratifiedKFold, KFold
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset
图像特征统计
刚刚我们完成了基本的问题分析,在这个 Baseline 中,我们使用图像特征统计方法来解决本次问题,会带领大家跑通数据收集与准备、特征提取、模型训练、模型评估与优化、结果输出的全部竞赛实践流程。 图像特征统计是指对图像中的像素或图像区域进行统计分析,以提取和描述图像的特征信息。
- 占比特征:通过统计图像中不同像素值或颜色通道的像素数量,计算其在整个图像中的比例或占比。
- 边缘特征:边缘是图像中像素值或颜色发生剧烈变化的区域,通常表示物体的边界或纹理的边界。
- 纹理特征:描述了图像中的纹理信息,反映了图像的细节和结构。
方法优缺点:
- 思路简单,不需要GPU就可以运行完成
- 精度较差,且容易受到形态比较形似的影响
这段代码实现了一个基于ResNet18的图像分类系统,用于处理训练和测试图像数据。下面我将详细分析代码的工作原理:
数据准备部分
- 数据加载:
- 从
./data/train/
和./data/test/
目录加载图像路径 - 从CSV文件(
data/train.csv
)加载训练标签 - 对数据进行排序以确保图像和标签对齐
- 从
- 数据缓存:
- 使用
DATA_CACHE
字典缓存已加载的图像,避免重复读取
- 使用
- 数据增强:
- 使用Albumentations库进行多种数据增强:
- 随机旋转90度
- 调整大小(256×256)
- 随机裁剪(224×224)
- 水平翻转
- 随机亮度和对比度调整
- 标准化(使用ImageNet的均值和标准差)
- 使用Albumentations库进行多种数据增强:
train_path = glob.glob('./data/train/*')
test_path = glob.glob('./data/test/*')
train_path.sort()
test_path.sort()
train_df = pd.read_csv('data/train.csv')
train_df = train_df.sort_values(by='name')
train_label = train_df['label'].values
new_size = 3000
train_path = train_path[:new_size]
# train_label = train_label[:new_size]
DATA_CACHE = {}
class XunFeiDataset(Dataset):
def __init__(self, img_path, img_label, transform=None):
self.img_path = img_path
self.img_label = img_label
if transform is not None:
self.transform = transform
else:
self.transform = None
def __getitem__(self, index):
if self.img_path[index] in DATA_CACHE:
img = DATA_CACHE[self.img_path[index]]
else:
img = cv2.imread(self.img_path[index])
DATA_CACHE[self.img_path[index]] = img
if self.transform is not None:
img = self.transform(image = img)['image']
img = img.transpose([2,0,1])
return img, torch.from_numpy(np.array(self.img_label[index]))
def __len__(self):
return len(self.img_path)
模型架构
- XunFeiNet类:
- 基于预训练的ResNet18模型
- 修改了最后的平均池化层为自适应平均池化
- 将全连接层输出改为25类(原始ResNet18是1000类)
class XunFeiNet(nn.Module):
def __init__(self):
super(XunFeiNet, self).__init__()
model = models.resnet18(True)
model.avgpool = nn.AdaptiveAvgPool2d(1)
model.fc = nn.Linear(512, 25)
self.resnet = model
def forward(self, img):
out = self.resnet(img)
return out
训练函数定义
- 数据划分:
- 使用最后1000个样本作为验证集
- 其余作为训练集
- 训练配置:
- 使用交叉熵损失函数
- 使用AdamW优化器,学习率0.001
- 批量大小30
def train(train_loader, model, criterion, optimizer):
model.train()
train_loss = 0.0
for i, (input, target) in enumerate(train_loader):
input = input.cuda(non_blocking=True)
target = target.cuda(non_blocking=True)
# compute output
output = model(input)
loss = criterion(output, target)
# compute gradient and do SGD step
optimizer.zero_grad()
loss.backward()
optimizer.step()
if i % 100 == 0:
print('Train loss', loss.item())
train_loss += loss.item()
return train_loss/len(train_loader)
def validate(val_loader, model, criterion):
model.eval()
val_acc = 0.0
with torch.no_grad():
end = time.time()
for i, (input, target) in enumerate(val_loader):
input = input.cuda()
target = target.cuda()
# compute output
output = model(input)
loss = criterion(output, target)
val_acc += (output.argmax(1) == target).sum().item()
return val_acc / len(val_loader.dataset)
训练
import albumentations as A
train_loader = torch.utils.data.DataLoader(
XunFeiDataset(train_path[:-1000], train_label[:-1000],
A.Compose([
A.RandomRotate90(),
A.Resize(256, 256),
A.RandomCrop(224, 224),
A.HorizontalFlip(p=0.5),
# A.RandomContrast(p=0.5),
A.RandomBrightnessContrast(p=0.5, brightness_limit=0.2, contrast_limit=0.2),
A.RandomBrightnessContrast(p=0.5),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=30, shuffle=True, num_workers=1, pin_memory=False
)
val_loader = torch.utils.data.DataLoader(
XunFeiDataset(train_path[-1000:], train_label[-1000:],
A.Compose([
A.Resize(256, 256),
A.RandomCrop(224, 224),
# A.HorizontalFlip(p=0.5),
# A.RandomContrast(p=0.5),
A.RandomBrightnessContrast(p=0.5, brightness_limit=0.2, contrast_limit=0.2),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=30, shuffle=False, num_workers=1, pin_memory=False
)
test_loader = torch.utils.data.DataLoader(
XunFeiDataset(test_path, [0] * len(test_path),
A.Compose([
A.Resize(256, 256),
A.RandomCrop(224, 224),
A.HorizontalFlip(p=0.5),
# A.RandomContrast(p=0.5),
A.RandomBrightnessContrast(p=0.5, brightness_limit=0.2, contrast_limit=0.2),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=2, shuffle=False, num_workers=1, pin_memory=False
)
预测函数定义
def predict(test_loader, model, criterion):
model.eval()
val_acc = 0.0
test_pred = []
with torch.no_grad():
end = time.time()
for i, (input, target) in enumerate(test_loader):
input = input.cuda()
target = target.cuda()
# compute output
output = model(input)
test_pred.append(output.data.cpu().numpy())
return np.vstack(test_pred)
训练循环
- 共进行8个epoch
- 每个epoch计算训练损失、训练集准确率和验证集准确率
model = XunFeiNet()
model = model.to('cuda')
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.AdamW(model.parameters(), 0.001)
for i in range(8):
print(f"train()当前循环值: {i} ")
train_loss = train(train_loader, model, criterion, optimizer)
print(f"validate(val_loader)当前循环值: {i}")
val_acc = validate(val_loader, model, criterion)
print(f"validate(train_loader)当前循环值: {i}")
train_acc = validate(train_loader, model, criterion)
print(train_loss, train_acc, val_acc)
测试预测
- 对测试集进行10次预测
- 将多次预测结果累加(相当于集成)
- 最终预测结果是这10次预测的平均
pred = None
for _ in range(10):
if pred is None:
pred = predict(test_loader, model, criterion)
else:
pred += predict(test_loader, model, criterion)
进阶实践:CNN模型
在baseline阶段,我们使用图像特征统计的传统方法完成了解决图像分类的全部流程,得到了基础的分数。
在进阶实践部分,将采用CNN模型来解决图像分类问题。
卷积神经网络(Convolutional Neural Network,CNN)是一种深度学习模型,广泛用于图像识别、计算机视觉和模式识别任务中。CNN 在处理具有网格结构数据(如图像)时表现出色,它能够自动学习和提取图像中的特征,并在分类、定位和分割等任务中取得优秀的性能。
方法优缺点
(CPU运行时间20分钟,如果有GPU可以更快):
- CNN带来的精度更好,但需要训练更长的时间
- CNN模型调优需要GPU
运行结果
数据集中的标注信息:
np.max(train_label), np.min(train_label), np.unique(train_label)
# 输出:
(24,
0,
array([ 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],
dtype=int64))
训练:
Train loss 3.3764824867248535
Train loss 2.962838649749756
Train loss 2.735387086868286
Train loss 2.7186777591705322
Train loss 2.479957342147827
Train loss 2.2384378910064697
Train loss 2.1143879890441895
Train loss 2.109005928039551
2.505831791829476 0.24671412924424974 0.28
Train loss 1.9086700677871704
Train loss 2.347980499267578
Train loss 1.9721630811691284
Train loss 1.816836953163147
Train loss 1.8843234777450562
Train loss 1.2556504011154175
Train loss 2.098001480102539
Train loss 1.935025930404663
1.8574956458179146 0.462166849215042 0.515
...