Pythonで新しく学んだこと

同名のクラス変数とインスタンス変数

クラス変数とインスタンス変数で同じ名前を使う場合、インスタンス変数が作られる前まではインスタンス変数とクラス変数が同じ実体になっているが、インスタンス変数が作られた時点でクラス変数とは別の実体になっていることがわかる。

class Class1:
    value = 5
    def set(self):
    self.value = self.value + 1

a = Class1()
print(f'instance value = {a.value}')
print(f'class value = {Class1.value}')
Class1.value += 5
print(f'instance value = {a.value}')
print(f'class value = {Class1.value}')
a.value += 2
print(f'instance value = {a.value}')
print(f'class value = {Class1.value}')
Class1.value += 3
print(f'instance value = {a.value}')
print(f'class value = {Class1.value}')

出力

instance value = 5
class value = 5
instance value = 10
class value = 10
instance value = 12
class value = 10
instance value = 12
class value = 13

C++新しく学んだこと

Parameter pack

0個以上の可変長の複数の変数をまとめて扱い展開していく時に使う。

Reference

Parameter pack(since C++11) - cppreference.com

Example1

template<typename T, typename... Targs>
void tprintf(const char* format, T value, Targs... Fargs) {
  for (; *format != '0\n'; format++) {
    if (*format == '%') {
      tprintf(format+1, Fargs);
      return;
    }
  }
}

int main()
{
  tprintf("% world% %\n","Hello",'!',123);
}

出力

Hello world! 123

動作イメージ

parameter packの動作イメージ

コメント

型としては必ずテンプレートを使わないといけない。例えば以下のような使い方はできない。

void value_print(int value, int... values) {
  std::cout << value << std::endl;
  value_print(values);
}

Example2

デバッグモードの時のみ値をprintする関数

/// @brief デバッグモードの時のみ値をprintする関数
template <typename... Targs>
void print(Targs... args) {
# if DEBUG
  printf(args...); // 引数に...をつけることに注意
# endif
  return; 
}

テンプレートと非テンプレートで関数の引数につけられた&&の挙動の違い

非テンプレートの場合、&&がついた引数は右辺値しか受け取ることができない。一方テンプレートの場合は&&がついた引数は右辺値のみではなく左辺値も受け取ることができる。

class Hoge {
};
template <typename T>
void templae_func(T && h){
  (void)h;
}

void nontemplae_func(Hoge && h){
  (void)h;
}
int main() {
  Hoge h;
  templae_func(h);
  nontemplae_func(h); // これはエラーが
}

Reference

std::forwardについて

std::forward

上で書いたように、テンプレート関数の引数に&&がついていると、その引数は右辺値と左辺値の両方の値がくる可能性がある。ただ、それをそのまま別の関数に渡してしまうと仮に引数が右辺値型で来ていたとしても右辺値を受け取った引数は左辺値として扱われるため別の関数に左辺値として渡ってしまうという問題がある。例えば以下のようにすると右辺値引数の関数func2を呼べずエラーになる。

class Hoge {
};
void func2(Hoge && h) {
  std::cout << "func2 double and" << std::endl;
}
template <typename T>
void func(T && h){
  func2(h);
}
int main() {
  func(Hoge());
}

そこでstd::forwardの出番である。これは左辺値で来たものを左辺値のまま、右辺値で来たものを右辺値のままにキャストするものであり、func内でこれを使うとこれがエラーではなくなる。

class Hoge {
};
void func2(Hoge && h) {
  std::cout << "func2 double and" << std::endl;
}
template <typename T>
void func(T && h){
  func2(std::forward<T>(h));
}
int main() {
  func(Hoge());
}

マクロの可変長引数展開

やりたいこと

MACRO(3, 4, 5);

とやったらある処理を3、4、5の3ケースについてループしてくれるようなことや

MACRO(add, subt, multiple);

とやったらある処理をaddsubtmultipleの3関数についてループしてくれるようなこと。

マクロループ展開のやり方

マクロループは(a)(b)(c)のようなマクロ形式の集合の形で渡す必要がある。例えば

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>

// dataの取り扱いは不明
#define MACRO(r, data, i, elem) std::cout << "r="<<r<<",i="<<i <<",elem="<<elem<< std::endl;
// 第二引数は使わなくてよさようだ
#define MACRO2() BOOST_PP_SEQ_FOR_EACH_I(MACRO, _, (3)(4)(5)); 
int main() {
  MACRO2();
  return 0;
}

とやると

r=1,i=0,elem=3
r=2,i=1,elem=4
r=3,i=2,elem=5

が出力される。

マクロ形式の集合の形の作り方

BOOST_PP_VARIADIC_TO_SEQを使うと (a, b, c)と渡した引数を(a)(b)(c)と変換できる。またマクロの引数を...としそのマクロ内で__VA_ARGS__を使うと可変長引数を受けられる。例えば

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>
#define MACRO(r, data, i, elem) std::cout << "r="<<r<<",i="<<i <<",elem="<<elem<< std::endl;
#define MACRO2(...) BOOST_PP_SEQ_FOR_EACH_I(MACRO, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__));
int main() {
  MACRO2("3", "4", "5");
  return 0;
}

とやると

r=1,i=0,elem=3
r=2,i=1,elem=4
r=3,i=2,elem=5

と出力される。

マクロに関数を渡す。

マクロには数値や文字だけでなく関数も渡すことができる。

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>
int add(int i) {
  return i+1;
}
int subt(int i) {
  return i-1;
}
int multiple(int i) {
  return i*2;
}
#define MACRO(r, data, i, elem) std::cout << "i="<<i<<",elem="<<elem(3) << std::endl;
#define MACRO2(...) BOOST_PP_SEQ_FOR_EACH_I(MACRO, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__));
int main() {
  MACRO2(add, subt, multiple)
  return 0;
}

出力結果は

i=0,elem=4
i=1,elem=2
i=2,elem=6

headerの引数にconstをつけない

// hpp
void f(int v);

// cpp
void f(const int v) {
}

のように、値で渡す引数については、headerの方はconstをつけずcppの方にはconstをつける。

ポリモーフィズムの基底クラスのデストラクタにはvirtualをつける

ポリモーフィズムとは

int main() {
  Base *b = new   Derived();
  delete b;
  return 0;
}

のように、Base型のポインタの変数にDerived型のポインタを割り当てることである。このようなポリモーフィズムの使い方をされる場合は、基底クラスのデストラクタにvirtualをつけないといけない。

virtualをつけた場合

class Base {
public:
  Base() = default;
  virtual ~Base() = default;
};

class Derived :    public Base {
public:
  Derived() { std::cout<<"Derived constructor called" << std::endl;}
  ~Derived() { std::cout<<"Derived destructor called" << std::endl;}
};

出力は

Derived constructor called
Derived destructor called

virtualをつけなかった場合

class Base {
public:
  Base() = default;
  ~Base() = default;                                                                                                       
};

class Derived :    public Base {
public:
  Derived() { std::cout<<"Derived constructor called" << std::endl;}
  ~Derived() { std::cout<<"Derived destructor called" << std::endl;}
};

出力は

Derived constructor called

となりデストラクタが呼ばれなくなってしまう。

無理やりキャスト

普通は違うクラスへのポインタを変換できない

Class1* p1 = new Class1();
Class2 * p2 =   p1;

以下のようなエラーが出る。

error: cannot initialize a variable of type 'Class2 *' with an lvalue of type 'Class1 *'

reinterpret_castを使うと無理やりキャストすることができる。

Class1* p1 = new Class1();
Class2* p2 = reinterpret_cast<Class2*>(p1);

矢印を使った関数の定義のしかた

void b(int v) {
  (void)v;
}

と全く同じ関数定義を

auto b(int v) -> void {
  (void)v;
}

のような書き方で記述することができる。これが使われるケースは、引数の型によって返り値の型を変更したい時。

例えば

#include <iostream>

template <typename T>
auto func(T v) -> decltype(v) {
  return v;
}

int main() {
  std::cout << func(3) << std::endl;
  std::cout << func(4.2) << std::endl;
  return 0;
}

のようなことができる。 通常のように

decltype(v) func(T v) {
  ...
}

と記載してしまうとvの定義の前にdecltype(v)が呼ばれてしまいエラーとなってしまう。

参考

c++ - Arrow operator (->) in function heading - Stack Overflow

条件に応じた型設定

条件に応じて変数の型を変えたい場合はstd::conditionalを使えば良い。

#include <iostream>

int main() {
  std::conditional<true, int, double>::type a = 4.2;
  std::conditional<false, int, double>::type b = 4.2;
  std::cout << "a=" << a << std::endl;
  std::cout << "b=" << b << std::endl;
  return 0;
}

とすると、aint型、bdouble型として定義される。

出力は

a=4
b=4.2

具体的な条件を設定してみると

#include <iostream>

// 引数がint型ならint型の値を出力し、それ以外ならdouble型の値を出力する。
template <typename T>
void func(const T v) {
  typename std::conditional<std::is_same<T, int>::value, int, double>::type c = 4.2;
  std::cout << c << std::endl;
}

int main() {
  func(3);
  func(2.4);
  return 0;
}

ポイントとしては、std::conditionalの前にtypenameをつけないとエラーになる。

出力は

4
4.2

となる。

条件に応じてテンプレート関数を使い分ける。

decltypeを使ったやり方

#include <iostream>
#include <vector>

// 引数がbegin()を持つデータ構造の時だけ定義される関数                                                                       
template <typename T>
auto func(const T v) -> decltype(v.begin(), void()){
  std::cout << "begin() defined" << std::endl;
}

// 引数が+1の演算が定義できるデータ構造の時だけ定義される関数                                                                
template <typename T>
auto func(const T v) -> decltype(v + 1, void()){
  std::cout << "+1 defined" << std::endl;
}

// こういう関数を定義してもエラーにならない                                                                         
template <typename T>
auto func(const T v) -> decltype(v.bbbegin(), void()){
  std::cout << "+1 defined" << std::endl;
}

int main() {
  func(3);
  std::vector<int > a{3, 4, 5};
  func(a);
  //func({3, 4, 5}); // これはエラー                                                                                   
  return 0;
}

ポイントはdecltype(v + 1, void())の部分で、,演算子としてみなされ、,の左側の返り値は無視されて,の右側の返り値が代入される。またvoidなどの型に()をつける必要があるようだ。すなわち、decltype(v + 1, void())の部分は 「v+1が定義されているのであればvoid型」となる。

出力は

+1 defined
begin() defined

enable_ifを使ったやり方

decltypeを使うと少し冗長な記述になるので、enable_ifを使った記述方法についてまとめた。

#include <iostream>
#include <vector>
#include <type_traits>

// fundamental型の引数のときのみ使えるテンプレート関数                                                           
template <typename T>
typename std::enable_if<std::is_fundamental<T>::value, void>::type func(const T v) {
  std::cout << "fundamental" << std::endl;
}

int main() {
  func(3);
  // func("hoge"); // 未定義エラー                                                                                           
  return 0;
}

ここで、C++14以降であればenable_if_tを使うことができる。これはcppreference.comを読むと

template< bool B, class T = void >
using enable_if_t = typename enable_if<B,T>::type;

とあり、defaultでvoidの型が選択されるようだ。

// C++14以降の別の書き方                                                                                             
template <typename T>
typename std::enable_if_t<std::is_fundamental<T>::value> func(const T v) {
  std::cout << "fundamental" << std::endl;
}

また、fundamental型以外の型が引数として与えられた時に呼ばれる関数を定義したい時はfunc(...)とすれば良い。すなわち

#include <iostream>
#include <vector>
#include <type_traits>

// fundamental型の引数のときのみ使えるテンプレート関数                                                           
template <typename T>
typename std::enable_if<std::is_fundamental<T>::value, void>::type func(const T v) {
  std::cout << "fundamental" << std::endl;
}

void func(...) {
  std::cout << "others"    << std::endl;
}

int main() {
  func(3);
  func("hoge");
  return 0;
}

とすると

fundamental
others

と出力される。

インライン

関数の呼び出しにはコストがかかるので、何度も呼ばれる短い関数はインラインにすると定義をそのまま呼び出し元にコピーするので高速化できることがある。
インラインの仕方は、cppとhppのように定義と実装を分けず、hppに定義実装をしてinlineをつけること。ただしこのように定義してもコンパイラがインラインにすることは保証されない。

As-if ルール

プログラムの観測結果を変えない限りはどんなコード変換も許されるというルール。 copy/move elisionのときだけ例外がある。

The as-if rule - cppreference.com

VIO, VSLAM, SfM

VO, VSLAM, SfMの違い

SfM

  • 時間的に連続していない画像セットからカメラposeと対象物の3D形状を推定。
  • オフラインで動く

VO

  • 時間的に連続した画像からlocally consistentなトラジェクトリを生成。
  • 時間的に連続した画像には同じ静止物が継続して写っていることを想定。
  • オンラインで動く

VSLAM

  • 時間的に連続した画像からglobally consistentなトラジェクトリを生成。
  • オンラインで動く
  • 特徴点座標をずっとメモリに保存しておき、loop closureやグラフ最適化を行う。

VOとVSLAMを比較すると、VSLAMはglobally consistentな特徴点が得られるがその分計算量は多い。

VOの詳細

VOの目的は2つの連続する時刻 t=t^{k} t=t^{k+1}の間でのカメラ姿勢の変換 T_{k}^{k+1}を求めることである。求め方は主に2つある。

Feature-based method

 t=t^{k} t=t^{k+1}の画像で観測された同じ点 \vec{u}^{i} \vec{u}^{i+1}を比較し、その辻褄が合うように T_{k}^{k+1}を求める方法である。このとき、特徴点の3次元座標 \vec{p}^{i}は既知であるとする。projection関数を \piとすると、

\begin{eqnarray}
T_{k}^{k+1} = \mathrm{argmin}_{T} \sum_{i}|| \vec{u}^{i+1} - \pi(T\vec{p}^{i}) || ^{2}
\end{eqnarray}

ただし深さ方向の自由度は決定できないので別手法で測定する必要がある。これはどういうことかというと、 t=T^{k}の観測された2点を使ってmatchingする場合、Aにおいて[tex: P{A1}]と[tex: P{A2}]の2点があったというケースと、Bにおいて[tex: P{B1}]と[tex: P{B2}]の2点があったというケースでどちらも画像における特徴点位置は同じであり、両者が区別できないからである。

f:id:salpik:20211018234841p:plain
深さ方向の自由度が決定できない理由

メリット

  • 2つの画像間のTransformが大きくても同じ点が観測される限り使える

Direct method

 Iピクセル強度(0から255までの値)として、

\begin{eqnarray}
T_{k}^{k+1} &=& \mathrm{argmin}_{T} \sum_{i}|| I^{k+1}(\vec{u}^{i'}) - I^{k}(\vec{u}^{i}) || ^{2} \\
&=& \mathrm{argmin}_{T} \sum_{i}|| I^{k+1}(\pi(T\vec{p}^{i})) - I^{k}(\vec{u}^{i}) || ^{2} \\
&=& \mathrm{argmin}_{T} \sum_{i}|| I^{k+1}(\pi(T \pi^{-1}(\vec{u}^{i})d)) - I^{k}(\vec{u}^{i}) || ^{2} \\
\end{eqnarray}

ここでは物体までの距離 dは別のセンサーなどで観測済みであるとした。

メリット

  • フレームレートが大きいと前フレームからのTransform変位があまりないので前回値がほぼそのまま解として使える。

Matchingによるpose estimation

2D to 2D

最初の段階ではまだ特徴点の3次元座標が決まっていないので、異なるタイムスタンプの画像平面上で特徴点座標を比較する。

3D to 2D

ある程度時間が経って特徴点の3次元座標が決まってくると、その3次元座標と画像上の特徴点位置を比較する。

3D to 3D

ステレオカメラなどで三角測量により1タイムスタンプから3次元位置がわかるときは、異なるタイムスタンプ同士の3次元座標同士を比較する。

Adjustment

Pose adjustment

異なるフレーム間の相対Transformを拘束条件として全体のtrajectoryを調整すること

Bundle adjustment

異なるフレーム間の相対Transformだけでなく、feature pointsの3次元位置も合わせて調整(最適化)すること。Bundle adjustmentの方が精度が高いが計算コストが高い。自由度が多いので最適化に使う初期解の精度が重要。

Bundle adjustmentの定式化

問題設定

2つの点を3つの異なる位置から撮影した画像があるとし、その3次元点座標とカメラ位置姿勢を推定する問題とする。 world座標系に \vec{p}_{A}^{w}, \vec{p}_{B}^{w}があり、3つの位置 a, b, cから観測した画像位置をそれぞれ \vec{u}_{A}^{a}, \vec{u}_{B}^{a} \vec{u}_{A}^{b}, \vec{u}_{B}^{b} \vec{u}_{A}^{c}, \vec{u}_{B}^{c}とする。\ このとき \vec{p}_{A}^{w}, \vec{p}_{B}^{w}およびカメラ位置姿勢 T_{a}^{w}, T_{b}^{w}, T_{c}^{w}を求めたい。

f:id:salpik:20211107171325p:plain
問題設定

コスト関数

画像平面上で実際に観測されたピクセル座標と、3次元座標の位置をprojectionしたピクセル座標の距離をコスト関数 Cとしこれを最小化する。例えば特徴点 Aの位置 aにおけるコスト関数は

\begin{eqnarray}
C_{A}^{a} \equiv || \vec{e}_{A}^{a} ||^{2} \equiv || \vec{u}_{A}^{a} - \pi (T_{w}^{a} \vec{p}_{A}^{w}) ||^{2}
\end{eqnarray}

と表される。ここで

\begin{eqnarray}
\pi(x, y, z) \equiv \frac{1}{z} \left( \begin{array}{ccc} f_{x} & 0 & c_{x} \\ 0 & f_{y} & c_{y} \end{array}\right) \left( \begin{array}{c} x \\ y \\ z  \end{array}\right) 
\end{eqnarray}

はカメラ座標の点をピンホールカメラを使って画像上の点に射影する関数である。\ 全体としてのコスト関数は

\begin{eqnarray}
C &\equiv& \sum_{i=a, b, c. I=A, B} C_{I}^{i} \\
&=& (\vec{e}_{A}^{a \mathrm{T}}, \vec{e}_{B}^{a \mathrm{T}}, \vec{e}_{A}^{b \mathrm{T}}, \vec{e}_{B}^{b \mathrm{T}}, \vec{e}_{A}^{c \mathrm{T}}, \vec{e}_{B}^{c \mathrm{T}}) \left( \begin{array}{c}  \vec{e}_{A}^{a} \\  \vec{e}_{B}^{a} \\ \vec{e}_{A}^{b} \\ \vec{e}_{B}^{b} \\ \vec{e}_{A}^{c} \\ \vec{e}_{B}^{c} \end{array}\right)
\end{eqnarray}

一般的に誤差の重みが異なる場合も考慮して

\begin{eqnarray}
C &=& (\vec{e}_{A}^{a \mathrm{T}}, \vec{e}_{B}^{a \mathrm{T}}, \vec{e}_{A}^{b \mathrm{T}}, \vec{e}_{B}^{b \mathrm{T}}, \vec{e}_{A}^{c \mathrm{T}}, \vec{e}_{B}^{c \mathrm{T}}) \left( \begin{array}{ccc} \lambda_{1}^{2} & 0 & \dots \\ 0 & \lambda_{2}^{2} & 0 \\ \vdots & &  \end{array}\right)  \left( \begin{array}{c}  \vec{e}_{A}^{a} \\  \vec{e}_{B}^{a} \\ \vec{e}_{A}^{b} \\ \vec{e}_{B}^{b} \\ \vec{e}_{A}^{c} \\ \vec{e}_{B}^{c} \end{array} \right) \\
&\equiv & \vec{e}^{\mathrm{T}} H^{2} \vec{e}
\end{eqnarray}

ガウスニュートンによる最適化

最適化変数は A, Bの座標と a, b, cのカメラ位置姿勢であるが、これをまとめて Xと記述することにする。 Xの現在の値を X_{0}としこの値を反復的に更新していきたいので、その更新量 \Delta Xを求めたい。 \vec{e} X_{0}まわりでテーラー展開すると

\begin{eqnarray}
\vec{e} = \vec{e}(X_{0}) + \frac{\partial \vec{e}}{\partial X} (X-X_{0})
\end{eqnarray}

これをコスト関数に代入して

\begin{eqnarray}
C &=& \left[ \vec{e}(X_{0}) + \frac{\partial \vec{e}}{\partial X} (X-X_{0}) \right] ^\mathrm{T} H^{2}  \left[ \vec{e}(X_{0}) + \frac{\partial \vec{e}}{\partial X} (X-X_{0})\right] \\
&=& \vec{e}(X_{0})^\mathrm{T} H^{2} \vec{e}(X_{0}) + 2\vec{e}(X_{0})^\mathrm{T}H^{2} \frac{\partial \vec{e}}{\partial X} (X-X_{0}) + (X-X_{0})^\mathrm{T} \frac{\partial \vec{e}}{\partial X}^\mathrm{T} H^{2} \frac{\partial \vec{e}}{\partial X} (X-X_{0}) \\
\end{eqnarray}

 C Xに対して極値となっている必要があるから、

\begin{eqnarray}
\vec{0}^{\mathrm{T}} &=& \frac{\partial C}{\partial X} \\
&=& 2\vec{e}(X_{0})^\mathrm{T}H^{2} \frac{\partial \vec{e}}{\partial X}  + 2(X-X_{0})^\mathrm{T} \frac{\partial \vec{e}}{\partial X}^\mathrm{T} H^{2} \frac{\partial \vec{e}}{\partial X} \\
\vec{0} &=& \frac{\partial \vec{e}}{\partial X}^\mathrm{T}H^{2} \vec{e}(X_{0}) + \frac{\partial \vec{e}}{\partial X}^\mathrm{T}H^{2} \frac{\partial \vec{e}}{\partial X} (X-X_{0}) \\
(X-X_{0}) &=& -\left[ \frac{\partial \vec{e}}{\partial X}^\mathrm{T}H^{2} \frac{\partial \vec{e}}{\partial X} \right]^{-1} \frac{\partial \vec{e}}{\partial X}^\mathrm{T}H^{2} \vec{e}(X_{0}) \\

X &=& X_{0} - \left[ \frac{\partial \vec{e}}{\partial X}^\mathrm{T}H^{2} \frac{\partial \vec{e}}{\partial X} \right]^{-1} \frac{\partial \vec{e}}{\partial X}^\mathrm{T}H^{2} \vec{e}(X_{0}) \\
&=& X_{0} - (J^{\mathrm{T}} J)^{-1} J^{\mathrm{T}} H \vec{e}(X_{0}) \ \ \ \ \ \ (J \equiv H\frac{\partial \vec{e}}{\partial X})
\end{eqnarray}

となり、このように Xを更新していけば良いことがわかった。

微分のチェーンルール定義

 \vec{a}^{\mathrm{T}}=(a_{1}, a_{2}, a_{3}) \vec{b}^{\mathrm{T}}=(b_{1}, b_{2})に対して

\begin{eqnarray}
\frac{\partial \vec{a}}{\partial \vec{b}} \equiv \left( \begin{array}{cc} \frac{\partial a_{1}}{\partial b_{1}} & \frac{\partial a_{1}}{\partial b_{2}} \\ \frac{\partial a_{2}}{\partial b_{1}} & \frac{\partial a_{2}}{\partial b_{2}} \\ \frac{\partial a_{3}}{\partial b_{1}} & \frac{\partial a_{3}}{\partial b_{2}} \end{array}\right)
\end{eqnarray}

と定義する。こうすると

\begin{eqnarray}
\frac{\partial \vec{a}}{\partial \vec{b}} \frac{\partial \vec{b}}{\partial \vec{c}} &=& \left( \begin{array}{cc} \frac{\partial a_{1}}{\partial b_{1}} & \frac{\partial a_{1}}{\partial b_{2}} \\ \frac{\partial a_{2}}{\partial b_{1}} & \frac{\partial a_{2}}{\partial b_{2}} \\ \frac{\partial a_{3}}{\partial b_{1}} & \frac{\partial a_{3}}{\partial b_{2}} \end{array}\right) \left( \begin{array}{ccc} \frac{\partial b_{1}}{\partial c_{1}} & \frac{\partial b_{1}}{\partial c_{2}} & \frac{\partial b_{1}}{\partial c_{3}} \\ \frac{\partial b_{2}}{\partial c_{1}} & \frac{\partial b_{2}}{\partial c_{2}} & \frac{\partial b_{2}}{\partial c_{3}}
 \end{array}\right) \\
&=& \left( \begin{array}{ccc}
\frac{\partial a_{1}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{1}} + \frac{\partial a_{1}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{1}} & \frac{\partial a_{1}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{2}} + \frac{\partial a_{1}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{2}} & \frac{\partial a_{1}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{3}} + \frac{\partial a_{1}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{3}} \\
\frac{\partial a_{2}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{1}} + \frac{\partial a_{2}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{1}} & \frac{\partial a_{2}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{2}} + \frac{\partial a_{2}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{2}} & \frac{\partial a_{2}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{3}} + \frac{\partial a_{2}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{3}} \\
\frac{\partial a_{3}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{1}} + \frac{\partial a_{3}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{1}} & \frac{\partial a_{3}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{2}} + \frac{\partial a_{3}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{2}} & \frac{\partial a_{3}}{\partial b_{1}}\frac{\partial b_{1}}{\partial c_{3}} + \frac{\partial a_{3}}{\partial b_{2}}\frac{\partial b_{2}}{\partial c_{3}} \\
 \end{array}\right) \\
&=& \left( \begin{array}{ccc} \frac{\partial a_{1}}{\partial c_{1}} & \frac{\partial a_{1}}{\partial c_{2}} & \frac{\partial a_{1}}{\partial c_{3}} \\ \frac{\partial a_{2}}{\partial c_{1}} & \frac{\partial a_{2}}{\partial c_{2}} & \frac{\partial a_{2}}{\partial c_{3}} \\  \frac{\partial a_{3}}{\partial c_{1}} & \frac{\partial a_{3}}{\partial c_{2}} & \frac{\partial a_{3}}{\partial c_{3}}  \end{array}\right) \\
&=& \frac{\partial \vec{a}}{\partial \vec{c}}
\end{eqnarray}

となり、チェーンルールが使えることがわかる。

 Jの求め方

ここからはどのように Jを求めていけば良いかを考えていく。まず、

\begin{eqnarray}
\vec{e} &=& \vec{u} - \vec{\hat{u}} \\
&=& \vec{u} - K\vec{\hat{p}} \ \ \ \ \ (\vec{\hat{u}} \equiv K\vec{\hat{p}}) \\
&=& \vec{u} - K\frac{\vec{p}}{z} \ \ \ \ \ (\vec{\hat{p}} \equiv \frac{\vec{p}}{z}) \\
&=& \vec{u} - K\frac{R_{w}^{a}\vec{p}_{A}^{w}+\vec{t}_{w}^{a}}{z} \ \ \ \ \ (\vec{p} \equiv R_{w}^{a}\vec{p}_{a}^{w} + \vec{t}_{w}^{a}) \\
\end{eqnarray}

となる。ここで、関係式を整理しておくと、

\begin{eqnarray}
\vec{\hat{u}} &=& K\vec{\hat{p}} \\
&=& \left( \begin{array}{ccc} f_{x} & 0 & c_{x} \\ 0 & f_{y} & c_{y} \end{array}\right) \\
&=&  \left( \begin{array}{c} f_{x}\hat{x} + c_{x} \\ f_{y}\hat{y} + c_{y} \end{array}\right) \\
\vec{\hat{p}} &=& \left( \begin{array}{c} \frac{x}{z} \\ \frac{y}{z} \end{array}\right)
\end{eqnarray}

これを用いると、特徴点座標に関するJは

\begin{eqnarray}
\frac{\partial \vec{e}}{\partial \vec{p}_{A}^{w}} &=& \frac{\partial \vec{e}}{\partial \vec{\hat{u}}} \frac{\partial \vec{\hat{u}}}{\partial \vec{\hat{p}}} \frac{\partial \vec{\hat{p}}}{\partial \vec{p}} \frac{\partial \vec{p}}{\partial \vec{p}_{A}^{w}} \\
&=& \left( \begin{array}{cc} -1 & 0 \\ 0 & -1 \end{array}\right) \left( \begin{array}{cc} f_{x} & 0 \\ 0 & f_{y} \end{array}\right) \left( \begin{array}{ccc} \frac{1}{z} & 0 & -\frac{x}{z^{2}} \\ 0 & \frac{1}{z} & -\frac{y}{z^{2}} \end{array}\right) R_{w}^{a}
\end{eqnarray}

であり、カメラの並進部分に関するJは

\begin{eqnarray}
\frac{\partial \vec{e}}{\partial \vec{t}_{w}^{a}} &=& \frac{\partial \vec{e}}{\partial \vec{\hat{u}}} \frac{\partial \vec{\hat{u}}}{\partial \vec{\hat{p}}} \frac{\partial \vec{\hat{p}}}{\partial \vec{p}} \frac{\partial \vec{p}}{\partial \vec{t}_{w}^{a}} \\
&=& \left( \begin{array}{cc} -1 & 0 \\ 0 & -1 \end{array}\right) \left( \begin{array}{cc} f_{x} & 0 \\ 0 & f_{y} \end{array}\right) \left( \begin{array}{ccc} \frac{1}{z} & 0 & -\frac{x}{z^{2}} \\ 0 & \frac{1}{z} & -\frac{y}{z^{2}} \end{array}\right) I
\end{eqnarray}

であり、カメラの回転部分に関するJは

\begin{eqnarray}
\frac{\partial \vec{e}}{\partial R_{w}^{a}} &=& \frac{\partial \vec{e}}{\partial \vec{\hat{u}}} \frac{\partial \vec{\hat{u}}}{\partial \vec{\hat{p}}} \frac{\partial \vec{\hat{p}}}{\partial \vec{p}} \frac{\partial \vec{p}}{\partial R_{w}^{a}} \\
&=& \left( \begin{array}{cc} -1 & 0 \\ 0 & -1 \end{array}\right) \left( \begin{array}{cc} f_{x} & 0 \\ 0 & f_{y} \end{array}\right) \left( \begin{array}{ccc} \frac{1}{z} & 0 & -\frac{x}{z^{2}} \\ 0 & \frac{1}{z} & -\frac{y}{z^{2}} \end{array}\right) \frac{\partial \vec{p}}{\partial R_{w}^{a}}
\end{eqnarray}

となり、最後の項をどう計算すればよいかがわからない。ここで、 R_{w}^{a}が直行行列であることから、 R_{w}^{a}をあるパラメータ tによって連続的に変化する行列であるとすると、

\begin{eqnarray}
R_{w}^{a}(t) (R_{w}^{a}(t))^\mathrm{T} &=& I \\
\dot{R}_{w}^{a}(t) (R_{w}^{a}(t))^\mathrm{T} + R_{w}^{a}(t) (\dot{R}_{w}^{a}(t))^\mathrm{T} &=& 0 \\
\dot{R}_{w}^{a}(t) (R_{w}^{a}(t))^\mathrm{T} &=& -R_{w}^{a}(t) (\dot{R}_{w}^{a}(t))^\mathrm{T} \\
&=& - \left[ \dot{R}_{w}^{a}(t) R_{w}^{a}(t) \right]^\mathrm{T} \\
\end{eqnarray}

となるので、 \dot{R}_{w}^{a}(t) (R_{w}^{a}(t))^\mathrm{T} は交代行列であることがわかる。したがって

\begin{eqnarray}
\lfloor \vec{\theta} \rfloor \equiv  \left( \begin{array}{ccc} 0 & -\theta_{3} & \theta_{2} \\ \theta_{3} & 0 & -\theta_{1} \\ -\theta_{2} & \theta_{1} & 0 \end{array}\right)
\end{eqnarray}

と定義される \lfloor \vec{\theta} \rfloorを用いて

\begin{eqnarray}
\dot{R}_{w}^{a}(t) (R_{w}^{a}(t))^\mathrm{T} &=& \lfloor \vec{\theta} \rfloor \\
\dot{R}_{w}^{a}(t) &=& \lfloor \vec{\theta} \rfloor R_{w}^{a}(t) \\
R_{w}^{a}(t) &=& R_{w}^{a}(0) \exp(\lfloor \vec{\theta} \rfloor) \\
&\sim& R_{w}^{a}(0)(I + \lfloor \vec{\theta} \rfloor)
\end{eqnarray}

これを用いて、先ほどの Jの計算において R_{w}^{a}ではなく \vec{\theta}_{w}^{a}微分することを考えると、

\begin{eqnarray}
\frac{\partial \vec{p}}{\partial \vec{\theta}_{w}^{a}} &=& \frac{R_{w}^{a}(I + \lfloor \Delta \vec{\theta}_{w}^{a} \rfloor)\vec{p}_{a}^{w} - R_{w}^{a}\vec{p}_{a}^{w}}{\Delta \vec{\theta}_{w}^{a}} \\
&=& \frac{R_{w}^{a}  \lfloor \Delta \vec{\theta}_{w}^{a} \rfloor \vec{p}_{a}^{w}}{\Delta \vec{\theta}_{w}^{a}} \\
&=& - \frac{R_{w}^{a}  \lfloor \vec{p}_{a}^{w} \rfloor \Delta \vec{\theta}_{w}^{a}}{\Delta \vec{\theta}_{w}^{a}} \ \ \ \ \ \ (「公式まとめ」のブログを参照) \\ 
&=& - R_{w}^{a} \lfloor \vec{p}_{a}^{w} \rfloor
\end{eqnarray}

微分が計算できることがわかった。

Reference

https://www.rsj.or.jp/databox/international/iros16tutorial_2.pdf

競プロ典型049

問題

atcoder.jp

考察過程

  1. この問題は \mathbb{R}^{N}のベクトル空間を張れるようなベクトルを基底を選ぶことに相当している。
  2. 一般に \mathbb{R}^{N}のベクトルを表現するには N個の成分で表現する必要があるが、今回の問題では少し考え方を変えて、隣同士の値に変化があるかという情報をノードにして0/1で表現することにする。すると、各商品についてLとRの部分だけがビットの変わり目となるのでそこのノードのみを1とするような状態として保持しておけば情報として十分である。なお便宜上ベクトル空間の両端に0の成分があることにする。
    f:id:salpik:20211002183250p:plain
    ある商品のbit情報の表現
  3. そして 2^{N}の全てのビットを表現するには、 N+1このノードを全て自由に決定できることが必要十分条件となる。ただし両端のビットは0なので、 N+1このノードの1の数は偶数でないといけない制約条件があり、自由度が1下がり実質の自由度は Nとなる。
    f:id:salpik:20211002183323p:plain
     2^{N}のうちのあるbitの表現
  4. それぞれの商品について、1となっている2つのノード間に辺を張ることにする。
  5. 互いに行き来できるノードを同じグループと呼ぶことにすると、同じグループにあるノードは商品を選ぶ操作をしても1の数は偶数を保ったままである。
  6. したがって、もし別のグループに存在しているノードAとBがあった場合、ノードAとノードBのみを1にするようなビットは実現できないことがわかる。
  7. この考察から、全てのノードが同じグループでないといけない。
  8. 逆に全てのノードがつながっている時、最低 N本の辺でつながっているので、自由度 Nで0/1を表現でき、全てのbitの組み合わせを表現できることになる。
  9. 以上の考察からノードが全てつながっていれば 2^{N}の全てのビットを表現できると言えるので、最小全域木を求める問題に帰着したことがわかる。

コンストラクタ周りの挙動確認

背景

コンストラクタ周りの挙動で不明点があったので改めて動きを確認した

出力結果

単なるクラスの定義

{
  std::cout << "========= Start =========" << std::endl;
  MyClass m;
  std::cout << "========= End =========" << std::endl;
}

コードブロックを出るまでオブジェクトが維持される。

========= Start =========
Constructor
========= End =========
Destructor

クラスを定義するが変数をつけず右辺値として定義

{
  std::cout << "========= Start =========" << std::endl;
  MyClass();
  std::cout << "========= End =========" << std::endl;
}

クラスはすぐに破棄される

========= Start =========
Constructor
Destructor
========= End =========

右辺値参照でクラスに変数をつける

{
  std::cout << "========= Start =========" << std::endl;
  MyClass && m = MyClass();
  std::cout << "========= End =========" << std::endl;
}

コードブロックが終わるまで生き残る

========= Start =========
Constructor
========= End =========
Destructor

コピー的な定義(昔よくやっていた方法)

{
  std::cout << "========= Start =========" << std::endl;
  MyClass m = MyClass();
  std::cout << "========= End =========" << std::endl;
}

2回コンストラクタが呼ばれると思いきや1回しか呼ばれない

========= Start =========
Constructor
========= End =========
Destructor

2つめのオブジェクトをコピーとして定義

{
  std::cout << "========= Start =========" << std::endl;
  MyClass m1;
  MyClass m2 = m1;
  std::cout << "========= End =========" << std::endl;
}

コピーコンストラクタが呼ばれる。

========= Start =========
Constructor
Copy constructor
========= End =========
Destructor
Destructor

2つめのオブジェクトを代入として定義

{
  std::cout << "========= Start =========" << std::endl;
  MyClass m1;
  MyClass m2;
  m2 = m1;
  std::cout << "========= End =========" << std::endl;
}

コピーアサイメントが呼ばれる。

========= Start =========
Constructor
Constructor
Copy assignment
========= End =========
Destructor
Destructor

ムーブを使う

{
  std::cout << "========= Start =========" << std::endl;
  MyClass m1;
  MyClass m2 = std::move(m1);
  // ここ以降m1の状態がどうなっているかは不定なのでm1を使ってはいけない
  std::cout << "========= End =========" << std::endl;
}

ムーブコンストラクタが呼ばれる

========= Start =========
Constructor
Move constructor
========= End =========
Destructor
Destructor

参考

qiita.com

vectorに詰めるときの動きの確認

オブジェクトの定義の仕方

A: 一度変数に詰めてから渡す

MyClass myclass;
func(myclass);

B: 一次オブジェクトとして渡す

func(MyClass());

関数の受け取り方

普通にvalueとして受け取る

void push_back_no_and(const MyClass a) {
  std::vector<MyClass> vec;
  vec.push_back(a);
}

&を1つつけて受け取る

void push_back_single_and(const MyClass & a) {
  std::vector<MyClass> vec;
  vec.push_back(a);
}

&を2つつけて右辺値として受け取る

引数の部分にconstをつけられないことに注意。

void push_back_double_and(MyClass && a) { // constつけちゃダメ                                                               
  std::vector<MyClass> vec;
  vec.push_back(std::move(a));
}

結果

受け取り方. 一度変数に詰めてから渡す 一次オブジェクトとして渡す
&なし Const, Copy×2(関数に渡すときとvectorに詰めるときで2回コピーされる) Const, Copy (一時オブジェクトを値渡しするときはコピーされない?)
&1つ Const, Copy (vectorに詰めるときのコピーのみ) Const, Copy (vectorに詰めるときのコピーのみ)
& 2つ Const, Move (※) (ムーブなので効率が良い) Const, Move (ムーブなので効率が良い)

(※)

MyClass myclass;
push_back_double_and(myclass);

だとエラーになるので

MyClass myclass;
push_back_double_and(std::move(myclass));

としている。

テンプレートタイプをvectorに詰めるとき

template <typename T>
void func(T value) {
  vec.push_back(static_cast<T>(val));
}

とすると、型がrvalueだったときはstd::moveしたのと近い振る舞いとなりムーブセマンティクスが使える。

ソースコード全文

ソースコード展開

#include <iostream>
#include <vector>
#include <array>

class MyClass {
private:
  std::array<int, 10000> data;
public:
  MyClass() {
    std::cout << "Constructor" << std::endl;
  }
  ~MyClass() {
    std::cout << "Destructor" << std::endl;
  }
  MyClass(const MyClass & a) {
    //    this->data = a.data;                                                                                               
    std::cout << "Copy constructor" << std::endl;
  }
  MyClass(MyClass && a) {
    //this->data = std::move(a.data);                                                                                        
    std::cout << "Move constructor" << std::endl;
  }
  MyClass & operator=(const MyClass & a) {
    std::cout << "Copy assignment" << std::endl;
    return *this;
  }
  MyClass & operator=(MyClass && a) {
    std::cout << "Move assignment" << std::endl;
    return *this;
  }
  void PrintSize() {
    std::cout << data.size() << std::endl;
  }
};
void push_back_no_and(const MyClass a) {
  std::vector<MyClass> vec;
  vec.push_back(a);
}
void push_back_single_and(const MyClass & a) {
  std::vector<MyClass> vec;
  vec.push_back(a);
}
void push_back_double_and(MyClass && a) { // constつけちゃダメ                                                               
  std::vector<MyClass> vec;
  vec.push_back(std::move(a));
}

int main() {
  // Constructor / Destructor timing check                                                                                   
  {
    std::cout << "========= Start =========" << std::endl;
    MyClass m;
    std::cout << "========= End =========" << std::endl;
  }

  {
    std::cout << "========= Start =========" << std::endl;
    MyClass();
    std::cout << "========= End =========" << std::endl;
  }
  {
    std::cout << "========= Start =========" << std::endl;
    MyClass && m = MyClass();
    std::cout << "========= End =========" << std::endl;
  }

  {
    std::cout << "========= Start =========" << std::endl;
    MyClass m = MyClass();
    std::cout << "========= End =========" << std::endl;
  }

  {
    std::cout << "========= Start =========" << std::endl;
    MyClass m1;
    MyClass m2 = m1;
    std::cout << "========= End =========" << std::endl;
  }

  {
    std::cout << "========= Start =========" << std::endl;
    MyClass m1;
    MyClass m2;
    m2 = m1;
    std::cout << "========= End =========" << std::endl;
  }

  {
    std::cout << "========= Start =========" << std::endl;
    MyClass m1;
    MyClass m2 = std::move(m1);
    // ここ以降m1の状態がどうなっているかは不定なのでm1を使ってはいけない                                                    
    std::cout << "========= End =========" << std::endl;
  }

  {
    std::cout << "1-A" << std::endl;
    MyClass myclass;
    push_back_no_and(myclass);
    std::cout << "1-B" << std::endl;
    push_back_no_and(MyClass());
  }

  {
    std::cout << "2-A" << std::endl;
    MyClass myclass;
    push_back_single_and(myclass);
    std::cout << "2-B" << std::endl;
    push_back_single_and(MyClass());
  }

  {
    std::cout << "3-A" << std::endl;
    MyClass myclass;
    //push_back_double_and(myclass); // error                                                                                
    push_back_double_and(std::move(myclass));
    std::cout << "3-B" << std::endl;
    push_back_double_and(MyClass());
  }


  return 0;
}

参考

c++ - is there any difference between static cast to rvalue reference and std::move - Stack Overflow

JenkinsジョブをAWS EC2で実行する

背景

Pythonを含むジョブをAWS EC2インスタンスで実行したくなった。ジョブは頻繁に実行されるわけではないのでEC2インスタンスは常時起動しておく必要はなく、ジョブのたびにEC2インスタンスを立ち上げる仕様とする必要があった。

作業手順

EC2インスタンスの環境構築

UbuntuベースでEC2インスタンスを立ち上げた。そこに必要な環境をインストールした。以下にPython3.8を使うために行ったセットアップを記録しておく。

# Update
sudo apt update -y && sudo apt upgrade -y && sudo apt dist-upgrade -y && sudo apt autoremove -y && sudo apt autoclean -y

# Install Python and venv
sudo apt update && sudo apt-get install python3.8 python3.8-venv python3-pip
python3.8 -m venv project_name

# Install required libraries
# awscli is necessary for Ubuntu, not for Amazon Linux
sudo apt update && apt-get install awscli default-jre

# Install tools
sudo apt update && sudo apt-get emacs

# Change timezone to JST
sudo timedatectl set-timezone Asia/Tokyo

AMIを作成

EC2コンソール画面からAMIを作成する。この際EC2インスタンスは停止する必要はなく起動したままでよい。

Jenkins設定

(初めにおそらくEC2プラグインを入れる必要がある)「Jenkinsの設定」→「ノードの管理」→「Configure clouds」から新しい「Amazon EC2」設定を追加する。重要な設定項目は以下の通り

  • EC2 key Pair's Private Key: EC2にSSHするのに必要な公開鍵を指定する.
  • AMI ID: 作成したAMIのID(ami-******)を記載する.
  • Instance type: 立ち上げたいEC2のインスタンスタイプを指定する.
  • Security group: "ssh-connect"を設定する.
  • Remote FS root: Ubuntu の場合は"/home/ubuntu"、Amazon Linuxなら"/home/ec2-user".
  • Remote user: Ubuntu の場合は"ubuntu"、Amazon Linuxなら"ec2-user"
  • AMI type: "unix".
  • Labels: Jenkinsジョブ側から参照するときの識別ID.

Jenkinsジョブの作成

普段のJenkinsジョブ作成と基本的に同じだが、「General」→「実行するノードを制限」→「ラベル式」のところでは、先ほどのクラウド設定で「Labels」に書いたものと同じ文字列を記載する。

メモ

デフォルトのPyhtonバージョン

Ubuntu18.04ベースでEC2インスタンスを作成したが、デフォルトのPythonバージョンが3.6であった。Python3.8をインストールしてデフォルトバージョンにしようとしたが、以下のようなエラーが出て、作成したAMIからEC2を作成したときにステータスチェックで失敗してしまうので、venvを使いそこに環境を作ることにした。

AMI作成前に環境構築をしておく

Jenkinsジョブに依存しない設定(Pythonのインストールやタイムゾーンの設定など)はAMIを作るまえに設定しておけば、そのAMIをベースに作ったEC2インスタンスにもその設定が反映される。

Ubuntuタイムゾーン設定

デフォルトのEC2インスタンスUTCタイムゾーンになっていた。現在のタイムゾーンがどこであるかを確認するには

timedatectl

コマンドで確認できる。タイムゾーンを東京に変更するには

sudo timedatectl set-timezone Asia/Tokyo

とすれば良い。

EC2の起動時間

Jenkinsでジョブを実行してから実際にビルドが始まるまでは10-20分ほど時間がかかることがある。これはAMIからEC2インスタンスを起動するのに時間がかかっているため。

JenkinsジョブからEC2インスタンスへのSSH接続が失敗する

EC2インスタンスコネクトというのをセットアップしないとJenkinsからEC2へSSH接続できないらしい。Ubuntuの場合はドキュメントにあるようにawscliをインスタンスにインストールする必要があるらしい。Amazon Linuxの場合は不要らしく、この原因調査に時間がかかった。 参考:

docs.aws.amazon.com

Jenkisジョブに対して常にEC2を起動しておく場合

「Jenkinsの設定」→「ノードの管理」→「新規ノード作成」で「Permanent Agent」を追加すれば良いはず(未トライ)。

ラズベリーパイによる自動水やり装置

成果物

f:id:salpik:20210813001307j:plain
全体写真

youtu.be

回路図

f:id:salpik:20210813001007p:plain
全体回路図

製作メモ

  • ポンプはUSB給電の仕様だったが、ケーブルを切断してプラス側とマイナス側の2本を引き出した。
  • リレー回路は、オフ時(入力INがGNDと短絡していない時)はCOM端子はNC端子と繋がっている。オン時(入力INがGNDと短絡している時)はCOM端子はNO端子と繋がっている。オンオフを切り替えた時にスイッチが切り替わるカチッという音がする。
  • リレー回路の短絡・断線を切り替えることでポンプの電源オンオフを切り替える仕様になっている。したがってラズベリーパイであるケーブルの短絡・断線を切り替えたいが、これをトランジスタで実現した。
  • トランジスタはNPN型のトランジスタを使っており、これはベースエミッタ間にプラスの電位差があるとコレクタエミッタ間が結線される仕様。最初間違えてPNP型のトランジスタを使ってしまったが、これはベースエミッタ間にマイナスの電位差がある想定のタイプ。
  • ラズベリーパイは初期設定時はHDMIでディスプレイ端子に繋いでマウスやキーボードもつながないといけない。SSHできるようにすれば、イーサーネットケーブルをノートパソコンに繋ぐことでSSHできるのでディスプレイやマウスやキーボードを繋ぐ必要はなくなる。
ssh pi@raspberrypi.local
password: raspberry

主要備品

参考情報

リレーモジュールの回路図は以下を参考にしました。

かずいの雑記帳4 : 怪しいリレーモジュールを買ってみた