framework/simulation/object_detection/objs2Yolov4dataset.py
2024-04-14 18:54:47 +00:00

296 lines
No EOL
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import blenderproc as bproc
"""
objs2Yolov4dataset
Общая задача: обнаружение объекта (Object detection)
Реализуемая функция: создание датасета в формате YoloV4 для серии заданных объектов (*.obj) в заданной сцене (*.blend)
Используется модуль blenderproc
17.02.2023 @shalenikol release 0.1
22.02.2023 @shalenikol release 0.2 исправлен расчёт x,y в convert2relative
"""
import sys
import numpy as np
import argparse
import random
import os
import shutil
import json
def convert2relative(height, width, bbox):
"""
YOLO format use relative coordinates for annotation
"""
x, y, w, h = bbox
x += w/2
y += h/2
return x/width, y/height, w/width, h/height
parser = argparse.ArgumentParser()
parser.add_argument('scene', nargs='?', default="resources/sklad.blend", help="Path to the scene object.")
parser.add_argument('obj_path', nargs='?', default="resources/in_obj", help="Path to the object files.")
parser.add_argument('output_dir', nargs='?', default="output", help="Path to where the final files, will be saved")
parser.add_argument('vhacd_path', nargs='?', default="blenderproc_resources/vhacd", help="The directory in which vhacd should be installed or is already installed.")
parser.add_argument('--imgs', default=2, type=int, help="The number of times the objects should be rendered.")
args = parser.parse_args()
if not os.path.isdir(args.obj_path):
print(f"{args.obj_path} : no object directory")
sys.exit()
if not os.path.isdir(args.output_dir):
os.mkdir(args.output_dir)
bproc.init()
# ? загрузим свет из сцены
#cam = bproc.loader.load_blend(args.scene, data_blocks=["cameras"])
#lights = bproc.loader.load_blend(args.scene, data_blocks=["lights"])
# загрузим объекты
list_files = os.listdir(args.obj_path)
meshs = []
i = 0
for f in list_files:
if (os.path.splitext(f))[1] == ".obj":
f = os.path.join(args.obj_path, f) # путь к файлу объекта
if os.path.isfile(f):
meshs += bproc.loader.load_obj(f)
i += 1
if i == 0:
print("Objects not found")
sys.exit()
for i,o in enumerate(meshs):
o.set_cp("category_id", i+1)
# загрузим сцену
scene = bproc.loader.load_blend(args.scene, data_blocks=["objects"])
#scene = bproc.loader.load_obj(args.scene)
# найдём пол
floor = None
for o in scene:
o.set_cp("category_id", 999)
s = o.get_name()
if s.find("floor") >= 0:
floor = o
if floor == None:
print("Floor not found in the scene")
sys.exit()
floor.enable_rigidbody(False, collision_shape='BOX')
objs = meshs + scene
for obj in meshs:
# Make the object actively participate in the physics simulation
obj.enable_rigidbody(active=True, collision_shape="COMPOUND")
# Also use convex decomposition as collision shapes
obj.build_convex_decomposition_collision_shape(args.vhacd_path)
with open(os.path.join(args.output_dir,"res.txt"), "w") as fh:
# fh.write(str(type(scene[0]))+"\n")
i = 0
for o in objs:
i += 1
loc = o.get_location()
euler = o.get_rotation_euler()
fh.write(f"{i} : {o.get_name()} {loc} {euler}\n")
# define a light and set its location and energy level
light = bproc.types.Light()
light.set_type("POINT")
light.set_location([5, -5, 5])
#light.set_energy(900)
#light.set_color([0.7, 0.7, 0.7])
light1 = bproc.types.Light(name="light1")
light1.set_type("SUN")
light1.set_location([0, 0, 0])
light1.set_rotation_euler([-0.063, 0.6177, -0.1985])
#light1.set_energy(7)
light1.set_color([1, 1, 1])
"""
# Sample its location around the object
light.set_location(bproc.sampler.shell(
center=obj.get_location(),
radius_min=2.5,
radius_max=5,
elevation_min=1,
elevation_max=89
))
"""
# define the camera intrinsics
bproc.camera.set_intrinsics_from_blender_params(1, 640, 480, lens_unit="FOV")
bproc.renderer.enable_segmentation_output(map_by=["category_id", "instance", "name"])
res_dir = os.path.join(args.output_dir, 'coco_data')
# Цикл рендеринга
n_cam_location = 5 # количество случайных локаций камеры
n_cam_poses = 3 # количество сэмплов для каждой локации камеры
# Do multiple times: Position the shapenet objects using the physics simulator and render X images with random camera poses
for r in range(args.imgs):
# Randomly set the color and energy
light.set_color(np.random.uniform([0.5, 0.5, 0.5], [1, 1, 1]))
light.set_energy(random.uniform(500, 1000))
light1.set_energy(random.uniform(3, 11))
for i,o in enumerate(objs):
mat = o.get_materials()[0]
mat.set_principled_shader_value("Specular", random.uniform(0, 1))
mat.set_principled_shader_value("Roughness", random.uniform(0, 1))
mat.set_principled_shader_value("Base Color", np.random.uniform([0, 0, 0, 1], [1, 1, 1, 1]))
mat.set_principled_shader_value("Metallic", random.uniform(0, 1))
# Clear all key frames from the previous run
bproc.utility.reset_keyframes()
# Define a function that samples 6-DoF poses
def sample_pose(obj: bproc.types.MeshObject):
obj.set_location(np.random.uniform([-1, -1.5, 0.2], [1, 2, 1.2])) #[-1, -1, 0], [1, 1, 2]))
obj.set_rotation_euler(bproc.sampler.uniformSO3())
# Sample the poses of all shapenet objects above the ground without any collisions in-between
bproc.object.sample_poses(meshs, objects_to_check_collisions = meshs + [floor], sample_pose_func = sample_pose)
# Run the simulation and fix the poses of the shapenet objects at the end
bproc.object.simulate_physics_and_fix_final_poses(min_simulation_time=4, max_simulation_time=20, check_object_interval=1)
# Find point of interest, all cam poses should look towards it
poi = bproc.object.compute_poi(meshs)
coord_max = [0.1, 0.1, 0.1]
coord_min = [0., 0., 0.]
with open(os.path.join(args.output_dir,"res.txt"), "a") as fh:
fh.write("*****************\n")
fh.write(f"{r}) poi = {poi}\n")
i = 0
for o in meshs:
i += 1
loc = o.get_location()
euler = o.get_rotation_euler()
fh.write(f" {i} : {o.get_name()} {loc} {euler}\n")
for j in range(3):
if loc[j] < coord_min[j]:
coord_min[j] = loc[j]
if loc[j] > coord_max[j]:
coord_max[j] = loc[j]
# Sample up to X camera poses
#an = np.random.uniform(0.78, 1.2) #1. #0.35
for i in range(n_cam_location):
# Sample location
location = bproc.sampler.shell(center=[0, 0, 0],
radius_min=1.1,
radius_max=3.3,
elevation_min=5,
elevation_max=89)
# координата, по которой будем сэмплировать положение камеры
j = random.randint(0, 2)
# разовый сдвиг по случайной координате
d = (coord_max[j] - coord_min[j]) / n_cam_poses
if location[j] < 0:
d = -d
for k in range(n_cam_poses):
# Compute rotation based on vector going from location towards poi
rotation_matrix = bproc.camera.rotation_from_forward_vec(poi - location, inplane_rot=np.random.uniform(-0.7854, 0.7854))
# Add homog cam pose based on location an rotation
cam2world_matrix = bproc.math.build_transformation_mat(location, rotation_matrix)
bproc.camera.add_camera_pose(cam2world_matrix)
location[j] -= d
#world_matrix = bproc.math.build_transformation_mat([2.3, -0.4, 0.66], [1.396, 0., an])
#bproc.camera.add_camera_pose(world_matrix)
#an += 0.2
# render the whole pipeline
data = bproc.renderer.render()
# Write data to coco file
bproc.writer.write_coco_annotations(res_dir,
instance_segmaps=data["instance_segmaps"],
instance_attribute_maps=data["instance_attribute_maps"],
color_file_format='JPEG',
colors=data["colors"],
append_to_existing_output=True)
#загрузим аннотацию
with open(os.path.join(res_dir,"coco_annotations.json"), "r") as fh:
y = json.load(fh)
# список имен объектов
n_obj = 0
obj_list = []
with open(os.path.join(res_dir,"obj.names"), "w") as fh:
for cat in y["categories"]:
if cat["id"] < 999:
n = cat["name"]
i = cat["id"]
obj_list.append([n,i,n_obj])
fh.write(n+"\n")
n_obj += 1
# содадим или очистим папку data для датасета
res_data = os.path.join(res_dir, 'data')
if os.path.isdir(res_data):
for f in os.listdir(res_data):
os.remove(os.path.join(res_data, f))
else:
os.mkdir(res_data)
# список имен файлов с изображениями
fn_image = os.path.join(res_dir,"images.txt")
img_list = []
with open(fn_image, "w") as fh:
for i in y["images"]:
filename = i["file_name"]
shutil.copy(os.path.join(res_dir,filename),res_data)
fh.write(filename.replace('images','data')+"\n")
img_list.append([i["id"], (os.path.split(filename))[1]])
# создадим 2 списка имен файлов для train и valid
n_image_in_series = n_cam_location * n_cam_poses # количество изображений в серии
i = 0
fh = open(fn_image, "r")
f1 = open(os.path.join(res_dir,"i_train.txt"), "w")
f2 = open(os.path.join(res_dir,"i_val.txt"), "w")
for line in fh:
i += 1
if i % n_image_in_series == 0:
f2.write(line)
else:
f1.write(line)
fh.close()
f1.close()
f2.close()
# заполним файлы с метками bbox
for i in y["annotations"]:
cat_id = i["category_id"]
if cat_id < 999:
im_id = i["image_id"]
bbox = i["bbox"]
im_h = i["height"]
im_w = i["width"]
rel = convert2relative(im_h,im_w,bbox)
# находим индекс списка с нужным изображением
j = next(k for k, (x, _) in enumerate(img_list) if x == im_id)
filename = img_list[j][1]
fn = (os.path.splitext(filename))[0] # только имя файла
with open(os.path.join(res_data,fn+".txt"), "a") as fh:
# находим индекс списка с нужным объектом
j = next(k for k, (_, x, _) in enumerate(obj_list) if x == cat_id)
# формат: <target> <x-center> <y-center> <width> <height>
fh.write(f"{obj_list[j][2]} {rel[0]} {rel[1]} {rel[2]} {rel[3]}\n")
# создадим файл описания датасета для darknet
with open(os.path.join(res_dir,"yolov4_objs2.data"), "w") as fh:
fh.write(f"classes = {n_obj}\n")
fh.write("train = i_train.txt\n")
fh.write("valid = i_val.txt\n")
fh.write("names = obj.names\n")
fh.write("backup = backup\n")
fh.write("eval = coco\n")