ホーム » 「TensorFlow Graphics」タグがついた投稿

タグアーカイブ: TensorFlow Graphics

TensorFlow Graphics : Tutorials : 球面調和関数レンダリング

TensorFlow Graphics : Advanced Tutorials : 球面調和関数レンダリング (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/31/2019

* 本ページは、TensorFlow Graphics の github レポジトリの次のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

TensorFlow Graphics : Advanced Tutorials : 球面調和関数レンダリング

このチュートリアルは進んだトピックをカバーしておりそれ故に step by step に詳細を提供するよりも、球面調和関数の高位な理解とライティングのためのそれらの利用を形成するために制御可能な toy サンプルを提供することに注力しています。球面調和関数とライティングのためのそれらの利用の良い理解を形成するための良い資料は Spherical Harmonics Lighting: the Gritty Details です。

このチュートリアルは球面調和関数を使用して球面に渡り定義された関数をどのように近似するかを示します。これらはライティングと反射率を近似するために使用できて、非常に効率的なレンダリングに繋がります。

より詳細において、後述のセルは以下を示します :

  • 調和関数 (SH) によるライティング環境の近似
  • 帯域調和関数 (ZH) によりランバート BRDF の近似
  • 帯域調和関数の回転
  • SH ライティングと ZH BRDF の球面調和関数畳み込みによるレンダリング

 

セットアップ & Imports

このノートブックに含まれるデモを実行するために必要な総てを import しましょう。

###########
# Imports #
###########
import math

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

from tensorflow_graphics.geometry.representation import grid
from tensorflow_graphics.geometry.representation import ray
from tensorflow_graphics.geometry.representation import vector
from tensorflow_graphics.rendering.camera import orthographic
from tensorflow_graphics.math import spherical_harmonics
from tensorflow_graphics.math import math_helpers as tf_math

tf.enable_eager_execution()

 

調和関数によるライティングの近似

#@title Controls { vertical-output: false, run: "auto" }
max_band = 2  #@param { type: "slider", min: 0, max: 10 , step: 1 }

#########################################################################
# This cell creates a lighting function which we approximate with an SH #
#########################################################################

def image_to_spherical_coordinates(image_width, image_height):
  pixel_grid_start = np.array((0, 0), dtype=type)
  pixel_grid_end = np.array((image_width - 1, image_height - 1), dtype=type)
  pixel_nb = np.array((image_width, image_height))
  pixels = grid.generate(pixel_grid_start, pixel_grid_end, pixel_nb)
  normalized_pixels = pixels / (image_width - 1, image_height - 1)
  spherical_coordinates = tf_math.square_to_spherical_coordinates(
      normalized_pixels)
  return spherical_coordinates


def light_function(theta, phi):
  theta = tf.convert_to_tensor(theta)
  phi = tf.convert_to_tensor(phi)
  zero = tf.zeros_like(theta)
  return tf.maximum(zero,
                    -4.0 * tf.sin(theta - np.pi) * tf.cos(phi - 2.5) - 3.0)


light_image_width = 30
light_image_height = 30
type = np.float64

# Builds the pixels grid and compute corresponding spherical coordinates.
spherical_coordinates = image_to_spherical_coordinates(light_image_width,
                                                       light_image_height)
theta = spherical_coordinates[:, :, 1]
phi = spherical_coordinates[:, :, 2]

# Samples the light function.
sampled_light_function = light_function(theta, phi)
ones_normal = tf.ones_like(theta)
spherical_coordinates_3d = tf.stack((ones_normal, theta, phi), axis=-1)
samples_direction_to_light = tf_math.spherical_to_cartesian_coordinates(
    spherical_coordinates_3d)

# Samples the SH.
l, m = spherical_harmonics.generate_l_m_permutations(max_band)
l = tf.convert_to_tensor(l)
m = tf.convert_to_tensor(m)
l_broadcasted = tf.broadcast_to(l, [light_image_width, light_image_height] +
                                l.shape.as_list())
m_broadcasted = tf.broadcast_to(m, [light_image_width, light_image_height] +
                                l.shape.as_list())
theta = tf.expand_dims(theta, axis=-1)
theta_broadcasted = tf.broadcast_to(
    theta, [light_image_width, light_image_height, 1])
phi = tf.expand_dims(phi, axis=-1)
phi_broadcasted = tf.broadcast_to(phi, [light_image_width, light_image_height, 1])
sh_coefficients = spherical_harmonics.evaluate_spherical_harmonics(
    l_broadcasted, m_broadcasted, theta_broadcasted, phi_broadcasted)
sampled_light_function_broadcasted = tf.expand_dims(
    sampled_light_function, axis=-1)
sampled_light_function_broadcasted = tf.broadcast_to(
    sampled_light_function_broadcasted,
    [light_image_width, light_image_height] + l.shape.as_list())

# Integrates the light function times SH over the sphere.
projection = sh_coefficients * sampled_light_function_broadcasted * 4.0 * math.pi / (
    light_image_width * light_image_height)
light_coeffs = tf.reduce_sum(projection, (0, 1))

# Reconstructs the image.
reconstructed_light_function = tf.squeeze(
    vector.dot(sh_coefficients, light_coeffs))

print(
    "average l2 reconstruction error ",
    np.linalg.norm(sampled_light_function - reconstructed_light_function) /
    (light_image_width * light_image_height))

vmin = np.minimum(
    np.amin(np.minimum(sampled_light_function, reconstructed_light_function)),
    0.0)
vmax = np.maximum(
    np.amax(np.maximum(sampled_light_function, reconstructed_light_function)),
    1.0)
# Plots results.
plt.figure(figsize=(10, 10))
ax = plt.subplot("131")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Original lighting function")
_ = ax.imshow(sampled_light_function, vmin=vmin, vmax=vmax)
ax = plt.subplot("132")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Spherical Harmonics approximation")
_ = ax.imshow(reconstructed_light_function, vmin=vmin, vmax=vmax)
ax = plt.subplot("133")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Difference")
_ = ax.imshow(
    np.abs(reconstructed_light_function - sampled_light_function),
    vmin=vmin,
    vmax=vmax)

average l2 reconstruction error  0.004166945812788392

 

帯域調和関数によりランバート BRDF を近似する

#################################################################
# This cell creates an SH that approximates the Lambertian BRDF #
#################################################################

# The image dimensions control how many uniform samples we draw from the BRDF.
brdf_image_width = 30
brdf_image_height = 30
type = np.float64

# Builds the pixels grid and compute corresponding spherical coordinates.
spherical_coordinates = image_to_spherical_coordinates(brdf_image_width,
                                                       brdf_image_height)

# Samples the BRDF function.
cos_theta = tf.cos(spherical_coordinates[:, :, 1])
sampled_brdf = tf.maximum(tf.zeros_like(cos_theta), cos_theta / np.pi)

# Samples the zonal SH.
l, m = spherical_harmonics.generate_l_m_zonal(max_band)
l_broadcasted = tf.broadcast_to(l, [brdf_image_width, brdf_image_height] +
                                l.shape.as_list())
m_broadcasted = tf.broadcast_to(m, [brdf_image_width, brdf_image_height] +
                                l.shape.as_list())
theta = tf.expand_dims(spherical_coordinates[:, :, 1], axis=-1)
theta_broadcasted = tf.broadcast_to(
    theta, [brdf_image_width, brdf_image_height, 1])
phi = tf.expand_dims(spherical_coordinates[:, :, 2], axis=-1)
phi_broadcasted = tf.broadcast_to(phi, [brdf_image_width, brdf_image_height, 1])
sh_coefficients = spherical_harmonics.evaluate_spherical_harmonics(
    l_broadcasted, m_broadcasted, theta_broadcasted, phi_broadcasted)
sampled_brdf_broadcasted = tf.expand_dims(sampled_brdf, axis=-1)
sampled_brdf_broadcasted = tf.broadcast_to(
    sampled_brdf_broadcasted,
    [brdf_image_width, brdf_image_height] + l.shape.as_list())

# Integrates the BRDF function times SH over the sphere.
projection = sh_coefficients * sampled_brdf_broadcasted * 4.0 * math.pi / (
    brdf_image_width * brdf_image_height)
brdf_coeffs = tf.reduce_sum(projection, (0, 1))

# Reconstructs the image.
reconstructed_brdf = tf.squeeze(vector.dot(sh_coefficients, brdf_coeffs))

print(
    "average l2 reconstruction error ",
    np.linalg.norm(sampled_brdf - reconstructed_brdf) /
    (brdf_image_width * brdf_image_height))

vmin = np.minimum(np.amin(np.minimum(sampled_brdf, reconstructed_brdf)), 0.0)
vmax = np.maximum(
    np.amax(np.maximum(sampled_brdf, reconstructed_brdf)), 1.0 / np.pi)
# Plots results.
plt.figure(figsize=(10, 10))
ax = plt.subplot("131")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Original reflectance function")
_ = ax.imshow(sampled_brdf, vmin=vmin, vmax=vmax)
ax = plt.subplot("132")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Zonal Harmonics approximation")
_ = ax.imshow(reconstructed_brdf, vmin=vmin, vmax=vmax)
ax = plt.subplot("133")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Difference")
_ = ax.imshow(np.abs(sampled_brdf - reconstructed_brdf), vmin=vmin, vmax=vmax)

plt.figure(figsize=(10, 5))
plt.plot(
    spherical_coordinates[:, 0, 1],
    sampled_brdf[:, 0],
    label="max(0,cos(x) / pi)")
plt.plot(
    spherical_coordinates[:, 0, 1],
    reconstructed_brdf[:, 0],
    label="SH approximation")
plt.title("Approximation quality")
plt.legend()
plt.show()
average l2 reconstruction error  0.0006432566780716586

 

帯域調和関数の回転

###############################
# Rotation of zonal harmonics #
###############################

r_theta = tf.constant(np.pi / 2, shape=(1,), dtype=brdf_coeffs.dtype)
r_phi = tf.constant(0.0, shape=(1,), dtype=brdf_coeffs.dtype)
rotated_zonal_coefficients = spherical_harmonics.rotate_zonal_harmonics(
    brdf_coeffs, r_theta, r_phi)

# Builds the pixels grid and compute corresponding spherical coordinates.
pixel_grid_start = np.array((0, 0), dtype=type)
pixel_grid_end = np.array((brdf_image_width - 1, brdf_image_height - 1),
                          dtype=type)
pixel_nb = np.array((brdf_image_width, brdf_image_height))
pixels = grid.generate(pixel_grid_start, pixel_grid_end, pixel_nb)
normalized_pixels = pixels / (brdf_image_width - 1, brdf_image_height - 1)
spherical_coordinates = tf_math.square_to_spherical_coordinates(
    normalized_pixels)

# reconstruction.
l, m = spherical_harmonics.generate_l_m_permutations(max_band)
l_broadcasted = tf.broadcast_to(
    l, [light_image_width, light_image_height] + l.shape.as_list())
m_broadcasted = tf.broadcast_to(
    m, [light_image_width, light_image_height] + l.shape.as_list())
theta = tf.expand_dims(spherical_coordinates[:, :, 1], axis=-1)
theta_broadcasted = tf.broadcast_to(
    theta, [light_image_width, light_image_height, 1])
phi = tf.expand_dims(spherical_coordinates[:, :, 2], axis=-1)
phi_broadcasted = tf.broadcast_to(
    phi, [light_image_width, light_image_height, 1])
sh_coefficients = spherical_harmonics.evaluate_spherical_harmonics(
    l_broadcasted, m_broadcasted, theta_broadcasted, phi_broadcasted)

reconstructed_rotated_brdf_function = tf.squeeze(
    vector.dot(sh_coefficients, rotated_zonal_coefficients))

plt.figure(figsize=(10, 10))
ax = plt.subplot("121")
ax.set_title("Zonal SH")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
_ = ax.imshow(reconstructed_brdf)
ax = plt.subplot("122")
ax.set_title("Rotated version")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
_ = ax.imshow(reconstructed_rotated_brdf_function)

 

SH ライティングと ZH BRDF の球面調和関数畳み込みによる再構築

############################################################################################
# Helper function allowing to estimate sphere normal and depth for each pixel in the image #
############################################################################################
def compute_intersection_normal_sphere(image_width, image_height, sphere_radius,
                                       sphere_center, type):
  pixel_grid_start = np.array((0.5, 0.5), dtype=type)
  pixel_grid_end = np.array((image_width - 0.5, image_height - 0.5), dtype=type)
  pixel_nb = np.array((image_width, image_height))
  pixels = grid.generate(pixel_grid_start, pixel_grid_end, pixel_nb)

  pixel_ray = tf.math.l2_normalize(orthographic.ray(pixels), axis=-1)
  zero_depth = np.zeros([image_width, image_height, 1])
  pixels_3d = orthographic.unproject(pixels, zero_depth)

  intersections_points, normals = ray.intersection_ray_sphere(
      sphere_center, sphere_radius, pixel_ray, pixels_3d)
  return intersections_points[0, :, :, :], normals[0, :, :, :]


###############################
# Setup the image, and sphere #
###############################
# Image dimensions
image_width = 100
image_height = 80

# Sphere center and radius
sphere_radius = np.array((30.0,), dtype=type)
sphere_center = np.array((image_width / 2.0, image_height / 2.0, 100.0),
                         dtype=type)

# Builds the pixels grid and compute corresponding spherical coordinates.
pixel_grid_start = np.array((0, 0), dtype=type)
pixel_grid_end = np.array((image_width - 1, image_height - 1), dtype=type)
pixel_nb = np.array((image_width, image_height))
pixels = grid.generate(pixel_grid_start, pixel_grid_end, pixel_nb)
normalized_pixels = pixels / (image_width - 1, image_height - 1)
spherical_coordinates = tf_math.square_to_spherical_coordinates(
    normalized_pixels)

################################################################################################
# For each pixel in the image, estimate the corresponding surface point and associated normal. #
################################################################################################
intersection_3d, surface_normal = compute_intersection_normal_sphere(
    image_width, image_height, sphere_radius, sphere_center, type)

surface_normals_spherical_coordinates = tf_math.cartesian_to_spherical_coordinates(
    surface_normal)

# SH
l, m = spherical_harmonics.generate_l_m_permutations(
    max_band)  # recomputed => optimize
l = tf.convert_to_tensor(l)
m = tf.convert_to_tensor(m)
l_broadcasted = tf.broadcast_to(l,
                                [image_width, image_height] + l.shape.as_list())
m_broadcasted = tf.broadcast_to(m,
                                [image_width, image_height] + l.shape.as_list())

#################################################
# Estimates result using SH convolution - cheap #
#################################################

sh_integration = spherical_harmonics.integration_product(
    light_coeffs,
    spherical_harmonics.rotate_zonal_harmonics(
        brdf_coeffs,
        tf.expand_dims(surface_normals_spherical_coordinates[:, :, 1], axis=-1),
        tf.expand_dims(surface_normals_spherical_coordinates[:, :, 2],
                       axis=-1)),
    keepdims=False)

# Sets pixels not belonging to the sphere to 0.
sh_integration = tf.where(
    tf.greater(intersection_3d[:, :, 2], 0.0), sh_integration,
    tf.zeros_like(sh_integration))
# Sets pixels with negative light to 0.
sh_integration = tf.where(
    tf.greater(sh_integration, 0.0), sh_integration,
    tf.zeros_like(sh_integration))

###########################################
# 'Brute force' solution - very expensive #
###########################################

factor = 4.0 * np.pi / (light_image_width * light_image_height)
gt = tf.einsum(
    "hwn,uvn->hwuv", surface_normal,
    samples_direction_to_light *
    tf.expand_dims(sampled_light_function, axis=-1))
gt = tf.maximum(gt, 0.0)  # removes negative dot products
gt = tf.reduce_sum(gt, axis=(2, 3))
# Sets pixels not belonging to the sphere to 0.
gt = tf.where(tf.greater(intersection_3d[:, :, 2], 0.0), gt, tf.zeros_like(gt))
gt *= factor

# TODO: gt and sh_integration differ by a factor of pi.
sh_integration = np.transpose(sh_integration, (1, 0))
gt = np.transpose(gt, (1, 0))

plt.figure(figsize=(10, 20))
ax = plt.subplot("121")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("SH light and SH BRDF")
_ = ax.imshow(sh_integration, vmin=0.0)
ax = plt.subplot("122")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("GT light and GT BRDF")
_ = ax.imshow(gt, vmin=0.0)

 

以上






TensorFlow Graphics : Tutorials : 非剛体サーフェス

TensorFlow Graphics : Intermediate Tutorials : 非剛体サーフェス (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/30/2019

* 本ページは、TensorFlow Graphics の github レポジトリの次のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

TensorFlow Graphics : Intermediate Tutorials : 非剛体サーフェス

非剛体サーフェス変形はとりわけ、メッシュを対話的に操作したりポイントクラウドに fit させるためにテンプレートメッシュを変形するために使用できるテクニックです。メッシュを操作するとき、これは例えばユーザにキャラクターの手を動かして腕の残りを現実的な方法で変形させることを可能にします。変形はまたパーツのスケールやメッシュ全体に渡り遂行できることに注意することは興味深いでしょう。

このノートブックは上の画像に含まれる一つに類似した変形を遂行するためにどのように TensorFlow Graphics を使用するかを示します。

 

セットアップ & Imports

このノートブックに含まれるデモを実行するために必要な総てを import しましょう。

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np
import tensorflow as tf

from tensorflow_graphics.geometry.deformation_energy import as_conformal_as_possible
from tensorflow_graphics.geometry.representation.mesh import utils as mesh_utils
from tensorflow_graphics.geometry.transformation import quaternion
from tensorflow_graphics.math.optimizer import levenberg_marquardt
from tensorflow_graphics.notebooks import threejs_visualization
from tensorflow_graphics.notebooks.resources import triangulated_stripe

tf.enable_eager_execution()

この例では、flat と矩形サーフェスに対応するメッシュを構築します。スライダーを使用して、そのサーフェスに適用される変形コンストレイントの位置を制御できます。これらはそれぞれメッシュの左境界、中心、そして右境界に沿った総てのポイントに対応します。

mesh_rest_pose = triangulated_stripe.mesh
connectivity = mesh_utils.extract_unique_edges_from_triangular_mesh(triangulated_stripe.mesh['faces'])
camera = threejs_visualization.build_perspective_camera(
    field_of_view=40.0, position=(0.0, -5.0, 5.0))
width = 500
height = 500
_ = threejs_visualization.triangular_mesh_renderer([mesh_rest_pose],
                                                   width=width,
                                                   height=height,
                                                   camera=camera)

 

以上






TensorFlow Graphics : Tutorials : マテリアルとのライト相互作用 (リフレクション)

TensorFlow Graphics : Intermediate Tutorials : マテリアルとのライト相互作用 (リフレクション) (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/30/2019

* 本ページは、TensorFlow Graphics の github レポジトリの次のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

TensorFlow Graphics : Intermediate Tutorials : マテリアルとのライト相互作用 (リフレクション)

私達の回りの世界は非常に複雑でガラスから木に渡る多様なマテリアル (素材) から構成されます。各マテリアルはそれ自身の内部プロパティを所持していてライトと異なった相互作用をします。例えば、幾つかは拡散して (e.g. 紙や大理石) ライティング条件が与えられたとき、どのようなアングルからでも同じように見えます。他の素材 (e.g. 金属) は大きく異なる外見を持ち鏡面 (= specularities) のような視点依存効果を示します。

ライトがどのように素材と相互作用するかを正確にモデル化することは、サブサーフェイス・スキャタリング (e.g. スキン) と反射率 (e.g. 水) のような効果を伴う複雑な過程です。このチュートリアルでは最も一般的な効果に注目します、それは反射です。反射率をモデル化する際、双方向反射率分布関数 (BRDF, Bidirectional Reflectance Distribution Functions) が選択するメソッドです。入射光の方向が与えられた時、BRDF はサーフェスが観測されている方向に跳ね返るライトの総量を制御します (下の画像で任意の gray ベクトル)。

Lambertian = ランバート反射 ; specular = 鏡面反射

 
このチュートリアルでは、3 つの球上にライトをあてます、それぞれ上の画像で記述されたマテリアルを持ち、そこでは鏡面マテリアルは Phong の鏡面モデルでモデル化されます。

Note: このチュートリアルは進んだトピックをカバーしておりそれ故に step by step に詳細を提供するよりも、BRDF の高位な理解を形成するために制御可能な toy サンプルを提供することに注力しています。

 

セットアップ & Imports

このノートブックに含まれるデモを実行するために必要な総てを import しましょう。

###########
# Imports #
###########
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import math as m
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

from tensorflow_graphics.rendering.reflectance import lambertian
from tensorflow_graphics.rendering.reflectance import phong
from tensorflow_graphics.rendering.camera import orthographic
from tensorflow_graphics.geometry.representation import grid
from tensorflow_graphics.geometry.representation import ray
from tensorflow_graphics.geometry.representation import vector

tf.enable_eager_execution()

 

球体の制御可能なライティング

###########
# Imports #
###########
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import math as m
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

from tensorflow_graphics.rendering.reflectance import lambertian
from tensorflow_graphics.rendering.reflectance import phong
from tensorflow_graphics.rendering.camera import orthographic
from tensorflow_graphics.geometry.representation import grid
from tensorflow_graphics.geometry.representation import ray
from tensorflow_graphics.geometry.representation import vector

tf.enable_eager_execution()
###############
# UI controls #
###############
#@title Controls { vertical-output: false, run: "auto" }
light_x_position = -0.4  #@param { type: "slider", min: -1, max: 1 , step: 0.05 }
albedo_red = 0.7  #@param { type: "slider", min: 0.0, max: 1.0 , step: 0.1 }
albedo_green = 1  #@param { type: "slider", min: 0.0, max: 1.0 , step: 0.1 }
albedo_blue = 1  #@param { type: "slider", min: 0.0, max: 1.0 , step: 0.1 }
light_red = 1  #@param { type: "slider", min: 0.0, max: 1.0 , step: 0.1 }
light_green = 1  #@param { type: "slider", min: 0.0, max: 1.0 , step: 0.1 }
light_blue = 1  #@param { type: "slider", min: 0.0, max: 1.0 , step: 0.1 }
specular_percentage = 0.25  #@param { type: "slider", min: 0, max: 1 , step: 0.01 }
shininess = 4  #@param { type: "slider", min: 0, max: 10, step: 1 }
diffuse_percentage = 1.0 - specular_percentage
dtype = np.float64
albedo = np.array((albedo_red, albedo_green, albedo_blue), dtype=dtype)

def compute_intersection_normal_sphere(image_width, image_height, sphere_radius,
                                       sphere_center, dtype):
  pixel_grid_start = np.array((0.5, 0.5), dtype=dtype)
  pixel_grid_end = np.array((image_width - 0.5, image_height - 0.5), dtype=dtype)
  pixel_nb = np.array((image_width, image_height))
  pixels = grid.generate(pixel_grid_start, pixel_grid_end, pixel_nb)

  pixel_ray = tf.math.l2_normalize(orthographic.ray(pixels), axis=-1)
  zero_depth = np.zeros([image_width, image_height, 1])
  pixels_3d = orthographic.unproject(pixels, zero_depth)

  intersections_points, normals = ray.intersection_ray_sphere(
      sphere_center, sphere_radius, pixel_ray, pixels_3d)
  intersections_points = np.nan_to_num(intersections_points)
  normals = np.nan_to_num(normals)
  return intersections_points[0, :, :, :], normals[0, :, :, :]

#####################################
# Setup the image, sphere and light #
#####################################
# Image dimensions
image_width = 400
image_height = 300

# Sphere center and radius
sphere_radius = np.array((100.0,), dtype=dtype)
sphere_center = np.array((image_width / 2.0, image_height / 2.0, 300.0),
                         dtype=dtype)

# Set the light along the image plane
light_position = np.array((image_width / 2.0 + light_x_position * image_width,
                           image_height / 2.0, 0.0),
                          dtype=dtype)
vector_light_to_sphere_center = light_position - sphere_center
light_intensity_scale = vector.dot(
    vector_light_to_sphere_center, vector_light_to_sphere_center,
    axis=-1) * 4.0 * m.pi
light_intensity = np.array(
    (light_red, light_green, light_blue)) * light_intensity_scale

################################################################################################
# For each pixel in the image, estimate the corresponding surface point and associated normal. #
################################################################################################
intersection_3d, surface_normal = compute_intersection_normal_sphere(
    image_width, image_height, sphere_radius, sphere_center, dtype)

#######################################
# Reflectance and radiance estimation #
#######################################
incoming_light_direction = tf.math.l2_normalize(
    intersection_3d - light_position, axis=-1)
outgoing_ray = np.array((0.0, 0.0, -1.0), dtype=dtype)
albedo = tf.broadcast_to(albedo, tf.shape(surface_normal))

# Lambertian BRDF
brdf_lambertian = diffuse_percentage * lambertian.brdf(incoming_light_direction, outgoing_ray,
                                  surface_normal, albedo)
# Phong BRDF
brdf_phong = specular_percentage * phong.brdf(incoming_light_direction, outgoing_ray, surface_normal,
                        np.array((shininess,), dtype=dtype), albedo)
# Composite BRDF
brdf_composite = brdf_lambertian + brdf_phong
# Irradiance
cosine_term = vector.dot(surface_normal, -incoming_light_direction)
cosine_term = tf.math.maximum(tf.zeros_like(cosine_term), cosine_term)
vector_light_to_surface = intersection_3d - light_position
light_to_surface_distance_squared = vector.dot(
    vector_light_to_surface, vector_light_to_surface, axis=-1)
irradiance = light_intensity / (4 * m.pi *
                                light_to_surface_distance_squared) * cosine_term
# Rendering equation
zeros = tf.zeros(intersection_3d.shape)
radiance = brdf_composite * irradiance
radiance_lambertian = brdf_lambertian * irradiance
radiance_phong = brdf_phong * irradiance

###############################
# Display the rendered sphere #
###############################
# Saturates radiances at 1 for rendering purposes.
radiance = np.minimum(radiance, 1.0)
radiance_lambertian = np.minimum(radiance_lambertian, 1.0)
radiance_phong = np.minimum(radiance_phong, 1.0)
# Gammma correction
radiance = np.power(radiance, 1.0 / 2.2)
radiance_lambertian = np.power(radiance_lambertian, 1.0 / 2.2)
radiance_phong = np.power(radiance_phong, 1.0 / 2.2)

plt.figure(figsize=(20, 20))

# Diffuse
radiance_lambertian = np.transpose(radiance_lambertian, (1, 0, 2))
ax = plt.subplot("131")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Lambertian")
_ = ax.imshow(radiance_lambertian)

# Specular
radiance_phong = np.transpose(radiance_phong, (1, 0, 2))
ax = plt.subplot("132")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Specular - Phong")
_ = ax.imshow(radiance_phong)

# Diffuse + specular
radiance = np.transpose(radiance, (1, 0, 2))
ax = plt.subplot("133")
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.grid(False)
ax.set_title("Combined lambertian and specular")
_ = ax.imshow(radiance)

 

以上






TensorFlow Graphics : Tutorials : B-スプラインと Slerp 補間

TensorFlow Graphics : Intermediate Tutorials : B-スプラインと Slerp 補間 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/28/2019

* 本ページは、TensorFlow Graphics の github レポジトリの次のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

TensorFlow Graphics : Intermediate Tutorials : B-スプラインと slerp 補間

限定的な数の事前定義されたポイントと関連する値が与えられたとき、補間は新しいデータポイントを事前定義されたポイントの範囲内で予測することを可能にします。

下の例で、左のプロットは青いドットで表わされるサンプルを示します。これらのサンプルが滑らかな関数に由来すると仮定すると、これらのドット間のもっともらしい (= plausible) 値を見つけるために利用可能な多くの選択肢があります。最初の選択肢は中央のプロットで観察できるように、隣り合う点の任意のペアを線で結びつける区分線形関数を構築することです。もう一つの広く使用される選択肢はこれらのサンプルに多項式を fit させることです。右のプロットはサンプルに fit された三次多項式を示します。

このノートブックは B-スプラインと球面線形補間を TensorFlow Graphicsを使用してどのように遂行するかを示します。

 

セットアップ & Imports

このノートブックに含まれるデモを実行するために必要な総てを import しましょう。

import matplotlib.pyplot as plt
import tensorflow as tf

from tensorflow_graphics.math.interpolation import bspline
from tensorflow_graphics.math.interpolation import slerp

tf.enable_eager_execution()

 

Slerp 補間

Lerp はポイント間を線形に補間することを可能にする広く使用される補間テクニックです。冒頭で記述された区間線形補間は線形補間のピースから効果的に構成されます。しかしデータが円上または球面に在る場合はどうでしょう?その場合、Lerp は補間するための良い方法を提供しませんが、幸い、Slear は提供するでしょう!Slerp は球面線形補間を表しクォータニオンを補間するコンテキストで導入されました、これは回転の形式化です。

次のデモは 2 つのベクトルを定義することを可能にします。それぞれ円の中心か始まり円上で終わります。これらは間を補間することを望むベクトルを定義し、そしてスライダー「パーセント」は各ベクトルが補間されたベクトルに影響を与える程度を制御します。結果のベクトルはまた円上で終わることに注意してください。

#@title Slerp - Vectors will be normalized first { vertical-output: true, run: "auto"}

vector_1_x = -0.56  #@param { type: "slider", min: -1.0, max: 1.0, step: 0.01}
vector_1_y = -0.39  #@param { type: "slider", min: -1.0, max: 1.0, step: 0.01}
vector_2_x = 0.47  #@param { type: "slider", min: -1.0, max: 1.0, step: 0.01}
vector_2_y = 0.74  #@param { type: "slider", min: -1.0, max: 1.0, step: 0.01}
percent = 0.7  #@param { type: "slider", min: 0.0, max: 1.0, step: 0.01}

vector_1 = tf.constant((vector_1_x, vector_1_y), dtype=tf.float32)
vector_2 = tf.constant((vector_2_x, vector_2_y), dtype=tf.float32)
vector_1 = tf.nn.l2_normalize(vector_1)
vector_2 = tf.nn.l2_normalize(vector_2)
vector_3 = slerp.interpolate(
    vector_1, vector_2, percent, method=slerp.InterpolationType.VECTOR)

v1 = vector_1.numpy()
v2 = vector_2.numpy()
v3 = vector_3.numpy()

plt.figure(figsize=(10, 10))
circle = plt.Circle((0, 0), 1.0, color='g', fill=False)
ax = plt.gca()
ax.add_artist(circle)
plt.arrow(
    0.0, 0.0, v1[0], v1[1], width=0.001, color='k', length_includes_head=True)
plt.arrow(
    0.0, 0.0, v2[0], v2[1], width=0.001, color='b', length_includes_head=True)
plt.arrow(
    0.0, 0.0, v3[0], v3[1], width=0.001, color='r', length_includes_head=True)
plt.axis((-1.1, 1.1, -1.1, 1.1))
plt.show()

 

B-スプライン補間

グローバルサポートを持つ他の補間テクニック (e.g. ベジェ曲線、グローバル多項式補間) とは対照的に、B-スプラインはローカルコントロールを持つ区間多項式関数です。コントールは「ノット」と呼ばれるポイントの位置に由来します。

#@title B-Spline Interpolation { vertical-output: true, run: "auto"}

num_knots = 5
cyclical = True  #@param { type: "boolean" }
degree = 3  #@param { type: "slider", min: 1, max: 4, step: 1}
knot_1_x = -2.5  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_1_y = -1  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_2_x = -1.5  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_2_y = 2  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_3_x = 0  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_3_y = -3  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_4_x = 1.5  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_4_y = 3  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_5_x = 3  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}
knot_5_y = 0  #@param { type: "slider", min: -3.0, max: 3.0, step: 0.5}

max_pos = num_knots if cyclical else num_knots - degree
knots = tf.constant(((knot_1_x, knot_2_x, knot_3_x, knot_4_x, knot_5_x),
                     (knot_1_y, knot_2_y, knot_3_y, knot_4_y, knot_5_y)))

positions = tf.expand_dims(
    tf.range(start=0.0, limit=max_pos, delta=0.01, dtype=knots.dtype), axis=-1)

spline = bspline.interpolate(knots, positions, degree, cyclical)
spline = tf.squeeze(spline, axis=1)

plt.figure(figsize=(10, 10))
plt.plot(spline[:, 0], spline[:, 1], 'r')
plt.plot(knots[0, :], knots[1, :], 'b.')
plt.axis((-3.5, 3.5, -3.5, 3.5))
plt.show()

 

以上






TensorFlow Graphics : Tutorials : カメラ・キャリブレーション

TensorFlow Graphics : Beginner Tutorials : カメラ・キャリブレーション (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/28/2019

* 本ページは、TensorFlow Graphics の github レポジトリの次のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

TensorFlow Graphics : Beginner Tutorials : カメラ・キャリブレーション

カメラは 3D オブジェクトの 2D 画像を捕捉できるハードウェアの複雑な部品群です。実際に、リアルなカメラを正確にモデル化するためには、レンズ・ディストーション (= distortion, 歪曲収差)、ISO、焦点距離そして露出時間を含む多くのパラメータを推定する必要があります。以下では、私達の興味を射影カメラモデルに制限します。

このモデルはしばしば内部パラメータとして参照される 2 つのパラメータから成ります :

  • 主点、これは画像上の光学中心の投影です。理想的には、主点は画像の中心に近いです。
  • 焦点距離、これは光学中心とイメージプレーンの間の距離です。このパラメータはズームのレベルを制御することを可能にします。

このノートブックは射影カメラの内部パラメータを推定するために Tensorflow Graphics をどのように利用するかを示します。これらのパラメータのリカバーは 3D 再構成を含む幾つかのタスクを遂行するために特に重要です。

この Colab では、目標は観察とその観察と現在のソリューションのレンダリングの間の対応が与えられたときにカメラの内部パラメータをリカバーすることです。3D シーンに矩形だけを挿入してそれを最適化の間の対応のソースとして使用することにより単純さが保持されます。最小化はレーベンバーグ・マーカート・アルゴリズムを使用して遂行されます。

 

セットアップ & Imports

このノートブックに含まれるデモを実行するために必要な総てを import しましょう。

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

#################################
# Imports the necessary modules #
#################################

from tensorflow_graphics.math.optimizer import levenberg_marquardt
from tensorflow_graphics.rendering.camera import perspective  

tf.enable_eager_execution()

 

射影カメラモデルを理解する

このモデルがどのように動作するかを示すため、カメラの前のシーンには矩形以外は何もないと仮定します。最初にこの矩形をカメラにより観測されたものとしてレンダーする関数を定義しましょう。

def render_rectangle(rectangle_vertices, focal, principal_point, image_dimensions):
  """Renders a rectangle on the image plane.

  Args:
    rectangle_vertices: the four 3D corners of a rectangle.
    focal: the focal lengths of a projective camera.
    principal_point: the position of the principal point on the image plane.
    image_dimensions: the dimensions (in pixels) of the image.

  Returns:
    A 2d image of the 3D rectangle.
  """
  image = np.zeros((int(image_dimensions[0]), int(image_dimensions[1]), 3))
  vertices_2d = perspective.project(rectangle_vertices, focal, principal_point)
  vertices_2d_np = vertices_2d.numpy()
  top_left_corner = np.maximum(vertices_2d_np[0, :], (0, 0)).astype(int)
  bottom_right_corner = np.minimum(
      vertices_2d_np[1, :],
      (image_dimensions[1] - 1, image_dimensions[0] - 1)).astype(int)
  for x in range(top_left_corner[0], bottom_right_corner[0] + 1):
    for y in range(top_left_corner[1], bottom_right_corner[1] + 1):
      c1 = float(bottom_right_corner[0] + 1 -
                 x) / float(bottom_right_corner[0] + 1 - top_left_corner[0])
      c2 = float(bottom_right_corner[1] + 1 -
                 y) / float(bottom_right_corner[1] + 1 - top_left_corner[1])
      image[y, x] = (c1, c2, 1)
  return image

次のセルはデフォルト内部パラメータを定義してこれらのパラメータを使用して矩形をレンダーします。


# Sets up the vertices of the rectangle.
rectangle_depth = 1000.0
rectangle_vertices = np.array(
    ((-150.0, -75.0, rectangle_depth), (150.0, 75.0, rectangle_depth)))

# Sets up the size of the image plane.
image_width = 400
image_height = 300
image_dimensions = np.array((image_height, image_width), dtype=np.float64)

# Sets the horizontal and vertical focal length to be the same. The focal length
# picked yields a field of view around 50degrees.
focal_lengths = np.array((image_height, image_height), dtype=np.float64)
# Sets the principal point at the image center.
ideal_principal_point = np.array(
    (image_width, image_height), dtype=np.float64) / 2.0

# Let's see what our scene looks like using the intrinsic paramters defined above.
render = render_rectangle(rectangle_vertices, focal_lengths, ideal_principal_point,
                          image_dimensions)
_ = plt.imshow(render)

焦点距離と光学中心の位置は最終的なイメージ上で非常に異なる効果を持ちます。これらのパラメータの各々の効果を納得するために下のスライダーの異なる configuration を使用してください。

###############
# UI controls #
###############
#@title model parameters { vertical-output: false, run: "auto" }
focal_length_x = 300  #@param { type: "slider", min: 100.0, max: 500.0, step: 1.0 }
focal_length_y = 300  #@param { type: "slider", min: 100.0, max: 500.0, step: 1.0 }
optical_center_x = 200  #@param { type: "slider", min: 0.0, max: 400.0, step: 1.0 }
optical_center_y = 150.0  #@param { type: "slider", min: 0.0, max: 300.0, step: 1.0 }

render = render_rectangle(
    rectangle_vertices,
    np.array((focal_length_x, focal_length_y), dtype=np.float64),
    np.array((optical_center_x, optical_center_y), dtype=np.float64),
    image_dimensions)
_ = plt.imshow(render)

(訳注: 以下はパラメータを変更して得られたイメージです : )

焦点距離 100 焦点距離 500

光学中心 x == 0 光学中心 x == 400

光学中心 y == 0 光学中心 y == 300


 

内部パラメータを最適化する

総てのカメラ (e.g. スマートフォン・カメラ) はそれ自身の内部パラメータのセットを持ちます。他のアプリケーションの中では、3D シーン再構成、ロボティクス、そしてナビゲーションシステムにおいて正確な内部パラメータが使用されます。

内部パラメータを推定するための一般的な方法は既知の 3D オブジェクトを使用することです。内部パラメータの現在の推定を使用して、既知の 3D オブジェクトがどのように「見える」べきかを予測して、そしてそれを実際の観測と比較す比較することができます。

結果をプロットする助けとなる幾つかのヘルパー関数を定義することから始めましょう。

def plot_optimization_step(observation, prediction):
  plt.figure(figsize=(20, 10))
  ax = plt.subplot("131")
  ax.set_title("Observation")
  _ = ax.imshow(observation)
  ax = plt.subplot("132")
  ax.set_title("Prediction using estimated intrinsics")
  _ = ax.imshow(prediction)
  ax = plt.subplot("133")
  ax.set_title("Difference image")
  _ = ax.imshow(np.abs(observation - prediction))
  plt.show()


def print_errors(focal_error, center_error):
  print("Error focal length %f" % (focal_error,))
  print("Err principal point %f" % (center_error,))

さて私達が求めている内部パラメータの値、そしてこれらのパラメータの値の初期推測を定義しましょう。

def build_parameters():
  # Constructs the intrinsic parameters we wish to recover.
  real_focal_lengths = focal_lengths * np.random.uniform(0.8, 1.2, size=(2,))
  real_principal_point = ideal_principal_point + (np.random.random(2) -
                                                  0.5) * image_width / 5.0

  # Initializes the first estimate of the intrinsic parameters.
  estimate_focal_lengths = tf.Variable(real_focal_lengths +
                                       (np.random.random(2) - 0.5) *
                                       image_width)
  estimate_principal_point = tf.Variable(real_principal_point +
                                         (np.random.random(2) - 0.5) *
                                         image_width / 4)
  return real_focal_lengths, real_principal_point, estimate_focal_lengths, estimate_principal_point

前述したように、内部パラメータの現在の推定を使用して 3D オブジェクトがどのように見えるか比較することができます、それを実際の観測と比較することができます。次の関数は私達が最小化することを求めるこれら 2 つのイメージ間の距離を捕捉します。

def residuals(estimate_focal_lengths, estimate_principal_point):
  vertices_2d_gt = perspective.project(rectangle_vertices, real_focal_lengths,
                                       real_principal_point)
  vertices_2d_observed = perspective.project(rectangle_vertices,
                                             estimate_focal_lengths,
                                             estimate_principal_point)
  return vertices_2d_gt - vertices_2d_observed

今では問題を解くための総てのピースが適所にあります; let’s give it a go!

Note: 残差は Levenberg-Marquard を使用して最小化されます、これは特にこの問題のために示されます。ファーストクラスの optimizers (e.g. Adam や勾配降下) もまた使用できます。

# Samples intrinsic parameters to recover and an initial solution.
real_focal_lengths, real_principal_point, estimate_focal_lengths, estimate_principal_point = build_parameters(
)

# Contructs the observed image.
observation = render_rectangle(rectangle_vertices, real_focal_lengths,
                               real_principal_point, image_dimensions)

# Displays the initial solution.
print("Initial configuration:")
print_errors(
    np.linalg.norm(estimate_focal_lengths - real_focal_lengths),
    np.linalg.norm(estimate_principal_point - real_principal_point))
image = render_rectangle(rectangle_vertices, estimate_focal_lengths,
                         estimate_principal_point, image_dimensions)
plot_optimization_step(observation, image)

# Optimization.
_, (estimate_focal_lengths,
    estimate_principal_point) = levenberg_marquardt.minimize(
        residuals, (estimate_focal_lengths, estimate_principal_point), 1)

print("Predicted configuration:")
print_errors(
    np.linalg.norm(estimate_focal_lengths - real_focal_lengths),
    np.linalg.norm(estimate_principal_point - real_principal_point))
image = render_rectangle(rectangle_vertices, estimate_focal_lengths,
                         estimate_principal_point, image_dimensions)
plot_optimization_step(observation, image)

 

以上






TensorFlow Graphics : Tutorials : 物体ポーズ推定 / 整列

TensorFlow Graphics : Beginner Tutorials : 物体ポーズ推定 / 整列 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/25/2019

* 本ページは、TensorFlow Graphics の github レポジトリの次のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

TensorFlow Graphics : Beginner Tutorials : 物体ポーズ推定 / 整列

物体のポーズを正確に推定することは多くの業種 (= industries) で基本的です。例えば拡張現実と仮想現実において、これらのオブジェクトと相互作用することによりそれはユーザに幾つかの変数の状態を変更することを可能にします (e.g. ユーザのデスクの上のマグにより制御される容量)。

このノートブックは既知の 3D 物体の回転と移動を推定するために TensorFlow Graphics をどのように使用するかを示します。

この能力は 2 つの異なるデモにより示されます :

  1. 機械学習デモ、これは与えられた物体の参照ポーズに関する回転と移動を正確に見積もることができる単純なニューラルネットワークをどのように訓練するかを示します。
  2. 数学的最適化デモ、これはこの問題への異なるアプローチを取ります ; 機械学習は使用しません。

 

セットアップ & Imports

このノートブックに含まれるデモを実行するために必要な総てを import しましょう。

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import time

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

from tensorflow_graphics.geometry.transformation import quaternion
from tensorflow_graphics.math import vector
from tensorflow_graphics.notebooks import threejs_visualization
from tensorflow_graphics.notebooks.resources import tfg_simplified_logo

tf.enable_eager_execution()

# Loads the Tensorflow Graphics simplified logo.
vertices = tfg_simplified_logo.mesh['vertices'].astype(np.float32)
faces = tfg_simplified_logo.mesh['faces']
num_vertices = vertices.shape[0]

 

1. 機械学習

モデル定義

既知のメッシュの総ての頂点の 3D 位置が与えられたとき、参照ポーズに関するこのメッシュの (クォータニオン (4 次元ベクトル) によりパラメータ化された) 回転と移動 (3 次元ベクトル) を予測する能力があるネットワークを望みます。今、タスクのための非常に単純な 3-層完全結合ネットワークと損失を作成しましょう。このモデルは非常に単純で明らかに最善ではないことに注意してください、それはこのノートブックのためには範囲外です。

# モデルを構築する。
model = keras.Sequential()
model.add(layers.Flatten(input_shape=(num_vertices, 3)))
model.add(layers.Dense(64, activation=tf.nn.tanh))
model.add(layers.Dense(64, activation=tf.nn.relu))
model.add(layers.Dense(7))


def pose_estimation_loss(y_true, y_pred):
  """訓練のために使用されるポーズ推定損失。

  This loss measures the average of squared distance between some vertices
  of the mesh in 'rest pose' and the transformed mesh to which the predicted
  inverse pose is applied. Comparing this loss with a regular L2 loss on the
  quaternion and translation values is left as exercise to the interested
  reader.

  Args:
    y_true: The ground-truth value.
    y_pred: The prediction we want to evaluate the loss for.

  Returns:
    A scalar value containing the loss described in the description above.
  """
  # y_true.shape : (batch, 7)
  y_true_q, y_true_t = tf.split(y_true, (4, 3), axis=-1)
  # y_pred.shape : (batch, 7)
  y_pred_q, y_pred_t = tf.split(y_pred, (4, 3), axis=-1)

  # vertices.shape: (num_vertices, 3)
  # corners.shape:(num_vertices, 1, 3)
  corners = tf.expand_dims(vertices, axis=1)

  # transformed_corners.shape: (num_vertices, batch, 3)
  # q and t shapes get pre-pre-padded with 1's following standard broadcast rules.
  transformed_corners = quaternion.rotate(corners, y_pred_q) + y_pred_t

  # recovered_corners.shape: (num_vertices, batch, 3)
  recovered_corners = quaternion.rotate(transformed_corners - y_true_t,
                                        quaternion.inverse(y_true_q))

  # vertex_error.shape: (num_vertices, batch)
  vertex_error = tf.reduce_sum((recovered_corners - corners)**2, axis=-1)

  return tf.reduce_mean(vertex_error)


optimizer = keras.optimizers.Adam()
model.compile(loss=pose_estimation_loss, optimizer=optimizer)
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten (Flatten)            (None, 114)               0         
_________________________________________________________________
dense (Dense)                (None, 64)                7360      
_________________________________________________________________
dense_1 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_2 (Dense)              (None, 7)                 455       
=================================================================
Total params: 11,975
Trainable params: 11,975
Non-trainable params: 0
_________________________________________________________________

 

データ生成

モデルを定義した今、それを訓練するためのデータが必要です。訓練セットの各サンプルについて、ランダムな 3D 回転と 3D 移動がサンプリングされて物体の頂点に適用されます。各訓練サンプルは総ての変換された頂点と (サンプルに適用された) 回転と移動を戻すことを可能にする反対の回転と移動から成ります。

def generate_training_data(num_samples):
  # random_angles.shape: (num_samples, 3)
  random_angles = np.random.uniform(-np.pi, np.pi,
                                    (num_samples, 3)).astype(np.float32)

  # random_quaternion.shape: (num_samples, 4)
  random_quaternion = quaternion.from_euler(random_angles)

  # random_translation.shape: (num_samples, 3)
  random_translation = np.random.uniform(-2.0, 2.0,
                                         (num_samples, 3)).astype(np.float32)

  # data.shape : (num_samples, num_vertices, 3)
  data = quaternion.rotate(vertices[tf.newaxis, :, :],
                           random_quaternion[:, tf.newaxis, :]
                          ) + random_translation[:, tf.newaxis, :]

  # target.shape : (num_samples, 4+3)
  target = tf.concat((random_quaternion, random_translation), axis=-1)

  return np.array(data), np.array(target)
num_samples = 10000

data, target = generate_training_data(num_samples)

print(data.shape)   # (num_samples, num_vertices, 3): the vertices
print(target.shape)  # (num_samples, 4+3): the quaternion and translation
(10000, 38, 3)
(10000, 7)

 

訓練

この時点で、ニューラルネットワークの訓練を開始するために総てが適切です!

# Callback allowing to display the progression of the training task.
class ProgressTracker(keras.callbacks.Callback):

  def __init__(self, num_epochs, step=5):
    self.num_epochs = num_epochs
    self.current_epoch = 0.
    self.step = step
    self.last_percentage_report = 0

  def on_epoch_end(self, batch, logs={}):
    self.current_epoch += 1.
    training_percentage = int(self.current_epoch * 100.0 / self.num_epochs)
    if training_percentage - self.last_percentage_report >= self.step:
      print('Training ' + str(
          training_percentage) + '% complete. Training loss: ' + str(
              logs.get('loss')) + ' | Validation loss: ' + str(
                  logs.get('val_loss')))
      self.last_percentage_report = training_percentage
reduce_lr_callback = keras.callbacks.ReduceLROnPlateau(
    monitor='val_loss',
    factor=0.5,
    patience=10,
    verbose=0,
    mode='auto',
    min_delta=0.0001,
    cooldown=0,
    min_lr=0)
# Everything is now in place to train.
EPOCHS = 100
pt = ProgressTracker(EPOCHS)
history = model.fit(
    data,
    target,
    epochs=EPOCHS,
    validation_split=0.2,
    verbose=0,
    batch_size=32,
    callbacks=[reduce_lr_callback, pt])

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.ylim([0, 1])
plt.legend(['loss', 'val loss'], loc='upper left')
plt.xlabel('Train epoch')
_ = plt.ylabel('Error [mean square distance]')
Training 5% complete. Training loss: 1.009950121998787 | Validation loss: 0.9137270336151123
Training 10% complete. Training loss: 0.5268501298427581 | Validation loss: 0.5240717251300812
Training 15% complete. Training loss: 0.3752533705532551 | Validation loss: 0.37031882667541505
Training 20% complete. Training loss: 0.29893275210261344 | Validation loss: 0.29115591061115265
Training 25% complete. Training loss: 0.24757031705975532 | Validation loss: 0.23939514791965485
Training 30% complete. Training loss: 0.22420285016298294 | Validation loss: 0.21258240276575088
Training 35% complete. Training loss: 0.19799123887717723 | Validation loss: 0.1807250078320503
Training 40% complete. Training loss: 0.17767862628400327 | Validation loss: 0.1784087748527527
Training 45% complete. Training loss: 0.17318876099586486 | Validation loss: 0.15155328786373137
Training 50% complete. Training loss: 0.15236466732621193 | Validation loss: 0.16674353015422821
Training 55% complete. Training loss: 0.13417977157235145 | Validation loss: 0.13277354818582535
Training 60% complete. Training loss: 0.15286476877331734 | Validation loss: 0.13142506104707719
Training 65% complete. Training loss: 0.13228631572425364 | Validation loss: 0.13619410294294357
Training 70% complete. Training loss: 0.1253597312271595 | Validation loss: 0.11984566831588746
Training 75% complete. Training loss: 0.08613634746521712 | Validation loss: 0.09534638980031014
Training 80% complete. Training loss: 0.07220880922675132 | Validation loss: 0.07457096055150032
Training 85% complete. Training loss: 0.07194197417795659 | Validation loss: 0.055152468413114546
Training 90% complete. Training loss: 0.06660718904435635 | Validation loss: 0.07816255846619606
Training 95% complete. Training loss: 0.07537854766100645 | Validation loss: 0.08529620145261288
Training 100% complete. Training loss: 0.04841766462475061 | Validation loss: 0.05356932772696018

 

テスティング

今ではネットワークは訓練されて利用する準備ができました!表示される結果は 2 つの画像から成ります。最初の画像は「静止ポーズ (= rest pose)」にある物体 (パステル・レモン色) と回転されて移動された物体 (パステル・ハニーデュー色) を含みます。これは 2 つの構成がどのように異なるかを観察することを効果的に可能にします。2 番目の画像はまた静止ポーズの物体を示しますが、今回は訓練されたニューラルネットワークにより予測された変換が回転されて移動されたバージョンに適用されます。望ましくは、2 つの物体が今では非常に類似したポーズであることです。

Note: 異なるテストケースをサンプリングするために複数回プレーを押してください。貴方は時々物体のスケーリングが無効であることに気付くでしょう。これはクォータニオンがスケールをエンコードできるという事実に由来します。単位ノルムのクォータニオンの使用は結果のスケールを変更しないという結果になるでしょう。ネットワーク・アーキテクチャか損失関数でこの制約を追加する実験を興味ある読者に委ねます。

クォータニオンと移動を適用するヘルパー関数から始めます :

# Defines the loss function to be optimized.
def transform_points(target_points, quaternion_variable, translation_variable):
  return quaternion.rotate(target_points,
                           quaternion_variable) + translation_variable

変換された形状のための threejs ビューアを定義します :

class Viewer(object):

  def __init__(self, my_vertices):
    my_vertices = np.asarray(my_vertices)
    context = threejs_visualization.build_context()
    light1 = context.THREE.PointLight.new_object(0x808080)
    light1.position.set(10., 10., 10.)
    light2 = context.THREE.AmbientLight.new_object(0x808080)
    lights = (light1, light2)

    material = context.THREE.MeshLambertMaterial.new_object({
        'color': 0xfffacd,
    })

    material_deformed = context.THREE.MeshLambertMaterial.new_object({
        'color': 0xf0fff0,
    })

    camera = threejs_visualization.build_perspective_camera(
        field_of_view=30, position=(10.0, 10.0, 10.0))

    mesh = {'vertices': vertices, 'faces': faces, 'material': material}
    transformed_mesh = {
        'vertices': my_vertices,
        'faces': faces,
        'material': material_deformed
    }
    geometries = threejs_visualization.triangular_mesh_renderer(
        [mesh, transformed_mesh],
        lights=lights,
        camera=camera,
        width=400,
        height=400)

    self.geometries = geometries

  def update(self, transformed_points):
    self.geometries[1].getAttribute('position').copyArray(
        transformed_points.numpy().ravel().tolist())
    self.geometries[1].getAttribute('position').needsUpdate = True

ランダム回転と移動を定義します :

def get_random_transform():
  # Forms a random translation
  with tf.name_scope('translation_variable'):
    random_translation = tf.Variable(
        np.random.uniform(-2.0, 2.0, (3,)), dtype=tf.float32)

  # Forms a random quaternion
  hi = np.pi
  lo = -hi
  random_angles = np.random.uniform(lo, hi, (3,)).astype(np.float32)
  with tf.name_scope('rotation_variable'):
    random_quaternion = tf.Variable(quaternion.from_euler(random_angles))

  return random_quaternion, random_translation

変換パラメータを予測するモデルを実行して、結果を可視化します :

random_quaternion, random_translation = get_random_transform()

initial_orientation = transform_points(vertices, random_quaternion,
                                       random_translation).numpy()
viewer = Viewer(initial_orientation)

predicted_transformation = model.predict(initial_orientation[tf.newaxis, :, :])

predicted_inverse_q = quaternion.inverse(predicted_transformation[0, 0:4])
predicted_inverse_t = -predicted_transformation[0, 4:]

predicted_aligned = quaternion.rotate(initial_orientation + predicted_inverse_t,
                                      predicted_inverse_q)

viewer = Viewer(predicted_aligned)

 
(訳注 : 幾つかの実行サンプルを以下に示します : )

 

2. 数学的最適化

ここでは問題は数学的最適化を使用して取り組まれます、これは物体ポーズ推定の問題にアプローチするためのもう一つの伝統的な方法です。「静止ポーズ」にある物体 (パステルレモン色) とその回転されて移動された対応物体 (パステルハニーデュー色) 間の対応が与えられたとき、問題は最小化問題として定式化できます。損失関数は例えば、変換された物体の回転と移動の現在の推定を使用して対応する点のユークリッド距離の総計として定義できます。そしてこの損失に関する回転と移動パラメータの導関数を計算して、収束まで勾配方向をフォローできます。次のセルはこの手続きを密接にフォローし、そして 2 つの物体を整列するために勾配効果を使用します。結果は良いですが、注目すべき点としてこの特定の問題を解くためにより効率的な方法があります。興味ある読者はより詳細について Kabsch アルゴリズムを参照してください。

Note: 異なるテストケースをサンプリングするために複数回プレーを押してください。

損失と勾配関数を定義します :

def loss(target_points, quaternion_variable, translation_variable):
  transformed_points = transform_points(target_points, quaternion_variable,
                                        translation_variable)
  error = (vertices - transformed_points) / num_vertices
  return vector.dot(error, error)


def gradient_loss(target_points, quaternion, translation):
  with tf.GradientTape() as tape:
    loss_value = loss(target_points, quaternion, translation)
  return tape.gradient(loss_value, [quaternion, translation])

optimizer を作成します。

learning_rate = 0.05
with tf.name_scope('optimization'):
  optimizer = tf.train.AdamOptimizer(learning_rate)

ランダム変換を初期化して、最適化を実行して結果をアニメーションにします。

random_quaternion, random_translation = get_random_transform()

transformed_points = transform_points(vertices, random_quaternion,
                                      random_translation)

viewer = Viewer(transformed_points)

nb_iterations = 100
for it in range(nb_iterations):
  gradients_loss = gradient_loss(vertices, random_quaternion,
                                 random_translation)
  optimizer.apply_gradients(
      zip(gradients_loss, (random_quaternion, random_translation)))
  transformed_points = transform_points(vertices, random_quaternion,
                                        random_translation)

  viewer.update(transformed_points)
  time.sleep(0.1)
 

以上






TensorFlow Graphics : README

TensorFlow Graphics : README (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/24/2019

* 本ページは、TensorFlow Graphics の github レポジトリの次のページを翻訳した上で適宜、補足説明したものです:


* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

TensorFlow Graphics : README

ここ数年、ニューラルネットワーク・アーキテクチャに挿入可能な新奇な微分可能なグラフィックレイヤーが出現してきています。空間的な変換から微分可能なグラフィックレンダラーまで、これらの新しいレイヤーは新しいより効率的なネットワーク・アーキテクチャを構築するために長年に渡るコンピュータビジョンとグラフィックス研究で獲得された知識を活用しています。幾何学的事前分布 (= geometric priors) と制約をニューラルネットワークに明示的にモデリングすることは堅固に効率的にそしてより重要なことに self-supervised (自己教師あり) 流儀で訓練可能なアーキテクチャへのドアを開けます。

 

概要

高位レベルでは、コンピュータグラフィックス・パイプラインは 3D オブジェクトの表現とシーンにおけるそれらの絶対的な位置、それらが成る素材の記述、ライトとカメラを必要とします。そしてこのシーン記述は合成レンダリングを生成するためにレンダラーにより解釈されます。

比較すると、コンピュータビジョン・システムは画像から始めてシーンのパラメータを推論することを試みるでしょう。これはどのオブジェクトがシーンにあるか、どの素材からそれらが成るか、そしてそれらの 3-次元位置と方向の予測を可能にします。

これらの複雑な 3D ビジョン・タスクを解ける機械学習システムの訓練は殆どの場合に巨大な量のデータを必要とします。データのラベル付けはコストのかかる複雑なプロセスですので、多大な監視 (= supervision) なしで訓練されながら 3 次元世界を理解できる機械学習モデルを設計するためのメカニズムを持つことは重要です。コンピュータビジョンとコンピュータグラフィックス技術の結合は巨大な総量の容易に利用可能なラベル付けされていないデータを活用する唯一無二のチャンスです。下の画像で示されるように、これは例えば analysis by synthesis (合成による分析) の使用により獲得されます、そこではビジョンシステムはシーン・パラメータを抽出してグラフィックスシステムはそれらに基づき画像をレンダリングし戻します。レンダリングが元の画像と一致すれば、このビジョンシステムはシーン・パラメータを正確に抽出しています。このセットアップでは、コンピュータビジョンとコンピュータグラフックスは協力して、self-supervised 流儀で訓練可能な、オートエンコーダに類似した単一の機械学習システムを形成します。

TensorFlow Graphics はこれらのタイプの挑戦に取り組むことを助けるために開発されていてそしてそのために、貴方の最適な機械学習モデルを訓練してデバッグするために使用できる、微分可能なグラフィックスとジオメトリ・レイヤー (e.g. カメラ、反射 (= reflectance) モデル、空間的変換、メッシュ convolution) そして 3D ビューア機能 (e.g. 3D TensorBoard) を提供します。

 

TensorBoard 3D

視覚的デバッグは実験が正しい方向に進んでいるかを査定するための良い方法です。この目的のために、TensorFlow Graphics は 3D メッシュとポイントクラウドを対話的に可視化するために TensorBoard プラグインを装備しています。このデモ はプラグインをどのように使用するかを示します。TensorBoard 3D をインストールして configure するためには これらの手順 をフォローしてください。TensorBoard 3D は現在のところ eager execution と TensorFlow 2 と互換でないことに注意してください。

 

 

以上






AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com