かみのメモ

コンピュータビジョン・プログラムな話題中心の勉強メモ

勝手に作るCMake入門 その4 外部ライブラリを利用する

勝手に作るCMake入門の4本目です。

1本目の記事ではCMakeのhello_worldプロジェクトを作成し、2本目の記事では静的ライブラリを利用してプロジェクトを階層化し、3本目の記事ではキャッシュ変数を利用したプロジェクト設定の方法を紹介しました。

今回はCMakeで外部ライブラリを利用する方法についてまとめていきます。 主にOSSで公開されているライブラリをインストールする方法や、find_package()コマンドの使い方について解説します。

全体のもくじ

  1. 基本的な使い方
  2. プロジェクトの階層化
  3. プロジェクトの設定
  4. 外部ライブラリを利用する【今ここ】

この記事のもくじ

スポンサーリンク

7. find_packageとは

具体的な外部ライブラリの使い方を説明する前にfind_package()コマンドについて解説しておきます。

find_package(<PackageName>)は、特定の名前のCMakeスクリプトを探し出し、それを実行するコマンドです。 対象となるCMakeスクリプトはその役割に応じてModule, Configの2種類に分類されます。 find_packageはModule, Configの順で探索を行い、目的のスクリプトファイルが見つかったらその中身を実行してコマンドを終了します。

具体的な動作は以下のとおりです。

  1. Moduleモード:以下のパスで<PackageName>.cmakeもしくはFind<PackageName>.cmakeを探す
    1. CMakeキャッシュCMAKE_MODULE_PATHに指定されたパス
    2. CMakeにデフォルトで付いてくるモジュールの置き場(/usr/local/share/cmake/Modulesなど)
  2. Configモード:以下のパスとそれに適当なサフィックスを付けたパスで<PackageName>Config.cmakeもしくは<lower-case-package-name>-config.cmakeを探す
    1. CMake変数<PackageName>_DIRで指定されたパス
    2. CMakeキャッシュ変数<PackageName>_ROOTで指定されたパス
    3. 環境変数<PackageName>_ROOTで指定されたパス
    4. CMakeキャッシュ変数CMAKE_PREFIX_PATH, CMAKE_FRAMEWORK_PATH, CMAKE_APPBUNDLE_PATHで指定されたパス
    5. 環境変数<PackageName>_DIR, CMAKE_PREFIX_PATH, CMAKE_FRAMEWORK_PATH, CMAKE_APPBUNDLE_PATHで指定されたパス
    6. 環境変数PATHで指定されたパス(bin, sbinで終わるパスはその親ディレクトリに読み替える)
    7. (他にもあるが省略)

なお、コマンド終了時に<PackageName>_FOUNDというCMake変数に0 or 1 (FALSE or TRUE)が格納されるので、発見できたかどうかによって分岐処理を書くこともできます。

「適当なサフィックス」には./cmake/, ./<name>*/, ./lib/x64/cmake/<name>*/などが含まれます。 詳しいことは公式ドキュメントを参照してください。

以上がfind_packageの概要です。 それではModuleとConfigについて解説していきましょう。

7.1. Module

まずはModuleと呼ばれるCMakeスクリプトについてです。

Moduleは以下の2種類に分類されます。

  1. よく使うCMake関数をまとめたUtility Modules
  2. 外部ライブラリの情報を収集するFind Modules

CMakeでは、インストール時にデフォルトで多くのモジュールがインストールされています。 モジュールの一覧は公式ドキュメントから確認できます。

また、自分でCMakeスクリプトを書いて自作Moduleを作ることもできます。

Utility Modules

Utility Modulesはよく使うCMake関数をまとめたものです。 例えば、CheckLanguage.cmake(あるプログラミング言語の開発ツールがインストールされているか確認するための機能をまとめたもの)やBundleUtilities.cmake(主にMac*.appの中身にアクセスするための機能をまとめたもの)などです。 これらのスクリプトを読み込むとcheck_language(), get_bundle_main_executable()などの関数が定義されるので、それらを呼び出すことで機能を利用できます。

Find Modules

一方、Find ModulesはPC内にインストールされているライブラリを発見するためのスクリプトです。 ファイル名はFind<PackageName>.cmakeです。

ご存知の通り、C++のライブラリを自分のプログラムにリンクするときには①ヘッダファイルのディレクトリ, ②静的リンクライブラリファイルのディレクトリ, ③静的リンクライブラリファイルの名前, の3つの情報が必要になりますが、これらの情報をConfigureの度に手入力するのは非常に面倒です。 そこで、これらの情報を自動で収集してくれるのがFindモジュールです。

多くの開発環境では、ライブラリをインストールするときにapt, yum, brewなどのパッケージマネージャーや開発元が配布しているインストーラーを使います。 そのため「このOSならこのパスにライブラリがインストールされているだろう」というのが推測できます。 Findモジュールはそのパスを探索して、①, ②の情報とライブラリのバージョン情報などを取得します。 ③はほぼ固定なのでFindモジュールのスクリプト内で決め打ちされていることが多いです。

CMakeではBoost, BLAS, GLEW, OpenGL, X11, Qt4などのライブラリのFindモジュールがデフォルトで用意されています。

大抵のFindモジュールは、以下のようなCMake変数に値をセットします。

  • <PackageName>_INCLUDE_DIRS:インクルードディレクトリのパス
  • <PackageName>_LIBRARY_DIRS:ライブラリファイルがあるディレクトリのパス
  • <PackageName>_LIBRARIES:ライブラリファイル名のリスト
  • <PackageName>_DEFINITIONSコンパイル時のdefinitionフラグ

find_packageコマンドでこれらの変数をセットさせ、target_include_directories(main PUBLIC Boost_INCLUDE_DIRS)などのように適宜設定を反映させていくわけです。 ただし、変数名は必ずしもこの通りになっているわけではないですし、ライブラリの情報を変数にまとめるのではなくターゲット(ライブラリ)を作成しそのプロパティにまとめるケースもあります。 親切なFindスクリプトは冒頭にコメントで仕様を書いてくれているので、それを確認しながら自分のスクリプトを書いていきます。

自作のModule

CMakeに付属しているModuleの他にも、自分で書いたCMakeスクリプトを自作Moduleとして読み込ませることもできます。 書いたスクリプト<PackageName>.cmakeFind<PackageName>.cmakeのファイル名で適当なパスに保存しておき、Configure時にCMake変数CMAKE_MODULE_PATHにそのパスを指定してやれば、find_package(<PackageName>)で読み込めるようになります。

多くのOSSではプロジェクト直下にcmakeというフォルダを作って自作Moduleをまとめておき、set(CMAKE_MODULE_PATH './cmake')のようにしてからfind_packageで読み込む方法が使われています。

7.2. Config

次に、Configと呼ばれるCMakeスクリプトについてです。

Configファイルは、CMakeプロジェクトをビルドしてライブラリを作成したときに一緒に作成できる設定ファイルです。 ファイル名は<PackageName>Config.cmakeもしくは<lower-case-package-name>-config.cmakeです。

ライブラリの情報を提供するという点ではFindモジュールと同じですが、Configファイルはライブラリの開発元が直接提供している設定ファイルであるという違いがあります。 ライブラリをインストールしたとき、cmakeshareという名前のディレクトリの中に含まれていることが多いです。

Findモジュールと同じく、大抵のConfigファイルは以下のようなCMake変数に値をセットします。

  • <PackageName>_INCLUDE_DIRS:インクルードディレクトリのパス
  • <PackageName>_LIBRARY_DIRS:ライブラリファイルがあるディレクトリのパス
  • <PackageName>_LIBRARIES:ライブラリファイル名のリスト
  • <PackageName>_DEFINITIONSコンパイル時のdefinitionフラグ

また、これらの情報を1つのターゲットにまとめているケースもあります。 Configファイルを使うときは、開発元が公開しているドキュメントやConfigファイルの中身を読み、Configファイルによってどのような変数やターゲットが作成されるのかを確認しながら、自分のスクリプトを書いていくことになります。

*nix系のConfigファイル事情

*nix系のOSにライブラリをインストールするときは、/usr/local/lib/の中に静的ライブラリファイル, /usr/local/include/の中にヘッダファイル, /usr/local/bin/に実行ファイルを設置するのが標準的です。 パッケージマネージャによっては、ライブラリ自体は別のディレクトリにインストールしておき/usr/local/*/シンボリックリンクだけ貼っておくというパターンもあります。 どちらにせよ、/usr/local/*/にライブラリ関係のファイルが配置されるというわけです。

ライブラリのConfigファイルは/usr/local/share/<PackageName>//usr/local/lib/cmake/<PackageName>/に配置されます。 大抵の場合/usr/local/bin/はPATHに追加されているので、CMakeはここに置かれたConfigファイルを発見することができます。

つまり*nix系のOSではパッケージマネージャを通してライブラリをインストールするだけで(ほぼ)find_packageで発見できるようになるということです。 もしライブラリにConfigファイルが付属していなかった場合は、自分でFindモジュールを用意する必要があります。 また、変則的な場所にライブラリをインストールしている場合は、Configure時にキャッシュ変数<PackageName>_DIRにそのパスをセットすることでCMakeにConfigファイルを発見させることができます。

WindowsのConfigファイル事情

Windowsにライブラリをインストールするときは、C:\Program Files\C:\Program Files(x86)\以下に<ライブラリ名>もしくは<ベンダー名\ライブラリ名>のようなフォルダを作ってその中に配置することが多いです。 面倒なことに、*nix系OSの/usr/localのようにパスが通っている場所にまとめるという文化はないので、個別にパスを通さない限りfind_package()で発見できるようになりません。

とはいえ、PATHにどんどんライブラリのパスを追加していくのは環境が汚染されるので遠慮したいところです。 ライブラリをCMakeから利用する前提なら環境変数<PackageName>_DIRを作成するか、CMAKE_PREFIX_PATHにConfigファイルへのパスを追加していくのがよいでしょう。

8. CMakeで外部ライブラリを利用する

それでは、find_package()の挙動を踏まえた上で外部のCMakeライブラリを利用する方法を見ていきます。 ここでは一例として、Googleが開発している数理最適化ライブラリceres-solverを利用する手順を紹介していきます。 最終目標はceres-solverのサンプルコードを動かして、非線形の最小二乗問題を解いてみることです。

ceres-solver線形代数ライブラリEigenglogに依存しているので、作業手順は以下のようになります。

  • Eigenとglogをインストールする
  • CMakeを使ってceres-solverをインストールする
  • 自分用のプロジェクトを作りceres-solverのサンプルコードを動かす

8.1. Eigenとglogをインストールする

Eigenとglogはかなり有名なOSSですので、色々なインストール方法が用意されています。

*nix系OSの場合

*nix系のOSを使っているなら、パッケージマネージャーを通してインストールすることができるはずです。 例えば、aptからインストールするにはapt install libeigen3-dev libgoogle-glog-devを実行すればいいです。

前述のように、OSパッケージマネージャーを通してインストールされたライブラリは/usr/local以下に適切にシンボリックリンクが作成されるので、そのままfind_package()で発見できるようになります。

Windowsの場合

一方、WindowsなどこれというOSパッケージマネージャーに乏しい環境では、CMakeを使ってソースからビルドするのが無難だと思います。

ビルドとインストールの手順はこれまでのCMakeプロジェクトと同じです。

git clone https://github.com/eigenteam/eigen-git-mirror.git
cd eigen-git-mirror
git checkout 3.3.7
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX='C:/lib/eigen3.3.7'
cmake --build . --target install

cd ..

git clone https://github.com/google/glog.git
cd glog
git checkout v0.4.0
mkdir build_dir
cd build_dir
cmake .. -DCMAKE_INSTALL_PREFIX='C:/lib/glog0.4.0'
cmake --build . --target install

デフォルトではC:\Program Files(x86)\にインストールされますが、インストーラーを使わず手動で入れたことをわかりやすくするため、C:\lib\eigen3.3.7C:\lib\glog0.4.0に変えておきます。 また、glogではbazel用のBUILDというファイルが存在するのでbuild_dirというディレクトリの中でConfigureしています。 他にも色々ビルドオプションを設定できるので、必要に応じてccmakeやcmake-guiでキャッシュ変数を編集してからビルドしてください。

Eigenはヘッダオンリーライブラリなのでビルドは行われず、所定の場所にファイルがコピーされるだけですが、これでインストールは完了です。 glogもビルド後に諸々のファイルがコピーされます。

インストール後は、Configファイルのパスを通しておきましょう。 Eigenとglogはどちらも親切にConfigファイルを作成してくれます。 例えば、EigenはC:/lib/eigen3.3.7/share/eigen3/cmake/の中にEigen3Config.cmakeが用意されているはずです。

これらのファイルをCMakeLists.txtのfind_package()から発見できるよう設定しておきます。 いくつか方法はありますが、環境変数CMAKE_PREFIX_PATHを作成し、そこにC:/lib/eigen3.3.7/;C:/lib/glog0.4.0;を設定しておくのがスタンダードな方法だと思います。

(このあたりの手順が煩雑なのが、Windowsで開発しづらくしてますよね…)


これでEigenとglogをCMakeから利用するための環境が整いました。

8.2. ceres-solverをインストールする

次に、ceres-solverをインストールします。

まずは、適当なディレクトリにGitHubのリポジトリをクローンします。 ceres-solverは1.9から2.0に移行するに当たってbazelをサポートしたりCMakeスクリプトを改善したりしているようなので、開発中の2.0.0を使うためにmasterをビルドします。 執筆時のHEADは059bcb7fです。

git clone https://github.com/ceres-solver/ceres-solver
cd ceres-solver
mkdir build_dir
cd build_dir

cmake .. -DCMAKE_INSTALL_PREFIX='install_dir' -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF
cmake --build . --target install

先ほども述べたように、CMakeはデフォルトでは/usr/local/C:/Program Files(x86)/にインストールしようとします。 しかし、今回はceres-solverを試してみたいだけなので、本格的にインストールするのは避けたいところです。 そこで、ceres-solver/build_dir/install_dirに仮インストールするよう設定しています。

また、ceres-solverのサンプルとテストのビルドは必要ないのでOFFにしておきます。

ビルドに数分かかりますが、ceres-solver/build_dir/install_dir以下にceres-solverのライブラリがインストールされます。

8.3. サンプルプロジェクトを作る

次に、ceres-solverを利用するCMakeプロジェクトを書いてみます。

適当なディレクトリにmain.cppを作成し、以下のコードをコピーしましょう。 ceres-solverのサンプルcurve_fitting.ccを流用しています。

main.cppソースコード

// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2010, 2011, 2012 Google Inc. All rights reserved.
// http://code.google.com/p/ceres-solver/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors may be
//   used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: sameeragarwal@google.com (Sameer Agarwal)

#include "ceres/ceres.h"

using ceres::AutoDiffCostFunction;
using ceres::CostFunction;
using ceres::Problem;
using ceres::Solver;
using ceres::Solve;

// Data generated using the following octave code.
//   randn('seed', 23497);
//   m = 0.3;
//   c = 0.1;
//   x=[0:0.075:5];
//   y = exp(m * x + c);
//   noise = randn(size(x)) * 0.2;
//   y_observed = y + noise;
//   data = [x', y_observed'];

const int kNumObservations = 67;
const double data[] = {
  0.000000e+00, 1.133898e+00,
  7.500000e-02, 1.334902e+00,
  1.500000e-01, 1.213546e+00,
  2.250000e-01, 1.252016e+00,
  3.000000e-01, 1.392265e+00,
  3.750000e-01, 1.314458e+00,
  4.500000e-01, 1.472541e+00,
  5.250000e-01, 1.536218e+00,
  6.000000e-01, 1.355679e+00,
  6.750000e-01, 1.463566e+00,
  7.500000e-01, 1.490201e+00,
  8.250000e-01, 1.658699e+00,
  9.000000e-01, 1.067574e+00,
  9.750000e-01, 1.464629e+00,
  1.050000e+00, 1.402653e+00,
  1.125000e+00, 1.713141e+00,
  1.200000e+00, 1.527021e+00,
  1.275000e+00, 1.702632e+00,
  1.350000e+00, 1.423899e+00,
  1.425000e+00, 1.543078e+00,
  1.500000e+00, 1.664015e+00,
  1.575000e+00, 1.732484e+00,
  1.650000e+00, 1.543296e+00,
  1.725000e+00, 1.959523e+00,
  1.800000e+00, 1.685132e+00,
  1.875000e+00, 1.951791e+00,
  1.950000e+00, 2.095346e+00,
  2.025000e+00, 2.361460e+00,
  2.100000e+00, 2.169119e+00,
  2.175000e+00, 2.061745e+00,
  2.250000e+00, 2.178641e+00,
  2.325000e+00, 2.104346e+00,
  2.400000e+00, 2.584470e+00,
  2.475000e+00, 1.914158e+00,
  2.550000e+00, 2.368375e+00,
  2.625000e+00, 2.686125e+00,
  2.700000e+00, 2.712395e+00,
  2.775000e+00, 2.499511e+00,
  2.850000e+00, 2.558897e+00,
  2.925000e+00, 2.309154e+00,
  3.000000e+00, 2.869503e+00,
  3.075000e+00, 3.116645e+00,
  3.150000e+00, 3.094907e+00,
  3.225000e+00, 2.471759e+00,
  3.300000e+00, 3.017131e+00,
  3.375000e+00, 3.232381e+00,
  3.450000e+00, 2.944596e+00,
  3.525000e+00, 3.385343e+00,
  3.600000e+00, 3.199826e+00,
  3.675000e+00, 3.423039e+00,
  3.750000e+00, 3.621552e+00,
  3.825000e+00, 3.559255e+00,
  3.900000e+00, 3.530713e+00,
  3.975000e+00, 3.561766e+00,
  4.050000e+00, 3.544574e+00,
  4.125000e+00, 3.867945e+00,
  4.200000e+00, 4.049776e+00,
  4.275000e+00, 3.885601e+00,
  4.350000e+00, 4.110505e+00,
  4.425000e+00, 4.345320e+00,
  4.500000e+00, 4.161241e+00,
  4.575000e+00, 4.363407e+00,
  4.650000e+00, 4.161576e+00,
  4.725000e+00, 4.619728e+00,
  4.800000e+00, 4.737410e+00,
  4.875000e+00, 4.727863e+00,
  4.950000e+00, 4.669206e+00,
};

struct ExponentialResidual {
  ExponentialResidual(double x, double y)
      : x_(x), y_(y) {}

  template <typename T> bool operator()(const T* const m,
                                        const T* const c,
                                        T* residual) const {
    residual[0] = T(y_) - exp(m[0] * T(x_) + c[0]);
    return true;
  }

 private:
  const double x_;
  const double y_;
};

int main(int argc, char** argv) {
  double m = 0.0;
  double c = 0.0;

  Problem problem;
  for (int i = 0; i < kNumObservations; ++i) {
    problem.AddResidualBlock(
        new AutoDiffCostFunction<ExponentialResidual, 1, 1, 1>(
            new ExponentialResidual(data[2 * i], data[2 * i + 1])),
        NULL,
        &m, &c);
  }

  Solver::Options options;
  options.max_num_iterations = 25;
  options.linear_solver_type = ceres::DENSE_QR;
  options.minimizer_progress_to_stdout = true;

  Solver::Summary summary;
  Solve(options, &problem, &summary);
  std::cout << summary.BriefReport() << "\n";
  std::cout << "Initial m: " << 0.0 << " c: " << 0.0 << "\n";
  std::cout << "Final   m: " << m << " c: " << c << "\n";
  return 0;
}

次にCMakeLists.txtを書いていきます。

ライブラリのリンクの基本

通常、C++のライブラリはヘッダファイル, 静的リンクライブラリファイル(, 動的リンクライブラリファイル)の組み合わせで配布されます。 これを自分の実行ファイルにリンクするときには①ヘッダファイルのディレクトリ, ②静的リンクライブラリファイルのディレクトリ, ③静的リンクライブラリファイルの名前, の3つが必要になります。

CMakeでこの3つを行うときの基本構文は以下のようになっています。

# ライブラリディレクトリの指定
link_directories("xxxx/lib")

add_executable(main_app main.cpp)

# インクルードディレクトリの指定
target_include_directories(main_app PRIVATE
  "xxxx/include")
# ライブラリファイル名の指定
target_link_libraries(main_app xxxx)

ライブラリディレクトリの指定にはlink_directories()コマンドを使います。 この設定はこのプロジェクト全体で共有されます。

インクルードディレクトリの指定にはtarget_include_directories()コマンドを使います。

ライブラリファイル名の指定にはtarget_link_libraries()コマンドを使います。 以前の記事で登場したときは自分で作成したライブラリをリンクするために利用していましたが、このように外部ライブラリのファイル名を指定するためにも利用されます。

この構文が外部ライブラリを利用するときの基本となります。

CeresConfig.cmakeを利用したリンク

しかし今回のケースでは、main.cppをビルドするときにceres-solverのヘッダファイル群を参照させ、ライブラリファイルlibceres.aをリンクする必要があり、またEigenやglogといった間接的に参照しているライブラリもリンクする必要があります。 これはかなり面倒な作業です。

そこで、この辺りの面倒な作業は全てCeresConfig.cmakeに任せます。 CeresConfig.cmakeは必要な設定を詰め込んだターゲットceresを作成してくれるので、我々はこれをmain_appにリンクするだけで必要なセッティングが完了します。 ターゲットceresに対しては、以前の記事で紹介したのと同じように、target_include_directories()などのコマンドを駆使して依存ターゲットへのインクルードやリンクの設定が行われているので、我々はtarget_link_libraries(main_app ceres)を呼び出すだけで必要な設定が行われるというわけです。

ちなみに、この辺のConfigファイルの仕様はライブラリ開発者次第なので、ドキュメント要確認です。 大抵の場合、Configファイルの先頭にコメントで使い方を書いてくれています。

cmake_minimum_required(VERSION 3.1)

find_package(Ceres REQUIRED)

add_executable(main_app main.cpp)

target_link_libraries(main_app ceres)

if(MSVC AND ${MSVC_VERSION} GREATER_EQUAL 1915)
  target_compile_definitions(main_app PRIVATE _ENABLE_EXTENDED_ALIGNED_STORAGE)
endif()

最後の部分は、MSVCでのみ必要なオプションの設定です。 CeresではC++alignof()を利用していますが、過去のMSVCではこの挙動にバグがありバグ修正の前後でMSVCの挙動が変わっています。 この挙動の変化を把握していることを明示するため_ENABLE_EXTENDED_ALIGNED_STORAGEを指定しています。

このCMakeプロジェクトをビルドします。 今回はceres-solverを変則的な場所にインストールしているので、以下のようにCeresConfig.cmakeの場所を教えてやります。 ここではフルパスを指定する必要があります。

mkdir build
cd build
cmake .. -DCeres_DIR='<path to directory of CeresConfig.cmake>'
cmake --build .

作成されたmain_appを実行してみます。 main_appは区間 {x = [0, 5)} において { y = e^{mx + c} \ | _ {m=0.3, c=0.1}} にノイズを混ぜたサンプル点に対してカーブフィッティングを行うことで {m, c} の値を復元するプログラムです。

iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
   0  1.211734e+02    0.00e+00    3.61e+02   0.00e+00   0.00e+00  1.00e+04        0    4.41e-04    5.31e-04
   1  2.334822e+03   -2.21e+03    0.00e+00   7.52e-01  -1.87e+01  5.00e+03        1    1.50e-04    7.78e-04
   2  2.331438e+03   -2.21e+03    0.00e+00   7.51e-01  -1.86e+01  1.25e+03        1    9.20e-05    9.23e-04
   3  2.311313e+03   -2.19e+03    0.00e+00   7.48e-01  -1.85e+01  1.56e+02        1    8.68e-05    1.07e-03
   4  2.137268e+03   -2.02e+03    0.00e+00   7.22e-01  -1.70e+01  9.77e+00        1    1.12e-04    1.44e-03
   5  8.553131e+02   -7.34e+02    0.00e+00   5.78e-01  -6.32e+00  3.05e-01        1    1.27e-04    1.69e-03
   6  3.306595e+01    8.81e+01    4.10e+02   3.18e-01   1.37e+00  9.16e-01        1    7.88e-04    2.62e-03
   7  6.426770e+00    2.66e+01    1.81e+02   1.29e-01   1.10e+00  2.75e+00        1    6.55e-04    3.33e-03
   8  3.344546e+00    3.08e+00    5.51e+01   3.05e-02   1.03e+00  8.24e+00        1    6.38e-04    4.00e-03
   9  1.987485e+00    1.36e+00    2.33e+01   8.87e-02   9.94e-01  2.47e+01        1    7.29e-04    4.79e-03
  10  1.211585e+00    7.76e-01    8.22e+00   1.05e-01   9.89e-01  7.42e+01        1    6.92e-04    5.51e-03
  11  1.063265e+00    1.48e-01    1.44e+00   6.06e-02   9.97e-01  2.22e+02        1    7.33e-04    6.31e-03
  12  1.056795e+00    6.47e-03    1.18e-01   1.47e-02   1.00e+00  6.67e+02        1    8.05e-04    7.17e-03
  13  1.056751e+00    4.39e-05    3.79e-03   1.28e-03   1.00e+00  2.00e+03        1    9.01e-04    8.13e-03
Ceres Solver Report: Iterations: 14, Initial cost: 1.211734e+02, Final cost: 1.056751e+00, Termination: CONVERGENCE
Initial m: 0 c: 0
Final   m: 0.291861 c: 0.131439

{m=0.3, c=0.1} に近い値が復元されていますね。


以上、CMakeで外部ライブラリを利用するために必要な知識を解説しました。

とりあえず、この記事でCMake連載は完結のつもりです。 まだ紹介していないトピックとして、プロジェクトのインストール設定, Configファイルの作成, CMake関数の挙動などが残っていますが、このあたりの書き方はケースバイケースなのでうまくまとめるのが難しいかな、と感じています。

紹介したサンプルが動かない、間違いを見つけた等ありましたらお知らせいただけると幸いです。


もしよければ↓の☆を1クリックお願いします!