جمع آوری و حاشیه نویسی داده های تصویری یکی از منابع فشرده ترین کارها در هر پروژه بینایی کامپیوتری است. جمع آوری، تجزیه و تحلیل، و آزمایش کامل جریان های تصویر در سطحی که برای رقابت در بازار فعلی نیاز دارید، ممکن است ماه ها طول بکشد. حتی پس از جمعآوری موفقیتآمیز دادهها، همچنان یک جریان ثابت از خطاهای حاشیهنویسی، تصاویر با کادربندی ضعیف، مقادیر کمی دادههای معنادار در دریایی از عکسهای ناخواسته و موارد دیگر دارید. این تنگناهای اصلی دلیلی است که ایجاد داده های مصنوعی باید در جعبه ابزار هر مهندس مدرن باشد. با ایجاد نمایشهای سهبعدی از اشیایی که میخواهیم مدلسازی کنیم، میتوانیم به سرعت الگوریتمهایی را نمونهسازی کنیم و همزمان دادههای زنده را جمعآوری کنیم.
در این پست، مثالی از استفاده از کتابخانه انیمیشن منبع باز Blender برای ایجاد خط لوله داده مصنوعی سرتاسر، با استفاده از ناگت های مرغ به عنوان مثال، به شما آموزش می دهم. تصویر زیر تصویری از داده های تولید شده در این پست وبلاگ است.
بلندر چیست؟
بلندر یک نرم افزار گرافیک سه بعدی منبع باز است که عمدتاً در انیمیشن، پرینت سه بعدی و واقعیت مجازی استفاده می شود. این مجموعه دارای مجموعهای از تقلب، انیمیشن و شبیهسازی بسیار جامع است که امکان ایجاد جهانهای سه بعدی را برای تقریباً هر مورد استفاده از بینایی رایانه فراهم میکند. همچنین دارای یک انجمن پشتیبانی بسیار فعال است که در آن اکثر، اگر نه همه، خطاهای کاربر حل می شوند.
محیط محلی خود را تنظیم کنید
ما دو نسخه از Blender را نصب می کنیم: یکی روی یک ماشین محلی با دسترسی به یک رابط کاربری گرافیکی و دیگری در یک ابر محاسبه الاستیک آمازون (Amazon EC2) نمونه P2.
Blender و ZPY را نصب کنید
Blender را از وب سایت بلندر.
سپس مراحل زیر را انجام دهید:
- دستورات زیر را اجرا کنید:
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
- هدرهای لازم پایتون را در نسخه 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
- نسخه 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
- دانلود
zpy
و از منبع نصب کنید:
git clone https://github.com/ZumoLabs/zpy
cd zpy
vi requirements.txt
- نسخه 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
- برای اطمینان از سازگاری با Blender 3.2، وارد شوید
zpy/render.py
و دو خط زیر را نظر دهید (برای اطلاعات بیشتر به رجوع کنید Blender 3.0 Failure #54):
#scene.render.tile_x = tile_size
#scene.render.tile_y = tile_size
- بعد ، نصب کنید
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__)"
- دانلود نسخه افزونه های
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/
- فایلی به نام ذخیره کنید
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
نصب نمی شود (به هر دلیلی)، می توانید آن را از طریق رابط کاربری گرافیکی نصب کنید.
- در بلندر، در ویرایش منو ، انتخاب کنید تنظیمات.
- را انتخاب کنید افزودنیهای فایرفاکس را در قسمت ناوبری قرار داده و فعال کنید
zpy
.
شما باید صفحه ای را در رابط کاربری گرافیکی باز کنید و می توانید انتخاب کنید ZPY. این تایید می کند که بلندر بارگذاری شده است.
AliceVision و Meshroom
AliceVision و Meshroom را از مخازن GitHub مربوطه خود نصب کنید:
FFmpeg به
سیستم شما باید داشته باشد ffmpeg
، اما اگر این کار را نکرد، باید انجام دهید دانلود آن است.
مش های فوری
شما می توانید کتابخانه را خودتان کامپایل کنید یا باینری های از پیش کامپایل شده موجود را دانلود کنید (که من انجام دادم) مش های فوری.
محیط AWS خود را تنظیم کنید
اکنون محیط AWS را بر روی یک نمونه EC2 تنظیم می کنیم. ما مراحل قسمت قبل را تکرار می کنیم، اما فقط برای بلندر و zpy
.
- در کنسول آمازون EC2، را انتخاب کنید راه اندازی نمونه ها.
- AMI خود را انتخاب کنید. چند گزینه از اینجا وجود دارد. ما میتوانیم یک تصویر استاندارد اوبونتو انتخاب کنیم، یک نمونه GPU را انتخاب کنیم، و سپس به صورت دستی درایورها را نصب کنیم و همه چیز را تنظیم کنیم، یا میتوانیم مسیر آسان را انتخاب کنیم و با یک AMI از پیش تنظیمشده Deep Learning شروع کنیم و فقط نگران نصب Blender باشیم. پست، از گزینه دوم استفاده می کنم و آخرین نسخه Deep Learning AMI را برای اوبونتو انتخاب می کنم (Deep Learning AMI (Ubuntu 18.04) نسخه 61.0).
- برای نوع نمونهانتخاب کنید p2.xlarge.
- اگر جفت کلید ندارید، یک مورد جدید ایجاد کنید یا موجودی را انتخاب کنید.
- برای این پست از تنظیمات پیش فرض شبکه و ذخیره سازی استفاده کنید.
- را انتخاب کنید راه اندازی نمونه ها.
- را انتخاب کنید اتصال و دستورالعملهای ورود به نمونه خود را از SSH در آن پیدا کنید کلاینت SSH تب.
- اتصال با 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 ما کامل شد، مراحل زیر را تکمیل کنید:
- رابط کاربری گرافیکی Blender را باز کنید و روی پرونده منو ، انتخاب کنید وارد كردن، پس از آن را انتخاب کنید موج Wavefront (.obj) به فایل بافت ایجاد شده خود از Meshroom.
فایل باید در آن ذخیره شود path/to/MeshroomCache/Texturing/uuid-string/texturedMesh.obj
.
- فایل را بارگذاری کنید و هیولایی را که شی 3 بعدی شما است مشاهده کنید.
اینجاست که کمی مشکل می شود.
- به سمت راست بالا بروید و آن را انتخاب کنید قاب وایرلس نماد در سایه نما درگاه.
- شیء خود را در نمای سمت راست انتخاب کنید و مطمئن شوید که برجسته شده است، به نمای طرح بندی اصلی بروید و یکی را فشار دهید برگ یا به صورت دستی انتخاب کنید حالت ویرایش.
- در مرحله بعد، نما را به گونه ای مانور دهید که به خود اجازه دهید بتوانید جسم خود را با کمترین میزان ممکن پشت آن ببینید. شما باید این کار را چند بار انجام دهید تا واقعاً درست شود.
- یک کادر محدود کننده را روی شی کلیک کرده و بکشید تا فقط قطعه برجسته شود.
- پس از اینکه مانند تصویر زیر برجسته شد، با کلیک چپ، قطعه خود را از جرم سه بعدی جدا می کنیم. جداگانه، و سپس انتخاب.
اکنون به سمت راست حرکت می کنیم، جایی که باید دو شیء بافت دار را ببینیم: texturedMesh
و texturedMesh.001
.
- شی جدید ما باید باشد
texturedMesh.001
، بنابراین ما انتخاب می کنیم texturedMesh
و انتخاب کنید حذف برای حذف جرم ناخواسته
- شی را انتخاب کنید (
texturedMesh.001
) در سمت راست، به سمت بیننده خود حرکت کنید و شی را انتخاب کنید، تنظیم مبداو منشاء مرکز توده.
حال اگر بخواهیم می توانیم شیء خود را به مرکز ویوپورت منتقل کنیم (یا به سادگی آن را در همان جایی که هست رها کنیم) و آن را با شکوه کامل مشاهده کنیم. به سیاهچاله بزرگی توجه کنید که ما واقعاً از آن پوشش فیلم خوبی دریافت نکردیم! ما باید این را اصلاح کنیم.
برای پاک کردن شیء خود از هرگونه ناخالصی پیکسلی، شیء خود را به یک فایل .obj صادر می کنیم. حتما انتخاب کنید فقط انتخاب هنگام صادرات
جمع آوری داده ها: با Instant Meshes پاکسازی کنید
اکنون ما دو مشکل داریم: تصویر ما دارای یک شکاف پیکسلی است که در اثر فیلمبرداری ضعیف ما ایجاد میشود و باید آن را تمیز کنیم، و تصویر ما فوقالعاده متراکم است (که باعث میشود تولید تصاویر بسیار زمانبر باشد). برای رفع هر دو مشکل، باید از نرم افزاری به نام Instant Meshes استفاده کنیم تا سطح پیکسل خود را برای پوشاندن سیاهچاله برون یابی کنیم و همچنین کل جسم را به اندازه کوچکتر و چگالی کمتر کوچک کنیم.
- Instant Meshes را باز کنید و ذخیره شده اخیر ما را بارگیری کنید
nugget.obj
فایل.
- تحت زمینه جهت یابی، انتخاب کنید حل.
- تحت فیلد موقعیت، انتخاب کنید حل.
اینجاست که جالب می شود. اگر شیء خود را کاوش کردید و متوجه شدید که خطوط متقاطع حلگر Position ناهمگون به نظر می رسند، می توانید نماد شانه را در زیر انتخاب کنید. زمینه جهت یابی و خطوط را به درستی دوباره ترسیم کنید.
- را انتخاب کنید حل برای هردو زمینه جهت یابی و فیلد موقعیت.
- اگر همه چیز خوب به نظر می رسد، مش را صادر کنید، نام آن را چیزی شبیه به آن بگذارید
nugget_refined.obj
، و آن را در دیسک ذخیره کنید.
جمع آوری داده ها: تکان دهید و بپزید!
از آنجایی که مش کم پلی ما هیچ بافت تصویری مرتبط با آن ندارد و مش پلی پلی بالا ما دارد، یا باید بافت پلی بالا را روی مش کم پلی بپزیم یا یک بافت جدید ایجاد کنیم و آن را به آن اختصاص دهیم. شی ما برای سادگی، ما یک بافت تصویر را از ابتدا ایجاد می کنیم و آن را روی قطعه خود اعمال می کنیم.
من از جستجوی تصویر گوگل برای قطعات و سایر چیزهای سرخ شده استفاده کردم تا تصویری با وضوح بالا از سطح یک شی سرخ شده بدست بیاورم. من یک تصویر فوق العاده با وضوح بالا از کشک پنیر سرخ شده پیدا کردم و یک تصویر جدید پر از بافت سرخ شده درست کردم.
با این تصویر، من آماده هستم تا مراحل زیر را انجام دهم:
- Blender را باز کرده و جدید را بارگذاری کنید
nugget_refined.obj
به همان روشی که شی اولیه خود را بارگذاری کردید: روی پرونده منو ، انتخاب کنید وارد كردن, موج Wavefront (.obj)، و را انتخاب کنید nugget_refined.obj
فایل.
- بعد ، به سایه تب.
در پایین باید به دو کادر با عنوان توجه کنید BDSF اصولی و خروجی مواد.
- بر اضافه کردن منو ، انتخاب کنید بافت و بافت تصویر.
An بافت تصویر کادر باید ظاهر شود
- را انتخاب کنید باز کردن تصویر و تصویر بافت سرخ شده خود را بارگذاری کنید.
- ماوس خود را بین آن بکشید رنگ در بافت تصویر جعبه و رنگ پایه در BDSF اصولی جعبه.
حالا قطعه شما باید خوب باشد!
جمع آوری داده ها: ایجاد متغیرهای محیطی Blender
اکنون که ما شیء پایه خود را داریم، باید چند مجموعه و متغیر محیطی ایجاد کنیم تا به ما در فرآیند کمک کنند.
- روی ناحیه صحنه دست کلیک چپ کرده و انتخاب کنید مجموعه جدید.
- مجموعه های زیر را ایجاد کنید: زمینه, NUGGETو تخم ریزی شد.
- قطعه را به سمت NUGGET جمع آوری کرده و نام آن را تغییر دهید nugget_base.
جمع آوری داده ها: یک هواپیما بسازید
ما یک شی پسزمینه ایجاد میکنیم که هنگام رندر کردن تصاویر، قطعات ما از آن تولید میشوند. در یک مورد استفاده در دنیای واقعی، این هواپیما جایی است که قطعات ما مانند سینی یا سطل زباله قرار می گیرند.
- بر اضافه کردن منو ، انتخاب کنید شبکه و پس از آن هواپیما.
از اینجا به سمت راست صفحه رفته و کادر نارنجی رنگ (ویژگی های شی).
- در دگرگون کردن پنجره، برای XYZ اویلر، تنظیم X به 46.968، Y تا 46.968 ، و Z به 1.0.
- برای هردو موقعیت مکانی: و چرخش، تنظیم X, Yو Z به 0.
جمع آوری داده ها: دوربین و محور را تنظیم کنید
در مرحله بعد، دوربین هایمان را به درستی تنظیم می کنیم تا بتوانیم تصاویر را تولید کنیم.
- بر اضافه کردن منو ، انتخاب کنید خالی و محور دشت.
- شی را نام ببرید محور اصلی.
- مطمئن شوید که محور ما برای همه متغیرها 0 است (بنابراین مستقیماً در مرکز قرار دارد).
- اگر قبلاً دوربینی ساختهاید، آن دوربین را به زیر محور اصلی بکشید.
- را انتخاب کنید مورد و دگرگون کردن.
- برای موقعیت مکانی:، تنظیم X به 0، Y تا 0 ، و Z به 100.
جمع آوری داده ها: اینجا خورشید می آید
بعد، یک شی Sun اضافه می کنیم.
- بر اضافه کردن منو ، انتخاب کنید نور و خورشید.
مکان این جسم لزوماً اهمیتی ندارد تا زمانی که در جایی بر روی جسم صفحه ای که ما تنظیم کرده ایم متمرکز باشد.
- نماد لامپ سبز را در قسمت پایین سمت راست انتخاب کنید (ویژگی های داده های شی) و قدرت را روی 5.0 قرار دهید.
- همین روش را برای اضافه کردن 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 است