Luo synteettistä dataa AWS:n tietokonenäköputkistoja varten

Lähdesolmu: 1726779

Kuvatietojen kerääminen ja merkitseminen on yksi resurssiintensiivisimmista tehtävistä kaikissa tietokonenäköprojekteissa. Voi kestää kuukausia kerrallaan kuvavirtojen täydellinen kerääminen, analysointi ja kokeilu sellaisella tasolla, jota tarvitset kilpaillaksesi nykyisellä markkinapaikalla. Senkin jälkeen, kun olet kerännyt tiedot onnistuneesti, sinulla on edelleen jatkuva virta merkintävirheitä, huonosti kehystettyjä kuvia, pieniä määriä merkityksellistä dataa ei-toivottujen kaappausten meressä ja paljon muuta. Näiden suurten pullonkaulojen vuoksi synteettisen tiedon luomisen on oltava jokaisen nykyaikaisen insinöörin työkalupakki. Luomalla 3D-esityksiä kohteista, joita haluamme mallintaa, voimme nopeasti prototyyppiä algoritmeja ja samalla kerätä live-dataa.

Tässä viestissä opastan sinulle esimerkin avoimen lähdekoodin animaatiokirjaston käyttämisestä Blenderistä päästä-päähän synteettisen dataputken rakentamiseen käyttämällä esimerkkinä kananuggetteja. Seuraava kuva havainnollistaa tässä blogikirjoituksessa luotuja tietoja.

Mikä on Blender?

tehosekoitin on avoimen lähdekoodin 3D-grafiikkaohjelmisto, jota käytetään pääasiassa animaatioissa, 3D-tulostuksessa ja virtuaalitodellisuudessa. Siinä on erittäin kattava takila-, animaatio- ja simulaatiosarja, joka mahdollistaa 3D-maailmojen luomisen lähes kaikkiin tietokonenäön käyttötapauksiin. Sillä on myös erittäin aktiivinen tukiyhteisö, jossa useimmat, ellei kaikki, käyttäjän virheet ratkaistaan.

Aseta paikallinen ympäristösi

Asennamme kaksi versiota Blenderistä: toisen paikalliselle koneelle, jolla on pääsy graafiseen käyttöliittymään, ja toisen Amazonin elastinen laskentapilvi (Amazon EC2) P2-esiintymä.

Asenna Blender ja ZPY

Asenna Blender osoitteesta Blenderin verkkosivusto.

Suorita sitten seuraavat vaiheet:

  1. Suorita seuraavat komennot:
    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. Kopioi tarvittavat Python-otsikot Pythonin Blender-versioon, jotta voit käyttää muita kuin Blender-kirjastoja:
    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. Ohita Blender-versiosi ja pakota asennukset, jotta Blenderin toimittama Python toimii:
    /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. Lataa zpy ja asenna lähteestä:
    git clone https://github.com/ZumoLabs/zpy
    cd zpy
    vi requirements.txt

  5. Vaihda NumPy-versioksi >=1.19.4 ja scikit-image>=0.18.1 asennuksen suorittamiseksi 3.10.2 mahdollista, joten et saa ylikirjoituksia:
    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. Varmista yhteensopivuus Blender 3.2:n kanssa siirtymällä kohtaan zpy/render.py ja kommentoi seuraavat kaksi riviä (lisätietoja on kohdassa Blender 3.0 -virhe #54):
    #scene.render.tile_x = tile_size
    #scene.render.tile_y = tile_size

  7. Asenna seuraavaksi zpy kirjasto:
    /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. Lataa lisäosien versio zpy mistä GitHub repo jotta voit käyttää esiintymääsi aktiivisesti:
    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. Tallenna tiedosto nimeltä enable_zpy_addon.py oman /home hakemistoon ja suorita käyttöönottokomento, koska sinulla ei ole graafista käyttöliittymää sen aktivoimiseksi:
    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 ei asennu (jostain syystä), voit asentaa sen graafisen käyttöliittymän kautta.

  10. Blenderissä muokata valikosta, valitse Asetukset.
  11. Valita Lisäosat navigointiruudussa ja aktivoi zpy.

Sinun pitäisi nähdä sivu auki GUI:ssa, ja voit valita ZPY. Tämä vahvistaa, että Blender on ladattu.

AliceVision ja Meshroom

Asenna AliceVision ja Meshrooom vastaavista GitHub-varastoista:

FFmpeg

Järjestelmässäsi pitäisi olla ffmpeg, mutta jos ei, sinun on tehtävä se download se.

Instant Meshes

Voit joko kääntää kirjaston itse tai ladata valmiiksi käännetyt binaarit (joten minä tein) Instant Meshes.

Määritä AWS-ympäristösi

Nyt määritämme AWS-ympäristön EC2-esiintymään. Toistamme edellisen osan vaiheet, mutta vain Blenderille ja zpy.

  1. Valitse Amazon EC2 -konsolissa Käynnistä instanssit.
  2. Valitse AMI. Täältä löytyy muutamia vaihtoehtoja. Voimme joko valita tavallisen Ubuntu-kuvan, valita GPU-esiintymän ja asentaa sitten ohjaimet manuaalisesti ja saada kaikki asetukset, tai voimme valita helpon tien ja aloittaa esikonfiguroidulla Deep Learning AMI:llä ja huolehtia vain Blenderin asentamisesta. käytän toista vaihtoehtoa ja valitsen Deep Learning AMI:n uusimman version Ubuntulle (Deep Learning AMI (Ubuntu 18.04) -versio 61.0).
  3. varten Ilmentymän tyyppi¸ valitse p2.xlarge.
  4. Jos sinulla ei ole avainparia, luo uusi tai valitse olemassa oleva.
  5. Käytä tässä viestissä verkon ja tallennustilan oletusasetuksia.
  6. Valita Käynnistä instanssit.
  7. Valita kytkeä ja etsi ohjeet instanssiin kirjautumiseen SSH:sta osoitteessa SSH-asiakas Tab.
  8. Yhdistä SSH:hen: ssh -i "your-pem" ubuntu@IPADDRESS.YOUR-REGION.compute.amazonaws.com

Kun olet muodostanut yhteyden ilmentymääsi, noudata samoja asennusvaiheita kuin edellisessä osiossa asentaaksesi Blender ja zpy.

Tiedonkeruu: Nuggetimme 3D-skannaus

Tätä vaihetta varten käytän iPhonea 360 asteen videon tallentamiseen melko hitaasti nuggetin ympärillä. Kiinnitin kanan nuggetin hammastikkuun ja teippasin hammastikkun työtasolleni ja yksinkertaisesti käänsin kameraani nupin ympäri saadakseni mahdollisimman monta kuvakulmaa. Mitä nopeammin kuvaat, sitä epätodennäköisemmin saat hyviä kuvia käytettäväksi suljinnopeudesta riippuen.

Kun lopetin kuvaamisen, lähetin videon sähköpostiini ja purin videon paikalliselle asemalle. Sieltä käytin ffmepg leikata video kehyksiksi, mikä helpottaa Meshroomin käyttöä:

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

Avaa Meshroom ja vedä graafinen käyttöliittymä nugget_images kansio vasemmalla olevaan ruutuun. Valitse sieltä Aloita ja odota muutama tunti (tai vähemmän) riippuen videon pituudesta ja siitä, onko sinulla CUDA-yhteensopiva kone.

Sinun pitäisi nähdä jotain seuraavan kuvakaappauksen kaltaista, kun se on melkein valmis.

Tiedonkeruu: Blenderin käsittely

Kun Meshroom-rekonstruointi on valmis, suorita seuraavat vaiheet:

  1. Avaa Blenderin käyttöliittymä ja filee valikosta, valitse Tuo, valitse sitten Wavefront (.obj) luomaasi tekstuuritiedostoon Meshroomista.
    Tiedosto tulee tallentaa sisään path/to/MeshroomCache/Texturing/uuid-string/texturedMesh.obj.
  2. Lataa tiedosto ja tarkkaile hirviötä, joka on 3D-objektisi.

    Tässä se menee hieman hankalaksi.
  3. Vieritä oikeaan yläkulmaan ja valitse rautalanka kuvake Näkymäportin varjostus.
  4. Valitse objektisi oikeanpuoleisesta näkymästä ja varmista, että se on korostettu, vieritä pääasettelun näyttöporttiin ja paina joko Kieleke tai valitse manuaalisesti Muokkausmoodi.
  5. Ohjaa seuraavaksi kuvaporttia siten, että voit nähdä objektisi mahdollisimman vähän sen takana. Sinun on tehtävä tämä muutaman kerran saadaksesi sen oikein.
  6. Napsauta ja vedä rajauslaatikko objektin päälle niin, että vain nugget on korostettuna.
  7. Kun se on korostettu kuten seuraavassa kuvakaappauksessa, erottelemme hippumme 3D-massasta napsauttamalla hiiren vasenta painiketta ja valitsemalla ErillinenJa sitten Valinta.

    Siirrymme nyt oikealle, jossa meidän pitäisi nähdä kaksi teksturoitua objektia: texturedMesh ja texturedMesh.001.
  8. Uuden kohteen pitäisi olla texturedMesh.001, joten valitsemme texturedMesh Ja valitse Poista ei-toivotun massan poistamiseksi.
  9. Valitse kohde (texturedMesh.001) oikealla, siirry katsojaamme ja valitse kohde, Aseta alkuperäja Alkuperä massakeskukseen.

Nyt, jos haluamme, voimme siirtää objektimme katseluportin keskelle (tai yksinkertaisesti jättää sen sinne, missä se on) ja tarkastella sitä kaikessa loistossaan. Huomaa suuri musta aukko, josta emme todellakaan saaneet hyvää elokuvakattavuutta! Meidän on korjattava tämä.

Puhdistaaksemme objektimme pikselien epäpuhtauksista viemme objektimme .obj-tiedostoon. Muista valita Vain valinta viennin aikana.

Tiedonkeruu: Puhdista pikaverkoilla

Meillä on nyt kaksi ongelmaa: kuvassamme on huonosta kuvauksesta johtuva pikseliaukko, joka meidän on siivottava, ja kuvamme on uskomattoman tiheä (mikä tekee kuvien luomisesta erittäin aikaa vievää). Molempien ongelmien ratkaisemiseksi meidän on käytettävä Instant Meshes -nimistä ohjelmistoa ekstrapoloimaan pikselipinta peittämään mustan aukon ja myös kutistamaan koko kohteen pienempään, vähemmän tiheään kokoon.

  1. Avaa Instant Meshes ja lataa äskettäin tallennetut nugget.obj tiedosto.
  2. Alle Suuntautumiskenttä, valitse Ratkaista.
  3. Alle Sijaintikenttä, valitse Ratkaista.
    Tästä tulee mielenkiintoista. Jos tutkit kohdettasi ja huomaat, että paikanratkaisijan ristikkäiset viivat näyttävät hajaantuneilta, voit valita kampakuvakkeen alta. Suuntautumiskenttä ja piirrä viivat oikein.
  4. Valita Ratkaista sekä Suuntautumiskenttä ja Sijaintikenttä.
  5. Jos kaikki näyttää hyvältä, vie verkko ja anna sille jokin nimi nugget_refined.objja tallenna se levylle.

Tiedonkeruu: Ravista ja paista!

Koska low-poly-verkkoomme ei liity mitään kuvatekstuuria ja high-poly-verkkoomme liittyy, meidän on joko paistettava high-poly-tekstuuri matalan polymeeliinin päälle tai luotava uusi pintakuvio ja määritettävä se meidän kohde. Yksinkertaisuuden vuoksi aiomme luoda kuvatekstuurin tyhjästä ja soveltaa sitä nuggetimme.

Käytin Googlen kuvahakua nuggeteille ja muille paistetuille esineille saadakseni korkearesoluutioisen kuvan paistetun esineen pinnasta. Löysin superkorkean resoluution kuvan paistetusta juustomassasta ja tein uuden kuvan, joka oli täynnä paistettua rakennetta.

Tämän kuvan avulla olen valmis suorittamaan seuraavat vaiheet:

  1. Avaa Blender ja lataa uusi nugget_refined.obj samalla tavalla kuin latasit alkuperäisen objektin: on filee valikosta, valitse Tuo, Wavefront (.obj)ja valitse nugget_refined.obj tiedosto.
  2. Siirry seuraavaksi kohtaan Varjostus Tab.
    Alareunassa sinun pitäisi huomata kaksi ruutua otsikoineen Periaate BDSF ja Materiaalin lähtö.
  3. On Lisää valikosta, valitse Rakenne ja Kuvan rakenne.
    An Kuvan rakenne laatikon pitäisi ilmestyä.
  4. Valita Avaa kuva ja lataa paistettu tekstuurikuvasi.
  5. Vedä hiirtä välillä Väri vuonna Kuvan rakenne laatikko ja Pohjaväri vuonna Periaate BDSF laatikko.

Nyt nuggetin pitäisi olla kunnossa!

Tiedonkeruu: Luo Blender-ympäristömuuttujia

Nyt kun meillä on perusnugget-objekti, meidän on luotava muutamia kokoelmia ja ympäristömuuttujia auttamaan meitä prosessissamme.

  1. Napsauta hiiren vasemmalla painikkeella käsikohtausaluetta ja valitse Uusi kokoelma.
  2. Luo seuraavat kokoelmat: TAUSTA, NUGGETja KUUTUNUT.
  3. Vedä nugget kohtaan NUGGET kokoelma ja nimeä se uudelleen nugget_base.

Tiedonkeruu: Luo kone

Aiomme luoda taustaobjektin, josta meidän nuggetit luodaan, kun renderöimme kuvia. Todellisissa käyttötapauksissa tämä taso on paikka, johon pisteemme sijoitetaan, kuten tarjotin tai roskakori.

  1. On Lisää valikosta, valitse verkko ja sitten Kone.
    Sieltä siirrymme sivun oikealle puolelle ja löydämme oranssin laatikon (Objektin ominaisuudet).
  2. In Muuttaa ruutu, varten XYZ Euler, asetettu X 46.968: ään, Y 46.968: een ja Z on 1.0.
  3. Molemmille Sijainti ja Kierto, asetettu X, Yja Z on 0.

Tiedonkeruu: Aseta kamera ja akseli

Seuraavaksi aiomme asettaa kameramme oikein, jotta voimme luoda kuvia.

  1. On Lisää valikosta, valitse Tyhjä ja Tavallinen akseli.
  2. Nimeä kohde Pääakseli.
  3. Varmista, että akselimme on 0 kaikille muuttujille (joten se on suoraan keskellä).
  4. Jos sinulla on jo kamera, vedä se Pääakselin alle.
  5. Valita erä ja Muuttaa.
  6. varten Sijainti, asetettu X 0: ään, Y 0: een ja Z on 100.

Tiedonkeruu: Täältä tulee aurinko

Seuraavaksi lisäämme aurinkoobjektin.

  1. On Lisää valikosta, valitse Valo ja aurinko.
    Tämän objektin sijainnilla ei välttämättä ole väliä, kunhan se on keskitetty jonnekin asettamamme tasoobjektin päälle.
  2. Valitse vihreä hehkulamppukuvake oikeassa alakulmassa (Objektitietojen ominaisuudet) ja aseta vahvuudeksi 5.0.
  3. Toista sama toimenpide lisätäksesi a Valo esine ja aseta se satunnaiseen paikkaan koneen päällä.

Tiedonkeruu: Lataa satunnaisia ​​taustoja

Lisäämme kuviisi satunnaisuutta lataamalla niin monta satunnaista tekstuuria osoitteesta rakenne.ninja kuin pystymme (esim. tiilet). Lataa työtilasi kansioon nimeltä random_textures. Latasin noin 50.

Luo kuvia

Nyt päästään hauskoihin asioihin: kuvien luomiseen.

Kuvanluontiputki: Object3D ja DensityController

Aloitetaan muutamalla koodin määritelmällä:

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__()

Määrittelemme ensin perussäiliöluokan, jolla on joitain tärkeitä ominaisuuksia. Tämä luokka on pääasiassa olemassa, jotta voimme luoda BVH-puun (tapa esittää nugget-objektiamme 3D-avaruudessa), jossa meidän on käytettävä BVHTree.overlap menetelmä nähdäksesi, ovatko kaksi itsenäistä luotua nugget-objektia päällekkäisiä 3D-tilassamme. Tästä lisää myöhemmin.

Toinen koodinpala on tiheyssäätimemme. Tämä toimii tapana sitoutua todellisuuden sääntöihin, ei 3D-maailmaan. Esimerkiksi 3D Blender -maailmassa Blenderin objektit voivat olla toistensa sisällä; Kuitenkin, ellei joku tee jotain outoa tiedettä kanankimpaleillemme, haluamme varmistaa, että kaksi nuggettia ei mene päällekkäin siinä määrin, että se tekee siitä visuaalisesti epärealistisen.

Käytämme Plane objekti synnyttää joukon rajattuja näkymättömiä kuutioita, joista voidaan milloin tahansa kysyä, onko tila varattu vai ei.


Katso seuraava koodi:

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  
        """

Seuraavassa katkelmassa valitsemme nuggetin ja luomme rajaavan kuution sen ympärille. Tämä kuutio edustaa pseudo-kdtree-objektimme yhden pseudovokselin kokoa. Meidän on käytettävä bpy.context.view_layer.update() funktio, koska kun tätä koodia ajetaan funktion tai komentosarjan sisältä vs. blender-gui, näyttää siltä, ​​että view_layer ei päivity automaattisesti.

        # 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)

Seuraavaksi päivitämme hieman kuutioobjektiamme siten, että sen pituus ja leveys ovat neliön muotoisia, toisin kuin sen luodun kimpun luonnollinen koko:

        # 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.

Nyt käytämme päivitettyä kuutioobjektiamme luodaksemme tason, joka mahtuu tilavuudellisesti num_objects nuggettien määrä:

        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()

Otamme lentokoneemme ja luomme jättiläiskuution, joka on samanpituinen ja leveä kuin lentokoneemme, ja jonka korkeus on nugget-kuutiomme, KUUTI1:

        # 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()

Tästä eteenpäin haluamme luoda vokseleita kuutiostamme. Otamme sopivan määrän kuutioita num_objects ja leikkaa ne sitten kuutioobjektistamme. Etsimme kuutiomme ylöspäin osoittavaa verkkopinta-alaa ja valitsemme sen sitten leikkauksia varten. Katso seuraava koodi:

        # 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

Lopuksi laskemme jokaisen suuresta kuutiostamme tekemämme leikkauksen yläpinnan keskikohdan ja luomme todelliset kuutiot näistä leikkauksista. Jokainen näistä äskettäin luoduista kuutioista edustaa yhtä tilaa, joka synnyttää tai siirtää hippuja koneemme ympäri. Katso seuraava koodi:

        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

Seuraavaksi kehitämme algoritmin, joka ymmärtää, mitkä kuutiot ovat varattu kulloinkin, etsii mitkä kohteet menevät päällekkäin ja siirtää päällekkäiset objektit erikseen tyhjään tilaan. Emme pääse eroon kaikista päällekkäisyyksistä kokonaan, mutta voimme saada sen näyttämään riittävän todelliselta.



Katso seuraava koodi:

    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

Kuvanluontiputki: Hienoja suorituksia

Tässä osiossa erittelemme mitä run toiminto toimii.

Alustamme omamme DensityController ja luo jotain, jota kutsutaan säästäjäksi käyttämällä ImageSaver alkaen zpy. Tämä antaa meille mahdollisuuden tallentaa renderoidut kuvamme vaivattomasti mihin tahansa valitsemaamme paikkaan. Lisäämme sitten omamme nugget luokka (ja jos meillä olisi enemmän luokkia, lisäisimme ne tähän). Katso seuraava koodi:

@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")

Seuraavaksi meidän on tehtävä lähdeobjekti, josta luodaan kopiohippuja; tässä tapauksessa se on nugget_base jonka loimme:

    # 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)

Nyt kun meillä on perushippu, aiomme tallentaa kaikkien muiden objektien maailmanasennot (paikat), jotta voimme jokaisen renderöintiajon jälkeen käyttää näitä tallennettuja asentoja renderöinnin alustamiseen. Siirrämme myös perushippumme kokonaan pois tieltä, jotta kdtree ei havaitse tilan olevan varattu. Lopuksi alustamme kdtree-cube-objektit. Katso seuraava koodi:

    # 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()

Seuraava koodi kerää ladatut taustat texture.ninjasta, jossa niitä käytetään satunnaisesti projisoitavaksi koneellemme:

    # 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)
    ]

Tästä taika alkaa. Regeneroimme ensin kdtree-kuutiot tätä ajoa varten, jotta voimme aloittaa alusta:

    # 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):

Käytämme tiheyssäädintä luodaksemme satunnaisen syntypisteen nuggetillemme, luomme siitä kopion nugget_baseja siirrä kopio satunnaisesti luotuun syntypisteeseen:

            # 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)

Seuraavaksi värähtelemme satunnaisesti nuggetin kokoa, nuggetin verkkoa ja nuggetin mittakaavaa, jotta kaksi hippua ei näytä samalta:

            # 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)          

Muutamme nugget-kopiomme a Object3D objekti, jossa käytämme BVH-puutoimintoa nähdäksemme, leikkaako tasomme tai limittyykö jokin nugget-kopiomme pinta tai kärki. Jos löydämme päällekkäisyyden tason kanssa, siirrämme kimpun yksinkertaisesti ylöspäin sen Z-akselilla. Katso seuraava koodi:

            # 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)

Nyt kun kaikki nuggetit on luotu, käytämme meidän DensityController siirtääksesi hippuja ympäriinsä niin, että meillä on minimimäärä päällekkäisyyksiä, ja ne, jotka menevät päällekkäin, eivät näytä kamalalta:

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

Seuraavassa koodissa: palautamme Camera ja Main Axis poseeraa ja valitse satunnaisesti, kuinka kaukana kamera on Plane esine:

        # 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)

Päätämme kuinka satunnaisesti haluamme kameran kulkevan pitkin Main Axis. Riippuen siitä, haluammeko sen olevan pääasiassa yläpuolella vai välitämmekö erittäin paljon kulmasta, josta se näkee laudan, voimme säätää top_down_mostly parametri riippuen siitä, kuinka hyvin harjoitusmallimme poimii signaalin "Mikä edes on nugget?"

        # 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)

Seuraavassa koodissa teemme saman asian kanssa Sun objekti ja valitse satunnaisesti tekstuuri sille Plane esine:

        # 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)

Lopuksi piilotamme kaikki objektimme, joita emme halua renderöidä: nugget_base ja koko kuutiorakenne:

# 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)

Lopuksi käytämme zpy renderöidäksesi kohtauksemme, tallentaaksesi kuvamme ja tallentaaksesi huomautuksemme. Tätä postausta varten tein joitain pieniä muutoksia zpy huomautuskirjasto minun erityiseen käyttötapaukseeni (merkintä kuvaa kohti yhden tiedoston sijaan projektia kohden), mutta sinun ei pitäisi olla tätä viestiä varten).

        # 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!

Suorita päätön luomisskripti

Nyt kun meillä on tallennettu Blender-tiedosto, luotu nugget ja kaikki tukitiedot, pakataanpa työhakemistomme ja joko scp sen GPU-koneellemme tai ladannut sen kautta Amazonin yksinkertainen tallennuspalvelu (Amazon S3) tai muu palvelu:

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

Kirjaudu sisään EC2-instanssiisi ja pura working_blender-kansiosi:

tar xvf working_blender_dir.tar.gz

Nyt luomme tietomme kaikessa loistossaan:

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

Komentosarjan tulee suorittaa 500 kuvalle, ja tiedot tallennetaan /path/to/working_blender_dir/nugget_data.

Seuraava koodi näyttää yksittäisen tietojoukollamme luodun huomautuksen:

{
    "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
            ]
        },
...
...
...

Yhteenveto

Tässä viestissä osoitin, kuinka avoimen lähdekoodin animaatiokirjaston Blenderin avulla voidaan rakentaa päästä päähän synteettinen dataputki.

Blenderissä ja AWS:ssä voit tehdä paljon hienoja asioita; toivottavasti tämä demo voi auttaa sinua seuraavassa datan nälkäisessä projektissasi!

Viitteet


kirjailijasta

Matt Krzus on vanhempi datatutkija Amazon Web Servicessä AWS Professional Services -ryhmässä

Aikaleima:

Lisää aiheesta AWS-koneoppiminen