Buat data sintetis untuk pipeline visi komputer di AWS

Node Sumber: 1726779

Mengumpulkan dan membuat anotasi data gambar adalah salah satu tugas paling intensif sumber daya pada proyek visi komputer mana pun. Diperlukan waktu berbulan-bulan untuk mengumpulkan, menganalisis, dan bereksperimen sepenuhnya dengan aliran gambar pada tingkat yang Anda butuhkan untuk bersaing di pasar saat ini. Bahkan setelah Anda berhasil mengumpulkan data, Anda masih memiliki aliran kesalahan anotasi yang konstan, gambar berbingkai buruk, sejumlah kecil data bermakna di lautan tangkapan yang tidak diinginkan, dan banyak lagi. Hambatan utama inilah yang menjadi alasan mengapa pembuatan data sintetik perlu ada dalam perangkat setiap insinyur modern. Dengan membuat representasi 3D dari objek yang ingin kita modelkan, kita dapat dengan cepat membuat prototipe algoritma sambil mengumpulkan data langsung secara bersamaan.

Dalam posting ini, saya memandu Anda melalui contoh penggunaan perpustakaan animasi open-source Blender untuk membangun pipa data sintetis ujung ke ujung, menggunakan chicken nugget sebagai contoh. Gambar berikut adalah ilustrasi dari data yang dihasilkan dalam posting blog ini.

Apa itu Blender?

Pencampur adalah perangkat lunak grafis 3D sumber terbuka yang terutama digunakan dalam animasi, pencetakan 3D, dan realitas virtual. Ini memiliki rangkaian tali-temali, animasi, dan simulasi yang sangat komprehensif yang memungkinkan penciptaan dunia 3D untuk hampir semua kasus penggunaan visi komputer. Ini juga memiliki komunitas dukungan yang sangat aktif di mana sebagian besar, jika tidak semua, kesalahan pengguna diselesaikan.

Siapkan lingkungan lokal Anda

Kami menginstal dua versi Blender: satu di mesin lokal dengan akses ke GUI, dan yang lainnya di Cloud komputasi elastis Amazon (Amazon EC2) Contoh P2.

Instal Blender dan ZPY

Instal Blender dari Situs web blender.

Kemudian selesaikan langkah-langkah berikut:

  1. Jalankan perintah berikut:
    wget https://mirrors.ocf.berkeley.edu/blender/release/Blender3.2/blender-3.2.0-linux-x64.tar.xz
    sudo tar -Jxf blender-3.2.0-linux-x64.tar.xz --strip-components=1 -C /bin
    rm -rf blender*
    
    /bin/3.2/python/bin/python3.10 -m ensurepip
    /bin/3.2/python/bin/python3.10 -m pip install --upgrade pip

  2. Salin header Python yang diperlukan ke Python versi Blender sehingga Anda dapat menggunakan library non-Blender lainnya:
    wget https://www.python.org/ftp/python/3.10.2/Python-3.10.2.tgz
    tar -xzf Python-3.10.2.tgz
    sudo cp Python-3.10.2/Include/* /bin/3.2/python/include/python3.10

  3. Ganti versi Blender Anda dan instal paksa agar Python yang disediakan Blender berfungsi:
    /bin/3.2/python/bin/python3.10 -m pip install pybind11 pythran Cython numpy==1.22.1
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U Pillow --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U scipy --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U shapely --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U scikit-image --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U gin-config --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U versioneer --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U shapely --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U ptvsd --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U ptvseabornsd --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U zmq --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U pyyaml --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U requests --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U click --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U table-logger --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U tqdm --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U pydash --force
    sudo /bin/3.2/python/bin/python3.10 -m pip install -U matplotlib --force

  4. Unduh zpy dan instal dari sumber:
    git clone https://github.com/ZumoLabs/zpy
    cd zpy
    vi requirements.txt

  5. Ubah versi NumPy menjadi >=1.19.4 dan scikit-image>=0.18.1 untuk melakukan instalasi pada 3.10.2 mungkin dan agar Anda tidak mendapatkan penimpaan apa pun:
    numpy>=1.19.4
    gin-config>=0.3.0
    versioneer
    scikit-image>=0.18.1
    shapely>=1.7.1
    ptvsd>=4.3.2
    seaborn>=0.11.0
    zmq
    pyyaml
    requests
    click
    table-logger>=0.3.6
    tqdm
    pydash

  6. Untuk memastikan kompatibilitas dengan Blender 3.2, masuk ke zpy/render.py dan komentari dua baris berikut (untuk informasi lebih lanjut, lihat Kegagalan Blender 3.0 #54):
    #scene.render.tile_x = tile_size
    #scene.render.tile_y = tile_size

  7. Selanjutnya, instal zpy Perpustakaan:
    /bin/3.2/python/bin/python3.10 setup.py install --user
    /bin/3.2/python/bin/python3.10 -c "import zpy; print(zpy.__version__)"

  8. Unduh versi pengaya dari zpy dari GitHub repo sehingga Anda dapat secara aktif menjalankan instance Anda:
    cd ~
    curl -O -L -C - "https://github.com/ZumoLabs/zpy/releases/download/v1.4.1rc9/zpy_addon-v1.4.1rc9.zip"
    sudo unzip zpy_addon-v1.4.1rc9.zip -d /bin/3.2/scripts/addons/
    mkdir .config/blender/
    mkdir .config/blender/3.2
    mkdir .config/blender/3.2/scripts
    mkdir .config/blender/3.2/scripts/addons/
    mkdir .config/blender/3.2/scripts/addons/zpy_addon/
    sudo cp -r zpy/zpy_addon/* .config/blender/3.2/scripts/addons/zpy_addon/

  9. Simpan file bernama enable_zpy_addon.py di Anda /home direktori dan jalankan perintah pengaktifan, karena Anda tidak memiliki GUI untuk mengaktifkannya:
    import bpy, os
    p = os.path.abspath('zpy_addon-v1.4.1rc9.zip')
    bpy.ops.preferences.addon_install(overwrite=True, filepath=p)
    bpy.ops.preferences.addon_enable(module='zpy_addon')
    bpy.ops.wm.save_userpref()
    
    sudo blender -b -y --python enable_zpy_addon.py

    If zpy-addon tidak menginstal (untuk alasan apa pun), Anda dapat menginstalnya melalui GUI.

  10. Di Blender, di Edit menu, pilih preferensi.
  11. Pilih Pengaya di panel navigasi dan aktifkan zpy.

Anda akan melihat halaman terbuka di GUI, dan Anda akan dapat memilih ZPY. Ini akan mengkonfirmasi bahwa Blender dimuat.

AliceVision dan Meshroom

Instal AliceVision dan Meshrooom dari repo GitHub masing-masing:

FFmpeg

Sistem Anda seharusnya memiliki ffmpeg, tetapi jika tidak, Anda harus Download .

Jerat Instan

Anda dapat mengkompilasi perpustakaan sendiri atau mengunduh binari pra-kompilasi yang tersedia (yang saya lakukan) untuk Jerat Instan.

Siapkan lingkungan AWS Anda

Sekarang kami menyiapkan lingkungan AWS pada instans EC2. Kami mengulangi langkah-langkah dari bagian sebelumnya, tetapi hanya untuk Blender dan zpy.

  1. Di konsol Amazon EC2, pilih Luncurkan instance.
  2. Pilih AMI Anda. Ada beberapa opsi dari sini. Kita dapat memilih image Ubuntu standar, memilih instance GPU, dan kemudian menginstal driver secara manual dan menyiapkan semuanya, atau kita dapat mengambil rute yang mudah dan mulai dengan AMI Deep Learning yang telah dikonfigurasi sebelumnya dan hanya khawatir tentang menginstal Blender.Untuk ini posting, saya menggunakan opsi kedua, dan memilih versi terbaru dari Deep Learning AMI untuk Ubuntu (Versi Deep Learning AMI (Ubuntu 18.04) 61.0).
  3. Untuk Jenis instanceยธ pilih p2.xlarge.
  4. Jika Anda tidak memiliki pasangan kunci, buat yang baru atau pilih yang sudah ada.
  5. Untuk posting ini, gunakan pengaturan default untuk jaringan dan penyimpanan.
  6. Pilih Luncurkan instance.
  7. Pilih Terhubung dan temukan instruksi untuk masuk ke instance kami dari SSH di klien SSH Tab.
  8. Terhubung dengan SSH: ssh -i "your-pem" ubuntu@IPADDRESS.YOUR-REGION.compute.amazonaws.com

Setelah Anda terhubung ke instance Anda, ikuti langkah penginstalan yang sama dari bagian sebelumnya untuk menginstal Blender dan zpy.

Pengumpulan data: pemindaian 3D nugget kami

Untuk langkah ini, saya menggunakan iPhone untuk merekam video 360 derajat dengan kecepatan yang cukup lambat di sekitar nugget saya. Saya memasukkan nugget ayam ke tusuk gigi dan menempelkan tusuk gigi ke meja saya, dan cukup memutar kamera saya di sekitar nugget untuk mendapatkan sudut sebanyak yang saya bisa. Semakin cepat Anda memfilmkan, semakin kecil kemungkinan Anda mendapatkan gambar yang bagus untuk dikerjakan tergantung pada kecepatan rana.

Setelah saya selesai syuting, saya mengirim video ke email saya dan mengekstrak video ke drive lokal. Dari sana, saya menggunakan ffmepg untuk memotong video menjadi bingkai agar penyerapan Meshroom lebih mudah:

mkdir nugget_images
ffmpeg -i VIDEO.mov ffmpeg nugget_images/nugget_%06d.jpg

Buka Meshroom dan gunakan GUI untuk menyeret nugget_images folder ke panel di sebelah kiri. Dari sana, pilih Start dan tunggu beberapa jam (atau kurang) tergantung pada panjang video dan jika Anda memiliki mesin yang mendukung CUDA.

Anda akan melihat sesuatu seperti tangkapan layar berikut ketika hampir selesai.

Pengumpulan data: Manipulasi blender

Ketika rekonstruksi Meshroom kami selesai, selesaikan langkah-langkah berikut:

  1. Buka GUI Blender dan di File menu, pilih impor, Lalu pilih Wavefront (.obj) ke file tekstur yang Anda buat dari Meshroom.
    File harus disimpan di path/to/MeshroomCache/Texturing/uuid-string/texturedMesh.obj.
  2. Muat file dan amati keburukan yang merupakan objek 3D Anda.

    Di sinilah hal itu menjadi sedikit rumit.
  3. Gulir ke sisi kanan atas dan pilih Wireframe ikon di Shading Viewport.
  4. Pilih objek Anda di viewport kanan dan pastikan itu disorot, gulir ke viewport tata letak utama, dan tekan salah satu Tab atau pilih secara manual Mode Edit.
  5. Selanjutnya, manuver viewport sedemikian rupa sehingga memungkinkan diri Anda untuk dapat melihat objek Anda dengan sesedikit mungkin di belakangnya. Anda harus melakukan ini beberapa kali untuk benar-benar melakukannya dengan benar.
  6. Klik dan seret kotak pembatas di atas objek sehingga hanya nugget yang disorot.
  7. Setelah disorot seperti pada tangkapan layar berikut, kami memisahkan nugget kami dari massa 3D dengan mengklik kiri, memilih Terpisah, Dan kemudian Seleksi.

    Kita sekarang pindah ke kanan, di mana kita akan melihat dua objek bertekstur: texturedMesh dan texturedMesh.001.
  8. Objek baru kita seharusnya texturedMesh.001, jadi kita pilih texturedMesh Dan pilihlah Delete untuk menghilangkan massa yang tidak diinginkan.
  9. Pilih objek (texturedMesh.001) di sebelah kanan, pindah ke penampil kami, dan pilih objek, Setel Asal, dan Asal ke Pusat Massa.

Sekarang, jika kita mau, kita bisa memindahkan objek kita ke tengah viewport (atau membiarkannya begitu saja) dan melihatnya dengan segala kemegahannya. Perhatikan lubang hitam besar tempat kami tidak mendapatkan liputan film yang bagus! Kita akan perlu untuk memperbaiki ini.

Untuk membersihkan objek kita dari pengotor piksel, kita mengekspor objek ke file .obj. Pastikan untuk memilih Seleksi Saja saat mengekspor.

Pengumpulan data: Bersihkan dengan Jerat Instan

Sekarang kami memiliki dua masalah: gambar kami memiliki celah piksel yang dibuat oleh pembuatan film kami yang buruk yang perlu kami bersihkan, dan gambar kami sangat padat (yang akan membuat menghasilkan gambar sangat memakan waktu). Untuk mengatasi kedua masalah tersebut, kita perlu menggunakan perangkat lunak yang disebut Instant Meshes untuk mengekstrapolasi permukaan piksel kita untuk menutupi lubang hitam dan juga untuk mengecilkan objek total ke ukuran yang lebih kecil dan kurang padat.

  1. Buka Instant Meshes dan muat yang baru saja disimpan nugget.obj file.
  2. Bawah Bidang orientasi, pilih Memecahkan.
  3. Bawah Bidang posisi, pilih Memecahkan.
    Di sinilah hal itu menjadi menarik. Jika Anda menjelajahi objek Anda dan melihat bahwa garis silang dari pemecah posisi terlihat tidak terhubung, Anda dapat memilih ikon sisir di bawah Bidang orientasi dan menggambar ulang garis dengan benar.
  4. Pilih Memecahkan untuk kedua Bidang orientasi dan Bidang posisi.
  5. Jika semuanya terlihat bagus, ekspor mesh, beri nama seperti nugget_refined.obj, dan simpan ke disk.

Pengumpulan data: Kocok dan panggang!

Karena mesh poli rendah kami tidak memiliki tekstur gambar yang terkait dengannya dan mesh poli tinggi kami memilikinya, kami juga perlu memanggang tekstur poli tinggi ke mesh poli rendah, atau membuat tekstur baru dan menetapkannya ke objek kita. Demi kesederhanaan, kita akan membuat tekstur gambar dari awal dan menerapkannya ke nugget kita.

Saya menggunakan pencarian gambar Google untuk nugget dan hal-hal yang digoreng lainnya untuk mendapatkan gambar permukaan objek yang digoreng dengan resolusi tinggi. Saya menemukan gambar dadih keju goreng beresolusi super tinggi dan membuat gambar baru yang penuh dengan tekstur goreng.

Dengan gambar ini, saya siap untuk menyelesaikan langkah-langkah berikut:

  1. Buka Blender dan muat yang baru nugget_refined.obj dengan cara yang sama Anda memuat objek awal Anda: di File menu, pilih impor, Wavefront (.obj), dan pilih nugget_refined.obj file.
  2. Selanjutnya, pergi ke menu Shading Tab.
    Di bagian bawah Anda akan melihat dua kotak dengan judul BDSF berprinsip dan Keluaran Bahan.
  3. pada Add menu, pilih Tekstur dan Tekstur Gambar.
    An Tekstur Gambar kotak akan muncul.
  4. Pilih Buka Gambar dan muat gambar tekstur goreng Anda.
  5. Seret mouse Anda di antara Warna dalam Tekstur Gambar kotak dan Warna Dasar dalam BDSF berprinsip kotak.

Sekarang nugget Anda seharusnya sudah siap!

Pengumpulan data: Buat variabel lingkungan Blender

Sekarang kita memiliki objek nugget dasar kita, kita perlu membuat beberapa koleksi dan variabel lingkungan untuk membantu kita dalam proses kita.

  1. Klik kiri pada area pemandangan tangan dan pilih Koleksi Baru.
  2. Buat koleksi berikut: LATAR BELAKANG, NUGGET, dan bertelur.
  3. Seret nugget ke NUGGET koleksi dan ganti namanya nugget_base.

Pengumpulan data: Buat pesawat

Kita akan membuat objek latar belakang dari mana nugget kita akan dihasilkan saat kita merender gambar. Dalam kasus penggunaan di dunia nyata, bidang ini adalah tempat nugget kita ditempatkan, seperti baki atau nampan.

  1. pada Add menu, pilih bertautan lalu Pesawat.
    Dari sini, kami pindah ke sisi kanan halaman dan menemukan kotak oranye (Properti Objek).
  2. Dalam majalah Mengubah panel, untuk XYZ Euler, atur X Untuk 46.968, Y ke 46.968, dan Z ke 1.0.
  3. Untuk berdua Lokasi dan Rotasi, atur X, Y, dan Z ke 0.

Pengumpulan data: Atur kamera dan sumbu

Selanjutnya, kita akan mengatur kamera kita dengan benar sehingga kita dapat menghasilkan gambar.

  1. pada Add menu, pilih Empty dan Sumbu Polos.
  2. Beri nama objek Sumbu Utama.
  3. Pastikan sumbu kita adalah 0 untuk semua variabel (jadi langsung di tengah).
  4. Jika Anda memiliki kamera yang sudah dibuat, seret kamera itu ke bawah Sumbu Utama.
  5. Pilih Barang dan Mengubah.
  6. Untuk Lokasi, atur X Untuk 0, Y ke 0, dan Z ke 100.

Pengumpulan data: Ini dia mataharinya

Selanjutnya, kita menambahkan objek Sun.

  1. pada Add menu, pilih Cahaya dan matahari.
    Lokasi objek ini tidak selalu penting selama itu berpusat di suatu tempat di atas objek bidang yang telah kita tetapkan.
  2. Pilih ikon bola lampu hijau di panel kanan bawah (Properti Data Objek) dan atur kekuatan ke 5.0.
  3. Ulangi prosedur yang sama untuk menambahkan a Cahaya objek dan meletakkannya di tempat acak di atas pesawat.

Pengumpulan data: Unduh latar belakang acak

Untuk menyuntikkan keacakan ke dalam gambar kami, kami mengunduh sebanyak mungkin tekstur acak dari tekstur.ninja yang kita bisa (misalnya, batu bata). Unduh ke folder di dalam ruang kerja Anda yang disebut random_textures. Saya mengunduh sekitar 50.

Hasilkan gambar

Sekarang kita sampai pada hal-hal yang menyenangkan: menghasilkan gambar.

Pipeline pembuatan gambar: Object3D dan DensityController

Mari kita mulai dengan beberapa definisi kode:

class Object3D:
	'''
	object container to store mesh information about the
	given object

	Returns
	the Object3D object
	'''
	def __init__(self, object: Union[bpy.types.Object, str]):
		"""Creates a Object3D object.

		Args:
		obj (Union[bpy.types.Object, str]): Scene object (or it's name)
		"""
		self.object = object
		self.obj_poly = None
		self.mat = None
		self.vert = None
		self.poly = None
		self.bvht = None
		self.calc_mat()
		self.calc_world_vert()
		self.calc_poly()
		self.calc_bvht()

	def calc_mat(self) -> None:
		"""store an instance of the object's matrix_world"""
		self.mat = self.object.matrix_world

	def calc_world_vert(self) -> None:
		"""calculate the verticies from object's matrix_world perspective"""
		self.vert = [self.mat @ v.co for v in self.object.data.vertices]
		self.obj_poly = np.array(self.vert)

	def calc_poly(self) -> None:
		"""store an instance of the object's polygons"""
		self.poly = [p.vertices for p in self.object.data.polygons]

	def calc_bvht(self) -> None:
		"""create a BVHTree from the object's polygon"""
		self.bvht = BVHTree.FromPolygons( self.vert, self.poly )

	def regenerate(self) -> None:
		"""reinstantiate the object's variables;
		used when the object is manipulated after it's creation"""
		self.calc_mat()
		self.calc_world_vert()
		self.calc_poly()
		self.calc_bvht()

	def __repr__(self):
		return "Object3D: " + self.object.__repr__()

Kami pertama-tama mendefinisikan Kelas wadah dasar dengan beberapa properti penting. Kelas ini terutama ada untuk memungkinkan kita membuat pohon BVH (cara untuk merepresentasikan objek nugget kita dalam ruang 3D), di mana kita harus menggunakan BVHTree.overlap metode untuk melihat apakah dua objek nugget yang dihasilkan secara independen tumpang tindih di ruang 3D kami. Lebih lanjut tentang ini nanti.

Potongan kode kedua adalah pengontrol kepadatan kami. Ini berfungsi sebagai cara untuk mengikat diri kita pada aturan realitas dan bukan dunia 3D. Misalnya, di dunia Blender 3D, objek di Blender bisa ada di dalam satu sama lain; namun, kecuali seseorang melakukan beberapa ilmu aneh pada nugget ayam kami, kami ingin memastikan tidak ada dua nugget yang tumpang tindih dengan tingkat yang membuatnya tidak realistis secara visual.

Kami menggunakan kami Plane objek untuk menelurkan satu set kubus tak terlihat terbatas yang dapat ditanyakan pada waktu tertentu untuk melihat apakah ruang tersebut ditempati atau tidak.


Lihat kode berikut:

class DensityController:
    """Container that controlls the spacial relationship between 3D objects

    Returns:
        DensityController: The DensityController object.
    """
    def __init__(self):
        self.bvhtrees = None
        self.overlaps = None
        self.occupied = None
        self.unoccupied = None
        self.objects3d = []

    def auto_generate_kdtree_cubes(
        self,
        num_objects: int = 100, # max size of nuggets
    ) -> None:
        """
        function to generate physical kdtree cubes given a plane of -resize- size
        this allows us to access each cube's overlap/occupancy status at any given
        time
        
        creates a KDTree collection, a cube, a set of individual cubes, and the 
        BVHTree object for each individual cube

        Args:
            resize (Tuple[float]): the size of a cube to create XYZ.
            cuts (int): how many cuts are made to the cube face
                12 cuts == 13 Rows x 13 Columns  
        """

Dalam cuplikan berikut, kami memilih nugget dan membuat kubus pembatas di sekitar nugget itu. Kubus ini mewakili ukuran satu pseudo-voxel dari objek psuedo-kdtree kita. Kita perlu menggunakan bpy.context.view_layer.update() fungsi karena ketika kode ini dijalankan dari dalam fungsi atau skrip vs blender-gui, tampaknya view_layer tidak diperbarui secara otomatis.

        # read the nugget,
        # see how large the cube needs to be to encompass a single nugget
        # then touch a parameter to allow it to be smaller or larger (eg more touching)
        bpy.context.view_layer.objects.active = bpy.context.scene.objects.get('nugget_base')
        bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
        #create a cube for the bounding box
        bpy.ops.mesh.primitive_cube_add(location=Vector((0,0,0))) 
        #our new cube is now the active object, so we can keep track of it in a variable:
        bound_box = bpy.context.active_object
        bound_box.name = 'CUBE1'
        bpy.context.view_layer.update()
        #copy transforms
        nug_dims = bpy.data.objects["nugget_base"].dimensions
        bpy.data.objects["CUBE1"].dimensions = nug_dims
        bpy.context.view_layer.update()
        bpy.data.objects["CUBE1"].location = bpy.data.objects["nugget_base"].location
        bpy.context.view_layer.update()
        bpy.data.objects["CUBE1"].rotation_euler = bpy.data.objects["nugget_base"].rotation_euler
        bpy.context.view_layer.update()
        print("bound_box.dimensions: ", bound_box.dimensions)
        print("bound_box.location:", bound_box.location)

Selanjutnya, kami sedikit memperbarui objek kubus kami sehingga panjang dan lebarnya persegi, berbeda dengan ukuran alami nugget yang dibuatnya:

        # this cube created isn't always square, but we're going to make it square
        # to fit into our 
        x, y, z = bound_box.dimensions
        v = max(x, y)
        if np.round(v) < v:
            v = np.round(v)+1
        bb_x, bb_y = v, v
        bound_box.dimensions = Vector((v, v, z))
        bpy.context.view_layer.update()
        print("bound_box.dimensions updated: ", bound_box.dimensions)
        # now we generate a plane
        # calc the size of the plane given a max number of boxes.

Sekarang kami menggunakan objek kubus yang diperbarui untuk membuat bidang yang dapat menampung volumetrik num_objects jumlah nugget:

        x, y, z = bound_box.dimensions
        bb_loc = bound_box.location
        bb_rot_eu = bound_box.rotation_euler
        min_area = (x*y)*num_objects
        min_length = min_area / num_objects
        print(min_length)
        # now we generate a plane
        # calc the size of the plane given a max number of boxes.
        bpy.ops.mesh.primitive_plane_add(location=Vector((0,0,0)), size = min_length)
        plane = bpy.context.selected_objects[0]
        plane.name = 'PLANE'
        # move our plane to our background collection
        # current_collection = plane.users_collection
        link_object('PLANE', 'BACKGROUND')
        bpy.context.view_layer.update()

Kami mengambil objek pesawat kami dan membuat kubus raksasa dengan panjang dan lebar yang sama dengan bidang kami, dengan tinggi kubus nugget kami, KUBUS1:

        # New Collection
        my_coll = bpy.data.collections.new("KDTREE")
        # Add collection to scene collection
        bpy.context.scene.collection.children.link(my_coll)
        # now we generate cubes based on the size of the plane.
        bpy.ops.mesh.primitive_cube_add(location=Vector((0,0,0)), size = min_length)
        bpy.context.view_layer.update()
        cube = bpy.context.selected_objects[0]
        cube_dimensions = cube.dimensions
        bpy.context.view_layer.update()
        cube.dimensions = Vector((cube_dimensions[0], cube_dimensions[1], z))
        bpy.context.view_layer.update()
        cube.location = bb_loc
        bpy.context.view_layer.update()
        cube.rotation_euler = bb_rot_eu
        bpy.context.view_layer.update()
        cube.name = 'cube'
        bpy.context.view_layer.update()
        current_collection = cube.users_collection
        link_object('cube', 'KDTREE')
        bpy.context.view_layer.update()

Dari sini, kami ingin membuat voxel dari kubus kami. Kami mengambil jumlah kubus yang akan kami muat num_objects dan kemudian potong dari objek kubus kita. Kami mencari mesh-face yang menghadap ke atas dari kubus kami, dan kemudian memilih wajah itu untuk membuat potongan kami. Lihat kode berikut:

        # get the bb volume and make the proper cuts to the object 
        bb_vol = x*y*z
        cube_vol = cube_dimensions[0]*cube_dimensions[1]*cube_dimensions[2]
        n_cubes = cube_vol / bb_vol
        cuts = n_cubes / ((x+y) / 2)
        cuts = int(np.round(cuts)) - 1 # 
        # select the cube
        for object in bpy.data.objects:
            object.select_set(False)
        bpy.context.view_layer.update()
        for object in bpy.data.objects:
            object.select_set(False)
        bpy.data.objects['cube'].select_set(True) # Blender 2.8x
        bpy.context.view_layer.objects.active = bpy.context.scene.objects.get('cube')
        # set to edit mode
        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
        print('edit mode success')
        # get face_data
        context = bpy.context
        obj = context.edit_object
        me = obj.data
        mat = obj.matrix_world
        bm = bmesh.from_edit_mesh(me)
        up_face = None
        # select upwards facing cube-face
        # https://blender.stackexchange.com/questions/43067/get-a-face-selected-pointing-upwards
        for face in bm.faces:
            if (face.normal-UP_VECTOR).length < EPSILON:
                up_face = face
                break
        assert(up_face)
        # subdivide the edges to get the perfect kdtree cubes
        bmesh.ops.subdivide_edges(bm,
                edges=up_face.edges,
                use_grid_fill=True,
                cuts=cuts)
        bpy.context.view_layer.update()
        # get the center point of each face

Terakhir, kami menghitung bagian tengah wajah atas setiap potongan yang kami buat dari kubus besar kami dan membuat kubus sebenarnya dari potongan tersebut. Masing-masing kubus yang baru dibuat ini mewakili satu bagian ruang untuk menelurkan atau memindahkan nugget di sekitar bidang kita. Lihat kode berikut:

        face_data = {}
        sizes = []
        for f, face in enumerate(bm.faces): 
            face_data[f] = {}
            face_data[f]['calc_center_bounds'] = face.calc_center_bounds()
            loc = mat @ face_data[f]['calc_center_bounds']
            face_data[f]['loc'] = loc
            sizes.append(loc[-1])
        # get the most common cube-z; we use this to determine the correct loc
        counter = Counter()
        counter.update(sizes)
        most_common = counter.most_common()[0][0]
        cube_loc = mat @ cube.location
        # get out of edit mode
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
        # go to new colection
        bvhtrees = {}
        for f in face_data:
            loc = face_data[f]['loc']
            loc = mat @ face_data[f]['calc_center_bounds']
            print(loc)
            if loc[-1] == most_common:
                # set it back down to the floor because the face is elevated to the
                # top surface of the cube
                loc[-1] = cube_loc[-1]
                bpy.ops.mesh.primitive_cube_add(location=loc, size = x)
                cube = bpy.context.selected_objects[0]
                cube.dimensions = Vector((x, y, z))
                # bpy.context.view_layer.update()
                cube.name = "cube_{}".format(f)
                #my_coll.objects.link(cube)
                link_object("cube_{}".format(f), 'KDTREE')
                #bpy.context.view_layer.update()
                bvhtrees[f] = {
                    'occupied' : 0,
                    'object' : Object3D(cube)
                }
        for object in bpy.data.objects:
            object.select_set(False)
        bpy.data.objects['CUBE1'].select_set(True) # Blender 2.8x
        bpy.ops.object.delete()
        return bvhtrees

Selanjutnya, kami mengembangkan algoritme yang memahami kubus mana yang ditempati pada waktu tertentu, menemukan objek mana yang tumpang tindih satu sama lain, dan memindahkan objek yang tumpang tindih secara terpisah ke ruang kosong. Kami tidak akan dapat menghilangkan semua tumpang tindih sepenuhnya, tetapi kami dapat membuatnya terlihat cukup nyata.



Lihat kode berikut:

    def find_occupied_space(
        self, 
        objects3d: List[Object3D],
    ) -> None:
        """
        discover which cube's bvhtree is occupied in our kdtree space

        Args:
            list of Object3D objects

        """
        count = 0
        occupied = []
        for i in self.bvhtrees:
            bvhtree = self.bvhtrees[i]['object']
            for object3d in objects3d:
                if object3d.bvht.overlap(bvhtree.bvht):
                    self.bvhtrees[i]['occupied'] = 1

    def find_overlapping_objects(
        self, 
        objects3d: List[Object3D],
    ) -> List[Tuple[int]]:
        """
        returns which Object3D objects are overlapping

        Args:
            list of Object3D objects
        
        Returns:
            List of indicies from objects3d that are overlap
        """
        count = 0
        overlaps = []
        for i, x_object3d in enumerate(objects3d):
            for ii, y_object3d in enumerate(objects3d[i+1:]):
                if x_object3d.bvht.overlap(y_object3d.bvht):
                    overlaps.append((i, ii))
        return overlaps

    def calc_most_overlapped(
        self,
        overlaps: List[Tuple[int]]
    ) -> List[Tuple[int]]:
        """
        Algorithm to count the number of edges each index has
        and return a sorted list from most->least with the number
        of edges each index has. 

        Args:
            list of indicies that are overlapping
        
        Returns:
            list of indicies with the total number of overlapps they have 
            [index, count]
        """
        keys = {}
        for x,y in overlaps:
            if x not in keys:
                keys[x] = 0
            if y not in keys:
                keys[y] = 0
            keys[x]+=1
            keys[y]+=1
        # sort by most edges first
        index_counts = sorted(keys.items(), key=lambda x: x[1])[::-1]
        return index_counts
    
    def get_random_unoccupied(
        self
    ) -> Union[int,None]:
        """
        returns a randomly chosen unoccuped kdtree cube

        Return
            either the kdtree cube's key or None (meaning all spaces are
            currently occupied)
            Union[int,None]
        """
        unoccupied = []
        for i in self.bvhtrees:
            if not self.bvhtrees[i]['occupied']:
                unoccupied.append(i)
        if unoccupied:
            random.shuffle(unoccupied)
            return unoccupied[0]
        else:
            return None

    def regenerate(
        self,
        iterable: Union[None, List[Object3D]] = None
    ) -> None:
        """
        this function recalculates each objects world-view information
        we default to None, which means we're recalculating the self.bvhtree cubes

        Args:
            iterable (None or List of Object3D objects). if None, we default to
            recalculating the kdtree
        """
        if isinstance(iterable, list):
            for object in iterable:
                object.regenerate()
        else:
            for idx in self.bvhtrees:
                self.bvhtrees[idx]['object'].regenerate()
                self.update_tree(idx, occupied=0)       

    def process_trees_and_objects(
        self,
        objects3d: List[Object3D],
    ) -> List[Tuple[int]]:
        """
        This function finds all overlapping objects within objects3d,
        calculates the objects with the most overlaps, searches within
        the kdtree cube space to see which cubes are occupied. It then returns 
        the edge-counts from the most overlapping objects

        Args:
            list of Object3D objects
        Returns
            this returns the output of most_overlapped
        """
        overlaps = self.find_overlapping_objects(objects3d)
        most_overlapped = self.calc_most_overlapped(overlaps)
        self.find_occupied_space(objects3d)
        return most_overlapped

    def move_objects(
        self, 
        objects3d: List[Object3D],
        most_overlapped: List[Tuple[int]],
        z_increase_offset: float = 2.,
    ) -> None:
        """
        This function iterates through most-overlapped, and uses 
        the index to extract the matching object from object3d - it then
        finds a random unoccupied kdtree cube and moves the given overlapping
        object to that space. It does this for each index from the most-overlapped
        function

        Args:
            objects3d: list of Object3D objects
            most_overlapped: a list of tuples (index, count) - where index relates to
                where it's found in objects3d and count - how many times it overlaps 
                with other objects
            z_increase_offset: this value increases the Z value of the object in order to
                make it appear as though it's off the floor. If you don't augment this value
                the object looks like it's 'inside' the ground plane
        """
        for idx, cnt in most_overlapped:
            object3d = objects3d[idx]
            unoccupied_idx = self.get_random_unoccupied()
            if unoccupied_idx:
                object3d.object.location =  self.bvhtrees[unoccupied_idx]['object'].object.location
                # ensure the nuggest is above the groundplane
                object3d.object.location[-1] = z_increase_offset
                self.update_tree(unoccupied_idx, occupied=1)
    
    def dynamic_movement(
        self, 
        objects3d: List[Object3D],
        tries: int = 100,
        z_offset: float = 2.,
    ) -> None:
        """
        This function resets all objects to get their current positioning
        and randomly moves objects around in an attempt to avoid any object
        overlaps (we don't want two objects to be spawned in the same position)

        Args:
            objects3d: list of Object3D objects
            tries: int the number of times we want to move objects to random spaces
                to ensure no overlaps are present.
            z_offset: this value increases the Z value of the object in order to
                make it appear as though it's off the floor. If you don't augment this value
                the object looks like it's 'inside' the ground plane (see `move_objects`)
        """
    
        # reset all objects
        self.regenerate(objects3d)
        # regenerate bvhtrees
        self.regenerate(None)

        most_overlapped = self.process_trees_and_objects(objects3d)
        attempts = 0
        while most_overlapped:
            if attempts>=tries:
                break
            self.move_objects(objects3d, most_overlapped, z_offset)
            attempts+=1
            # recalc objects
            self.regenerate(objects3d)
            # regenerate bvhtrees
            self.regenerate(None)
            # recalculate overlaps
            most_overlapped = self.process_trees_and_objects(objects3d)

    def generate_spawn_point(
        self,
    ) -> Vector:
        """
        this function generates a random spawn point by finding which
        of the kdtree-cubes are unoccupied, and returns one of those

        Returns
            the Vector location of the kdtree-cube that's unoccupied
        """
        idx = self.get_random_unoccupied()
        print(idx)
        self.update_tree(idx, occupied=1)
        return self.bvhtrees[idx]['object'].object.location

    def update_tree(
        self,
        idx: int,
        occupied: int,
    ) -> None:
        """
        this function updates the given state (occupied vs. unoccupied) of the
        kdtree given the idx

        Args:
            idx: int
            occupied: int
        """
        self.bvhtrees[idx]['occupied'] = occupied

Jalur pembuatan gambar: Proses keren

Di bagian ini, kami memecah apa yang kami run fungsi sedang dilakukan.

Kami menginisialisasi kami DensityController dan buat sesuatu yang disebut penghemat menggunakan ImageSaver dari zpy. Ini memungkinkan kita untuk menyimpan gambar yang dirender dengan mudah ke lokasi mana pun yang kita pilih. Kami kemudian menambahkan kami nugget kategori (dan jika kami memiliki lebih banyak kategori, kami akan menambahkannya di sini). Lihat kode berikut:

@gin.configurable("run")
@zpy.blender.save_and_revert
def run(
    max_num_nuggets: int = 100,
    jitter_mesh: bool = True,
    jitter_nugget_scale: bool = True,
    jitter_material: bool = True,
    jitter_nugget_material: bool = False,
    number_of_random_materials: int = 50,
    nugget_texture_path: str = os.getcwd()+"/nugget_textures",
    annotations_path = os.getcwd()+'/nugget_data',
):
    """
    Main run function.
    """
    density_controller = DensityController()
    # Random seed results in unique behavior
    zpy.blender.set_seed(random.randint(0,1000000000))

    # Create the saver object
    saver = zpy.saver_image.ImageSaver(
        description="Image of the randomized Amazon nuggets",
        output_dir=annotations_path,
    )
    saver.add_category(name="nugget")

Selanjutnya, kita perlu membuat objek sumber dari mana kita menelurkan nugget salinan; dalam hal ini adalah nugget_base yang kami buat:

    # Make a list of source nugget objects
    source_nugget_objects = []
    for obj in zpy.objects.for_obj_in_collections(
        [
            bpy.data.collections["NUGGET"],
        ]
    ):
        assert(obj!=None)

        # pass on everything not named nugget
        if 'nugget_base' not in obj.name:
            print('passing on {}'.format(obj.name))
            continue
        zpy.objects.segment(obj, name="nugget", as_category=True) #color=nugget_seg_color
        print("zpy.objects.segment: check {}".format(obj.name))
        source_nugget_objects.append(obj.name)

Sekarang setelah kita memiliki nugget dasar, kita akan menyimpan pose dunia (lokasi) dari semua objek lain sehingga setelah setiap rendering berjalan, kita dapat menggunakan pose yang disimpan ini untuk menginisialisasi ulang render. Kami juga memindahkan nugget basis kami sepenuhnya sehingga kdtree tidak merasakan ruang yang ditempati. Akhirnya, kami menginisialisasi objek kdtree-cube kami. Lihat kode berikut:

    # move nugget point up 10 z's so it won't collide with base-cube
    bpy.data.objects["nugget_base"].location[-1] = 10

    # Save the position of the camera and light
    # create light and camera
    zpy.objects.save_pose("Camera")
    zpy.objects.save_pose("Sun")
    zpy.objects.save_pose("Plane")
    zpy.objects.save_pose("Main Axis")
    axis = bpy.data.objects['Main Axis']
    print('saving poses')
    # add some parameters to this 

    # get the plane-3d object
    plane3d = Object3D(bpy.data.objects['Plane'])

    # generate kdtree cubes
    density_controller.generate_kdtree_cubes()

Kode berikut mengumpulkan latar belakang yang kami unduh dari texture.ninja, di mana mereka akan digunakan untuk diproyeksikan secara acak ke pesawat kami:

    # Pre-create a bunch of random textures
    #random_materials = [
    #    zpy.material.random_texture_mat() for _ in range(number_of_random_materials)
    #]
    p = os.path.abspath(os.getcwd()+'/random_textures')
    print(p)
    random_materials = []
    for x in os.listdir(p):
        texture_path = Path(os.path.join(p,x))
        y = zpy.material.make_mat_from_texture(texture_path, name=texture_path.stem)
        random_materials.append(y)
    #print(random_materials[0])

    # Pre-create a bunch of random textures
    random_nugget_materials = [
        random_nugget_texture_mat(Path(nugget_texture_path)) for _ in range(number_of_random_materials)
    ]

Di sinilah keajaiban dimulai. Kami pertama-tama membuat ulang kdtree-cubes untuk menjalankan ini sehingga kami dapat memulai yang baru:

    # Run the sim.
    for step_idx in zpy.blender.step():
        density_controller.generate_kdtree_cubes()

        objects3d = []
        num_nuggets = random.randint(40, max_num_nuggets)
        log.info(f"Spawning {num_nuggets} nuggets.")
        spawned_nugget_objects = []
        for _ in range(num_nuggets):

Kami menggunakan pengontrol kepadatan kami untuk menghasilkan titik spawn acak untuk nugget kami, buat salinan nugget_base, dan pindahkan salinan ke titik spawn yang dihasilkan secara acak:

            # Choose location to spawn nuggets
            spawn_point = density_controller.generate_spawn_point()
            # manually spawn above the floor
            # spawn_point[-1] = 1.8 #2.0

            # Pick a random object to spawn
            _name = random.choice(source_nugget_objects)
            log.info(f"Spawning a copy of source nugget {_name} at {spawn_point}")
            obj = zpy.objects.copy(
                bpy.data.objects[_name],
                collection=bpy.data.collections["SPAWNED"],
                is_copy=True,
            )

            obj.location = spawn_point
            obj.matrix_world = mathutils.Matrix.Translation(spawn_point)
            spawned_nugget_objects.append(obj)

Selanjutnya, kita acak ukuran nugget, mesh nugget, dan skala nugget sehingga tidak ada dua nugget yang terlihat sama:

            # Segment the newly spawned nugget as an instance
            zpy.objects.segment(obj)

            # Jitter final pose of the nugget a little
            zpy.objects.jitter(
                obj,
                rotate_range=(
                    (0.0, 0.0),
                    (0.0, 0.0),
                    (-math.pi * 2, math.pi * 2),
                ),
            )

            if jitter_nugget_scale:
                # Jitter the scale of each nugget
                zpy.objects.jitter(
                    obj,
                    scale_range=(
                        (0.8, 2.0), #1.2
                        (0.8, 2.0), #1.2
                        (0.8, 2.0), #1.2
                    ),
                )

            if jitter_mesh:
                # Jitter (deform) the mesh of each nugget
                zpy.objects.jitter_mesh(
                    obj=obj,
                    scale=(
                        random.uniform(0.01, 0.03),
                        random.uniform(0.01, 0.03),
                        random.uniform(0.01, 0.03),
                    ),
                )

            if jitter_nugget_material:
                # Jitter the material (apperance) of each nugget
                for i in range(len(obj.material_slots)):
                    obj.material_slots[i].material = random.choice(random_nugget_materials)
                    zpy.material.jitter(obj.material_slots[i].material)          

Kami mengubah salinan nugget kami menjadi Object3D objek tempat kita menggunakan fungsionalitas pohon BVH untuk melihat apakah bidang kita berpotongan atau tumpang tindih dengan wajah atau simpul apa pun pada salinan nugget kita. Jika kita menemukan tumpang tindih dengan pesawat, kita cukup memindahkan nugget ke atas pada sumbu Z-nya. Lihat kode berikut:

            # create 3d obj for movement
            nugget3d = Object3D(obj)

            # make sure the bottom most part of the nugget is NOT
            # inside the plane-object       
            plane_overlap(plane3d, nugget3d)

            objects3d.append(nugget3d)

Sekarang semua nugget dibuat, kami menggunakan DensityController untuk memindahkan nugget sehingga kita memiliki jumlah tumpang tindih minimum, dan nugget yang tumpang tindih tidak terlihat mengerikan:

        # ensure objects aren't on top of each other
        density_controller.dynamic_movement(objects3d)

Dalam kode berikut: kami mengembalikan Camera dan Main Axis berpose dan pilih secara acak seberapa jauh kamera ke Plane obyek:

        # Return camera to original position
        zpy.objects.restore_pose("Camera")
        zpy.objects.restore_pose("Main Axis")
        zpy.objects.restore_pose("Camera")
        zpy.objects.restore_pose("Main Axis")

        # assert these are the correct versions...
        assert(bpy.data.objects["Camera"].location == Vector((0,0,100)))
        assert(bpy.data.objects["Main Axis"].location == Vector((0,0,0)))
        assert(bpy.data.objects["Main Axis"].rotation_euler == Euler((0,0,0)))

        # alter the Z ditance with the camera
        bpy.data.objects["Camera"].location = (0, 0, random.uniform(0.75, 3.5)*100)

Kami memutuskan seberapa acak kami ingin kamera melakukan perjalanan di sepanjang Main Axis. Bergantung pada apakah kita menginginkannya terutama di atas kepala atau jika kita sangat peduli dengan sudut dari mana ia melihat papan, kita dapat menyesuaikan top_down_mostly parameter tergantung pada seberapa baik model pelatihan kami mengambil sinyal "Apa sih nugget itu?"

        # alter the main-axis beta/gamma params
        top_down_mostly = False 
        if top_down_mostly:
            zpy.objects.rotate(
                bpy.data.objects["Main Axis"],
                rotation=(
                    random.uniform(0.05, 0.05),
                    random.uniform(0.05, 0.05),
                    random.uniform(0.05, 0.05),
                ),
            )
        else:
            zpy.objects.rotate(
                bpy.data.objects["Main Axis"],
                rotation=(
                    random.uniform(-1., 1.),
                    random.uniform(-1., 1.),
                    random.uniform(-1., 1.),
                ),
            )

        print(bpy.data.objects["Main Axis"].rotation_euler)
        print(bpy.data.objects["Camera"].location)

Dalam kode berikut, kami melakukan hal yang sama dengan Sun objek, dan secara acak memilih tekstur untuk Plane obyek:

        # change the background material
        # Randomize texture of shelf, floors and walls
        for obj in bpy.data.collections["BACKGROUND"].all_objects:
            for i in range(len(obj.material_slots)):
                # TODO
                # Pick one of the random materials
                obj.material_slots[i].material = random.choice(random_materials)
                if jitter_material:
                    zpy.material.jitter(obj.material_slots[i].material)
                # Sets the material relative to the object
                obj.material_slots[i].link = "OBJECT"
        # Pick a random hdri (from the local textures folder for background background)
        zpy.hdris.random_hdri()
        # Return light to original position
        zpy.objects.restore_pose("Sun")

        # Jitter the light position
        zpy.objects.jitter(
            "Sun",
            translate_range=(
                (-5, 5),
                (-5, 5),
                (-5, 5),
            ),
        )
        bpy.data.objects["Sun"].data.energy = random.uniform(0.5, 7)

Terakhir, kami menyembunyikan semua objek yang tidak ingin kami render: the nugget_base dan seluruh struktur kubus kami:

# we hide the cube objects
for obj in # we hide the cube objects for obj in bpy.data.objects: if 'cube' in obj.name: obj.hide_render = True try: zpy.objects.toggle_hidden(obj, hidden=True) except: # deal with this exception here... pass # we hide our base nugget object bpy.data.objects["nugget_base"].hide_render = True zpy.objects.toggle_hidden(bpy.data.objects["nugget_base"], hidden=True)

Terakhir, kami menggunakan zpy untuk membuat adegan kami, menyimpan gambar kami, dan kemudian menyimpan anotasi kami. Untuk posting ini, saya membuat beberapa perubahan kecil pada zpy pustaka anotasi untuk kasus penggunaan khusus saya (anotasi per gambar alih-alih satu file per proyek), tetapi Anda tidak harus melakukannya untuk tujuan posting ini).

        # create the image name
        image_uuid = str(uuid.uuid4())

        # Name for each of the output images
        rgb_image_name = format_image_string(image_uuid, 'rgb')
        iseg_image_name = format_image_string(image_uuid, 'iseg')
        depth_image_name = format_image_string(image_uuid, 'depth')

        zpy.render.render(
            rgb_path=saver.output_dir / rgb_image_name,
            iseg_path=saver.output_dir / iseg_image_name,
            depth_path=saver.output_dir / depth_image_name,
        )

        # Add images to saver
        saver.add_image(
            name=rgb_image_name,
            style="default",
            output_path=saver.output_dir / rgb_image_name,
            frame=step_idx,
        )
    
        saver.add_image(
            name=iseg_image_name,
            style="segmentation",
            output_path=saver.output_dir / iseg_image_name,
            frame=step_idx,
        )
        saver.add_image(
            name=depth_image_name,
            style="depth",
            output_path=saver.output_dir / depth_image_name,
            frame=step_idx,
        )

        # ideally in this thread, we'll open the anno file
        # and write to it directly, saving it after each generation
        for obj in spawned_nugget_objects:
            # Add annotation to segmentation image
            saver.add_annotation(
                image=rgb_image_name,
                category="nugget",
                seg_image=iseg_image_name,
                seg_color=tuple(obj.seg.instance_color),
            )

        # Delete the spawned nuggets
        zpy.objects.empty_collection(bpy.data.collections["SPAWNED"])

        # Write out annotations
        saver.output_annotated_images()
        saver.output_meta_analysis()

        # # ZUMO Annotations
        _output_zumo = _OutputZUMO(saver=saver, annotation_filename = Path(image_uuid + ".zumo.json"))
        _output_zumo.output_annotations()
        # change the name here..
        saver.output_annotated_images()
        saver.output_meta_analysis()

        # remove the memory of the annotation to free RAM
        saver.annotations = []
        saver.images = {}
        saver.image_name_to_id = {}
        saver.seg_annotations_color_to_id = {}

    log.info("Simulation complete.")

if __name__ == "__main__":

    # Set the logger levels
    zpy.logging.set_log_levels("info")

    # Parse the gin-config text block
    # hack to read a specific gin config
    parse_config_from_file('nugget_config.gin')

    # Run the sim
    run()

Voila!

Jalankan skrip pembuatan tanpa kepala

Sekarang kita memiliki file Blender yang disimpan, nugget yang kita buat, dan semua informasi pendukung, mari kita zip direktori kerja kita dan scp ke mesin GPU kami atau mengunggahnya melalui Layanan Penyimpanan Sederhana Amazon (Amazon S3) atau layanan lain:

tar cvf working_blender_dir.tar.gz working_blender_dir
scp -i "your.pem" working_blender_dir.tar.gz ubuntu@EC2-INSTANCE.compute.amazonaws.com:/home/ubuntu/working_blender_dir.tar.gz

Masuk ke instans EC2 Anda dan dekompresi folder working_blender Anda:

tar xvf working_blender_dir.tar.gz

Sekarang kami membuat data kami dengan segala kemegahannya:

blender working_blender_dir/nugget.blend --background --python working_blender_dir/create_synthetic_nuggets.py

Script harus berjalan untuk 500 gambar, dan data disimpan di /path/to/working_blender_dir/nugget_data.

Kode berikut menunjukkan satu anotasi yang dibuat dengan kumpulan data kami:

{
    "metadata": {
        "description": "3D data of a nugget!",
        "contributor": "Matt Krzus",
        "url": "krzum@amazon.com",
        "year": "2021",
        "date_created": "20210924_000000",
        "save_path": "/home/ubuntu/working_blender_dir/nugget_data"
    },
    "categories": {
        "0": {
            "name": "nugget",
            "supercategories": [],
            "subcategories": [],
            "color": [
                0.0,
                0.0,
                0.0
            ],
            "count": 6700,
            "subcategory_count": [],
            "id": 0
        }
    },
    "images": {
        "0": {
            "name": "a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.rgb.png",
            "style": "default",
            "output_path": "/home/ubuntu/working_blender_dir/nugget_data/a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.rgb.png",
            "relative_path": "a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.rgb.png",
            "frame": 97,
            "width": 640,
            "height": 480,
            "id": 0
        },
        "1": {
            "name": "a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.iseg.png",
            "style": "segmentation",
            "output_path": "/home/ubuntu/working_blender_dir/nugget_data/a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.iseg.png",
            "relative_path": "a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.iseg.png",
            "frame": 97,
            "width": 640,
            "height": 480,
            "id": 1
        },
        "2": {
            "name": "a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.depth.png",
            "style": "depth",
            "output_path": "/home/ubuntu/working_blender_dir/nugget_data/a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.depth.png",
            "relative_path": "a0bb1fd3-c2ec-403c-aacf-07e0c07f4fdd.depth.png",
            "frame": 97,
            "width": 640,
            "height": 480,
            "id": 2
        }
    },
    "annotations": [
        {
            "image_id": 0,
            "category_id": 0,
            "id": 0,
            "seg_color": [
                1.0,
                0.6000000238418579,
                0.9333333373069763
            ],
            "color": [
                1.0,
                0.6,
                0.9333333333333333
            ],
            "segmentation": [
                [
                    299.0,
                    308.99,
                    292.0,
                    308.99,
                    283.01,
                    301.0,
                    286.01,
                    297.0,
                    285.01,
                    294.0,
                    288.01,
                    285.0,
                    283.01,
                    275.0,
                    287.0,
                    271.01,
                    294.0,
                    271.01,
                    302.99,
                    280.0,
                    305.99,
                    286.0,
                    305.99,
                    303.0,
                    302.0,
                    307.99,
                    299.0,
                    308.99
                ]
            ],
            "bbox": [
                283.01,
                271.01,
                22.980000000000018,
                37.98000000000002
            ],
            "area": 667.0802000000008,
            "bboxes": [
                [
                    283.01,
                    271.01,
                    22.980000000000018,
                    37.98000000000002
                ]
            ],
            "areas": [
                667.0802000000008
            ]
        },
        {
            "image_id": 0,
            "category_id": 0,
            "id": 1,
            "seg_color": [
                1.0,
                0.4000000059604645,
                1.0
            ],
            "color": [
                1.0,
                0.4,
                1.0
            ],
            "segmentation": [
                [
                    241.0,
                    273.99,
                    236.0,
                    271.99,
                    234.0,
                    273.99,
                    230.01,
                    270.0,
                    232.01,
                    268.0,
                    231.01,
                    263.0,
                    233.01,
                    261.0,
                    229.0,
                    257.99,
                    225.0,
                    257.99,
                    223.01,
                    255.0,
                    225.01,
                    253.0,
                    227.01,
                    246.0,
                    235.0,
                    239.01,
                    238.0,
                    239.01,
                    240.0,
                    237.01,
                    247.0,
                    237.01,
                    252.99,
                    245.0,
                    253.99,
                    252.0,
                    246.99,
                    269.0,
                    241.0,
                    273.99
                ]
            ],
            "bbox": [
                223.01,
                237.01,
                30.980000000000018,
                36.98000000000002
            ],
            "area": 743.5502000000008,
            "bboxes": [
                [
                    223.01,
                    237.01,
                    30.980000000000018,
                    36.98000000000002
                ]
            ],
            "areas": [
                743.5502000000008
            ]
        },
...
...
...

Kesimpulan

Dalam posting ini, saya mendemonstrasikan cara menggunakan Blender perpustakaan animasi open-source untuk membangun pipa data sintetis ujung ke ujung.

Ada banyak hal keren yang dapat Anda lakukan di Blender dan AWS; semoga demo ini dapat membantu Anda dalam proyek kekurangan data berikutnya!

Referensi


tentang Penulis

Matt Krzus adalah Sr. Data Scientist di Amazon Web Service dalam grup AWS Professional Services

Stempel Waktu:

Lebih dari Pembelajaran Mesin AWS