📈

C++ matplotlib-cpp / gunplotの使い方

2024/06/21に公開

海洋ロボコンをやってた人です。
今回はC++の描画ライブラリの導入を、備忘録として記載しておきます。

以下箇条書きになりますが、何卒宜しくお願い致します。

動作環境は以下です。

  • OS: Ubuntu 22.04
  • Python 3.11

追記事項があれば、別途記載します。

1: matplotlib-cpp

まずはmatplotlib-cppの紹介。C++で使用するにはライブラリの準備とPython系のインストールなどが必要です。

1.1: Input

  1. matplotlibのcpp版の準備

https://github.com/lava/matplotlib-cpp.git

クローンし以下のパスへ配置するところまで進めておきます。

/usr/local/include/matplotlib-cpp

  1. 必要なモジュールのインストール

matplotlibに必要なpythonモジュールをインストール

terminal
sudo apt install python3.11-dev
sudo apt install -y python3-matplotlib
python3-numpy

1.2: Implementation

CMakeLists.txtでPythonモジュールをインクルードできるように指定

CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(plot_test)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

# Find the Python 3.10 package
find_package(Python3 REQUIRED COMPONENTS Development)
include_directories(${Python3_INCLUDE_DIRS})

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# Include Python headers
add_executable(
  matplot_test src/matplot.cpp
)
target_link_libraries(matplot_test ${Python3_LIBRARIES})

2軸ロボットアームの順運動学を解き、その結果をmatplotlibへ描画するプログラムにしてみます。
2軸ロボットアームの各リンクの長さを l_1l_2、各関節の角度を \theta_1\theta_2として、このときのエンドエフェクタの位置 (x,y) を計算すると以下で表せるので、これを描画してみます。

\begin{aligned} x &= l_1 \cos(\theta_1) + l_2 \cos(\theta_1 + \theta_2) \\ y &= l_1 \sin(\theta_1) + l_2 \sin(\theta_1 + \theta_2) \end{aligned}
matplot.cpp
#include "matplotlib-cpp/matplotlibcpp.h"
#include <vector>
#include <cmath>

namespace plt = matplotlibcpp;

// 2軸アームの順運動学を計算
std::pair<std::vector<double>, std::vector<double>> forward_kinematics(
    const std::vector<double>& theta1,
    const std::vector<double>& theta2,
    double l1,
    double l2
) {
    std::vector<double> x, y;

    for (size_t i = 0; i < theta1.size(); ++i) {
        double x_i = l1 * cos(theta1[i]) + l2 * cos(theta1[i] + theta2[i]);
        double y_i = l1 * sin(theta1[i]) + l2 * sin(theta1[i] + theta2[i]);
        x.push_back(x_i);
        y.push_back(y_i);
    }

    return {x, y};
}

int main() {
    // アームの長さ
    double l1 = 1.0;
    double l2 = 1.0;

    // theta1, theta2の範囲を細かく分割
    int num_points = 20;
    std::vector<double> theta1, theta2;
    for (int i = 0; i < num_points; ++i) {
        theta1.push_back(M_PI/2 * i / num_points);
        theta2.push_back(M_PI/2 * i / num_points);
    }

    // 順運動学を計算
    auto [x, y] = forward_kinematics(theta1, theta2, l1, l2);

    // 軌跡を描画
    plt::plot(x, y, "r-"); // 軌跡
    plt::scatter(x, y); // 軌跡のポイント

    // 各軸の部分を別々に描画
    for (size_t i = 0; i < theta1.size(); ++i) {
        std::vector<double> x_segment = {0, l1 * cos(theta1[i]), x[i]};
        std::vector<double> y_segment = {0, l1 * sin(theta1[i]), y[i]};
        plt::plot(x_segment, y_segment, "b-");
    }

    // プロットを表示
    plt::title("2-Axis Robot Arm Trajectory");
    plt::xlabel("X");
    plt::ylabel("Y");
    plt::show();

    return 0;
}

1.3: Verification

2: gnuplot

matplotlib-cppと比較し、準備が簡単。一方で描画のためのファイル準備などが必要。

2.1: Input

gnuplotの基礎は以下を熟知すればOK

https://www.library.osaka-u.ac.jp/doc/LS_20190111_gnuplot.pdf

ディレクトリは以下になります。

folder_tree
|--- CMakeLists.txt
|--- build
|   |--- test.gp
|   |--- test
|   |--- test.txt
|--- folder_tree.md
|--- src
|   |--- test.cpp

2.2: Implementation

gnuplot_test.cpp
#include <iostream>
#include <cmath>
#include <vector>
#include <string.h>


class Test {
public:
    Test();
    double o_i_[2] = {};
    void update(double time);

private:
    std::string fname = "./test.txt";
    FILE *fp = fopen(fname.c_str(), "w");
};

Test::Test(){
    o_i_[0] = -0.2;
    o_i_[1] = 0.0;
}

void Test::update(double time){

    fprintf(fp, "%f, %f, %f\n", time, o_i_[0], o_i_[1]);
    std::cout << "Step " << time << ": Output0 = " << o_i_[0] << ", Output1 = " << o_i_[1] << std::endl;
}

int main() {

    Test test;

    double time;
    double dt = 0.01; // Hz / (2 * 10)
    double sim_time = 100.0;
    int num_step = static_cast<int>(sim_time/dt);
    for(int i=0; i<num_step; i++){
        time = i*dt;
        test.update(time);
    }

    return 0;
}

build下へ.gpファイルを作成する

test.gp
plot [0:10][-0.2:0.2] \
     'test.txt' using 1:2 with lines,\
     'test.txt' using 1:3 with lines linestyle 3,\

2.3: Verification

terminal
gnuplot
load "test.gp"


以上です。
Likeいただけると大変励みになりますので、よろしくお願いいたします。

Discussion