diff --git a/poetry.lock b/poetry.lock index 073c385..66eba0a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -125,6 +125,18 @@ files = [ {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] +[[package]] +name = "fake-bpy-module-latest" +version = "20230118" +description = "Collection of the fake Blender Python API module for the code completion." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fake-bpy-module-latest-20230118.tar.gz", hash = "sha256:a3f45879347ef37b6fa5cd66ccc49634a14b420fa1b0fd196dfbf177d8602053"}, + {file = "fake_bpy_module_latest-20230118-py3-none-any.whl", hash = "sha256:66d0b76ba65e01a91f74e77114705d744f3c2ad1078d060abf4a42c7fff9e79b"}, +] + [[package]] name = "filelock" version = "3.9.0" @@ -692,4 +704,4 @@ testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7 [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "78e5e2403aaceb237ee187dd395971a4ee3a07af884a3d2be70e201a8285b2f4" +content-hash = "70277d35aa55c1022524e37b75fcd711637ee16fb1737bc157244cf83d57efff" diff --git a/pyproject.toml b/pyproject.toml index 6fd152b..6fff4c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ numpy = "^1.23.5" python = ">=3.8.1,<4.0" rich = "^12.6.0" opencv-python = "^4.6.0.66" +fake-bpy-module-latest = "^20230118" [tool.poetry.group.dev.dependencies] Flake8-pyproject = "^1.1.0" diff --git a/get_proj.py b/src/blender/proj.py similarity index 79% rename from get_proj.py rename to src/blender/proj.py index 1c6003d..47dab32 100644 --- a/get_proj.py +++ b/src/blender/proj.py @@ -1,10 +1,5 @@ -# https://blender.stackexchange.com/questions/38009/3x4-camera-matrix-from-blender-camera - import bpy from mathutils import Matrix -import pickle -import numpy as np - #--------------------------------------------------------------- # 3x4 P matrix from Blender camera @@ -27,7 +22,7 @@ def get_sensor_fit(sensor_fit, size_x, size_y): # Build intrinsic camera parameters from Blender camera data # -# See notes on this in +# See notes on this in # blender.stackexchange.com/questions/15102/what-is-blenders-camera-projection-matrix-model # as well as # https://blender.stackexchange.com/a/120063/3581 @@ -66,7 +61,7 @@ def get_calibration_matrix_K_from_blender(camd): return K # Returns camera rotation and translation matrices from Blender. -# +# # There are 3 coordinate systems involved: # 1. The World coordinates: "world" # - right-handed @@ -76,7 +71,7 @@ def get_calibration_matrix_K_from_blender(camd): # - right-handed: negative z look-at direction # 3. The desired computer vision camera coordinates: "cv" # - x is horizontal -# - y is down (to align to the actual pixel coordinates +# - y is down (to align to the actual pixel coordinates # used in digital images) # - right-handed: positive z look-at direction def get_3x4_RT_matrix_from_blender(cam): @@ -86,7 +81,7 @@ def get_3x4_RT_matrix_from_blender(cam): (0, -1, 0), (0, 0, -1))) - # Transpose since the rotation is object rotation, + # Transpose since the rotation is object rotation, # and we want coordinate rotation # R_world2bcam = cam.rotation_euler.to_matrix().transposed() # T_world2bcam = -1*R_world2bcam @ location @@ -97,7 +92,7 @@ def get_3x4_RT_matrix_from_blender(cam): # Convert camera location to translation vector used in coordinate changes # T_world2bcam = -1*R_world2bcam @ cam.location - # Use location from matrix_world to account for constraints: + # Use location from matrix_world to account for constraints: T_world2bcam = -1*R_world2bcam @ location # Build the coordinate transform matrix from world to computer vision camera @@ -117,28 +112,25 @@ def get_3x4_P_matrix_from_blender(cam): RT = get_3x4_RT_matrix_from_blender(cam) return K@RT, K, RT -def run_script(scene): - with open(PICKLE_PATH, 'rb') as file: - matrices = pickle.load(file) +# ---------------------------------------------------------- +if __name__ == "__main__": + # Insert your camera name here + cam = bpy.data.objects['Camera'] + P, K, RT = get_3x4_P_matrix_from_blender(cam) + print("K") + print(K) + print("RT") + print(RT) + print("P") + print(P) - projection_matrix, _, _ = get_3x4_P_matrix_from_blender(scene.camera) - matrices.append(np.array(projection_matrix)) - - with open(PICKLE_PATH, 'wb') as file: - pickle.dump(matrices, file) + print("==== 3D Cursor projection ====") + pc = P @ bpy.context.scene.cursor.location + pc /= pc[2] + print("Projected cursor location") + print(pc) -def setup_script(scene): - matrices = [] - with open(PICKLE_PATH, 'wb') as file: - pickle.dump(matrices, file) - -PICKLE_PATH = "/tmp/pickle.truc" - -# clear handlers -bpy.app.handlers.render_init.clear() -bpy.app.handlers.frame_change_pre.clear() -bpy.app.handlers.frame_change_post.clear() - -# add handler -bpy.app.handlers.render_init.append(setup_script) -bpy.app.handlers.frame_change_post.append(run_script) + # Bonus code: save the 3x4 P matrix into a plain text file + # Don't forget to import numpy for this + #nP = numpy.matrix(P) + #numpy.savetxt("/tmp/P3x4.txt", nP) # to select precision, use e.g. fmt='%.2f' diff --git a/src/blender/render.py b/src/blender/render.py new file mode 100644 index 0000000..03a5fed --- /dev/null +++ b/src/blender/render.py @@ -0,0 +1,67 @@ +import bpy +import math +import itertools +import pathlib +import pickle +import sys +import numpy as np + +# dirty hack to import proj.py +dir = pathlib.Path(bpy.data.filepath).parent +dir = dir / "src" / "blender" +sys.path.append(str(dir)) + +from proj import get_3x4_P_matrix_from_blender + +# setup output path +EXPORT_PATH = pathlib.Path("/tmp/") + +# setup camera poses +phis = [-45, 0, 45] +thetas = [0, 45, 90, 135, 180, 225, 270, 315] + +# convert to radians +phis = [math.radians(phi) for phi in phis] +thetas = [math.radians(theta) for theta in thetas] + +# create all possible combinations +poses = list(itertools.product(phis, thetas)) + +# create export folders +pathlib.Path(EXPORT_PATH / "images").mkdir(parents=True, exist_ok=True) +pathlib.Path(EXPORT_PATH / "masks").mkdir(parents=True, exist_ok=True) +pathlib.Path(EXPORT_PATH / "cameras").mkdir(parents=True, exist_ok=True) + +# set "File Output" nodes' "Base Path" property +bpy.data.scenes["Scene"].node_tree.nodes["Image Output"].base_path = str(EXPORT_PATH / "images") +bpy.data.scenes["Scene"].node_tree.nodes["Mask Output"].base_path = str(EXPORT_PATH / "masks") + +# get camera +cam = bpy.data.objects["Camera"] + +for i, (phi, theta) in enumerate(poses): + print(f"Rendering pose {i}...") + + # set camera pose + bpy.context.scene.objects["Empty"].rotation_euler[0] = phi + bpy.context.scene.objects["Empty"].rotation_euler[2] = theta + + # get camera matrices + P, K, RT = get_3x4_P_matrix_from_blender(cam) + + # save camera matrices + with open(EXPORT_PATH / "cameras" / f"{i:04d}.pickle", "wb") as f: + pickle.dump({ + "P": np.array(P), + "K": np.array(K), + "RT": np.array(RT), + }, f) + print(f"Saved camera matrices: {i:04d}.pickle") + + + # set frame number + bpy.context.scene.frame_current = i + + # render the frame + bpy.ops.render.render(write_still=False) + diff --git a/fvi.py b/src/fvi.py similarity index 100% rename from fvi.py rename to src/fvi.py diff --git a/intersec_line_voxel.py b/src/intersec_line_voxel.py similarity index 100% rename from intersec_line_voxel.py rename to src/intersec_line_voxel.py diff --git a/main.py b/src/main.py similarity index 100% rename from main.py rename to src/main.py diff --git a/matrices_reader.py b/src/matrices_reader.py similarity index 100% rename from matrices_reader.py rename to src/matrices_reader.py diff --git a/torus.blend b/torus.blend index 2a27d71..e4672e1 100644 Binary files a/torus.blend and b/torus.blend differ