利用yolov5实现多类海船的目标检测
一 、yolov5环境配置
首先将YOLOv5项目(https://github.com/ultralytics/yolov5)下载到本地(改名为yolov5_seaship),然后新建虚拟环境(conda create -n yolov5 python==3.8);
操作系统:windows10
IDE:Pycharm
python版本:anaconda Pyhon3.8
pytorch版本:torch 1.12.1+cu116
torchvision版本: 0.13.1+cu116
显卡:RTX 2060 super
①安装pytorch-gpu版本进行离线安装并测试torch-gpu是否安装成功
下载离线安装包,地址:https://download.pytorch.org/whl/torch_stable.html
import torch torch.cuda.is_available() ## 判断pytorch-gpu是否安装成功
②进入虚拟环境,安装其它模块。
(更换国内源;此外,pip安装时不要挂代理(挂了什么都装不上,血的教训)!!)
# Base ---------------------------------------- pip install matplotlib pip install numpy pip install opencv-python
pip install pillow pip install pyyaml pip install requests pip install scipy pip install tqdm # Logging ------------------------------------- pip install tensorboard # Plotting -------------------------------------- pip install pandas pip install seaborn # Export -------------------------------------- # Extras --------------------------------------- pip install thop ## pip install --upgrade git+https://github.com/Lyken17/pytorch-OpCounter.git pip install Cython pip install pycocotools ##conda install pycocotools
二、准备工作
①海船数据集(https://github.com/jiaming-wang/SeaShips)(Shao等,20118)下载。
该数据集包含7000张不同类型(矿石运输船、散货船、杂货船、集装箱船、渔船、客船)的图片。
海船数据集文件夹,包含标注、样本集分割配置、原始图片三个文件夹;
②下载labelImg标注工具(https://github.com/tzutalin/labelImg)查看海船标注文件.xml。
a:下载后解压;
b:打开cmd并转到解压文件夹下;
c:打开labelImg(运行python labelImg.py);
d:加载原始图像(打开文件/目录,JPEGImages文件夹下)及其对应的.xml标注文件(开启标签,Annotations文件夹下)。
③将标注文件和原图片移动到yolov5_ship(yolov5项目源文件)中data文件夹下的Annotations和images文件夹下。
④在yolov5_ship的根目录下新建一个文件makeTxt.py用于分割数据集。
运行后ImagesSets文件夹中会出现四个文件(所生成的训练集和测试集的图片名称),同时data目录下也会出现四个文件(训练数据集和测试数据集的图片路径)。代码如下:

## 按训练集、验证集、测试集=8:1:1的比例分割原始数据集 import os import random trainval_percent = 0.9 train_percent = 0.9 xmlfilepath = 'data/Annotations' txtsavepath = 'data/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) ## 训练集+验证集数量,从整个数据集取trainval_percent train = random.sample(trainval, tr) ## 训练集,从训练集+验证集中取train_percennt ftrainval = open('data/ImageSets/trainval.txt', 'w') ftest = open('data/ImageSets/test.txt', 'w') ftrain = open('data/ImageSets/train.txt', 'w') fval = open('data/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()
⑤再新建另一个文件voc_label.py用于读取.xml标注信息并写入.txt文件。
运行后在labels文件夹中出现所有图片数据集的标注信息。

# -*- coding: utf-8 -*- # xml解析包 import xml.etree.ElementTree as ET import pickle import os from os import listdir, getcwd from os.path import join sets = ['train', 'test', 'val'] classes = ['ore carrier', 'bulk cargo carrier', 'general cargo ship', 'container ship', 'fishing boat', 'passenger ship'] # 进行归一化操作 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文件中的格式:calss x y w h 同时,一张图片对应的类别有多个,所以对应的bunding的信息也有多个 ''' # 对应的通过year 找到相应的文件夹,并且打开相应image_id的xml文件,其对应bund文件 in_file = open('data/Annotations/%s.xml' % (image_id), encoding='utf-8') # 准备在对应的image_id 中写入对应的label,分别为 # <object-class> <x> <y> <width> <height> out_file = open('data/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: if cls not in classes: 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: ''' 对所有的文件数据集进行遍历 做了两个工作: 1.将所有图片文件都遍历一遍,并且将其所有的全路径都写在对应的txt文件中去,方便定位 2.同时对所有的图片文件进行解析和转化,将其对应的bundingbox 以及类别的信息全部解析写到label 文件中去 最后再通过直接读取文件,就能找到对应的label 信息 ''' # 先找labels文件夹如果不存在则创建 if not os.path.exists('data/labels/'): os.makedirs('data/labels/') # 读取在ImageSets/Main 中的train、test..等文件的内容 # 包含对应的文件名称 image_ids = open('data/ImageSets/%s.txt' % (image_set)).read().strip().split() # 打开对应的2012_train.txt 文件对其进行写入准备 list_file = open('data/%s.txt' % (image_set), 'w') # 将对应的文件_id以及全路径写进去并换行 for image_id in image_ids: list_file.write('data/images/%s.jpg\n' % (image_id)) # 调用 year = 年份 image_id = 对应的文件名_id 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")
⑥在data文件夹下新建seaship.yaml文件,内容如下:
# Train command: python train.py --data data/cat.yaml # Dataset should be placed next to yolov5 folder: # parent # ├── yolov5 # └── data # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path: data # dataset root dir train: train.txt # train images (relative to 'path') val: val.txt # val images (relative to 'path') test: test.txt # test images (optional) # number of classes nc: 6 # class names names: ['ore carrier', 'bulk cargo carrier', 'general cargo ship', 'container ship', 'fishing boat', 'passenger ship']
⑦train.py修改。

parser = argparse.ArgumentParser() parser.add_argument('--weights', type=str, default=ROOT / 'yolov5l.pt', help='initial weights path') parser.add_argument('--cfg', type=str, default='models/yolov5l.yaml', help='model.yaml path') parser.add_argument('--data', type=str, default=ROOT / 'data/seaship.yaml', help='dataset.yaml path') parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=5, help='total training epochs') parser.add_argument('--batch-size', type=int, default=1, help='total batch size for all GPUs, -1 for autobatch') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)') parser.add_argument('--rect', action='store_true', help='rectangular training') parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--noval', action='store_true', help='only validate final epoch') parser.add_argument('--noautoanchor', action='store_true', help='disable AutoAnchor') parser.add_argument('--noplots', action='store_true', help='save no plot files') parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations') parser.add_argument('--bucket', type=str, default='', help='gsutil bucket') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='image --cache ram/disk') parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training') parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%') parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class') parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer') parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode') parser.add_argument('--workers', type=int, default=3, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default=ROOT / 'runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--quad', action='store_true', help='quad dataloader') parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler') parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon') parser.add_argument('--patience', type=int, default=100, help='EarlyStopping patience (epochs without improvement)') parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone=10, first3=0 1 2') parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)') parser.add_argument('--seed', type=int, default=0, help='Global training seed') parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify') # Logger arguments parser.add_argument('--entity', default=None, help='Entity') parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='Upload data, "val" option') parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval') parser.add_argument('--artifact_alias', type=str, default='latest', help='Version of dataset artifact to use')
⑧运行train.py,开始训练。
并在训练完成后利用验证集进行验证(在train.py中设定’–noval’)。(由于硬件问题,epoch设置为5、batchsize设置为1。即使这样也好像训练了2小时。。。)
⑨通过tensorboard可视化训练过程。
a:转到yolov5_seaship所在目录,在虚拟环境中运行
tensorboard --logdir runs/train/exp
b:打开http://localhost:6006/
⑩训练结果展示。
训练过程的损失和精度变化
精度-召回率曲线
验证结果的混淆矩阵
⑩①利用训练好的模型对整个数据集进行预测。
修改detect.py配置、运行detect.py:

parser = argparse.ArgumentParser() parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'runs/train/exp5/weights/best.pt', help='model path or triton URL') ## 这里改为训练好模型所在的路径 parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob/screen/0(webcam)') ## 所需检测的视频/图片所在文件夹 parser.add_argument('--data', type=str, default=ROOT / 'data/seaship.yaml', help='(optional) dataset.yaml path') parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w') parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold') parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image') parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--view-img', action='store_true', help='show results') parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels') 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') parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3') parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') parser.add_argument('--augment', action='store_true', help='augmented inference') parser.add_argument('--visualize', action='store_true', help='visualize features') parser.add_argument('--update', action='store_true', help='update all models') parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name') ## 检测结果所存放的路径 parser.add_argument('--name', default='exp', help='save results to 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') parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference') parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride') opt = parser.parse_args() opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand print_args(vars(opt)) return opt
⑩②检测结果展示。
(由于硬件限制,模型未充分训练使得测试结果中的置信度没那么理想)
实验完成后过了一段时间才间记录的,或有偏差。
参考:https://blog.csdn.net/oJiWuXuan/article/details/107558286
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了