目标检测标注数据的检查和可视化

坚持写博客💪,分享自己的在学习、工作中的所得

  1. 给自己做备忘
  2. 对知识点记录、总结,加深理解
  3. 给有需要的人一些帮助,少踩一个坑,多走几步路

尽量以合适的方式排版,图文兼有
如果写的有误,或者有不理解的,均可在评论区留言
如果内容对你有帮助,欢迎点赞 👍 收藏 ⭐留言 📝。
虽然平台并不会有任何奖励,但是我会很开心,可以让我保持写博客的热情🙏🙏🙏



注:本篇代码实现基于YOLO格式的标注,其他格式的数据可以参考思路实现。

🧁标注数据的检查和可视化

标注数据是一件十分枯燥的重复性工作。在标注数据时,有时因为数据量大,类别多,会出现错标、误标的情况,这时就需要对标注数据进行检查并且修改。

以labelimg标注目标检测为例,一般都两种方式:

  1. 先定义好类别文件classes.txt,先将所有需要的类别写入文件,在标注时进行选择即可
  2. 在标注时遇到新的类别再添加,会保存到标注文件路径的classes.txt

两种方式皆可。但也有可能遇到下面的问题:

第一种方式在标注时可能会遇到某些类别的数量为0的情况;
而第二种可能会出现的问题是:多个人标注时没有统一的类别名,导致每个人标注的文件不统一。

这篇文章倾向于第一种方式,在标注之前先确定好classes.txt

假设数据集有3个类别。classes.txt文件:

class_name1
class_name2
class_name3

标注出错也有两种情况,本文会针对这两种情况做相应的处理:

  1. 错误类别,即在原有类别的基础上增加了新的类别
  2. 类别之间出错,即class_name1误标为class_name2

一旦确定好文件,在标注时出现手误,标注错类别名时,比较容易发现,因为新的类别名会添加在classes.txt文件的末尾,比较容易发现和修改,而第二种如果在过程中出现,修改会比较麻烦

🧁错误标注类别

针对上面的第一种错误,在标注完成之后,如果classes.txt的类别比之前定义的多了,那么就说明在标注时出错了,可以使用以下代码,找到错误的类别的索引所对应的文件,再通过标注工具重新标注正确的类别,最后关闭标注工具并将classes.txt后面错误的类别删除即可。

注意:顺序是先找到误标的文件 >> 修改 >> 删除类别名
先删除类别名,在标注数据中或无法打开标注文件而报错

逻辑是:遍历所有标注文件,找到类别索引大于等于类别数的即打印出文件路径,根据路径在标注工具中重新打开对应文件修改即可。索引是从0开始的。

import os
from glob import glob

num_of_classes = 3
all_files = glob(r'path\to\labels\*.txt')
for file in all_files:
    if file.endswith('classes.txt'):
        continue
    with open(file, 'r') as f:
        for line in f.readlines():
            try:
                if int(line.split()[0]) >= num_of_classes:  # 一共有num_of_classes 类,从0开始。索引大于等于num_of_classes 的为错误标注的
                    print(file)
            except Exception as e:
                print(e)
                print(file)

🧁类别间标注出错

针对上面的第二种错误,在标注完成之后,将所有标注目标从原图片中裁剪下来,保存到对应的类别名文件中,这样一个文件在就是同一类目标,打开文件,如果其中有其他类别,则说明标注出错了。同样,根据错误目标的文件名,到标注文件重新标注即可。

import os
import numpy as np
from glob import glob
from PIL import Image, ImageDraw
from tqdm import tqdm

pillow读取图片以及数据格式转换的方法

def read_img_pillow(path):  # 40.5 ms
    with open(path, "rb") as f:
        img = Image.open(f)
        return img.convert("RGB")  # 20.1 ms

def xywh2xyxy(x):
    # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
    y = np.copy(x)
    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
    return y

创建每个类别的文件夹

category = ['class_name1', 'class_name2', 'class_name3']

images_dir = r'path\to\dataset\images'
labels_dir = r'path\to\dataset\labels'
save_dir = r'path\to\dataset\crop'
for cat in category:
    os.makedirs(os.path.join(save_dir, cat), exist_ok=False)

逻辑:通过标注文件,找到对应的图片文件,读取标注数据将对应位置的目标保存到对应的类别的文件夹中。

labels_path = glob(os.path.join(labels_dir, '*.txt'))
for label_path in tqdm(labels_path):
    image_name = os.path.basename(label_path).replace('.txt', '.jpg')
    image_path = os.path.join(images_dir, image_name)
    if not os.path.exists(image_path):
        continue
    im_pil = read_img_pillow(image_path)
    width, height = im_pil.size
    with open(label_path, 'r') as f:
        for i, l in enumerate(f.readlines(), start=1):
            line = l.split()  # ['1' '0.724877' '0.309082' '0.073938' '0.086914']
            xywh = np.array(list(map(float, line[1:]))) * np.array([width, height, width, height])
            [xyxy] = xywh2xyxy(np.array([xywh]))
            # print(xyxy)
            new_name = image_name.replace('.jpg', f'-{i}.jpg')
            new_path = os.path.join(save_dir, category[int(line[0])], new_name)
            im_crop = im_pil.crop(xyxy)
            im_crop.save(new_path)
        # break

注意:new_name = image_name.replace('.jpg', f'-{i}.jpg')中使用-i来给多个目标命名,避免同个图片中同个类别的目标被覆盖,在后续使用该文件名去修改标注数据时,记得删除-i才能找到对应的原图片。

每个文件夹检查修改完成后,将整个crop文件夹删除,再次运行这部分代码,可再次检查是否已经全部修正。



如果内容对你有帮助,或者觉得写的不错
🏳️‍🌈欢迎点赞 👍 收藏 ⭐留言 📝
有问题,请在评论区留言

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ayiya_Oese

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值