YOLO-obb+PyQt实现指针式仪表读数识别(一)

最近把之前项目整理了下,YOLO-obb+PyQt实现指针式仪表的读数识别,可完成单张和批量识别读数校正,并根据序号对照表保存图片名称、检测时间、表计、最终读数等数据,其中标签对应表计,如"叁"对应第1室103号表,模型共训练了共52种标签对应不同的表计,精度尚可,可批量读数保存到excel中,绘制单张表计和所有表计检测读数趋势,效果如下

在YOLO-obb+PyQt实现指针式仪表读数识别(一)中主要介绍YOLO-obb模型标注训练流程,在YOLO-obb+PyQt实现指针式仪表读数识别(二)中介绍读数原理和校正原理。

一、检测效果

1、指针式仪表识别效果

2、指针式仪表检测视频

YOLO-obb+PyQt实现指针式仪表读数识别

二、数据存储

1、序号对照表

2、表计

3、数据保存-单张

4、数据保存-批量

5、数据绘图-单张

6、数据绘图-批量

三、YOLO-obb模型标注训练流程

1、整体流程

初始图片经模型1检测得到表盘带标签图片,再分别经模型2和模型3得到表盘图片和标签图片,表盘图片和标签图片经obb模型4得到表计和最终读数。模型1、模型2、模型3的训练没什么好说的,LabelImg或者makesense正常标注训练就好。主要是obb模型4的标注训练流程。

2、YOLO-obb标注流程

2.1 标注软件

使用roLabelImg,在github.com/cgvict/roLabelImg下载解压,在cmd中输入python roLabelImg.py即可进入roLabelImg软件界面。

软件快捷键如下:

1) w: 创建水平矩形目标框;

2) e: 创建旋转矩形目标框;

3) zxcv: 旋转目标框,z和x逆时针旋转,c和v顺时针旋转,zv快速旋转,xc慢速旋转。

2.2 图片标注

在这里标注开始刻度线和结束刻度线,对应Scale,0.4刻度线对应Scale2(用于读数校正,具体校正方法后面再提),指针对应Pointer,图片标注完成后保存为xml文件,需进行文件格式转换。

 2.3 格式转换xml-txt
 2.3.1 xml转成dota格式txt

 将标注完成后的xml转成yolo可训练的txt文件,第一步转换代码,将xml转成dota格式txt:

# 文件名称   :roxml_to_dota.py
# 功能描述   :把rolabelimg标注的xml文件转换成dota能识别的xml文件,
#             再转换成dota格式的txt文件
#            把旋转框 cx,cy,w,h,angle,或者矩形框cx,cy,w,h,转换成四点坐标x1,y1,x2,y2,x3,y3,x4,y4
import os
import xml.etree.ElementTree as ET
import math


# 修改为自己的标签
cls_list = ['Scale', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10',
            '11', '12', '13', '14', '15', '16', '17', '18', '19', '20',
            '21', '22', '23', '24', '25', '26', '27', '28', '29', '30',
            '31', '32', '33', '34', '35', '36', '37', '38', '39', '40',
            '41', '42', '43', '44', '45', '46', '47', '48', '49', '50',
            '51', '52', 'Scale2', 'Pointer']

def edit_xml(xml_file, dotaxml_file):
    """
    修改xml文件
    :param xml_file:xml文件的路径
    :return:
    """

    # dxml_file = open(xml_file,encoding='gbk')
    # tree = ET.parse(dxml_file).getroot()

    tree = ET.parse(xml_file)
    objs = tree.findall('object')
    for ix, obj in enumerate(objs):
        x0 = ET.Element("x0")  # 创建节点
        y0 = ET.Element("y0")
        x1 = ET.Element("x1")
        y1 = ET.Element("y1")
        x2 = ET.Element("x2")
        y2 = ET.Element("y2")
        x3 = ET.Element("x3")
        y3 = ET.Element("y3")
        # obj_type = obj.find('bndbox')
        # type = obj_type.text
        # print(xml_file)

        if (obj.find('robndbox') == None):
            obj_bnd = obj.find('bndbox')
            obj_xmin = obj_bnd.find('xmin')
            obj_ymin = obj_bnd.find('ymin')
            obj_xmax = obj_bnd.find('xmax')
            obj_ymax = obj_bnd.find('ymax')
            # 以防有负值坐标
            xmin = max(float(obj_xmin.text), 0)
            ymin = max(float(obj_ymin.text), 0)
            xmax = max(float(obj_xmax.text), 0)
            ymax = max(float(obj_ymax.text), 0)
            obj_bnd.remove(obj_xmin)  # 删除节点
            obj_bnd.remove(obj_ymin)
            obj_bnd.remove(obj_xmax)
            obj_bnd.remove(obj_ymax)
            x0.text = str(xmin)
            y0.text = str(ymax)
            x1.text = str(xmax)
            y1.text = str(ymax)
            x2.text = str(xmax)
            y2.text = str(ymin)
            x3.text = str(xmin)
            y3.text = str(ymin)
        else:
            obj_bnd = obj.find('robndbox')
            obj_bnd.tag = 'bndbox'  # 修改节点名
            obj_cx = obj_bnd.find('cx')
            obj_cy = obj_bnd.find('cy')
            obj_w = obj_bnd.find('w')
            obj_h = obj_bnd.find('h')
            obj_angle = obj_bnd.find('angle')
            cx = float(obj_cx.text)
            cy = float(obj_cy.text)
            w = float(obj_w.text)
            h = float(obj_h.text)
            angle = float(obj_angle.text)
            obj_bnd.remove(obj_cx)  # 删除节点
            obj_bnd.remove(obj_cy)
            obj_bnd.remove(obj_w)
            obj_bnd.remove(obj_h)
            obj_bnd.remove(obj_angle)

            x0.text, y0.text = rotatePoint(cx, cy, cx - w / 2, cy - h / 2, -angle)
            x1.text, y1.text = rotatePoint(cx, cy, cx + w / 2, cy - h / 2, -angle)
            x2.text, y2.text = rotatePoint(cx, cy, cx + w / 2, cy + h / 2, -angle)
            x3.text, y3.text = rotatePoint(cx, cy, cx - w / 2, cy + h / 2, -angle)

        # obj.remove(obj_type)  # 删除节点
        obj_bnd.append(x0)  # 新增节点
        obj_bnd.append(y0)
        obj_bnd.append(x1)
        obj_bnd.append(y1)
        obj_bnd.append(x2)
        obj_bnd.append(y2)
        obj_bnd.append(x3)
        obj_bnd.append(y3)

        tree.write(dotaxml_file, method='xml', encoding='utf-8')  # 更新xml文件


# 转换成四点坐标
def rotatePoint(xc, yc, xp, yp, theta):
    xoff = xp - xc
    yoff = yp - yc
    cosTheta = math.cos(theta)
    sinTheta = math.sin(theta)
    pResx = cosTheta * xoff + sinTheta * yoff
    pResy = - sinTheta * xoff + cosTheta * yoff
    return str(xc + pResx), str(yc + pResy)


def totxt(xml_path, out_path):
    # 想要生成的txt文件保存的路径,这里可以自己修改

    files = os.listdir(xml_path)
    i = 0
    for file in files:

        tree = ET.parse(xml_path + os.sep + file)
        root = tree.getroot()

        name = file.split('.')[0]

        output = out_path + '\\' + name + '.txt'
        file = open(output, 'w')
        i = i + 1
        objs = tree.findall('object')
        for obj in objs:
            cls = obj.find('name').text
            box = obj.find('bndbox')
            x0 = round(float(box.find('x0').text), 4)
            y0 = round(float(box.find('y0').text), 4)
            x1 = round(float(box.find('x1').text), 4)
            y1 = round(float(box.find('y1').text), 4)
            x2 = round(float(box.find('x2').text), 4)
            y2 = round(float(box.find('y2').text), 4)
            x3 = round(float(box.find('x3').text), 4)
            y3 = round(float(box.find('y3').text), 4)
            if x0 < 0:
                x0 = 0
            if x1 < 0:
                x1 = 0
            if x2 < 0:
                x2 = 0
            if x3 < 0:
                x3 = 0
            if y0 < 0:
                y0 = 0
            if y1 < 0:
                y1 = 0
            if y2 < 0:
                y2 = 0
            if y3 < 0:
                y3 = 0
            for cls_index, cls_name in enumerate(cls_list):
                if cls == cls_name:
                    file.write("{} {} {} {} {} {} {} {} {} {}\n".format(x0, y0, x1, y1, x2, y2, x3, y3, cls, cls_index))
        file.close()
        # print(output)
        print(i)


if __name__ == '__main__':
    # 输入:标注的xml文件    输出:dota文件和txt文件
    # -----**** 第一步:把xml文件统一转换成旋转框的xml文件 ****-----
    roxml_path = r'xml_path'
    dotaxml_path = r'data_path'
    out_path = r'txt_path'
    filelist = os.listdir(roxml_path)
    for file in filelist:
        edit_xml(os.path.join(roxml_path, file), os.path.join(dotaxml_path, file))

    # -----**** 第二步:把旋转框xml文件转换成txt格式 ****-----
    totxt(dotaxml_path, out_path)

转换完成后数据格式如下:

 2.3.2 dota格式txt转成yolo可训练txt

第二步转换代码,将dota格式txt转成yolo可训练txt:

from ultralytics.data.converter import convert_dota_to_yolo_obb
# 注意your_data_path文件夹格式
convert_dota_to_yolo_obb('your_data_path')

这里有两个需要注意的地方,ctrl+鼠标左键,修改convert_dota_to_yolo_obb里class_mapping:

# Class names to indices mapping
    class_mapping = {
        "Scale": 0,
        "1": 1,
        "2": 2,
        "3": 3,
        "4": 4,
        "5": 5,
        "6": 6,
        "7": 7,
        "8": 8,
        "9": 9,
        "10": 10,
        "11": 11,
        "12": 12,
        "13": 13,
        "14": 14,
        "15": 15,
        "16": 16,
        "17": 17,
        "18": 18,
        "19": 19,
        "20": 20,
        "21": 21,
        "22": 22,
        "23": 23,
        "24": 24,
        "25": 25,
        "26": 26,
        "27": 27,
        "28": 28,
        "29": 29,
        "30": 30,
        "31": 31,
        "32": 32,
        "33": 33,
        "34": 34,
        "35": 35,
        "36": 36,
        "37": 37,
        "38": 38,
        "39": 39,
        "40": 40,
        "41": 41,
        "42": 42,
        "43": 43,
        "44": 44,
        "45": 45,
        "46": 46,
        "47": 47,
        "48": 48,
        "49": 49,
        "50": 50,
        "51": 51,
        "52": 52,
        "Scale2": 53,
        "Pointer": 54,
    }

此外,your_data_path文件夹格式要满足如下要求:

images/train和images/val放置原始图片文件,labels/train_original和labels/val_original放置原始dota_txt标签文件,labels/train和labels/val为空,运行代码,结束转换后的标签会保存在labels/train和labels/val中,转换后格式如下。至此,训练数据集准备完毕。

3、YOLO-obb训练流程

  3.1 数据集文件夹

  新建数据集文件夹,结构如下所示,test可为空,labels即为上一步骤转换完成的txt文件。

 3.2 创建dota-obb.yaml

创建yaml文件,更改数据集路径。

# 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: your_data_path  # dataset root dir
train: images/train  # train images (relative to 'path') 128 images
val: images/val  # val images (relative to 'path') 128 images
test:  # test images (optional)

# Classes
names:
  0: Scale
  1: 1
  2: 2
  3: 3
  4: 4
  5: 5
  6: 6
  7: 7
  8: 8
  9: 9
  10: 10
  11: 11
  12: 12
  13: 13
  14: 14
  15: 15
  16: 16
  17: 17
  18: 18
  19: 19
  20: 20
  21: 21
  22: 22
  23: 23
  24: 24
  25: 25
  26: 26
  27: 27
  28: 28
  29: 29
  30: 30
  31: 31
  32: 32
  33: 33
  34: 34
  35: 35
  36: 36
  37: 37
  38: 38
  39: 39
  40: 40
  41: 41
  42: 42
  43: 43
  44: 44
  45: 45
  46: 46
  47: 47
  48: 48
  49: 49
  50: 50
  51: 51
  52: 52
  53: Scale2
  54: Pointer
  3.3 创建yolov8-obb.yaml

创建yolov8-obb.yaml,修改nc类别数量即可。

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 Oriented Bounding Boxes (OBB) model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect

# Parameters
nc: 55  # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.33, 0.25, 1024]  # YOLOv8n summary: 225 layers,  3157200 parameters,  3157184 gradients,   8.9 GFLOPs
  s: [0.33, 0.50, 1024]  # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients,  28.8 GFLOPs
  m: [0.67, 0.75, 768]   # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients,  79.3 GFLOPs
  l: [1.00, 1.00, 512]   # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
  x: [1.00, 1.25, 512]   # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs

# YOLOv8.0n backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]]  # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]]  # 1-P2/4
  - [-1, 3, C2f, [128, True]]
  - [-1, 1, Conv, [256, 3, 2]]  # 3-P3/8
  - [-1, 6, C2f, [256, True]]
  - [-1, 1, Conv, [512, 3, 2]]  # 5-P4/16
  - [-1, 6, C2f, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]]  # 7-P5/32
  - [-1, 3, C2f, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]]  # 9

# YOLOv8.0n head
head:
  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]
  - [[-1, 6], 1, Concat, [1]]  # cat backbone P4
  - [-1, 3, C2f, [512]]  # 12

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

  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 12], 1, Concat, [1]]  # cat head P4
  - [-1, 3, C2f, [512]]  # 18 (P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 9], 1, Concat, [1]]  # cat head P5
  - [-1, 3, C2f, [1024]]  # 21 (P5/32-large)

  - [[15, 18, 21], 1, OBB, [nc, 1]]  # OBB(P3, P4, P5)
  3.4 创建train文件

创建train文件,可以开始训练。

from ultralytics import YOLO


model = YOLO('yolov8_obb.yaml').load('yolov8n-obb.pt')
model.train(data='data_obb.yaml', epochs=200, batch=16, workers=0)
  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值