分割掩码标注转YOLO多边形标注

news2024/11/14 23:05:22

Ultralytics 团队付出了巨大的努力,使创建自定义 YOLO 模型变得非常容易。但是,处理大型数据集仍然很痛苦。训练 yolo 分割模型需要数据集具有其特定格式,这可能与你从大型数据集中获得的格式不完全相同。如果你想使用巨大的 OpenImagesV7 作为图像和标签的来源,情况就是如此。

在本教程中,我们将介绍如何从 OpenImagesV7 获取数据(图像和分割掩码);如何将其转换为 YOLO 格式(这是本教程中最复杂的部分);以及如何使用我们的数据集训练 yolov8-seg 模型的预览。

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 

1、环境

要清楚:本教程需要 Python 3(在 3.10 下测试)。作为基础图像,我使用 AWS Sagemaker conda_pytorch_p310 ,它包含 PyTorch 和许多常用工具,如 Numpy 和 OpenCV。不过,我们需要一些额外的包来覆盖它:

sudo yum install -y openssl-devel openssl11-libs libcurl
pip install --upgrade pip setuptools wheel
pip install fiftyone
pip install fiftyone-db-rhel7 --force-reinstall
pip install shapely polars
pip install ultralytics

2、数据集

我们将使用 Google OpenImages Dataset v7 来训练我们的模型。这个数据集非常庞大,有数百万张图像,旨在完成一系列计算机视觉任务,例如对象检测、分类和实例分割。因此,每张图像都与这些任务的标签配对,涵盖了最常见的多种对象:人、脸、狗、猫、汽车、树木等。请注意,并非所有标签都适用于所有任务,因此在数据集网站上进行一些探索以了解它涵盖了您正在尝试构建的模型的扩展范围是值得的。对于大多数计算机视觉问题来说,这可能已经足够了。

因此,要实际获取数据,有一个很棒的工具叫做 FiftyOne,它能够只下载你需要的数据。这避免了下载和处理整个数据集的负担。我是否已经提到它非常庞大?如果没有 FiftyOne,处理起来真的很难。

第一步是选择一个好的位置来下载数据集。我假设 Sagemaker 环境采用默认设置,但您可以选择更适合您的设置。请记住,在此路径中将自动创建文件夹 open-images-v7,以保存数据。

# choose your prefered path to download the dataset
# a folder named open-images-v7 will be created automatically inside of it
dataset_path = '/home/ec2-user/SageMaker/dataset'

import os
import torch
import torchvision

import fiftyone as fo
fo.config.default_ml_backend = "torch"
fo.config.dataset_zoo_dir = dataset_path

下一个函数只是为了让我们的脚本更简洁,也更灵活。我们可以简单地将所有想要的标签放入 fiftyone 下载器函数中,让它决定要获取每个标签的样本数量。但是,在这里我需要更好地控制要下载每个类别的样本数量。当然,如果确实有可用的样本, fiftyone 只会下载我们要求的数字,否则它会尽可能多地获取样本。

def download_dataset(split, classes, max_samples=None):
    print(f'>> Split: {split}, classes: {classes}, max_samples: {max_samples}')
    return fo.zoo.load_zoo_dataset(
            "open-images-v7",
            label_types=["segmentations"],
            drop_existing_dataset=False,
            split=split,
            classes=classes,
            max_samples=max_samples,
    )

在调用下载器函数之前,我们先描述一下我们需要的标签以及每个标签的数量。请注意,如果我们输入 None,我们就可以实现“尽可能多”的行为,而无需猜测一个很大的随机数。为了简单起见,我们将获取包含人或汽车的样本。最多一千个人物样本和尽可能多的汽车样本。我们希望 70% 用于训练,20% 用于验证,10% 用于测试;这些百分比不能保证,因为每个子集可能都没有足够的样本。

target_split = {'train': 0.7, 'validation': 0.2, 'test': 0.1}
target_classes = {
    "Person": 1_000,
    "Car": None
}

现在我们可以运行下载器函数了。为此,我们将遍历上面定义的目标类。

for cls_name, total in target_classes.items():
    for split_name, split_pct in target_split.items():
        max_samples = int(total * split_pct) if total is not None else None
        download_dataset(split=split_name, classes=[cls_name], max_samples=max_samples)

即使在强大的 AWS Sagemaker 环境中,脚本也可能需要一段时间才能完成。当然,时间与请求的样本数量成正比。下载数据集后,我们仍未准备好将其与 yolo 一起使用。数据以彩色图像的形式出现,并配有一组分割蒙版(灰度图像),图像中的每个对象都有一个蒙版。但是,yolo 需要将彩色图像与文本文件配对,文本文件的每一行都描述对象的类别和描述其多边形的坐标。请查看 yolo 文档。这将引出我们的下一章,介绍如何将数据集转换为适合 YoloV8 的格式。

3、将 OpenImagesV7 转换为 Yolo 分割

原始分割标签以灰度图像的形式提供。如前所述,Yolo 要求分割标签位于文本文件中,其中包含图像中每个对象的一行,遵循以下模式:对象类 ID,然后是描述对象多边形的 XY 列表。

这种转换需要一些工具来加载图像、将蒙版转换为多边形、降低多边形复杂性并将多边形写在文本文件中。所以让我们在一个新的干净的笔记本或 Python 脚本中一步一步地完成这项工作。首先是基本配置:

# base path must be the same as our previous dataset_path
base_path = '/home/ec2-user/SageMaker/dataset'

# destination of the converted dataset
target_path = '/home/ec2-user/SageMaker/dataset-yolo'

# a list with same keys as on fetch.py
target_classes = [
  "Person",
  "Car"
]

import os
import cv2
import yaml
import shutil
import pandas as pd
import polars as pl
import multiprocessing
import numpy as np
from tqdm import tqdm
from joblib import Parallel, delayed
from shapely.geometry import Polygon
from matplotlib import pyplot as plt

现在我们定义一些函数来执行从灰度蒙版图像到简化多边形,然后再到 XY 列表的实际转换:

### Mask to Poly ###

def mask_to_polygon(mask_path):
    mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
    contours, _ = cv2.findContours(
        mask,
        cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE
    )

    polygons = []
    for contour in contours:
        polygon = contour.reshape(-1, 2)
        polygon_norm = polygon.astype(float)
        polygon_norm[:, 0] /= mask.shape[1]  # X
        polygon_norm[:, 1] /= mask.shape[0]  # Y
        polygon_norm = np.round(polygon_norm, 4)

        polygon_shapely = Polygon(polygon_norm)
        polygon_simplified = polygon_shapely.simplify(0.002, preserve_topology=True)
        polygons.append(polygon_simplified)

    return polygons

def polygon_to_yolo(polygon):
    x, y = polygon.exterior.coords.xy
    xy = []
    for xx, yy in zip(x, y):
        xy.append(xx)
        xy.append(yy)
    return xy

def polygon_to_mask(polygon, shape):
    mk = np.zeros(shape, dtype=np.uint8)
    x, y = polygon.exterior.coords.xy
    xy = [
        [int(xx * shape[1]), int(yy * shape[0])]
        for xx, yy in zip(x, y)
    ]
    cv2.fillConvexPoly(mk, np.array(xy, dtype='int32'), color=255)
    return mk

准备好图像处理工具后,下一步就是加载标签位置。这将遍历数据库文件系统查看图像文件,对于每幅图像,它将查看分割索引并获取相应的蒙版图像位置。到目前为止,图像仍未打开。有趣的事实:这里使用的是 polars 而不是 pandas,因为这个简单任务的处理速度差异很大。

### loading openimagesv7 labels ###

class_list_filepath = os.path.join(base_path, 'train/metadata/classes.csv')
class_df = pd.read_csv(class_list_filepath, header=None, names=['URI', 'ClassName'])
class_map_r = dict(zip(class_df.URI, class_df.ClassName))
class_map_r = {k: v for k, v in class_map_r.items() if v in target_classes}

# convert from openimagev7 label hash to an integer
class_map = { k: i for i, k in enumerate(list(class_map_r.keys()))}
# class_map = {
# '/m/01g317':  0, # 'Person'
# '/m/0k4j':    1, # 'Car'
# }
print('class_map:')
print(class_map)

def get_image_file_names(directory):
    image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']  # Add more extensions if needed
    image_file_names = set()

    for filename in os.listdir(directory):
        nm, ext = os.path.splitext(filename)
        if ext in image_extensions:
            image_file_names.add(nm)

    return image_file_names
  
def load_labels(split_name):
    df = pl.read_csv(os.path.join(base_path, split_name, 'labels/segmentations.csv'))
    df = df[['MaskPath', 'ImageID', 'LabelName']]

    image_ids = get_image_file_names(os.path.join(base_path, split_name, 'data'))
    df = df.filter(pl.col('ImageID').is_in(image_ids))
    
    target_ids = set(class_map.keys())
    df = df.filter(pl.col('LabelName').is_in(target_ids))

    df = df.with_columns(pl.col('MaskPath').map_elements(lambda x: x[0].upper()).alias('Subdir'))
    df = df.with_columns((base_path + f'{split_name}/labels/masks/' + pl.col('Subdir') + '/' + pl.col('MaskPath')).alias('MaskFullPath'))
    
    df = df.with_columns(pl.col(['LabelName']).map_dict(class_map).alias('LabelID'))

    return df


train_df = load_labels('train')    
valid_df = load_labels('validation')
test_df = load_labels('test')

最后,是时候遍历所有掩模图像,将其转换为 XY 多边形列表并按照 yolo 标准将结果写入文本文件中。

def macro_mask2yolopoly(p):
    try:
        poly = mask_to_polygon(p)
        xy = polygon_to_yolo(poly[0])
        return xy
    except:
        return []
    return []


def conv_mask_xy(df):
    return df.with_columns(
        pl.col('MaskFullPath').map_elements(
            lambda p: macro_mask2yolopoly(p)
        ).alias('XY')
    )

train_df = conv_mask_xy(train_df)
valid_df = conv_mask_xy(valid_df)
test_df = conv_mask_xy(test_df)

def write_yolo_labels(df, subset, persistence=True):
    df = df.filter(pl.col('XY').map_elements(len) > 0)
    
    df = df.with_columns(
        pl.col('XY').map_elements(lambda xy: xy.map_elements(lambda e: str(e))).list.join(' ').alias('TXY'))
    df = df.with_columns(
        (pl.col('LabelID').cast(pl.Utf8) + ' ' + pl.col('TXY')).alias('Sample'))
    
    g = df.group_by('ImageID').agg(['Sample'])
    g = g.with_columns(pl.col('Sample').list.join('\n').alias('StrSamples'))
    g = g.with_columns((target_path + subset + '/' + pl.col('ImageID') + '.txt').alias('Path'))

    if persistence:
        os.makedirs(os.path.join(target_path, subset), exist_ok=True)
        for row in g.iter_rows(named=True):
            with open(row['Path'], 'w') as f:
                f.write(row['StrSamples'])

    return g
            
train_df = write_yolo_labels(train_df, 'train')
valid_df = write_yolo_labels(valid_df, 'validation')
test_df = write_yolo_labels(test_df, 'test')

好吧,我们的新数据集最终被拆分成不同位置的图像和 yolo 标签。因此,为了统一,我们需要将下载的图像连同我们的标签一起复制到目标位置。这里有几种可能性,比如在 openimagesv7 文件夹中创建标签、移动图像等。我更喜欢复制并保留原始文件,以防脚本失败,稍后我可以简单地删除整个 openimages 缓存以释放一些空间。

def copy_data(df, subset):
    for iid in df.select(pl.col('ImageID')).get_columns()[0].to_list():
        try:
            fnm = f"{iid}.jpg"
            src = os.path.join(base_path, subset, "data", fnm)
            dst = os.path.join(target_path, subset)
            # print(f'{src} -> {dst}')
            shutil.copy2(src, dst)
        except:
            continue

copy_data(valid_df, 'validation')
copy_data(test_df, 'test')
copy_data(train_df, 'train')

Yolo 数据集仅包含描述它的 YAML 文件才完整。创建一个很容易,就像这样:

from pathlib import Path

yaml_content = f'''
path: /home/ec2-user/SageMaker/dataset-yolo
train: train
val: validation
test: test

# Classes - use the class_map as guide
names:
  0: person
  1: car
'''

with Path(os.path.join(target_path, 'seg_dataset.yaml')).open('w') as f:
    f.write(yaml_content)

4、训练 YoloV8-Seg 模型

本教程最难的部分是让数据集适合 Yolo。鉴于有更好的关于如何训练 Yolo 模型的教程,我将简短地讲解本课程。还请查看 Ultralytics 文档。但为了让你不要抱怨这是一个半生不熟的教程,我们开始吧:

# on the command line (CLI)
yolo segment train data=/home/ec2-user/SageMaker/dataset-yolo/seg_dataset.yaml model=yolov8n-seg.pt epochs=100 imgsz=640

这将使用预先训练好的 yolov8n-seg.pt 作为我们自定义分割模型的基础,并将更新超过 100 个 epoch。模型的输出可能位于 ./runs/exp/ 上。如果不是这种情况,请查看文档。

5、参考文档

  • vchaparro 的代码:将掩码转换为多边形
  • Alon Lekhtman 关于训练 YoloV8-Seg 的 Medium 帖子
  • Google OpenImagesV7 数据集
  • Ultralytics

原文链接:分割掩码转YOLO格式 - BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2163549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MIPI包分析

如下图所示,长方形圈出来的是数据包的规定区域,注意SOT不在packet的定义区间。 分析可知,第一byte就知道包的类型,姑且记作VC2DT6。DT6就是6bits的data type。显而易见总共定义了64中类型,规范中定义了一些&#xff0…

【C++掌中宝】C++ 中的空指针救世主——nullptr

文章目录 1. 什么是 NULL?2. NULL 在 C 和 C 中的区别3. C11 引入 nullptr 的原因4. nullptr 与 NULL 的区别5. nullptr 的应用场景6. 模拟 nullptr 的实现7. 总结结语 1. 什么是 NULL? 在 C 和 C 编程中,NULL 常用于表示空指针,…

展示中国客车硬实力!苏州金龙海格纯电客车惊艳汉诺威商用车展

德国当地时间2024年9月22日,IAA汉诺威商用车展在德国汉诺威展览中心圆满落幕。本届展会,苏州金龙海格客车携新能源公交明星产品Azure7、斯堪尼亚海格高端巴士Fencer F1 Integral EV纯电车型重磅亮相,获得了众多参会嘉宾的好评和关注。来自欧洲…

【活动】人工智能时代,程序员如何保持核心竞争力?需要掌握哪些技能?

人工智能时代,程序员如何保持核心竞争力? 随着人工智能(AI)技术的迅猛发展,程序员面临着前所未有的挑战和机遇。AI不仅改变了软件开发的方式,也重新定义了程序员的角色。在这种背景下,如何保持…

人工智能助力阿尔茨海默症治疗:微软与上海精神卫生中心的新研究

最近,微软研究院与上海市精神卫生中心合作,基于微软 Azure OpenAI 服务中的多模态大模型,开发了一种名为“忆我”(ReMe)的个性化认知训练框架。这一创新项目旨在通过数字化手段扩展自动化认知训练的范围,为…

2025考研,报名和初试时间定了!

2025年全国硕士研究生招生初试时间为2024年12月21日至22日,考试时长为6小时的考试科目在12月23日举行。 请考生届时登录“中国研究生招生信息网”(网址:https://yz.chsi.com.cn)参加报名,查看报名须知、招生单位招生章…

软考高级:云计算架构的五层 AI 解读

云计算架构通常由多个层次构成,每个层次负责不同的功能,协同工作来提供完整的云服务。下面我将详细解释云计算架构的五个主要层次:管理层、用户访问层、应用层、平台层和资源层。 生活化例子 想象一下,云计算架构就像是一座大型…

从金蝶云星空到管易云通过接口配置打通数据

从金蝶云星空到管易云通过接口配置打通数据 接通系统:金蝶云星空 金蝶K/3Cloud结合当今先进管理理论和数十万家国内客户最佳应用实践,面向事业部制、多地点、多工厂等运营协同与管控型企业及集团公司,提供一个通用的ERP服务平台。K/3Cloud支持…

yolov8环境安装

yolov8 git地址 https://github.com/ultralytics/ultralytics/tree/main 我的电脑显卡配置 nvidia-smi cuda11.8下载 https://developer.nvidia.com/cuda-11-8-0-download-archive?target_osWindows&target_archx86_64&target_version10&target_typeexe_local …

保姆级教程!0基础小白也适用,教你用AI美女在小红书“疯狂上分”

文章开始之前,先考考各位小伙伴的眼力:下面的图是真人还是 AI,你能分辨出来吗? 答案是:AI,AI,全是使用stable diffusion生成的AI美女! 说实话,最近AI美女是彻底杀疯了&a…

大数据Hive组件安装

组件版本 组件版本Hadoop3.3.0JDK1.8.0_241Mysql5.7.25Hive3.1.2 Hadoop集群服务分布 Node1Node2Node3NameNode DataNode DataNodeDataNode NodeManager NodeManagerResourceManagerSecondaryNameNode 安装前请确定Hadoop集群服务全部启动,不然后续测试时会报…

【学习笔记】手写 Tomcat 四

目录 一、Read 方法返回 -1 的问题 二、JDBC 优化 1. 创建配置文件 2. 创建工具类 3. 简化 JDBC 的步骤 三、修改密码 优化返回数据 创建修改密码的页面 注意 测试 四、优化响应动态资源 1. 创建 LoginServlet 类 2. 把登录功能的代码放到 LoginServlet 类 3. 创…

企业新购买的设备需要做仪器校准吗?为什么新设备也要做检测?

企业在定制一批设备仪器到厂后,会需要对设备进行验货和接收,而这个环节很多人对于设备是否同时需要先做校准的问题犹豫不决,一些企业觉得新设备都是刚买的,做仪器校准没有必要,但是另一部分认为即便是新买的仪器也要先…

easyexcel常见问题分析

文章目录 一、读取数字多了很多小数位的精度问题 一、读取数字多了很多小数位的精度问题 浮点型转成BigDecimal的时候会出现精度问题,例如 这儿设置的实体类对象类型是String,默认用到的是StringNumberConverter转换器 2.1.4 版本 public class Strin…

葡萄城亮相2024全球产品经理大会,共探创新之旅

葡萄城亮相2024全球产品经理大会,共探创新之旅 9 月 20-21 日,由 CSDN 举办的“2024 全球产品经理大会”于北京盛大开幕,现场超 40 位互联网与科技产品领域专业人士发表主题演讲,1000 与会者齐聚一堂。 大会现场气氛热烈&#…

【LangChain系列】实战案例5:用LangChain实现灵活的Agents+RAG,该查时查,不该查时就别查

目前为止,我们实现的RAG练习中,答案都是全部来源于检索到的文本内容。而检索过程可能在某些情况下是不需要的。 如何优化这个过程,让我们的RAG程序在必要时才去检索,不必要时,直接使用大模型原有数据来回答呢&#xf…

Python酷玩之旅_如何在Centos8顺利安装Python最新版(3.12)

全文导览 前言Q:如何在Centos8顺利安装Python最新版一. 下载安装包1.1 wget1.2. 官网下载 二. 执行安装2.1. 检查环境2.2. 安装依赖2.3. 解压tgz包2.4. 编译2.5. 安装2.6. 设置环境变量2.6.1 编辑/etc/profile2.6.2 激活生效 三. 操作示例3.1. helloworld 结语 前言…

DevOps学习路线图

DevOps 是软件工程领域中的一种文化和实践方法,它将开发 (Dev) 和运维 (Ops) 相结合,从而在应用程序规划、开发、交付和运营中统一人员、流程和技术。 DevOps 支持以前孤立角色(如开发、IT 运营、质量工程和安全)之间的协调和协作…

【Elasticsearch】-实现图片向量相似检索

1、http请求方式 如果elasticsearch服务设置账号密码,则在请求的header中添加 Basic Auth 认证 请求方式:Post 请求地址:/index_name/_search 请求body:json格式 {"size": 10, //返回条数"min_score": 0.…

【10000字pandas数据可视化超全笔记整理】Numpy Pandas | 常用API介绍 | 缺失值处理 matplotlib数据可视化介绍

文章目录 Numpy 部分NumPy的数组类被称作ndarray,通常被称作数组。属性创建方法内置函数运算基本运算矩阵运算 Pandas部分总述Serise 对象创建属性方法运算 DataFrame属性索引操作添加删除插入列 保存读取文件保存读取 数据加载分组聚合语法格式: 基本绘图 数据组合…