YOLOv8-pose キーポイント検出自作データセット

YOLOv8はリリースされてしばらく経ちますが、ターゲット検出、インスタンスセグメンテーション、キーポイント検出、追跡、分類の機能を備えており、その効果はSOTAにまで達しており、多くの研究者に愛用されています。ここでは主に、github ( https://github.com/ultralytics/ultralytics ) で公開されているUltralyticsコードを使用します

ここに画像の説明を挿入

次に、キーポイント検出データセットを自分で作成し、YOLOv8 プロジェクトを実行する方法を主に記録します。YOLOv8 プロジェクトのキー ポイント検出、つまりポーズブランチは、次の図に示すように、主に人体の骨格部分をマークして、人体の動きを洗練して表現します。

ここに画像の説明を挿入

YOLOv8-pose キーポイント検出データセットのアノテーション形式

データセットにラベルを付けるときは、まず YOLOv8 ポーズのキーポイント検出に必要なデータ形式が何であるかを理解する必要があります。

公式 Web サイトのコードには、 という名前のポーズ キー ポイント検出用のサンプル データ セットが提供されています。coco8-pose.yaml内部の URL に従ってデータ セットをダウンロードし、特定の注釈形式の要件を確認できます。

ここに画像の説明を挿入

coco8-poseのデータアノテーション形式とアノテーション画像は以下のとおりです。まずアノテーションの形式を見てみますと、このファイルは.txtで終わるファイルで、中には 56 個のデータが入っています。

最初のデータは 0 で対象のカテゴリー (人物) を示し、次の 4 つのデータは長方形の枠の座標を示し、後ろのデータは 51 個あり、17*3 で 17 個のキーポイントの座標を示します。そして見えるかどうか。

このうち、0.00000 は不可視なし、1.00000 はブロックされて不可視、2.00000 は可視を意味します。この画像の下半身は表示されていないため、ラベル データ内の次の数点の座標データとラベルは両方とも 0.00000 です。

0 0.671279 0.617945 0.645759 0.726859 0.519751 0.381250 2.000000 0.550936 0.348438 2.000000 0.488565 0.367188 2.000000 0.642412 0.354687 2.000000 0.488565 0.395313 2.000000 0.738046 0.526563 2.000000 0.446985 0.534375 2.000000 0.846154 0.771875 2.000000 0.442827 0.812500 2.000000 0.925156 0.964063 2.000000 0.507277 0.698438 2.000000 0.702703 0.942187 2.000000 0.555094 0.950000 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000

画像の説明を追加してください

ここでは牛を例に、キーポイント検出用のデータセットの作り方を紹介します。

牛の要所にラベルを付けるソフト「labelme」を使う

ラベルを付けるときは、まず次のいくつかの点に注意する必要があります。

まず、キー ポイントをマークするときは、最初に長方形のフレームを使用してターゲットを枠付けしてから、キー ポイントをマークします。第
2 に、キー ポイントの順序は固定する必要はありませんが、各画像に一貫性がなければなりません。言い換えると、ポイント 1 が鼻の場合、すべての画像のポイント 1 が鼻である必要があります。3
番目に、遮蔽されたポイントもマークされる必要があります。4
番目、labelme はキー ポイントが表示されているかどうかをマークできないため、デフォルトは 1.00000 です。ここでは何も処理せず、すべてを 2.00000 に変更します。

ここに画像の説明を挿入
ここに画像の説明を挿入

画像では、長方形のボックスが独自のデータ型であるラベルに設定されており、次の点を 1、2、3 ... で直接マークできます。より便利です。ここで、点と点の間の接続であるスケルトンがまだ存在すると考えられる場合は、心配しないでください。点と点の間の接続をマークする必要はありません。接続線は、観察の便宜のために描画された可視化部分にすぎません。ここでの主なポイントは、重要なポイントを予測することであり、残りは無視してかまいません。

キーポイントと長方形のボックスにラベルを付けると、 labelme 出力に準拠する.jsonファイルが得られます。次に、それを.txtファイルに変換する必要があります。ここでは 2 つの手順を実行します。まず lableme 出力を coco 形式に変換し、次に yolo 形式に変換します。

ココ形式にラベル付け

以下はcoco形式に変換したコードです。事前にデータセットをトレーニングセットとテストセットに分割し、2回処理する必要があります。トレーニング セットを例に挙げると、まずlabelme でマークされた.jsonファイルをjson フォルダーに配置し、出力結果を保存するための新しい coco フォルダーを作成します。

次のコードでは、209 ~ 212 行目の内容を変更し、default="cow" の牛を独自のカテゴリに変更し、212 行目の 17 を自分でマークしたキー ポイントの数に変更する必要があります。

import os
import sys
import glob
import json
import argparse
import numpy as np
from tqdm import tqdm
from labelme import utils


class Labelme2coco_keypoints():
    def __init__(self, args):
        """
        Lableme 关键点数据集转 COCO 数据集的构造函数:

        Args
            args:命令行输入的参数
                - class_name 根类名字

        """

        self.classname_to_id = {
    
    args.class_name: 1}
        self.images = []
        self.annotations = []
        self.categories = []
        self.ann_id = 0
        self.img_id = 0

    def save_coco_json(self, instance, save_path):
        json.dump(instance, open(save_path, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)

    def read_jsonfile(self, path):
        with open(path, "r", encoding='utf-8') as f:
            return json.load(f)

    def _get_box(self, points):
        min_x = min_y = np.inf
        max_x = max_y = 0
        for x, y in points:
            min_x = min(min_x, x)
            min_y = min(min_y, y)
            max_x = max(max_x, x)
            max_y = max(max_y, y)
        return [min_x, min_y, max_x - min_x, max_y - min_y]

    def _get_keypoints(self, points, keypoints, num_keypoints):
        """
        解析 labelme 的原始数据, 生成 coco 标注的 关键点对象

        例如:
            "keypoints": [
                67.06149888292556,  # x 的值
                122.5043507571318,  # y 的值
                1,                  # 相当于 Z 值,如果是2D关键点 0:不可见 1:表示可见。
                82.42582269256718,
                109.95672933232304,
                1,
                ...,
            ],

        """

        if points[0] == 0 and points[1] == 0:
            visable = 0
        else:
            visable = 1
            num_keypoints += 1
        keypoints.extend([points[0], points[1], visable])
        return keypoints, num_keypoints

    def _image(self, obj, path):
        """
        解析 labelme 的 obj 对象,生成 coco 的 image 对象

        生成包括:id,file_name,height,width 4个属性

        示例:
             {
                "file_name": "training/rgb/00031426.jpg",
                "height": 224,
                "width": 224,
                "id": 31426
            }

        """

        image = {
    
    }

        img_x = utils.img_b64_to_arr(obj['imageData'])  # 获得原始 labelme 标签的 imageData 属性,并通过 labelme 的工具方法转成 array
        image['height'], image['width'] = img_x.shape[:-1]  # 获得图片的宽高

        # self.img_id = int(os.path.basename(path).split(".json")[0])
        self.img_id = self.img_id + 1
        image['id'] = self.img_id

        image['file_name'] = os.path.basename(path).replace(".json", ".jpg")

        return image

    def _annotation(self, bboxes_list, keypoints_list, json_path):
        """
        生成coco标注

        Args:
            bboxes_list: 矩形标注框
            keypoints_list: 关键点
            json_path:json文件路径

        """

        if len(keypoints_list) != args.join_num * len(bboxes_list):
            print('you loss {} keypoint(s) with file {}'.format(args.join_num * len(bboxes_list) - len(keypoints_list), json_path))
            print('Please check !!!')
            sys.exit()
        i = 0
        for object in bboxes_list:
            annotation = {
    
    }
            keypoints = []
            num_keypoints = 0

            label = object['label']
            bbox = object['points']
            annotation['id'] = self.ann_id
            annotation['image_id'] = self.img_id
            annotation['category_id'] = int(self.classname_to_id[label])
            annotation['iscrowd'] = 0
            annotation['area'] = 1.0
            annotation['segmentation'] = [np.asarray(bbox).flatten().tolist()]
            annotation['bbox'] = self._get_box(bbox)

            for keypoint in keypoints_list[i * args.join_num: (i + 1) * args.join_num]:
                point = keypoint['points']
                annotation['keypoints'], num_keypoints = self._get_keypoints(point[0], keypoints, num_keypoints)
            annotation['num_keypoints'] = num_keypoints

            i += 1
            self.ann_id += 1
            self.annotations.append(annotation)

    def _init_categories(self):
        """
        初始化 COCO 的 标注类别

        例如:
        "categories": [
            {
                "supercategory": "hand",
                "id": 1,
                "name": "hand",
                "keypoints": [
                    "wrist",
                    "thumb1",
                    "thumb2",
                    ...,
                ],
                "skeleton": [
                ]
            }
        ]
        """

        for name, id in self.classname_to_id.items():
            category = {
    
    }

            category['supercategory'] = name
            category['id'] = id
            category['name'] = name
            # 17 个关键点数据
            category['keypoint'] = [str(i + 1) for i in range(args.join_num)]

            self.categories.append(category)

    def to_coco(self, json_path_list):
        """
        Labelme 原始标签转换成 coco 数据集格式,生成的包括标签和图像

        Args:
            json_path_list:原始数据集的目录

        """

        self._init_categories()

        for json_path in tqdm(json_path_list):
            obj = self.read_jsonfile(json_path)  # 解析一个标注文件
            self.images.append(self._image(obj, json_path))  # 解析图片
            shapes = obj['shapes']  # 读取 labelme shape 标注

            bboxes_list, keypoints_list = [], []
            for shape in shapes:
                if shape['shape_type'] == 'rectangle':  # bboxs
                    bboxes_list.append(shape)           # keypoints
                elif shape['shape_type'] == 'point':
                    keypoints_list.append(shape)

            self._annotation(bboxes_list, keypoints_list, json_path)

        keypoints = {
    
    }
        keypoints['info'] = {
    
    'description': 'Lableme Dataset', 'version': 1.0, 'year': 2021}
        keypoints['license'] = ['BUAA']
        keypoints['images'] = self.images
        keypoints['annotations'] = self.annotations
        keypoints['categories'] = self.categories
        return keypoints


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("--class_name", default="cow", help="class name", type=str)
    parser.add_argument("--input",  default="./json", help="json file path (labelme)", type=str)
    parser.add_argument("--output", default="./coco", help="output file path (coco format)", type=str)
    parser.add_argument("--join_num",  default=17, help="number of join", type=int)
    # parser.add_argument("--ratio", help="train and test split ratio", type=float, default=0.5)
    args = parser.parse_args()

    labelme_path = args.input
    saved_coco_path = args.output

    json_list_path = glob.glob(labelme_path + "/*.json")

    print('{} for json files'.format(len(json_list_path)))
    print('Start transform please wait ...')

    l2c_json = Labelme2coco_keypoints(args)  # 构造数据集生成类

    # 生成coco类型数据
    keypoints = l2c_json.to_coco(json_list_path)
    l2c_json.save_coco_json(keypoints, os.path.join(saved_coco_path, "keypoints.json"))

Coco 形式から yolo 形式へ

以下はyolo形式に変換されたコードです。出力結果を保存するために新しい txt フォルダーを作成する必要があります。上記のデータの保存場所が同じであればコードを変更する必要はありませんが、異なる場合は 12 ~ 17 行目の入力データと出力場所を変更するだけです。

# COCO 格式的数据集转化为 YOLO 格式的数据集
# --json_path 输入的json文件路径
# --save_path 保存的文件夹名字,默认为当前目录下的labels。

import os
import json
from tqdm import tqdm
import argparse

parser = argparse.ArgumentParser()
# 这里根据自己的json文件位置,换成自己的就行
parser.add_argument('--json_path',
                    default='coco/keypoints.json', type=str,
                    help="input: coco format(json)")
# 这里设置.txt文件保存位置
parser.add_argument('--save_path', default='txt', type=str,
                    help="specify where to save the output dir of labels")
arg = parser.parse_args()


def convert(size, box):
    dw = 1. / (size[0])
    dh = 1. / (size[1])
    x = box[0] + box[2] / 2.0
    y = box[1] + box[3] / 2.0
    w = box[2]
    h = box[3]

    x = round(x * dw, 6)
    w = round(w * dw, 6)
    y = round(y * dh, 6)
    h = round(h * dh, 6)
    return (x, y, w, h)


if __name__ == '__main__':
    json_file = arg.json_path  # COCO Object Instance 类型的标注
    ana_txt_save_path = arg.save_path  # 保存的路径

    data = json.load(open(json_file, 'r'))
    if not os.path.exists(ana_txt_save_path):
        os.makedirs(ana_txt_save_path)

    id_map = {
    
    }  # coco数据集的id不连续!重新映射一下再输出!
    with open(os.path.join(ana_txt_save_path, 'classes.txt'), 'w') as f:
        # 写入classes.txt
        for i, category in enumerate(data['categories']):
            f.write(category['name']+"\n")
            id_map[category['id']] = i
    # print(id_map)
    # 这里需要根据自己的需要,更改写入图像相对路径的文件位置。
    # list_file = open(os.path.join(ana_txt_save_path, 'train2017.txt'), 'w')
    for img in tqdm(data['images']):
        filename = img["file_name"]
        img_width = img["width"]
        img_height = img["height"]
        img_id = img["id"]
        head, tail = os.path.splitext(filename)
        ana_txt_name = head + ".txt"  # 对应的txt名字,与jpg一致
        f_txt = open(os.path.join(ana_txt_save_path, ana_txt_name), 'w')
        for ann in data['annotations']:
            if ann['image_id'] == img_id:
                box = convert((img_width, img_height), ann["bbox"])
                f_txt.write("%s %s %s %s %s" % (id_map[ann["category_id"]], box[0], box[1], box[2], box[3]))
                counter=0
                for i in range(len(ann["keypoints"])):
                    if ann["keypoints"][i] == 2 or ann["keypoints"][i] == 1 or ann["keypoints"][i] == 0:
                        f_txt.write(" %s " % format(ann["keypoints"][i] + 1,'6f'))
                        counter=0
                    else:
                        if counter==0:
                            f_txt.write(" %s " % round((ann["keypoints"][i] / img_width),6))
                        else:
                            f_txt.write(" %s " % round((ann["keypoints"][i] / img_height),6))
                        counter+=1
        f_txt.write("\n")
        f_txt.close()

これにより、次のように yolo 形式と同じキー ポイント アノテーション データを取得できます。

0 0.507031 0.44294 0.516927 0.542824 0.758333  0.49213  2.000000  0.706901  0.324306  2.000000  0.59362  0.245602  2.000000  0.477734  0.213194  2.000000  0.355339  0.188889  2.000000  0.323437  0.419213  2.000000  0.296094  0.463194  2.000000  0.266797  0.637963  2.000000  0.344271  0.463194  2.000000  0.336458  0.506019  2.000000  0.365755  0.663426  2.000000  0.526563  0.506019  2.000000  0.498568  0.58125  2.000000  0.482292  0.695833  2.000000  0.595573  0.515278  2.000000  0.608594  0.598611  2.000000  0.639193  0.696991  2.000000 

正しいスケルトン接続を表示する

直接トレーニングすると、予測結果はこのようになり、線分の接続が間違っており、意図したものではないことがわかります。

ここに画像の説明を挿入
ここでは、 ./ultralytics-main\ultralytics\ yolo\utils\plotting.py ファイルを変更し、self.skeletonパラメータを独自の接続線に変更するだけで済みます。ここでは、前にマークしたキー ポイント ラベルに従って接続します。

次のself.limb_colorパラメータは、先ほどの接続線の色設定、self.kpt_colorつまりキーポイントの色設定です。自分で調整してみて、数量が対応していることを確認する必要があります。

ここに画像の説明を挿入
これは私の予測であり、単なる試行実験ですが、すでにその通りになっていることがわかります。

ここに画像の説明を挿入

参考リンク:https://blog.csdn.net/m0_57458432/article/details/128220346
https://blog.csdn.net/m0_57458432/article/details/128222620?spm=1001.2014.3001.5502

日々の学習記録、一緒に交流・議論しましょう!権利侵害の連絡~

おすすめ

転載: blog.csdn.net/WYKB_Mr_Q/article/details/132035597