A题:农田害虫图像识别
赛题描述
一、问题背景
病虫害一直是农业生产中无法避免的问题,每年都会由此造成巨大的经济损失。为了对农田病虫害进行有效的预防和控制,需要收集有害生物信息,在此基础上进行虫情分析。由于农田害虫的多样性和信息类型的复杂性,通过人工观察统计的传统害虫监测方式已经难以满足现代大规模农业生产对虫害预防工作的需要。近年来出现的虫情测报灯是虫情信息采集的智能设备,可以在无人监管的情况下,实现自动诱集、杀虫、虫体分散、拍照等作业,并实时地将虫情信息上传至云平台。虫情测报灯的投入使用可帮助植保人员高效地进行虫情分析,提高测报工作效率和准确率,避免农药的滥用和误用,减少农产品的农药残留,改善农田生态环境。
二、解决问题
1. 利用附件1和附件2的信息,建立确定害虫位置及种类的模型和算法。
2. 应用问题1所建立的模型和算法对附件3中提及的图像进行识别,并确定害虫的位置,将结果存放在“result2.csv”文件中(模板文件见附件4中的result2.csv)。
3. 根据问题2得到的结果,对附件3中提及的图像文件中不同种类的害虫数量进行统计,将统计结果存放在“result3.csv”文件中(模板文件见附件4中的result3.csv)。
这种图像处理的问题解法基本都是一样,准备数据,选择模型,训练,预测,完事。
数据准备
在给定的数据中有部分杂乱的数据,我们手动剔除掉了这部分数据,然后对剩余的数据,依据其给定的目标位置信息,对图像进行随机裁剪,作为训练图像。
随机裁剪图像的代码如下:
# -*- coding: utf-8 -*-
import os
import shutil
import cv2
import time
import codecs
import xml.etree.ElementTree as ET
from tqdm import tqdm
import shutil
from tqdm import trange
from multiprocessing import Pool
import numpy as np
def setDir(filepath):
'''
如果文件夹不存在就创建,如果文件存在就清空!
:param filepath:需要创建的文件夹路径
:return:
'''
if not os.path.exists(filepath):
os.mkdir(filepath)
else:
shutil.rmtree(filepath)
os.mkdir(filepath)
def bbox_iou(box1, box2):
"""
:param box1: = [xmin1, ymin1, xmax1, ymax1]
:param box2: = [xmin2, ymin2, xmax2, ymax2]
:return:
"""
xmin1, ymin1, xmax1, ymax1 = box1
xmin2, ymin2, xmax2, ymax2 = box2
# 计算每个矩形的面积
s1 = (xmax1 - xmin1) * (ymax1 - ymin1) # b1的面积
s2 = (xmax2 - xmin2) * (ymax2 - ymin2) # b2的面积
# 计算相交矩形
xmin = max(xmin1, xmin2)
ymin = max(ymin1, ymin2)
xmax = min(xmax1, xmax2)
ymax = min(ymax1, ymax2)
w = max(0, xmax - xmin)
h = max(0, ymax - ymin)
a1 = w * h # C∩G的面积
a2 = s2# + s2 - a1
iou = a1 / a2 #iou = a1/ (s1 + s2 - a1)
return iou
def exist_objs_iou(list_1, list_2):
# 根据iou判断框是否保留,并返回bbox
return_objs=[]
s_xmin, s_ymin, s_xmax, s_ymax = list_1[0], list_1[1], list_1[2], list_1[3]
for single_box in list_2:
xmin, ymin, xmax, ymax, category=int(single_box[0]),int(single_box[1]),int(single_box[2]),int(single_box[3]),int(single_box[4])
iou = bbox_iou(list_1, [xmin, ymin, xmax, ymax])
if iou > 0.9:
if iou == 1:
x_new=xmin-s_xmin
y_new=ymin-s_ymin
return_objs.append([x_new, y_new, x_new+(xmax-xmin), y_new+(ymax-ymin),category]) # 保存新的坐标
else:
xlist = np.sort([xmin, xmax, s_xmin, s_xmax]) # 默认从小到大排序
ylist = np.sort([ymin, ymax, s_ymin, s_ymax])
return_objs.append([xlist[1] - s_xmin, ylist[1] - s_ymin, xlist[2] - s_xmin, ylist[2] - s_ymin, category])
return return_objs # 返回新的裁剪图片的坐标和类别
def read_txt(ann_path):
with open(ann_path, 'r') as f:
object_lists = []
info = f.readlines()
for object_list in info:
object_lists.append(object_list.split())
return object_lists
def write_txt(outpath,exiset_obj_list):
m=1200
with open(outpath+".txt", 'a+') as f:
for obj in exiset_obj_list:
cl=obj[4]
x_c=(obj[2]+obj[0])/(m*2)
y_c = (obj[1] + obj[3]) / (m * 2)
w=(obj[2]-obj[0])/m
h=(obj[3]-obj[1])/m
# o1 = [str(obj[0]), str(obj[1]),str(obj[2]),str(obj[3]),str(obj[4])]
o=[str(cl),str(x_c),str(y_c),str(w),str(h)]
f.write(" ".join(o) + "\n")
# with open(outpath+"re.txt", 'a+') as f:
# for obj in exiset_obj_list:
# o = [str(obj[0]), str(obj[1]),str(obj[2]),str(obj[3]),str(obj[4])]
# f.write(" ".join(o) + "\n")
def slice_im(List_subsets, outdir, raw_images_dir, raw_ann_dir, clip_ann_dir, sliceHeight=640, sliceWidth=640,
zero_frac_thresh=0.2, overlap=0.2, verbose=True):
cnt = 0
for per_img_name in tqdm(List_subsets):
o_name, _ = os.path.splitext(per_img_name) # 分离文件路径与拓展名,00001.jpg->('00001','.jpg')
out_name = str(o_name) + '_' + str(cnt) # 00001_0
image_path = os.path.join(raw_images_dir, per_img_name)# image/00001.jpg
ann_path = os.path.join(raw_ann_dir, per_img_name[:-4] + '.txt') # labels/00001.txt,里面包含了左上、右下坐标和类别
#print(ann_path)
image0 = cv2.imread(image_path, 1) # color
ext = '.' + image_path.split('.')[-1] # .jpg
object_list = read_txt(ann_path) # 00001.txt
index = np.argwhere(image0[:, :, 0]) # 蓝色分量图像
for i in range(500):
random_idx = np.random.choice(len(index)) # 随机截取
slice_idx = index[random_idx]
#x = slice_idx[0],y = slice_idx[1]
if slice_idx[0]+sliceWidth<=image0.shape[1] and slice_idx[1]+sliceHeight<=image0.shape[0]:
exiset_obj_list = exist_objs_iou([slice_idx[0], slice_idx[1],slice_idx[0]+sliceWidth,slice_idx[1]+sliceHeight],object_list)
if exiset_obj_list!=[]: # 如果为空,说明切出来的这一张图不存在目标
window_c = image0[slice_idx[1]:slice_idx[1] + sliceHeight, slice_idx[0]:slice_idx[0] + sliceWidth]
outpath = os.path.join(outdir, out_name + '_' +str(slice_idx[0]) + '_' + str(slice_idx[1]) + '_' + str(sliceHeight) + ext)
cnt += 1
cv2.imwrite(outpath, window_c)
outpath_ann = os.path.join(clip_ann_dir, out_name + '_' +str(slice_idx[0]) + '_' + str(slice_idx[1]) + '_' + str(sliceHeight))
write_txt(outpath_ann,exiset_obj_list)
if __name__ == "__main__":
# 筛选附件1过后所有有虫子的图片的位置
raw_images_dir = './img1'
# 对应虫子的位置信息转化为TXT文件后的位置
raw_ann_dir = './label1'
# 保存裁剪图片的位置
slice_voc_dir = './img11'
# 保存裁剪图片位置信息的TXT文件的位置
clip_ann_dir = './label11'
if not os.path.exists(slice_voc_dir):
os.makedirs(slice_voc_dir)
if not os.path.exists(clip_ann_dir):
os.makedirs(clip_ann_dir)
List_imgs = os.listdir(raw_images_dir)
slice_im(List_imgs, slice_voc_dir, raw_images_dir, raw_ann_dir, clip_ann_dir, sliceHeight=1200, sliceWidth=1200)
模型选择和训练
我们选择的YOLOv5s模型来训练,然后在训练参数中启用了数据增强的操作,如随机旋转,水平翻转,垂直翻转等等。我们按照8:2的比例来划分训练集和验证集,最终的mAP@.5=0.98左右,结果非常不错,其中的混淆矩阵也非常完成,模型学习到了很好的特征。
图像推理
直接用YOLOv5的检测程序来做就行,最后我们的预测结果也很好。