kaggle竞赛CowBoy Outfits Detection开源代码——windows下使用Yolov5l用于牛仔行头检测

windows下使用Yolov5l用于牛仔行头检测

Yolov5 系列2— 如何使用Yolov5训练你自己的数据集
本次牛仔行头检测我使用的是ultralytics/yolov5 ,该仓库提供了s、m、l、x四个规模的预训练模型,我使用的预训练模型是Yolov5l,最后可以在公榜(也就是valid.csv)上达到60的MAP值。

由于kaggle上连续训练不能超过6个小时,所以我是在本地上进行的训练和测试。我的电脑系统是windows10,显卡为RTX2070s-8G,训练了200个Epoch。

本次是目标检测竞赛,一张图片上可能存在多个目标,每个目标可以用四个值来进行表示,即目标的种类和目标的位置,位置用四个值来进行表示,目标检测常用的格式可以为了3种:coco、voc和yolo。coco的坐标表示为[x, y, w, h],即bbox左上角的坐标和bbox的宽和高;voc的坐标表示为[x_min, y_min, x_max, y_max],即bbox左上角的坐标和右下角的坐标;yolo的坐标表示为[center_x, center_y, w, h],yolo这里表示的是bbox中心点的坐标和bbox的宽高,注意yolo这里是值是经过归一化处理的,都是介于0到1之前的浮点数。本次比赛我们使用的是coco格式,我使用yolo模型来进行训练的,所以在训练和提交结果之前要对格式进行转换,否则大家可能遇到训练的MAP和提交测试的MAP相差过多的情况出现。

环境配置

配置之前请确保已经安装好了英伟达显卡的驱动,在命令行中输入nvidia-smi会输出相应的显卡信息。由于我们使用的是ananconda来控制虚拟环境,使用conda命令安装Pytorch的时候会自动检查依赖并进行安装,所以不需要额外在本地安装cudnn。

首先把代码下载到本地,执行下列命令。

git clone https://github.com/ultralytics/yolov5.git

创建并激活虚拟环境。

conda create -n yolo python==3.8.5
conda activate yolo

安装GPU版本的Pytorch,注意30系列显卡是安培架构,只支持11版本的cuda。

conda install pytorch==1.7.0 torchvision torchaudio cudatoolkit=10.2

安装程序所需的其他的第三方库。

cd yolov5
pip install -r requirements.txt

构建数据集

数据部分需要处理成yolo的数据格式,如果觉得比较麻烦的小伙伴可以使用我处理好的,链接如下:

链接:https://pan.baidu.com/s/1SGdjCTAq6Sa4LLgAG9Xx5w
提取码:v9re

原始的数据包含四部分的内容

images # 存放图片文件
test.csv # 私榜需要测试的图片 
train.json # 训练的数据以及标注
vaild.csv # 公榜需要测试的图片

这里的数据使用的是coco格式的数据,需要转化为yolo格式(.txt)的数据。我这里的方法比较笨,由于我之前写过一个统一的voc转yolo的代码,所以我的处理方案是先把coco格式的数据转化为voc(annotations,images,ImageSets,labels…xml)格式,然后再从voc格式的数据转化为yolo格式。

将数据集转化为统一的voc格式

voc中使用xml文件来对数据格式进行标注,下面的代码(json2xml.py)可以将json中的数据解析为xml文件放到mydata下的Annotations中

import json
from xml.dom.minidom import *
import xml.etree.ElementTree as ET
import numpy as np


# 美化xml文件
def pretty_xml(element, indent, newline, level=0):  # elemnt为传进来的Elment类,参数indent用于缩进,newline用于换行
    if element:  # 判断element是否有子元素
        if (element.text is None) or element.text.isspace():  # 如果element的text没有内容
            element.text = newline + indent * (level + 1)
        else:
            element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * (level + 1)
            # else:  # 此处两行如果把注释去掉,Element的text也会另起一行
            # element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * level
    temp = list(element)  # 将element转成list
    for subelement in temp:
        if temp.index(subelement) < (len(temp) - 1):  # 如果不是list的最后一个元素,说明下一个行是同级别元素的起始,缩进应一致
            subelement.tail = newline + indent * (level + 1)
        else:  # 如果是list的最后一个元素, 说明下一行是母元素的结束,缩进应该少一个
            subelement.tail = newline + indent * level
        pretty_xml(subelement, indent, newline, level=level + 1)  # 对子元素进行递归操作

# 写入xml文件
def write_xml(img_name, width, height, object_dicts, save_path, folder='./VOC2012'):
    '''
    object_dict = {'name': classes[int(object_category)],
                            'truncated': int(truncation),
                            'difficult': int(occlusion),
                            'xmin':int(bbox_left),
                            'ymin':int(bbox_top),
                            'xmax':int(bbox_left) + int(bbox_width),
                            'ymax':int(bbox_top) + int(bbox_height)
                            }
    '''
    doc = Document
    root = ET.Element('Annotation')
    ET.SubElement(root, 'folder').text = folder
    ET.SubElement(root, 'filename').text = img_name
    size_node = ET.SubElement(root, 'size')
    ET.SubElement(size_node, 'width').text = str(width)
    ET.SubElement(size_node, 'height').text = str(height)
    ET.SubElement(size_node, 'depth').text = '3'
    for object_dict in object_dicts:
        object_node = ET.SubElement(root, 'object')
        ET.SubElement(object_node, 'name').text = object_dict['name']
        ET.SubElement(object_node, 'pose').text = 'Unspecified'
        ET.SubElement(object_node, 'truncated').text = str(object_dict['truncated'])
        ET.SubElement(object_node, 'difficult').text = str(object_dict['difficult'])
        bndbox_node = ET.SubElement(object_node, 'bndbox')
        ET.SubElement(bndbox_node, 'xmin').text = str(object_dict['xmin'])
        ET.SubElement(bndbox_node, 'ymin').text = str(object_dict['ymin'])
        ET.SubElement(bndbox_node, 'xmax').text = str(object_dict['xmax'])
        ET.SubElement(bndbox_node, 'ymax').text = str(object_dict['ymax'])

    pretty_xml(root, '\t', '\n')
    tree = ET.ElementTree(root)
    tree.write(save_path, encoding='utf-8')

if __name__ == '__main__':
    # class_names = ['road', 'car', 'motorcycle', 'person']
    f = open("./cowboyoutfits/train.json", encoding='utf-8')
    data = json.load(f)
    annotations = data['annotations']
    images = data['images']
    cate = {
        "87": "belt",
        "1034": "sunglasses",
        "131": "boot",
        "318": "cowboy_hat",
        "588": "jacket"
    }
    images_num = len(images)
    # print()
    bbox_infos = {}
    for image in images:
        print()
        bbox_infos[str(image["id"])] = []

    for ann in annotations:
        print(ann)
        image_id = ann['image_id']
        bbox = ann['bbox']
        bbox_x = bbox[0]
        bbox_y = bbox[1]
        bbox_w = bbox[2]
        bbox_h = bbox[3]
        class_id = ann['category_id']
        object_dict = {'name': cate[str(class_id)],
                       'truncated': 0,
                       'difficult': 0,
                       'xmin': int(bbox_x),
                       'ymin': int(bbox_y),
                       'xmax': int(bbox_x+bbox_w),
                       'ymax': int(bbox_y+bbox_h)
                       }
        print(object_dict)
        bbox_infos[str(image_id)].append(object_dict)


    txt_results = []
    for image in images:
        print(image)
        image_height = image['height']
        image_width = image['width']
        image_id = image['id']
        image_file_name = image['file_name']
        object_dicts = bbox_infos[str(image_id)]
        xml_file_name = image_file_name.strip('.jpg') + '.xml'
        txt_results.append(image_file_name.strip('.jpg'))
        # print(image_file_name, image_width, image_height, object_dicts, )
        write_xml(image_file_name, image_width, image_height, object_dicts, "./VOC2012/Annotations/"+xml_file_name)

# np.savetxt()
#np.savetxt('train.txt', txt_results, fmt="%s", delimiter="\n")
#     for image in images:
#          print(image)

运行xml2txt_classify.py,主要是将数据集分类成训练数据集和测试数据集,默认train,val,test按照8:1:1的比例进行随机分类,运行后ImagesSets文件夹中会出现四个txt,主要是生成的训练数据集和测试数据集的图片名称。

# xml2txt_classify.py
import os
import random


trainval_percent = 0.9
train_percent = 0.8
xmlfilepath = 'mydata/Annotations'
txtsavepath = 'mydata/ImageSets'
total_xml = os.listdir(xmlfilepath)

num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)

ftrainval = open('mydata/ImageSets/trainval.txt', 'w')
ftest = open('mydata/ImageSets/test.txt', 'w')
ftrain = open('mydata/ImageSets/train.txt', 'w')
fval = open('mydata/ImageSets/val.txt', 'w')

for i in list:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)

ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

解析xml文件之后,我们需要按照VOC格式来放置我们的图片数据和标注文件数据,放在项目路径下的mydata文件里,放置的文件夹格式如下:

mydata
├─ Annotations # 存放新生成的xml文件
├─ ImageSets # Main目录下放train.txt,val.txt和test.txt,我们按照8比2的比例将数据分开
├─ JPEGImages # 放图片文件
└─ Labels # 新建一个空文件,后面放我们生成的yolo格式的文件,是txt格式

voc格式的数据生成mydata里的Labels文件

voc_label.py主要是将图片数据集标注后的xml文件中的标注信息读取出来并写入txt文件,运行后在labels文件夹中出现所有图片数据集的标注信息。
并且,data目录下也会出现这四个文件,内容是训练数据集和测试数据集的图片路径。这就可以用来我们做训练,主要是生成路径。

# -*- coding: utf-8 -*-
#  voc_label.py 

# xml解析包
import xml.etree.ElementTree as ET
import pickle
import os
# os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表
from os import listdir, getcwd
from os.path import join


sets = ['train', 'test', 'val']
classes = ['belt', 'sunglasses', 'boot', 'cowboy_hat', 'jacket']


# 进行归一化操作
def convert(size, box): # size:(原图w,原图h) , box:(xmin,xmax,ymin,ymax)
    dw = 1./size[0]     # 1/w
    dh = 1./size[1]     # 1/h
    x = (box[0] + box[1])/2.0   # 物体在图中的中心点x坐标
    y = (box[2] + box[3])/2.0   # 物体在图中的中心点y坐标
    w = box[1] - box[0]         # 物体实际像素宽度
    h = box[3] - box[2]         # 物体实际像素高度
    x = x*dw    # 物体中心点x的坐标比(相当于 x/原图w)
    w = w*dw    # 物体宽度的宽度比(相当于 w/原图w)
    y = y*dh    # 物体中心点y的坐标比(相当于 y/原图h)
    h = h*dh    # 物体宽度的宽度比(相当于 h/原图h)
    return (x, y, w, h)    # 返回 相对于原图的物体中心点的x坐标比,y坐标比,宽度比,高度比,取值范围[0-1]


# year ='2012', 对应图片的id(文件名)
def convert_annotation(image_id):
	'''
	将对应文件名的xml文件转化为label文件,xml文件包含了对应的bunding框以及图片长款大小等信息,
	通过对其解析,然后进行归一化最终读到label文件中去,也就是说
	一张图片文件对应一个xml文件,然后通过解析和归一化,能够将对应的信息保存到唯一一个label文件中去
	labal文件中的格式:class x y w h  同时,一张图片对应的类别有多个,所以对应的bounding的信息也有多个
	'''
    # 对应的通过year 找到相应的文件夹,并且打开相应image_id的xml文件,其对应bund文件
    in_file = open('mydata/Annotations/%s.xml' % (image_id), encoding='utf-8')
    # 准备在对应的image_id 中写入对应的label,分别为
    # <object-class> <x> <y> <width> <height>
    out_file = open('mydata/labels/%s.txt' % (image_id), 'w', encoding='utf-8')
    # 解析xml文件
    tree = ET.parse(in_file)
    # 获得对应的键值对
    root = tree.getroot()
    # 获得图片的尺寸大小
    size = root.find('size')
    # 如果xml内的标记为空,增加判断条件
    if size != None:
        # 获得宽
        w = int(size.find('width').text)
        # 获得高
        h = int(size.find('height').text)
        # 遍历目标obj
        for obj in root.iter('object'):
            # 获得difficult ??
            difficult = obj.find('difficult').text
            # 获得类别 =string 类型
            cls = obj.find('name').text
            # 如果类别不是对应在我们预定好的class文件中,或difficult==1则跳过
            if cls not in classes or int(difficult) == 1:
                continue
            # 通过类别名称找到id
            cls_id = classes.index(cls)
            # 找到bndbox 对象
            xmlbox = obj.find('bndbox')
            # 获取对应的bndbox的数组 = ['xmin','xmax','ymin','ymax']
            b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
                 float(xmlbox.find('ymax').text))
            print(image_id, cls, b)
            # 带入进行归一化操作
            # w = 宽, h = 高, b= bndbox的数组 = ['xmin','xmax','ymin','ymax']
            bb = convert((w, h), b)
            # bb 对应的是归一化后的(x,y,w,h)
            # 生成 calss x y w h 在label文件中
            out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')


# 返回当前工作目录
wd = getcwd()
print(wd)


for image_set in sets:
    '''
    sets = ['train', 'test', 'val']
    对所有的文件数据集进行遍历
    做了两个工作:
    1.将所有图片文件都遍历一遍,并且将其所有的全路径都写在对应的txt文件中去,方便定位
    2.同时对所有的图片文件进行解析和转化,将其对应的bundingbox 以及类别的信息全部解析写到label 文件中去
         最后再通过直接读取文件,就能找到对应的label 信息
    '''
    # 先找labels文件夹如果不存在则创建
    if not os.path.exists('mydata/labels/'):
        os.makedirs('mydata/labels/')
    # 读取在ImageSets中4个txt的内容
    image_ids = open('mydata/ImageSets/%s.txt' % (image_set)).read().strip().split()
    # 打开对应的.txt 文件对其进行写入准备
    list_file = open('mydata/%s.txt' % (image_set), 'w')
    # 将对应的文件_id以及全路径写进去并换行
    for image_id in image_ids:
        list_file.write('mydata/images/%s.jpg\n' % (image_id))
        # 生成label里的好多txt,每个txt4个参数
        convert_annotation(image_id)
    # 关闭文件
    list_file.close()

# os.system(‘comand’) 会执行括号中的命令,如果命令成功执行,这条语句返回0,否则返回1
# os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt")
# os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")

修改配置文件

在clone下来的项目下的data目录下设置数据集的配置文件

我们在mydata目录下新建一个cow_data.yaml的文件,在文件中指定训练集和验证集的地址、数据集的目标数目和目标类名

# Custom data for safety helmet

# train and val data as 1) directory: path/images/, 2) file: path/images.txt, or 3) list: [path1/images/, path2/images/]
train: mydata/train.txt
val: mydata/val.txt
test: mydata/test.txt

# number of classes
nc: 5

# class names
names: ["belt", "sunglasses", "boot", "cowboy_hat", "jacket"]

在models目录下设置模型的配置文件

在yolov5/model文件夹下模型配置文件,包含了s、m、l、x4个版本,逐渐增大(随着架构的增大,训练时间也是逐渐增大,这里以l版本为例,在mydata目录下新建一个cow_yolov5l.yaml的模型配置文件,具体内容如下

# parameters
nc: 5  # number of classes # 修改为我们模型的类别数目
depth_multiple: 1.0  # model depth multiple
width_multiple: 1.0  # layer channel multiple

# anchors
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

# YOLOv5 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Focus, [64, 3]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 9, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 1, SPP, [1024, [5, 9, 13]]],
   [-1, 3, C3, [1024, False]],  # 9
  ]

# YOLOv5 head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3, [512, False]],  # 13

   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, C3, [256, False]],  # 17 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, C3, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, C3, [1024, False]],  # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

训练模型

首先从https://github.com/ultralytics/yolov5/releases 下载预训练的模型到本地,存放在yolov5的weights目录下。

上面的过程中我们已经设置好了数据集和模型的配置文件,我们只需要在项目的根目录下执行下列代码即可开始训练,由于我的显卡只有8G,所以这里的batchsize设置为4,训练的轮数设置为200,具体的命令如下:

! python train.py --data mydata/cow_data.yaml --cfg mydata/yolov5l.yaml --weights yolov5l.pt --batch-size 4 --epochs 100

训练的结果会保存在runs/train/exp目录下,其中有各种可视化的过程图,weights目录下是训练好的权重文件,也就是我们最后需要的模型。

train.py中的一些参数修改:

parser = argparse.ArgumentParser()
# 加载的权重文件
parser.add_argument('--weights', type=str, default='yolov5s.pt', help='initial weights path')
# 模型配置文件,网络结构,使用修改好的yolov5m.yaml文件
parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model.yaml path')
# 数据集配置文件,数据集路径,类名等,使用数据集方面的cat.yaml文件
parser.add_argument('--data', type=str, default='data/cat.yaml', help='data.yaml path')
# 超参数文件
parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path')
# 训练总轮次,1个epoch等于使用训练集中的全部样本训练一次,值越大模型越精确,训练时间也越长。
parser.add_argument('--epochs', type=int, default=300)
# 批次大小,一次训练所选取的样本数,显卡垃圾的话,就调小点
parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs')
# 输入图片分辨率大小,nargs='+'表示参数可设置一个或多个
parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='[train, test] image sizes')
# 是否采用矩形训练,默认False,开启后可显著的减少推理时间
parser.add_argument('--rect', action='store_true', help='rectangular training')
# 接着打断训练上次的结果接着训练
parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
# 不保存模型,默认False
parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
# 不进行test,默认False
parser.add_argument('--notest', action='store_true', help='only test final epoch')
# 不自动调整anchor,默认False
parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')
# 是否进行超参数进化,默认False
parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters')
# 谷歌云盘bucket,一般不会用到
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
# 是否提前缓存图片到内存,以加快训练速度,默认False
parser.add_argument('--cache-images', action='store_true', help='cache images for faster training')
# 选用加权图像进行训练
parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
# 训练的设备,cpu;0(表示一个gpu设备cuda:0);0,1,2,3(多个gpu设备)。值为空时,训练时默认使用计算机自带的显卡或CPU
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
# 是否进行多尺度训练,默认False
parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
# 数据集是否只有一个类别,默认False
parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
# 是否使用adam优化器
parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer')
# 是否使用跨卡同步BN,在DDP模式使用
parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
# gpu编号
parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify')
# W&B记录的图像数,最大为100
parser.add_argument('--log-imgs', type=int, default=16, help='number of images for W&B logging, max 100')
# 记录最终训练的模型,即last.pt
parser.add_argument('--log-artifacts', action='store_true', help='log artifacts, i.e. final trained model')
# dataloader的最大worker数量
parser.add_argument('--workers', type=int, default=4, help='maximum number of dataloader workers')
# 训练结果所存放的路径,默认为runs/train
parser.add_argument('--project', default='runs/train', help='save to project/name')
# 训练结果所在文件夹的名称,默认为exp
parser.add_argument('--name', default='exp', help='save to project/name')
# 若现有的project/name存在,则不进行递增
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
opt = parser.parse_args()

测试模型&生成提交的结果数据

Yolov5中自带的代码可以直接调用模型对结果进行推理,我对detect的代码进行了修改,直接运行cow_detect_kaggle.py就能将图片和文本文件的检测结果保存在runs/detect目录下,代码如下:

# 用于生成txt文件
import argparse
import time
from pathlib import Path

import cv2
import torch
import torch.backends.cudnn as cudnn

from models.experimental import attempt_load
from utils.datasets import LoadStreams, LoadImages
from utils.general import check_img_size, check_requirements, check_imshow, non_max_suppression, apply_classifier, \
    scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path, save_one_box
from utils.plots import colors, plot_one_box
from utils.torch_utils import select_device, load_classifier, time_synchronized


@torch.no_grad()
def detect(opt):
    source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size
    save_img = not opt.nosave and not source.endswith('.txt')  # save inference images
    webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(
        ('rtsp://', 'rtmp://', 'http://', 'https://'))
    save_txt = True
    print(save_txt)

    # Directories
    save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok)  # increment run
    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

    # Initialize
    set_logging()
    device = select_device(opt.device)
    half = opt.half and device.type != 'cpu'  # half precision only supported on CUDA

    # Load model
    model = attempt_load(weights, map_location=device)  # load FP32 model
    stride = int(model.stride.max())  # model stride
    imgsz = check_img_size(imgsz, s=stride)  # check img_size
    names = model.module.names if hasattr(model, 'module') else model.names  # get class names
    if half:
        model.half()  # to FP16

    # Second-stage classifier
    classify = False
    if classify:
        modelc = load_classifier(name='resnet101', n=2)  # initialize
        modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()

    # Set Dataloader
    vid_path, vid_writer = None, None
    if webcam:
        view_img = check_imshow()
        cudnn.benchmark = True  # set True to speed up constant image size inference
        dataset = LoadStreams(source, img_size=imgsz, stride=stride)
    else:
        dataset = LoadImages(source, img_size=imgsz, stride=stride)

    # Run inference
    if device.type != 'cpu':
        model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters())))  # run once
    t0 = time.time()
    for path, img, im0s, vid_cap in dataset:
        img = torch.from_numpy(img).to(device)
        img = img.half() if half else img.float()  # uint8 to fp16/32
        img /= 255.0  # 0 - 255 to 0.0 - 1.0
        if img.ndimension() == 3:
            img = img.unsqueeze(0)

        # Inference
        t1 = time_synchronized()
        pred = model(img, augment=opt.augment)[0]

        # Apply NMS
        pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, opt.classes, opt.agnostic_nms,
                                   max_det=opt.max_det)
        t2 = time_synchronized()

        # Apply Classifier
        if classify:
            pred = apply_classifier(pred, modelc, img, im0s)

        # Process detections
        for i, det in enumerate(pred):  # detections per image
            if webcam:  # batch_size >= 1
                p, s, im0, frame = path[i], f'{i}: ', im0s[i].copy(), dataset.count
            else:
                p, s, im0, frame = path, '', im0s.copy(), getattr(dataset, 'frame', 0)

            p = Path(p)  # to Path
            save_path = str(save_dir / p.name)  # img.jpg
            txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # img.txt
            s += '%gx%g ' % img.shape[2:]  # print string
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
            imc = im0.copy() if opt.save_crop else im0  # for opt.save_crop
            if len(det):
                # Rescale boxes from img_size to im0 size
                det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()

                # Print results
                for c in det[:, -1].unique():
                    n = (det[:, -1] == c).sum()  # detections per class
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # add to string

                # Write results
                for *xyxy, conf, cls in reversed(det):
                    if save_txt:  # Write to file
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                        line = (cls, *xywh, conf) if opt.save_conf else (cls, *xywh)  # label format
                        with open(txt_path + '.txt', 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')

                    if save_img or opt.save_crop or view_img:  # Add bbox to image
                        c = int(cls)  # integer class
                        label = None if opt.hide_labels else (names[c] if opt.hide_conf else f'{names[c]} {conf:.2f}')
                        plot_one_box(xyxy, im0, label=label, color=colors(c, True), line_thickness=opt.line_thickness)
                        if opt.save_crop:
                            save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)

            # Print time (inference + NMS)
            print(f'{s}Done. ({t2 - t1:.3f}s)')

            # Stream results
            if view_img:
                cv2.imshow(str(p), im0)
                cv2.waitKey(1)  # 1 millisecond

            # Save results (image with detections)
            if save_img:
                if dataset.mode == 'image':
                    cv2.imwrite(save_path, im0)
                else:  # 'video' or 'stream'
                    if vid_path != save_path:  # new video
                        vid_path = save_path
                        if isinstance(vid_writer, cv2.VideoWriter):
                            vid_writer.release()  # release previous video writer
                        if vid_cap:  # video
                            fps = vid_cap.get(cv2.CAP_PROP_FPS)
                            w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                            h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                        else:  # stream
                            fps, w, h = 30, im0.shape[1], im0.shape[0]
                            save_path += '.mp4'
                        vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                    vid_writer.write(im0)

    if save_txt or save_img:
        s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
        print(f"Results saved to {save_dir}{s}")

    print(f'Done. ({time.time() - t0:.3f}s)')


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    # 选用训练的权重,可用根目录下的yolov5s.pt,也可用runs/train/exp/weights/best.pt
    parser.add_argument('--weights', nargs='+', type=str, default='./runs/train/exp/weights/best.pt',
                        help='model.pt path(s)')
    # 检测数据,可以是图片/视频路径,也可以是'0'(电脑自带摄像头),也可以是rtsp等视频流
    parser.add_argument('--source', type=str, default='../cowboyoutfits/val',
                        help='source')  # file/folder, 0 for webcam
    parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
    parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
    parser.add_argument('--max-det', type=int, default=1000, help='maximum number of detections per image')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='display results')
    # 是否将检测的框坐标以txt文件形式保存,默认False
    parser.add_argument('--save-txt', default="True", action='store_true',
                        help='save results to *.txt')  # todo 设置是否保存txt检测结果
    # 是否将检测的labels以txt文件形式保存,默认False
    parser.add_argument('--save-conf', default="True", action='store_true',
                        help='save confidences in --save-txt labels')  # todo 设置是否保存置信度
    parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    # 设置只保留某一部分类别,如0或者0 2 3
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
    # 进行nms是否也去除不同类别之间的框,默认False
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    # 推理的时候进行多尺度,翻转等操作(TTA)推理
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    # 如果为True,则对所有模型进行strip_optimizer操作,去除pt文件中的优化器等信息,默认为False
    parser.add_argument('--update', action='store_true', help='update all models')
    # 检测结果所存放的路径,默认为runs/detect
    parser.add_argument('--project', default='runs/detect', help='save results to project/name')
    # 检测结果所在文件夹的名称,默认为exp
    parser.add_argument('--name', default='exp', help='save results to project/name')
    # 若现有的project/name存在,则不进行递增
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
    parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
    parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
    opt = parser.parse_args()
    print(opt)
    check_requirements(exclude=('tensorboard', 'pycocotools', 'thop'))

    if opt.update:  # update all models (to fix SourceChangeWarning)
        for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt']:
            detect(opt=opt)
            strip_optimizer(opt.weights)
    else:
        detect(opt=opt)

检测效果如下:

image-20210728160922844

为了能够在竞赛的网站上进行提交,我们还需要将得到的txt格式的yolo文件转化一下,转化为竞赛网站所需的json格式,转化的代码如下:

import os
import pandas as pd
# import tqdm
from PIL import Image
import zipfile

valid_df = pd.read_csv('origin/valid.csv')
# test_df = pd.read_csv('test.csv')
valid_df.head()
cate_id_map = {87: 0, 1034: 1, 131: 2, 318: 3, 588: 4}
PRED_PATH = "E:/holiday/cow/yolov5_cowboy/runs/detect/exp/labels"
IMAGE_PATH = "E:/holiday/cow/cowboyoutfits/val"

# list our prediction files path
prediction_files = os.listdir(PRED_PATH)
print('Number of test images with detections: ', len(prediction_files))


# convert yolo to coco annotation format
def yolo2cc_bbox(img_width, img_height, bbox):
    x = (bbox[0] - bbox[2] * 0.5) * img_width
    y = (bbox[1] - bbox[3] * 0.5) * img_height
    w = bbox[2] * img_width
    h = bbox[3] * img_height
    return (x, y, w, h)


# reverse the categories numer to the origin id
re_cate_id_map = dict(zip(cate_id_map.values(), cate_id_map.keys()))

print(re_cate_id_map)


def make_submission(df, PRED_PATH, IMAGE_PATH):
    output = []
    # for i in tqdm(range(len(df))):
    for i in range(len(df)):
        print(i)
        row = df.loc[i]
        image_id = row['id']
        file_name = row['file_name'].split('.')[0]
        if f'{file_name}.txt' in prediction_files:
            img = Image.open(f'{IMAGE_PATH}/{file_name}.jpg')
            width, height = img.size
            with open(f'{PRED_PATH}/{file_name}.txt', 'r') as file:
                for line in file:
                    preds = line.strip('\n').split(' ')
                    preds = list(map(float, preds))  # conver string to float
                    print(preds)
                    print(preds[1:-1])
                    cc_bbox = yolo2cc_bbox(width, height, preds[1:-1])
                    result = {
                        'image_id': image_id,
                        'category_id': re_cate_id_map[preds[0]],
                        'bbox': cc_bbox,
                        'score': preds[-1]
                    }

                    output.append(result)
    return output


sub_data = make_submission(valid_df, PRED_PATH, IMAGE_PATH)

op_pd = pd.DataFrame(sub_data)

op_pd.sample(10)


op_pd.to_json('cow_answer/answer.json', orient='records')
zf = zipfile.ZipFile('cow_answer/sample_answer.zip', 'w')
zf.write('cow_answer/answer.json', 'answer.json')
zf.close()

https://blog.csdn.net/oJiWuXuan/article/details/107558286

https://blog.csdn.net/m0_53392188/article/details/119334634?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163651941416780262525304%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=163651941416780262525304&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-119334634.first_rank_v2_pc_rank_v29&utm_term=yolov5&spm=1018.2226.3001.4187

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值