CMakeでビルドした実行プログラムのあるディレクトリにリソースファイルをコピーする

やりたいこと

CMakeを用いてプロジェクトを管理しているときに、ビルドした実行ファイルと同じディレクトリ(またはそこから相対的に定義されるディレクトリ)に画像データ等のリソースデータをコピーしたいという状況を考える。

リソースファイルは、例えば次のように定義されているとする。

file(GLOB RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/*.png)

問題点

ビルド環境ごとにビルドした実行プログラムが配置されるディレクトリは異なる可能性がある。

例えば、Makefileを生成してmakeとした場合には${CMAKE_CURRENT_BINARY_DIR}に実行プログラムが配置されるが、Xcodeを用いてビルドすると、例えばDebugモードでビルドすると${CMAKE_CURRENT_BINARY_DIR}/Debugに配置される。

更に、macOS.appバンドルを生成する場合は、実行ファイルの本体は[...].app/Contents/MacOSに配置される。

したがって、リソースファイルを実行ファイルのあるディレクトリにコピーしたい場合、単にfileコマンドを使って

file(COPY ${RESOURCE_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

としても、ビルド環境によっては意図通りにコピーされないことがある。

解決策:Generator Expression

Generator Expressionを用いれば良い。これは、CMakeのドキュメントに

Generator expressions are evaluated during build system generation to produce information specific to each build configuration.

と説明にあるように、ビルド環境に応じて評価される式ということらしい。

したがって、ビルド環境依存の部分は、generator expressionsを用いることで統一的に記述することができるようになる。

CMakeに定義されているgenerator expressionsのうち、特に

$<TARGET_FILE_DIR:my-program>

という式を用いると、ビルドした実行プログラムが配置されるディレクトリを得ることができる。ただしmy-programの部分にはそれぞれのターゲット名が入る。

リソースファイルをコピーするには、これを用いて

add_custom_command(TARGET my-program POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${resources} $<TARGET_FILE_DIR:my-program>)

などと記述すれば良い。

オイラー角による回転行列の表現まとめ

免責・注意:本記事は、その正しさを一切保証しない。できるだけ出典を記すようにするが、特に、要出典・要確認等と書かれただけの箇所は注意が必要である。また、現時点では未完成で、随時追記・更新していくつもりである。

右手系・左手系について

座標系を考えるとき、右手系と左手系のどちらかを用いることになる。本記事では右手系のことしか考えない。左手系について知りたい人は、以下の内容を全て自分で変換し直して考えるか、別の記事を当たる必要がある。

右手系の採用例

左手系の採用例

世界座標系とモデル座標系のどちらの座標系を基準とみるか:Extrinsic (Fixed) vs. Intrinsic (Mobile)

本記事では extrinsic (fixed) なオイラー角表現のことしか考えない。Intrinsic (mobile) なオイラー角表現を暗黙的に採用しているシステムもあるので注意が必要である。

Extrinsic (fixed) な表現では世界座標系 (world coordinates) の座標軸を基準に回転することを考える。

Extrinsic rotations are elemental rotations that occur about the axes of the fixed coordinate system xyz. The XYZ system rotates, while xyz is fixed.

引用元:Wikipedia. Euler angles. https://en.wikipedia.org/wiki/Euler_angles

一方で、intrinsic (mobile) な表現ではモデル座標系 (model coordinates; local coordinates) の座標軸を基準に回転することを考える。そのため、回転を適用する度に次に回転する際の基準軸が世界座標系から見たときに変わる点で注意が必要である。

Intrinsic rotations are elemental rotations that occur about the axes of a coordinate system XYZ attached to a moving body.

引用元:Wikipedia. Euler angles. https://en.wikipedia.org/wiki/Euler_angles

Extrinsic (fixed) でも intrinsic (mobile) でも、どちらも同等な回転を表現することができる。

For example, the fixed XYZ Euler angle convention is described by the x→y→z sequence, while the mobile ZYX Euler angle convention is described by the z'→y'→x' sequence, but both are equivalent, as we will see later.

引用元:How is orientation in space represented with Euler angles? https://www.mecademic.com/resources/Euler-angles/Euler-angles

また、extrinsic (fixed) な回転と intrinsic (mobile) な回転を混ぜて考えることも可能である。ただし実際にこれが有益になることは少ないと思われる。

That said, each of the twelve combinations is equivalent to three other sequences. In other words, each Euler angle convention can be described in four different ways. For example, the ZYX convention is equivalent to the sequences z→y→x, x'→y'→z', y→z'→x and y→x→z'.

引用元:Mecademic, Inc. How is orientation in space represented with Euler angles? https://www.mecademic.com/resources/Euler-angles/Euler-angles

Extrinsic (Fixed) Euler XYZ

世界座標系の  x 軸周りに  \theta_x 回転させ、続いて世界座標系の  y 軸周りに  \theta_y 回転させ、続いて世界座標系の  z 軸周りに  \theta_z 回転させるような回転について、そのような記述の方法を extrinsic (fixed) Euler XYZ と表現したい。実際には単に Euler XYZ と呼ばれることも多いが、extrinsic と intrinsic の違いについて常に意識しておいた方が安全である。

そのような回転は以下の回転行列で与えられる。

\displaystyle{
\mathbf{R} = \mathbf{R}_z(\theta_z) \mathbf{R}_y(\theta_y) \mathbf{R}_x(\theta_x) \in \mathbb{R}^{3 \times 3}
}

あるモデル座標系の点  \mathbf{x}^\text{model} \in \mathbb{R}^{3 \times 1} を、上のような extrinsic (fixed) Euler XYZ によって世界座標系に配置する際には、世界座標系上での座標  \mathbf{x}^\text{world} \in \mathbb{R}^{3 \times 1}

\displaystyle{
\mathbf{x}^\text{world} = \mathbf{R}_z(\theta_z) \mathbf{R}_y(\theta_y) \mathbf{R}_x(\theta_x) \mathbf{x}^\text{model}
}

で与えられる。

XYZ とは回転行列を適用する順番を表現している。OpenGL の Legacy APIs では post-multiplication が採用されているため、記述するときには

glRotated(theta_z, 0.0, 0.0, 1.0);
glRotated(theta_y, 0.0, 1.0, 0.0);
glRotated(theta_x, 1.0, 0.0, 0.0);

という順番になると思われる(TODO:要確認)。

Extrinsic (Fixed) Euler XYZ の採用例

  • Maya のデフォルト(TODO:要出典)

オイラー角を三次元ベクトルで表現する際の注意

変数の格納順に注意が必要である。名前が XYZ となっていても、

vec3 euler_angles = { theta_x, theta_y, theta_z };

のような順序で変数を格納するのが必ずしも唯一の方法ではない。例えば、回転行列の掛け算の式に現れる順に

vec3 euler_angles = { theta_z, theta_y, theta_x };

と変数を格納する実装もあり得る。これは Eigen の eulerAngles() で採用されている(TODO:要確認)。

ベクトルによる微分のレイアウト(分子レイアウト記法と分母レイアウト記法)

2つの異なる記法と混乱

ベクトル(や行列)による微分には2つの異なるレイアウトが用いられる。

  • 分母レイアウト記法 (Denominator Layout)
  • 分子レイアウト記法 (Numerator Layout)

これらは演算結果が異なるため、意識して用いないとしばしば混乱の元となる。

以下では \mathbf{x} \in \mathbb{R}^{n \times 1} は縦ベクトルとし、y \in \mathbb{R}スカラーする。また、便宜上、ライプニッツの記法に則り「分子」は微分される関数の方、「分母」は微分する独立変数の方を表すことにする。

分母レイアウト記法 (Denominator Layout)

スカラー微分する際、分母が縦ベクトルなら結果も縦ベクトルになる。分母が横ベクトルなら結果も横ベクトルとなる。つまり、直感的には分母のベクトル(や行列)のレイアウトが結果にそのまま反映されると解釈できる。

\displaystyle{
\frac{\partial y}{\partial \mathbf{x}} = \begin{bmatrix} \frac{\partial y}{\partial x_1} \\ \vdots \\ \frac{\partial y}{\partial x_n} \end{bmatrix} \in \mathbb{R}^{n \times 1}
}

一方、スカラーによって微分する際は、分子が縦ベクトルなら結果は横ベクトルになる。分子が横ベクトルなら結果は縦ベクトルになる。

\displaystyle{
\frac{\partial \mathbf{x}}{\partial y} = \begin{bmatrix} \frac{\partial x_1}{\partial y} & \cdots & \frac{\partial x_n}{\partial y} \end{bmatrix} \in \mathbb{R}^{1 \times n}
}

分子レイアウト記法 (Numerator Layout)

スカラー微分する際、分母が縦ベクトルなら結果は横ベクトルになる。分母が横ベクトルなら結果は縦ベクトルになる。分母レイアウトとは転置したような関係となっている。

\displaystyle{
\frac{\partial y}{\partial \mathbf{x}} = \begin{bmatrix} \frac{\partial y}{\partial x_1} & \cdots & \frac{\partial y}{\partial x_n} \end{bmatrix} \in \mathbb{R}^{1 \times n}
}

一方、スカラーによって微分する際は、分子が縦ベクトルなら結果も縦ベクトルになる。分子が横ベクトルなら結果も横ベクトルになる。つまり、直感的には分子のベクトル(や行列)のレイアウトが結果にそのまま反映されると解釈できる。

\displaystyle{
\frac{\partial \mathbf{x}}{\partial y} = \begin{bmatrix} \frac{\partial x_1}{\partial y} \\ \vdots \\ \frac{\partial x_n}{\partial y} \end{bmatrix} \in \mathbb{R}^{n \times 1}
}

どちらを用いるべきか

分母レイアウト記法を目にする機会が多く感じるので、個人的にはこちらを推奨したい。

ただしヤコビ行列の定義は分子レイアウトを採用していることが多く感じるため、悩ましい。

関連リンク

知覚を考慮した色差 CIEDE2000 とその実装

色差の指標 CIEDE2000

色差、つまり2つの色の距離を計算する方法はいくつかあるが、人間の知覚を考慮した指標である CIEDE2000 を用いるのが良いことが多い。

知覚を考慮しているとは、例えば人間にとって見分けのつきにくい色同士は (たとえ RGB の値が遠くても) 距離が近く、人間にとって見分けのつきやすい色同士は距離が遠くなるように設計されていることを意味している。

f:id:yuki-koyama:20180504132639p:plain CIEDE2000 を用いることで、より人間の知覚に沿った色差を計算できる。両ペアは RGB 色空間では等しい距離を持つが、人間には左のペアの方が色が遠く、右のペアの方が色が近いと知覚されることが CIEDE2000 の計算に反映されている。*1

CIEDE2000 の数式

具体的な数式は複雑だが、下記文献を参照すると実装はそれほど難しくない。計算は CIELAB 色空間に基づいている。詳細は割愛する。

CIEDE2000 の実装

下記の GitHub レポジトリに CIEDE2000 の実装を公開した。なお、CIEDE2000 の計算に必要な色空間の変換(RGB から CIELAB へ)も実装に含まれている。C++11 で記述している。Header-only ライブラリとして利用可能になっている。ライセンスは MIT License とした。

github.com

他の色差計算手法との比較

RGB 色空間におけるユークリッド距離

これは色の距離を測る際の最も単純な方法の一つであり、しばしば用いられる。冒頭の図でも CIEDE2000 の結果との比較のために用いた。次の式で色差が計算される。

{ \displaystyle
d = \sqrt{ (r_1 - r_2)^2 + (g_1 - g_2)^2 + (b_1 - b_2)^2 }
}

しかし、この指標は人間の知覚特性を考慮していない。

CIELAB 色空間におけるユークリッド距離

人間の知覚を考慮するために、RGB 色空間ではなく CIELAB 色空間におけるユークリッド距離を用いる方法があり、これもしばしば用いられる。なお、CIELAB 色空間とはここではCIE 1976 (L*, a*, b*) 色空間を指す。単に Lab 色空間とも呼ばれる。次の式で色差が計算される。

{ \displaystyle
d = \sqrt{ (L^*_1 - L^*_2)^2 + (a^*_1 - a^*_2)^2 + (b^*_1 - b^*_2)^2 }
}

なお、この色差の指標は CIE76 とも呼ばれる。

設計された当時、CIE76 は人間の知覚をよく反映した指標だとということになっていたようだが、それでもやはり人間の知覚特性を完全に再現できているわけではない。後により精度が高いとされる指標が提案されることになり、現在では CIEDE2000 が人間の知覚をよく反映した指標だということになっている。

*1:ここでは sRGB 色空間を仮定して CIEDE2000 を計算している。詳しくはソースコードを参照。