はじめに
前回はアノテーションデータの作成を実施しました。
今回は、作成したアノテーションデータを COCO 形式に変換します。
前提条件
前提条件は以下の通りです。
- Windows11 (三次元モデルの準備にのみ使用)
- Ubuntu22 (モデル準備以降に使用)
- Python3.10.x
- CloudCompare
- open3d == 0.16.0
- こちらの記事を参考に 三次元モデルを作成していること
- シーンの作成が完了していること
- こちらの記事を参考に bop_toolkit_lib のインストールとプログラムの修正が完了していること
- マスクデータの作成が完了していること
- アノテーションデータの作成が完了していること
アノテーションデータを COCO 形式へ変換
変換プログラムは、bop_toolkit に既にありますので、少し修正を加えて使用します。
calc_gt_coco.py
# Author: Martin Sundermeyer (martin.sundermeyer@dlr.de)
# Robotics Institute at DLR, Department of Perception and Cognition
"""Calculates Instance Mask Annotations in Coco Format."""
import numpy as np
import os
import datetime
import json
from bop_toolkit_lib import pycoco_utils
from bop_toolkit_lib import config
from bop_toolkit_lib import dataset_params
from bop_toolkit_lib import inout
from bop_toolkit_lib import misc
# PARAMETERS.
################################################################################
p = {
  # See dataset_params.py for options.
  'dataset': 'lm',
  # Dataset split. Options: 'train', 'test'.
  'dataset_split': 'train',
  # Dataset split type. Options: 'synt', 'real', None = default. See dataset_params.py for options.
  'dataset_split_type': 'pbr',
  # bbox type. Options: 'modal', 'amodal'.
  'bbox_type': 'amodal',
  # Folder containing the BOP datasets.
  'datasets_path': "/path/to/makeNOCS/output_data/bop_data",
}
################################################################################
datasets_path = p['datasets_path']
dataset_name = p['dataset']
split = p['dataset_split']
split_type = p['dataset_split_type']
bbox_type = p['bbox_type']
dp_split = dataset_params.get_split_params(datasets_path, dataset_name, split, split_type=split_type)
dp_model = dataset_params.get_model_params(datasets_path, dataset_name)
complete_split = split
if dp_split['split_type'] is not None:
    complete_split += '_' + dp_split['split_type']
CATEGORIES = [{'id': obj_id, 'name':str(obj_id), 'supercategory': dataset_name} for obj_id in dp_model['obj_ids']]
INFO = {
    "description": dataset_name + '_' + split,
    "url": "https://github.com/thodan/bop_toolkit",
    "version": "0.1.0",
    "year": datetime.date.today().year,
    "contributor": "",
    "date_created": datetime.datetime.utcnow().isoformat(' ')
}
for scene_id in dp_split['scene_ids']:
    segmentation_id = 1
    coco_scene_output = {
        "info": INFO,
        "licenses": [],
        "categories": CATEGORIES,
        "images": [],
        "annotations": []
    }
    # Load info about the GT poses (e.g. visibility) for the current scene.
    scene_gt = inout.load_scene_gt(dp_split['scene_gt_tpath'].format(scene_id=scene_id))
    scene_gt_info = inout.load_json(dp_split['scene_gt_info_tpath'].format(scene_id=scene_id), keys_to_int=True)
    # Output coco path
    coco_gt_path = dp_split['scene_gt_coco_tpath'].format(scene_id=scene_id)
    if bbox_type == 'modal':
        coco_gt_path = coco_gt_path.replace('scene_gt_coco', 'scene_gt_coco_modal')
    misc.log('Calculating Coco Annotations - dataset: {} ({}, {}), scene: {}'.format(
          p['dataset'], p['dataset_split'], p['dataset_split_type'], scene_id))
    
    # Go through each view in scene_gt
    for scene_view, inst_list in scene_gt.items():
        im_id = int(scene_view)
        
        img_path = dp_split['rgb_tpath'].format(scene_id=scene_id, im_id=im_id)
        relative_img_path = os.path.relpath(img_path, os.path.dirname(coco_gt_path))
        image_info = pycoco_utils.create_image_info(im_id, relative_img_path, dp_split['im_size'])
        coco_scene_output["images"].append(image_info)
        gt_info = scene_gt_info[scene_view]
        
        # Go through each instance in view
        for idx,inst in enumerate(inst_list): 
            category_info = inst['obj_id']
            visibility = gt_info[idx]['visib_fract']
            # Add ignore flag for objects smaller than 10% visible
            ignore_gt = visibility < 0.1
            mask_visib_p = dp_split['mask_visib_tpath'].format(scene_id=scene_id, im_id=im_id, gt_id=idx)
            mask_full_p = dp_split['mask_tpath'].format(scene_id=scene_id, im_id=im_id, gt_id=idx)
            
            binary_inst_mask_visib = inout.load_depth(mask_visib_p).astype(np.bool)
            if binary_inst_mask_visib.sum() < 1:
                continue
            if bbox_type == 'amodal':
                binary_inst_mask_full = inout.load_depth(mask_full_p).astype(np.bool)
                if binary_inst_mask_full.sum() < 1:
                    continue
                bounding_box = pycoco_utils.bbox_from_binary_mask(binary_inst_mask_full)
            elif bbox_type == 'modal':
                bounding_box = pycoco_utils.bbox_from_binary_mask(binary_inst_mask_visib)
            else:
                raise Exception('{} is not a valid bounding box type'.format(p['bbox_type']))
            annotation_info = pycoco_utils.create_annotation_info(
                segmentation_id, im_id, category_info, binary_inst_mask_visib, bounding_box, tolerance=2, ignore=ignore_gt)
            if annotation_info is not None:
                coco_scene_output["annotations"].append(annotation_info)
            segmentation_id = segmentation_id + 1
    with open(coco_gt_path, 'w') as output_json_file:
        json.dump(coco_scene_output, output_json_file)
上記を実行すると、scene_gt_coco.json が作成されます。
cd makeNOCS/bop_toolkit
python3 scripts/calc_gt_coco.pyscene_gt_coco.json
{"info": {"description": "lm_train", 
          "url": "https://github.com/thodan/bop_toolkit", 
          "version": "0.1.0", 
          "year": 2023, 
          "contributor": "", 
          "date_created": "2023-05-15 06:32:14.908762"
         }, 
 "licenses": [], 
 "categories": [{"id": 1, "name": "1", "supercategory": "lm"}], 
 "images": [{"id": 0, "file_name": "rgb/000000.jpg", "width": 640, "height": 480, "date_captured": "2023-05-15 06:32:14.917984", "license": 1, "coco_url": "", "flickr_url": ""}, 
            {"id": 1, "file_name": "rgb/000001.jpg", "width": 640, "height": 480, "date_captured": "2023-05-15 06:32:15.171713", "license": 1, "coco_url": "", "flickr_url": ""}, 
       {"id": 2, "file_name": "rgb/000002.jpg", "width": 640, "height": 480, "date_captured": "2023-05-15 06:32:15.396059", "license": 1, "coco_url": "", "flickr_url": ""}, COCO 形式の見たことあるような構成になっています。
プログラム説明
import numpy as np
import os
import datetime
import json
from bop_toolkit_lib import pycoco_utils
from bop_toolkit_lib import config
from bop_toolkit_lib import dataset_params
from bop_toolkit_lib import inout
from bop_toolkit_lib import misc
# PARAMETERS.
################################################################################
p = {
  # See dataset_params.py for options.
  'dataset': 'lm',
  # Dataset split. Options: 'train', 'test'.
  'dataset_split': 'train',
  # Dataset split type. Options: 'synt', 'real', None = default. See dataset_params.py for options.
  'dataset_split_type': 'pbr',
  # bbox type. Options: 'modal', 'amodal'.
  'bbox_type': 'amodal',
  # Folder containing the BOP datasets.
  'datasets_path': "/path/to/makeNOCS/output_data/bop_data",
}
################################################################################
今までと異なるのは、bbox_type です。
- modal … 遮蔽された部分を無視して bbox を作成
- amodal … 遮蔽された部分も加味して bbox を作成
今回は物体の中心も知りたいので、amodal とします。
datasets_path = p['datasets_path']
dataset_name = p['dataset']
split = p['dataset_split']
split_type = p['dataset_split_type']
bbox_type = p['bbox_type']
dp_split = dataset_params.get_split_params(datasets_path, dataset_name, split, split_type=split_type)
dp_model = dataset_params.get_model_params(datasets_path, dataset_name)
complete_split = split
if dp_split['split_type'] is not None:
    complete_split += '_' + dp_split['split_type']この辺も今までと同じです。
dataset_params.py からデータを取得しています。
CATEGORIES = [{'id': obj_id, 'name':str(obj_id), 'supercategory': dataset_name} for obj_id in dp_model['obj_ids']]
INFO = {
    "description": dataset_name + '_' + split,
    "url": "https://github.com/thodan/bop_toolkit",
    "version": "0.1.0",
    "year": datetime.date.today().year,
    "contributor": "",
    "date_created": datetime.datetime.utcnow().isoformat(' ')
}COCO データとして出力するための初期情報を取得します。
for scene_id in dp_split['scene_ids']:
    segmentation_id = 1
    coco_scene_output = {
        "info": INFO,
        "licenses": [],
        "categories": CATEGORIES,
        "images": [],
        "annotations": []
    }各シーンの情報を揃えていきます。
segmentation_id は後述します。
# Load info about the GT poses (e.g. visibility) for the current scene.
scene_gt = inout.load_scene_gt(dp_split['scene_gt_tpath'].format(scene_id=scene_id))
scene_gt_info = inout.load_json(dp_split['scene_gt_info_tpath'].format(scene_id=scene_id), keys_to_int=True)
# Output coco path
coco_gt_path = dp_split['scene_gt_coco_tpath'].format(scene_id=scene_id)
if bbox_type == 'modal':
    coco_gt_path = coco_gt_path.replace('scene_gt_coco', 'scene_gt_coco_modal')
misc.log('Calculating Coco Annotations - dataset: {} ({}, {}), scene: {}'.format(
            p['dataset'], p['dataset_split'], p['dataset_split_type'], scene_id))scene_gt は scene_gt.json、scene_gt_info は、scene_gt_info.json を読込みます。
coco_gt_path は出力するファイル名を指定しますが、デフォルトで問題ありません。
bbox_type を modal にした場合は、ファイル名が変わります。
# Go through each view in scene_gt
for scene_view, inst_list in scene_gt.items():
    im_id = int(scene_view)scene_gt.json の情報を順次処理します。
img_path = dp_split['rgb_tpath'].format(scene_id=scene_id, im_id=im_id)
relative_img_path = os.path.relpath(img_path, os.path.dirname(coco_gt_path))
image_info = pycoco_utils.create_image_info(im_id, relative_img_path, dp_split['im_size'])
coco_scene_output["images"].append(image_info)
gt_info = scene_gt_info[scene_view]画像と scene_gt_info.json の情報を読込みます。
# Go through each instance in view
for idx,inst in enumerate(inst_list): 
    category_info = inst['obj_id']
    visibility = gt_info[idx]['visib_fract']
    # Add ignore flag for objects smaller than 10% visible
    ignore_gt = visibility < 0.1
    mask_visib_p = dp_split['mask_visib_tpath'].format(scene_id=scene_id, im_id=im_id, gt_id=idx)
    mask_full_p = dp_split['mask_tpath'].format(scene_id=scene_id, im_id=im_id, gt_id=idx)一枚の画像内のインスタンス毎にループを回していきます。
カテゴリ、可視度(視認度?)、可視部分のマスク画像、マスク画像を読込みます。
binary_inst_mask_visib = inout.load_depth(mask_visib_p).astype(np.bool)
if binary_inst_mask_visib.sum() < 1:
    continue
if bbox_type == 'amodal':
    binary_inst_mask_full = inout.load_depth(mask_full_p).astype(np.bool)
    if binary_inst_mask_full.sum() < 1:
        continue
    bounding_box = pycoco_utils.bbox_from_binary_mask(binary_inst_mask_full)
elif bbox_type == 'modal':
    bounding_box = pycoco_utils.bbox_from_binary_mask(binary_inst_mask_visib)
else:
    raise Exception('{} is not a valid bounding box type'.format(p['bbox_type']))可視部分のマスク画像を読込んで処理していきます。
オブジェクトがすべて隠れている場合は、パスします。
今回は amodal な bbox なので、オブジェクト全体のマスク画像から、bbox 情報を読込みます。
annotation_info = pycoco_utils.create_annotation_info(
       segmentation_id, im_id, category_info, binary_inst_mask_visib, bounding_box, tolerance=2, ignore=ignore_gt)
if annotation_info is not None:
    coco_scene_output["annotations"].append(annotation_info)
segmentation_id = segmentation_id + 1最後に COCO アノテーション情報を作成していきます。
- segmentation_id … インスタンスごとの重複無しの整数ID
- image_id … 画像ごとの重複無しの整数ID
- object_id … カテゴリに登録された番号、今回は 1 のみ
- binary_mask … バイナリ形式のマスクデータ
- bouding_box … [x, y, w, h] の bbox
- mask_encoding_format … デフォルトでは rle
- tolerance … ポリゴンをマスクにフィットさせるための許容誤差
- ignore … このアノテーションを無視するかどうか
with open(coco_gt_path, 'w') as output_json_file:
    json.dump(coco_scene_output, output_json_file)最後に、json データを出力して終了です。
おわりに
今回はCOCO形式のアノテーションデータを作成しました。
次回は、オブジェクトの情報を作成していきます。
また、現在シミュレーションデータから実データに置き換える部分を対応中ですが、もしかしたら上手くいかないかもしれません。
 
  
  
  
  



コメント