ایجاد داده های مصنوعی برای خطوط لوله بینایی کامپیوتر در AWS

گره منبع: 1726779

جمع آوری و حاشیه نویسی داده های تصویری یکی از منابع فشرده ترین کارها در هر پروژه بینایی کامپیوتری است. جمع آوری، تجزیه و تحلیل، و آزمایش کامل جریان های تصویر در سطحی که برای رقابت در بازار فعلی نیاز دارید، ممکن است ماه ها طول بکشد. حتی پس از جمع‌آوری موفقیت‌آمیز داده‌ها، همچنان یک جریان ثابت از خطاهای حاشیه‌نویسی، تصاویر با کادربندی ضعیف، مقادیر کمی داده‌های معنادار در دریایی از عکس‌های ناخواسته و موارد دیگر دارید. این تنگناهای اصلی دلیلی است که ایجاد داده های مصنوعی باید در جعبه ابزار هر مهندس مدرن باشد. با ایجاد نمایش‌های سه‌بعدی از اشیایی که می‌خواهیم مدل‌سازی کنیم، می‌توانیم به سرعت الگوریتم‌هایی را نمونه‌سازی کنیم و همزمان داده‌های زنده را جمع‌آوری کنیم.

در این پست، مثالی از استفاده از کتابخانه انیمیشن منبع باز Blender برای ایجاد خط لوله داده مصنوعی سرتاسر، با استفاده از ناگت های مرغ به عنوان مثال، به شما آموزش می دهم. تصویر زیر تصویری از داده های تولید شده در این پست وبلاگ است.

بلندر چیست؟

بلندر یک نرم افزار گرافیک سه بعدی منبع باز است که عمدتاً در انیمیشن، پرینت سه بعدی و واقعیت مجازی استفاده می شود. این مجموعه دارای مجموعه‌ای از تقلب، انیمیشن و شبیه‌سازی بسیار جامع است که امکان ایجاد جهان‌های سه بعدی را برای تقریباً هر مورد استفاده از بینایی رایانه فراهم می‌کند. همچنین دارای یک انجمن پشتیبانی بسیار فعال است که در آن اکثر، اگر نه همه، خطاهای کاربر حل می شوند.

محیط محلی خود را تنظیم کنید

ما دو نسخه از Blender را نصب می کنیم: یکی روی یک ماشین محلی با دسترسی به یک رابط کاربری گرافیکی و دیگری در یک ابر محاسبه الاستیک آمازون (Amazon EC2) نمونه P2.

Blender و ZPY را نصب کنید

Blender را از وب سایت بلندر.

سپس مراحل زیر را انجام دهید:

  1. دستورات زیر را اجرا کنید:
    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. هدرهای لازم پایتون را در نسخه Blender پایتون کپی کنید تا بتوانید از سایر کتابخانه های غیربلندر استفاده کنید:
    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. نسخه Blender خود را لغو کنید و نصب را مجبور کنید تا پایتون ارائه شده توسط Blender کار کند:
    /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. دانلود zpy و از منبع نصب کنید:
    git clone https://github.com/ZumoLabs/zpy
    cd zpy
    vi requirements.txt

  5. نسخه NumPy را به تغییر دهید >=1.19.4 و scikit-image>=0.18.1 برای انجام نصب در 3.10.2 امکان پذیر است و بنابراین هیچ رونویسی دریافت نمی کنید:
    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. برای اطمینان از سازگاری با Blender 3.2، وارد شوید zpy/render.py و دو خط زیر را نظر دهید (برای اطلاعات بیشتر به رجوع کنید Blender 3.0 Failure #54):
    #scene.render.tile_x = tile_size
    #scene.render.tile_y = tile_size

  7. بعد ، نصب کنید zpy کتابخانه:
    /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. دانلود نسخه افزونه های zpy از GitHub repo بنابراین می توانید به طور فعال نمونه خود را اجرا کنید:
    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. فایلی به نام ذخیره کنید enable_zpy_addon.py در خود /home دایرکتوری و دستور enablement را اجرا کنید، زیرا رابط کاربری گرافیکی برای فعال کردن آن ندارید:
    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 نصب نمی شود (به هر دلیلی)، می توانید آن را از طریق رابط کاربری گرافیکی نصب کنید.

  10. در بلندر، در ویرایش منو ، انتخاب کنید تنظیمات.
  11. را انتخاب کنید افزودنیهای فایرفاکس را در قسمت ناوبری قرار داده و فعال کنید zpy.

شما باید صفحه ای را در رابط کاربری گرافیکی باز کنید و می توانید انتخاب کنید ZPY. این تایید می کند که بلندر بارگذاری شده است.

AliceVision و Meshroom

AliceVision و Meshroom را از مخازن GitHub مربوطه خود نصب کنید:

FFmpeg به

سیستم شما باید داشته باشد ffmpeg، اما اگر این کار را نکرد، باید انجام دهید دانلود آن است.

مش های فوری

شما می توانید کتابخانه را خودتان کامپایل کنید یا باینری های از پیش کامپایل شده موجود را دانلود کنید (که من انجام دادم) مش های فوری.

محیط AWS خود را تنظیم کنید

اکنون محیط AWS را بر روی یک نمونه EC2 تنظیم می کنیم. ما مراحل قسمت قبل را تکرار می کنیم، اما فقط برای بلندر و zpy.

  1. در کنسول آمازون EC2، را انتخاب کنید راه اندازی نمونه ها.
  2. AMI خود را انتخاب کنید. چند گزینه از اینجا وجود دارد. ما می‌توانیم یک تصویر استاندارد اوبونتو انتخاب کنیم، یک نمونه GPU را انتخاب کنیم، و سپس به صورت دستی درایورها را نصب کنیم و همه چیز را تنظیم کنیم، یا می‌توانیم مسیر آسان را انتخاب کنیم و با یک AMI از پیش تنظیم‌شده Deep Learning شروع کنیم و فقط نگران نصب Blender باشیم. پست، از گزینه دوم استفاده می کنم و آخرین نسخه Deep Learning AMI را برای اوبونتو انتخاب می کنم (Deep Learning AMI (Ubuntu 18.04) نسخه 61.0).
  3. برای نوع نمونهانتخاب کنید p2.xlarge.
  4. اگر جفت کلید ندارید، یک مورد جدید ایجاد کنید یا موجودی را انتخاب کنید.
  5. برای این پست از تنظیمات پیش فرض شبکه و ذخیره سازی استفاده کنید.
  6. را انتخاب کنید راه اندازی نمونه ها.
  7. را انتخاب کنید اتصال و دستورالعمل‌های ورود به نمونه خود را از SSH در آن پیدا کنید کلاینت SSH تب.
  8. اتصال با SSH: ssh -i "your-pem" ubuntu@IPADDRESS.YOUR-REGION.compute.amazonaws.com

پس از اتصال به نمونه خود، همان مراحل نصب را از قسمت قبل برای نصب Blender و دنبال کنید zpy.

جمع آوری داده ها: اسکن سه بعدی قطعه ما

برای این مرحله، من از آیفون برای ضبط ویدیوی 360 درجه با سرعت نسبتاً آهسته در اطراف قطعه خود استفاده می کنم. من یک تکه مرغ را روی یک خلال دندان چسباندم و خلال دندان را به میز خود چسباندم و به سادگی دوربینم را به دور قطعه چرخاندم تا تا آنجا که می‌توانم زوایای بیشتری داشته باشم. هرچه سریعتر فیلم برداری کنید، بسته به سرعت شاتر، احتمال کمتری برای کار با تصاویر خوب خواهید داشت.

بعد از اتمام فیلمبرداری، ویدیو را به ایمیلم فرستادم و ویدیو را در یک درایو محلی استخراج کردم. از آنجا استفاده کردم ffmepg برای برش دادن ویدیو به قاب‌ها برای آسان‌تر کردن مصرف Meshroom:

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

Meshroom را باز کنید و از رابط کاربری گرافیکی برای کشیدن آن استفاده کنید nugget_images پوشه را در قسمت سمت چپ قرار دهید. از آنجا، انتخاب کنید آغاز و چند ساعت (یا کمتر) بسته به طول ویدیو و اگر دستگاهی با قابلیت CUDA دارید صبر کنید.

وقتی تقریباً کامل شد باید چیزی شبیه تصویر زیر را ببینید.

جمع آوری داده ها: دستکاری مخلوط کن

هنگامی که بازسازی Meshroom ما کامل شد، مراحل زیر را تکمیل کنید:

  1. رابط کاربری گرافیکی Blender را باز کنید و روی پرونده منو ، انتخاب کنید وارد كردن، پس از آن را انتخاب کنید موج Wavefront (.obj) به فایل بافت ایجاد شده خود از Meshroom.
    فایل باید در آن ذخیره شود path/to/MeshroomCache/Texturing/uuid-string/texturedMesh.obj.
  2. فایل را بارگذاری کنید و هیولایی را که شی 3 بعدی شما است مشاهده کنید.

    اینجاست که کمی مشکل می شود.
  3. به سمت راست بالا بروید و آن را انتخاب کنید قاب وایرلس نماد در سایه نما درگاه.
  4. شیء خود را در نمای سمت راست انتخاب کنید و مطمئن شوید که برجسته شده است، به نمای طرح بندی اصلی بروید و یکی را فشار دهید برگ یا به صورت دستی انتخاب کنید حالت ویرایش.
  5. در مرحله بعد، نما را به گونه ای مانور دهید که به خود اجازه دهید بتوانید جسم خود را با کمترین میزان ممکن پشت آن ببینید. شما باید این کار را چند بار انجام دهید تا واقعاً درست شود.
  6. یک کادر محدود کننده را روی شی کلیک کرده و بکشید تا فقط قطعه برجسته شود.
  7. پس از اینکه مانند تصویر زیر برجسته شد، با کلیک چپ، قطعه خود را از جرم سه بعدی جدا می کنیم. جداگانه، و سپس انتخاب.

    اکنون به سمت راست حرکت می کنیم، جایی که باید دو شیء بافت دار را ببینیم: texturedMesh و texturedMesh.001.
  8. شی جدید ما باید باشد texturedMesh.001، بنابراین ما انتخاب می کنیم texturedMesh و انتخاب کنید حذف برای حذف جرم ناخواسته
  9. شی را انتخاب کنید (texturedMesh.001) در سمت راست، به سمت بیننده خود حرکت کنید و شی را انتخاب کنید، تنظیم مبداو منشاء مرکز توده.

حال اگر بخواهیم می توانیم شیء خود را به مرکز ویوپورت منتقل کنیم (یا به سادگی آن را در همان جایی که هست رها کنیم) و آن را با شکوه کامل مشاهده کنیم. به سیاهچاله بزرگی توجه کنید که ما واقعاً از آن پوشش فیلم خوبی دریافت نکردیم! ما باید این را اصلاح کنیم.

برای پاک کردن شیء خود از هرگونه ناخالصی پیکسلی، شیء خود را به یک فایل .obj صادر می کنیم. حتما انتخاب کنید فقط انتخاب هنگام صادرات

جمع آوری داده ها: با Instant Meshes پاکسازی کنید

اکنون ما دو مشکل داریم: تصویر ما دارای یک شکاف پیکسلی است که در اثر فیلمبرداری ضعیف ما ایجاد می‌شود و باید آن را تمیز کنیم، و تصویر ما فوق‌العاده متراکم است (که باعث می‌شود تولید تصاویر بسیار زمان‌بر باشد). برای رفع هر دو مشکل، باید از نرم افزاری به نام Instant Meshes استفاده کنیم تا سطح پیکسل خود را برای پوشاندن سیاهچاله برون یابی کنیم و همچنین کل جسم را به اندازه کوچکتر و چگالی کمتر کوچک کنیم.

  1. Instant Meshes را باز کنید و ذخیره شده اخیر ما را بارگیری کنید nugget.obj فایل.
  2. تحت زمینه جهت یابی، انتخاب کنید حل.
  3. تحت فیلد موقعیت، انتخاب کنید حل.
    اینجاست که جالب می شود. اگر شیء خود را کاوش کردید و متوجه شدید که خطوط متقاطع حلگر Position ناهمگون به نظر می رسند، می توانید نماد شانه را در زیر انتخاب کنید. زمینه جهت یابی و خطوط را به درستی دوباره ترسیم کنید.
  4. را انتخاب کنید حل برای هردو زمینه جهت یابی و فیلد موقعیت.
  5. اگر همه چیز خوب به نظر می رسد، مش را صادر کنید، نام آن را چیزی شبیه به آن بگذارید nugget_refined.obj، و آن را در دیسک ذخیره کنید.

جمع آوری داده ها: تکان دهید و بپزید!

از آنجایی که مش کم پلی ما هیچ بافت تصویری مرتبط با آن ندارد و مش پلی پلی بالا ما دارد، یا باید بافت پلی بالا را روی مش کم پلی بپزیم یا یک بافت جدید ایجاد کنیم و آن را به آن اختصاص دهیم. شی ما برای سادگی، ما یک بافت تصویر را از ابتدا ایجاد می کنیم و آن را روی قطعه خود اعمال می کنیم.

من از جستجوی تصویر گوگل برای قطعات و سایر چیزهای سرخ شده استفاده کردم تا تصویری با وضوح بالا از سطح یک شی سرخ شده بدست بیاورم. من یک تصویر فوق العاده با وضوح بالا از کشک پنیر سرخ شده پیدا کردم و یک تصویر جدید پر از بافت سرخ شده درست کردم.

با این تصویر، من آماده هستم تا مراحل زیر را انجام دهم:

  1. Blender را باز کرده و جدید را بارگذاری کنید nugget_refined.obj به همان روشی که شی اولیه خود را بارگذاری کردید: روی پرونده منو ، انتخاب کنید وارد كردن, موج Wavefront (.obj)، و را انتخاب کنید nugget_refined.obj فایل.
  2. بعد ، به سایه تب.
    در پایین باید به دو کادر با عنوان توجه کنید BDSF اصولی و خروجی مواد.
  3. بر اضافه کردن منو ، انتخاب کنید بافت و بافت تصویر.
    An بافت تصویر کادر باید ظاهر شود
  4. را انتخاب کنید باز کردن تصویر و تصویر بافت سرخ شده خود را بارگذاری کنید.
  5. ماوس خود را بین آن بکشید رنگ در بافت تصویر جعبه و رنگ پایه در BDSF اصولی جعبه.

حالا قطعه شما باید خوب باشد!

جمع آوری داده ها: ایجاد متغیرهای محیطی Blender

اکنون که ما شیء پایه خود را داریم، باید چند مجموعه و متغیر محیطی ایجاد کنیم تا به ما در فرآیند کمک کنند.

  1. روی ناحیه صحنه دست کلیک چپ کرده و انتخاب کنید مجموعه جدید.
  2. مجموعه های زیر را ایجاد کنید: زمینه, NUGGETو تخم ریزی شد.
  3. قطعه را به سمت NUGGET جمع آوری کرده و نام آن را تغییر دهید nugget_base.

جمع آوری داده ها: یک هواپیما بسازید

ما یک شی پس‌زمینه ایجاد می‌کنیم که هنگام رندر کردن تصاویر، قطعات ما از آن تولید می‌شوند. در یک مورد استفاده در دنیای واقعی، این هواپیما جایی است که قطعات ما مانند سینی یا سطل زباله قرار می گیرند.

  1. بر اضافه کردن منو ، انتخاب کنید شبکه و پس از آن هواپیما.
    از اینجا به سمت راست صفحه رفته و کادر نارنجی رنگ (ویژگی های شی).
  2. در دگرگون کردن پنجره، برای XYZ اویلر، تنظیم X به 46.968، Y تا 46.968 ، و Z به 1.0.
  3. برای هردو موقعیت مکانی: و چرخش، تنظیم X, Yو Z به 0.

جمع آوری داده ها: دوربین و محور را تنظیم کنید

در مرحله بعد، دوربین هایمان را به درستی تنظیم می کنیم تا بتوانیم تصاویر را تولید کنیم.

  1. بر اضافه کردن منو ، انتخاب کنید خالی و محور دشت.
  2. شی را نام ببرید محور اصلی.
  3. مطمئن شوید که محور ما برای همه متغیرها 0 است (بنابراین مستقیماً در مرکز قرار دارد).
  4. اگر قبلاً دوربینی ساخته‌اید، آن دوربین را به زیر محور اصلی بکشید.
  5. را انتخاب کنید مورد و دگرگون کردن.
  6. برای موقعیت مکانی:، تنظیم X به 0، Y تا 0 ، و Z به 100.

جمع آوری داده ها: اینجا خورشید می آید

بعد، یک شی Sun اضافه می کنیم.

  1. بر اضافه کردن منو ، انتخاب کنید نور و خورشید.
    مکان این جسم لزوماً اهمیتی ندارد تا زمانی که در جایی بر روی جسم صفحه ای که ما تنظیم کرده ایم متمرکز باشد.
  2. نماد لامپ سبز را در قسمت پایین سمت راست انتخاب کنید (ویژگی های داده های شی) و قدرت را روی 5.0 قرار دهید.
  3. همین روش را برای اضافه کردن a تکرار کنید نور شی و آن را در نقطه ای تصادفی روی هواپیما قرار دهید.

جمع آوری داده ها: دانلود پس زمینه های تصادفی

برای تزریق تصادفی به تصاویر خود، به همان تعداد بافت تصادفی را دانلود می کنیم بافت.نینجا همانطور که می توانیم (مثلا آجر). در پوشه ای در فضای کاری خود به نام دانلود کنید random_textures. من حدود 50 دانلود کردم

تولید تصاویر

اکنون به چیزهای سرگرم کننده می رسیم: تولید تصاویر.

خط لوله تولید تصویر: Object3D و DensityController

بیایید با چند تعریف کد شروع کنیم:

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

ابتدا یک کلاس کانتینر پایه با چند ویژگی مهم تعریف می کنیم. این کلاس عمدتاً به ما امکان می دهد یک درخت BVH ایجاد کنیم (روشی برای نشان دادن شی قطعه ما در فضای سه بعدی)، جایی که باید از آن استفاده کنیم. BVHTree.overlap روشی برای دیدن اینکه آیا دو شیء قطعه تولید شده مستقل در فضای سه بعدی ما با هم همپوشانی دارند یا خیر. بیشتر در این مورد بعدا.

دومین قطعه کد، کنترل کننده چگالی ما است. این به عنوان راهی برای محدود کردن خودمان به قوانین واقعیت و نه دنیای سه بعدی عمل می کند. به عنوان مثال، در دنیای 3D Blender، اشیاء در Blender می توانند در داخل یکدیگر وجود داشته باشند. با این حال، مگر اینکه کسی علم عجیبی را روی ناگت‌های مرغ ما انجام دهد، ما می‌خواهیم مطمئن شویم که هیچ دو تکه‌ای با درجه‌ای همپوشانی ندارند که از نظر بصری غیر واقعی باشد.

ما از خودمان استفاده می کنیم Plane به ایجاد مجموعه‌ای از مکعب‌های نامرئی محدود می‌شود که می‌توان آن‌ها را در هر زمان مشخص کرد تا ببیند آیا فضا اشغال شده است یا خیر.


کد زیر را ببینید:

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

در قطعه زیر، ناگت را انتخاب کرده و یک مکعب محدود کننده در اطراف آن قطعه ایجاد می کنیم. این مکعب اندازه یک شبه وکسل از شیء psuedo-kdtree ما را نشان می دهد. ما باید از bpy.context.view_layer.update() تابع زیرا زمانی که این کد از داخل یک تابع یا اسکریپت در مقابل blender-gui اجرا می شود، به نظر می رسد که view_layer به طور خودکار به روز نمی شود

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

سپس، شی مکعب خود را کمی به روز می کنیم تا طول و عرض آن مربع باشد، برخلاف اندازه طبیعی قطعه ای که از آن ایجاد شده است:

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

اکنون از شی مکعب به روز شده خود برای ایجاد صفحه ای استفاده می کنیم که بتواند حجمی نگه دارد num_objects مقدار ناگت:

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

ما جسم هواپیمای خود را می گیریم و با ارتفاع مکعب قطعه خود یک مکعب غول پیکر به همان طول و عرض هواپیمای خود ایجاد می کنیم. مکعب 1:

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

از اینجا می خواهیم از مکعب خود وکسل بسازیم. تعداد مکعب هایی را که می خواهیم جا بیاندازیم num_objects و سپس آنها را از جسم مکعبی خود برش دهید. ما به دنبال صفحه مشبک رو به بالا مکعب خود می گردیم و سپس آن صورت را انتخاب می کنیم تا برش های خود را ایجاد کنیم. کد زیر را ببینید:

        # 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

در نهایت، مرکز بالای هر برش را که از مکعب بزرگ خود ساخته‌ایم محاسبه می‌کنیم و مکعب‌های واقعی را از آن برش‌ها ایجاد می‌کنیم. هر یک از این مکعب های تازه ایجاد شده نشان دهنده یک تکه فضا برای تخم ریزی یا حرکت قطعات در اطراف هواپیمای ما هستند. کد زیر را ببینید:

        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

در مرحله بعد، الگوریتمی را توسعه می‌دهیم که می‌فهمد کدام مکعب‌ها در هر زمان معین اشغال شده‌اند، پیدا می‌کند کدام اجسام با یکدیگر همپوشانی دارند، و اجسام همپوشانی را جداگانه به فضای خالی منتقل می‌کند. ما نمی توانیم به طور کامل از شر همه همپوشانی ها خلاص شویم، اما می توانیم آن را به اندازه کافی واقعی جلوه دهیم.



کد زیر را ببینید:

    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

خط لوله تولید تصویر: اجراهای جالب

در این بخش، آنچه را که ما داریم، تجزیه می کنیم run عملکرد در حال انجام است.

ما خود را مقداردهی اولیه می کنیم DensityController و با استفاده از ImageSaver از جانب zpy. این به ما این امکان را می‌دهد که تصاویر رندر شده خود را به‌طور بی‌نظیری در هر مکانی که انتخاب می‌کنیم ذخیره کنیم. سپس ما را اضافه می کنیم nugget دسته بندی (و اگر دسته های بیشتری داشتیم، آنها را در اینجا اضافه می کردیم). کد زیر را ببینید:

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

در مرحله بعد، ما باید یک شی منبع بسازیم که از آن قطعات کپی را تولید کنیم. در این مورد، آن است nugget_base که ما ایجاد کردیم:

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

اکنون که قطعه پایه خود را داریم، می‌خواهیم موقعیت‌های جهان (موقعیت) همه اشیاء دیگر را ذخیره کنیم تا پس از هر اجرا رندر، بتوانیم از این پوزهای ذخیره‌شده برای شروع مجدد یک رندر استفاده کنیم. همچنین قطعه پایه خود را به طور کامل از مسیر خارج می کنیم تا kdtree فضای اشغال شده را احساس نکند. در نهایت، اشیاء kdtree-cube خود را مقداردهی اولیه می کنیم. کد زیر را ببینید:

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

کد زیر پس‌زمینه‌های دانلود شده ما را از texture.ninja جمع‌آوری می‌کند، جایی که از آنها برای نمایش تصادفی در هواپیمای ما استفاده می‌شود:

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

اینجاست که جادو شروع می شود. ابتدا kdtree-cubes را برای این اجرا بازسازی می کنیم تا بتوانیم تازه شروع کنیم:

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

ما از کنترل کننده چگالی خود برای ایجاد یک نقطه تخم ریزی تصادفی برای قطعه خود استفاده می کنیم، یک کپی از آن ایجاد می کنیم nugget_base، و کپی را به نقطه تخم ریزی تصادفی تولید شده منتقل کنید:

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

در مرحله بعد، به طور تصادفی اندازه قطعه، مش قطعه، و مقیاس قطعه را تکان می دهیم تا هیچ دو قطعه یکسان به نظر نرسند:

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

ما کپی ناگت خود را به یک تبدیل می کنیم Object3D شیئی که در آن از عملکرد درخت BVH استفاده می کنیم تا ببینیم آیا صفحه ما با هر صورت یا رئوس روی کپی قطعه ما تلاقی یا همپوشانی دارد یا خیر. اگر همپوشانی با هواپیما پیدا کنیم، به سادگی قطعه را در محور Z به سمت بالا حرکت می دهیم. کد زیر را ببینید:

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

اکنون که همه ناگت‌ها ایجاد شده‌اند، از ما استفاده می‌کنیم DensityController برای جابجایی قطعات به اطراف به طوری که حداقل تعداد همپوشانی داشته باشیم، و آنهایی که همپوشانی دارند ظاهر زشتی ندارند:

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

در کد زیر: ما را بازیابی می کنیم Camera و Main Axis ژست گرفته و به طور تصادفی فاصله دوربین تا فاصله را انتخاب کنید Plane هدف - شی:

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

ما تصمیم می‌گیریم که دوربین به‌طور تصادفی در طول مسیر حرکت کند Main Axis. بسته به اینکه بخواهیم به طور عمده بالای سر باشد یا به زاویه ای که از آن صفحه می بیند اهمیت می دهیم، می توانیم تنظیم کنیم top_down_mostly پارامتر بسته به اینکه مدل آموزشی ما چقدر سیگنال "به هر حال یک قطعه حتی چیست؟" را دریافت می کند.

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

در کد زیر همین کار را با the انجام می دهیم Sun شی، و به طور تصادفی یک بافت برای آن انتخاب کنید Plane هدف - شی:

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

در نهایت، ما تمام اشیاء خود را که نمی‌خواهیم رندر شوند پنهان می‌کنیم: the nugget_base و کل ساختار مکعب ما:

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

در نهایت ما استفاده می کنیم zpy برای رندر کردن صحنه، ذخیره تصاویر و سپس ذخیره حاشیه نویسی. برای این پست، تغییرات کوچکی در آن ایجاد کردم zpy کتابخانه حاشیه نویسی برای مورد خاص من (یادداشت در هر تصویر به جای یک فایل در هر پروژه)، اما برای هدف این پست مجبور نیستید.

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

وولا!

اسکریپت ایجاد headless را اجرا کنید

اکنون که فایل Blender ذخیره شده، قطعه ایجاد شده و تمام اطلاعات پشتیبانی را داریم، بیایید فهرست کاری خود را فشرده کنیم و یکی scp آن را در دستگاه GPU ما یا از طریق آن آپلود کرد سرویس ذخیره سازی ساده آمازون (Amazon S3) یا سرویس دیگری:

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

وارد نمونه EC2 خود شوید و پوشه working_blender خود را از حالت فشرده خارج کنید:

tar xvf working_blender_dir.tar.gz

اکنون داده های خود را با شکوه کامل ایجاد می کنیم:

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

اسکریپت باید برای 500 تصویر اجرا شود و داده ها در آن ذخیره می شوند /path/to/working_blender_dir/nugget_data.

کد زیر یک حاشیه نویسی ایجاد شده با مجموعه داده ما را نشان می دهد:

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

نتیجه

در این پست، نحوه استفاده از کتابخانه انیمیشن منبع باز Blender را برای ایجاد خط لوله داده مصنوعی انتها به انتها نشان دادم.

تعداد زیادی کار جالب وجود دارد که می توانید در Blender و AWS انجام دهید. امیدواریم که این نسخه ی نمایشی بتواند به شما در پروژه بعدی خود که از نظر اطلاعات گرسنگی دارد کمک کند!

منابع


درباره نویسنده

مت کرزوس یک Sr. Scientist در وب سرویس آمازون در گروه خدمات حرفه ای AWS است

تمبر زمان:

بیشتر از آموزش ماشین AWS