detamamoruのブログ

興味を持ったことや勉強したことに関して記事を書きます。主に低レイヤー寄りの記事を公開。

初めてのロボットアーム作りに挑戦!0: サーボモータ操作


出田守です。
アイアンマンを観て、ロボットアームが欲しくなりました。
勉強しながら、初めてのロボットアーム作りに挑戦します。

今回の目標

まずは、1自由度で指示した角度に動かせるようになることを目指します。

材料

  1. サーボモータ FS90 1個
  2. ESP32DevKitCv4 1個
  3. マイクロUSBケーブル 1本

配線画像

f:id:detamamoru:20200901094810j:plain
ESP32DevKitCv4とサーボモータFS90の配線

サーボモータFS90はJRタイプというコネクタの種類です。今回はESP32DevKitCv4から直接モータ用電源を取っていますが、おそらくこれは良くなくて本来はモータ用の別電源を設ける必要があります。

線色 役割
GND
電源
制御

PWM制御

サーボモータFS90は、PWM制御で角度調整を行います。
ESP32DevKitCからPWM制御するにはledcSetup関数でPWMの値を設定し、ledcAttachPin関数でPWM出力ピンを設定します。ledcWrite関数にduty比を入力して、PWM信号を出力します。
以下のような感じです。

...
// pin
const int pwmPin = 25;

// PWM properties
const double frequency = 226.24434389140271493212669683258;
const int pwmChannel = 0;
const int resolution = 10;

...

void setup(){
  ...  
  ledcSetup(pwmChannel, frequency, resolution); // PWM値を設定
  
  ledcAttachPin(pwmPin, pwmChannel); // PWMピンを設定

  ledcWrite(pwmChannel, dutyCycle); // PWM信号出力
}
...

サーボモータ操作

FS90データシートには、1500usで中立位置として120°レンジでは900~2100us、180°レンジでは500~2500usとなっています。
しかし、私のやり方が悪いのか、中立位置を中心に120°や180°になってくれず、10°位の誤差が出てしまいました。
なので、オシロスコープで地道に調査しました。
調査した結果、以下ぐらいがだいたい良さげな気がしたのでこれで進めていきます。ちなみに角度レンジは余裕をみて今回は5°~175°としています。

角度 duty比
460us
90° 1220us
175° 2130us

若干のズレはあるものの今回は良しとしましょう。

どうせならスマホ(Android)から操作したかったので、アプリからBluetooth経由で操作します。

出来上がったプログラムを以下に置いています。
github.com

youtu.be

Raspberry Pi B+でinsta360 airの動画をOpenCVで処理する

出田 守です。
昔購入したRaspberry Pi B+が寂しそうにこちらを見ていたので、何かに使ってあげることにしました。
まず、ラズパイ自身の視点になって今どういう気持ちなのか確かめることにしました。

事前準備

  1. 最新のRaspbianをインストール(この時はRelease date: 2020-02-13のものが最新でした)
  2. OpenCV>=3.1.0をインストール
  3. mjpeg-streamerのインストール

OpenCVはなぜバージョンが3かというとこの後、使用するmjpg-streamerというツールでinput_opencvというプラグインを使おうと思っています。このプラグインがバージョン3しか対応してなさそうだったためです。ちなみに、最初pip3を使ってopencv-pythonをインストールしたのですが、mjpg-streamerでopencvを見つけてくれなかったので、ソースからビルドしました。以下のリンクなどを参考にしてビルドしました。めちゃめちゃ時間かかりました。
mjpeg-streamerは色んな入出力用のプラグインを組み合わせてjpegフレームを処理してくれるツールです。事前準備を済ませた上で、インストールはGitHubを見れば簡単にできました。

参考:

OpenCVで処理してからストリーミング

input_opencvプラグインを使って、撮影した動画をOpenCVに入力・処理してから、output_httpプラグインを使って出力された動画をブラウザで見てみようと思います。

./mjpg_streamer -i "./input_opencv.so --filter ./cvfilter_py.so --fargs ./plugins/input_opencv/filters/cvfilter_py/example_filter.py -f 10 -r 320x240 -d /dev/video0" -o "./output_http.so -w ./www -p 8080"

input_opencvプラグインの説明ではopencv3.1.0でしかテストしていませんと書いてあります。私は試しに3.4.9をインストールして試しましたが、サンプルを動かした限り大丈夫そうです。
実行コマンドはちょっと長いですが、簡単に各オプションは以下のような意味です。

オプション 説明
-i インプットプラグインを指定
--filter input_opencvを指定した場合に、フィルターファイルがPythonの場合はcvfilter_py.soを、C++の場合はcvfilter_cpp.soを指定
--fargs フィルターファイルを指定
-f フレームレート
-r 解像度
-d カメラのデバイスファイル
-o アウトプットプラグインを指定
-w 表示用ソースが入ったフォルダを指定
-p ポート番号

実行後無事起動すれば、LAN内でブラウザから魚眼2つを見ることができます。
これだけでは、ラズパイの気持ちが分からないので、次回はスティッチングという処理をして見やすくしようと思います。

Voice changerを作りたくて(5): WORLDでボイチェ

出田 守です。
このシリーズは、ほとんど何もわからない状況からVoice changerを作る過程の記録です。

環境(私も含めて)

[私について]
算数・数学: 苦手で避けてきた(後悔中)。おそらく中学2年生で止まっている。
プログラミング: 努力中
[開発環境 - PC0]
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.39.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

WORLD

WORLDとは

WORLDは、高品質な音声分析合成システムです。音声から、基本周波数、スペクトル包絡、非周期性指標というパラメータを高精度に推定できます。また、これら3つのパラメータを変換し、再び音声として合成することも簡単にできます。
既に、C++PythonでWORLDは実装されており、簡単に使うことができます。

参考:
http://www.kisc.meiji.ac.jp/~mmorise/world/introductions.html
https://github.com/mmorise/World
https://github.com/JeremyCCHsu/Python-Wrapper-for-World-Vocoder/blob/master/pyworld/pyworld.pyx
https://qiita.com/ohtaman/items/84426cee09c2ba4abc22

Rustラッパーを作る

今回のソフトウェア版Voice changerはRustで作ることに決めていました。しかも、2019年中に!
私が調べた限りでは、WORLDのRust用ラッパーがなかったので、FFIの勉強もかねて作ってみることにしました。
そしてRustのSlackコミュニティの方々にお世話になりつつ、暗中模索の末、(一応)完成したWORLDのRustラッパーが以下です。
github.com
crates.io - Rust-WORLD

Rust-WORLDでVoice changer(Vocoder)を作る

上で作ったWORLDのRust用ラッパーで作ったVoice changerが以下になります。
github.com

動作としては、

  1. 3秒間音声を読込み
  2. 音声データに対してWORLDを使ってパラメータを抽出
  3. パラメータを変換
  4. パラメータを音声に変換

ロボット声や、女性の(ような)声、早口、モザイク声に変換したりできます。一人でやっていて笑ってしまいました。結構面白いです。
これで、一応ソフトウェア版Voice changerは完成ということにしましょう!(笑)

Voice changerを作りたくて(4): 正弦波もわからへんから

出田 守です。
このシリーズは、ほとんど何もわからない状況からVoice changerを作る過程の記録です。

環境(私も含めて)

[私について]
算数・数学: 苦手で避けてきた(後悔中)。おそらく中学2年生で止まっている。
プログラミング: 努力中
[開発環境 - PC0]
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.39.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

周期と振動数の関係

周期T

媒質を一回振動させるのにかかる時間を周期といいます。Tと表され、単位はsです。

振動数f

1秒間に媒質が振動する回数を振動数といいます。fと表され、単位はHzです。

関係

周期と振動数について、時間と回数の関係を表すと以下になります。
 \begin{align}
周期T &=> 時間:回数 &= T:1 \\
振動数f &=> 時間:回数 &= 1:f
\end{align}
一定のスピードで振動させている場合この比は変わらないので、
 \begin{align}
T:1 &= 1:f \\
fT &= 1
\end{align}

波の基本式

波長λ

波から波までの長さを波長といいます。波長はλで表されます。波から波なので波の山を目印に、次の山までを波長としてもいいですし、谷を目印にしてもいいですし、それ以外の地点でもいいです。

f:id:detamamoru:20191124160238p:plain
図1: 波長λ

波の速さv

波は、波源から離れる方向に進んでいきます。この時の速さを、波の速さといいます。波の速さはvで表されます。波の移動は、もし媒質に変化がないのであれば等速直線運動をします。
つまり、

波の速さv = \frac{距離}{時間}

波の基本式

2つの波を連続して発生させた場合、1つ目の波は波源から波一つ分進みます。つまり、T秒経つとλm進みます。
このことから、波の速さvは
 \begin{align}
波の速さv &= \frac{距離}{時間}より \\
波の速さv &= \frac{λ}{T}
\end{align}
と表すことができます。
また、振動数と周期の関係の式を利用すると、

T = \frac{1}{f}を代入して \\
波の速さv = fλ
と表すことができます。

# 正弦波
## 波は円運動を横から見たもの?
波源の振動は周期的です。これはばねにおもりを付けて振動させたときと同じ運動だそうです。これを単振動といいます。
さらに、この単振動は、円周上を一定のスピードで回る運動つまり等速円運動を真横から見たものと同じになります。
このことを利用すると波源から時刻t秒後の波の変位を求めることができます。
 \begin{align}
y &= Asinωt \\
A&: 振幅 \\
ω&: 角振動数または角速度 \\
t&: 時刻
\end{align}
物体が一秒間で円周上を回る角度を、角振動数または角速度といいます。角振動数はωで表されます。例えばt秒間で回った角度をθとすると、
 \begin{align}
距離(角度) &= 速さ×時間より \\
θ &= ωt
\end{align}
となります。これを
 \begin{align}
y &= Asinθにθ=ωtを代入すると、\\
y &= Asinωt
\end{align}
となります。
円を一周できる時間を周期Tとすると、さらに式を変形できます。
 \begin{align}
ωT &= 2π \\
ω &= \frac{2π}{T} \\
\end{align}
これをy = Asinωtに代入すると、

y = Asin2π\frac{t}{T}
と表すことができます。
この式は、x=0のとき、つまり波源での波の変位を求めることができます。
任意のx位置にある、波の変位はx=0まで時間を戻してあげることで求めることができます。
時刻tの波は距離xまで波の速さvで移動することを考えると、x=0まで戻すには、時刻tから距離xと波の速さvで求める時刻を引いてあげることで求められます。
 \begin{align}
y &= Asinω(t-\frac{x}{v}) \\
y &= Asin\frac{2π}{T}(t-\frac{x}{v}) \\
y &= Asin2π(\frac{t}{T}-\frac{x}{vT}) \\
波の基本式vT &= λより \\
y &= Asin2π(\frac{t}{T}-\frac{x}{λ})
\end{align}
これが波の関数です。

Voice changerを作りたくて(3): 三角比を忘れてもうた

出田 守です。
このシリーズは、ほとんど何もわからない状況からVoice changerを作る過程の記録です。

環境(私も含めて)

[私について]
算数・数学: 苦手で避けてきた(後悔中)。おそらく中学2年生で止まっている。
プログラミング: 努力中
[開発環境 - PC0]
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.39.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

三角比

三角比を復習しておきます。

正三角形

f:id:detamamoru:20191115110706p:plain
図0: 正三角形
これは簡単ですね。長さの比が1:1:1で角度もすべて60°です。

45°の直角三角形

f:id:detamamoru:20191115111004p:plain
図1: 45°の直角三角形
長さの比が1:1:√2で角度が90°と45°の直角三角形です。正方形の半分です。

60°と30°の直角三角形

f:id:detamamoru:20191115111129p:plain
図2: 60°と30°の直角三角形
長さの比が1:2:√3で角度が90°と60°と30°の直角三角形です。正三角形の半分です。

ピタゴラスの定理

あーピタゴラスの定理。直角三角形の3辺をそれぞれa,b,cとした場合以下の公式が成り立ちます。

f:id:detamamoru:20191115111345p:plain
図3: ピタゴラスの定理

sin, cos, tan

sin, cos, tanは三角比の一種です。また、sin, cos, tanを考えるときは必ず直角三角形で考えます。
sin, cos, tanはある角度からみた2辺の長さの比を表します。また、ある角度のことをθと表します。ある角度θからみたsinをsinθと書きます。cosθもtanθも同様です。つまりはマクロみたいに、ある2辺の比をsin, cos, tanで省略して書きますよーと宣言したんですね。
ちなみに、単にθと書くと弧度法のラジアンを表します。ラジアンの節までは度数法の°を使います。

sinθ°

sinθ°は、ある角度θ°からみた斜辺の長さと垂線の長さの比です。

f:id:detamamoru:20191115144207p:plain
図4: sinθ°
以下の問題でsinになじんでみます。
f:id:detamamoru:20191115144327p:plain
図5: sinの練習問題
角度が30°で斜辺が8、垂線がxの直角三角形です。このxを求めます。
sinθの公式から、
sin30° = \frac{x}{8}
xの式に直すと、
x = {8} \times sin30°
sin30°は三角比から1/2となります。よって、
x = {8} \times \frac{1}{2} = 4

cosθ°

cosθ°は、ある角度θ°からみた斜辺の長さと底辺の長さの比です。

f:id:detamamoru:20191115152521p:plain
図6: cosθ°
先ほどと同じ問題でcosにもなじんでみます。
f:id:detamamoru:20191115152656p:plain
図7: cosの練習問題
角度が30°で、斜辺の長さが8、底辺がxの直角三角形です。xを求めます。
cosθの公式から、
 cos30° = \frac{x}{8}
xの式に直すと、
 x = 8 \times cos30°
cos30°は三角比から \frac{\sqrt{3}}{2}となります。よって、
 x = 8 \times \frac{\sqrt{3}}{2} = 4\sqrt{3}となります。
先の節の結果と合わせて、ピタゴラスの定理で確認してみましょう。
ピタゴラスの定理c^2 = a^2 + b^2から、
 8^2 = (4\sqrt{3})^2 + 4^2
右辺と左辺をそれぞれ計算すると、
 \begin{align}
8^2 &= (4\sqrt{3})^2 + 4^2 \\
64 &= 16 \times 3 + 16 \\
 &= 48 + 16 \\
 &= 64
\end{align}
合ってそうです。

tanθ°

tanθ°は、ある角度θ°からみた底辺の長さと垂線の長さの比です。

f:id:detamamoru:20191115154239p:plain
図8: tanθ°
同じように、問題でtanになじみます。
f:id:detamamoru:20191115154404p:plain
図9: tanの練習問題
角度が30°で、垂線の長さが4、底辺の長さがxです。xを求めます。
tanθの公式より、
 tan30° = \frac{4}{x}
xの式に直すと、
 x = \frac{4}{tan30°}
tan30°は三角比から \frac{1}{\sqrt{3}}となります。よって、
 \begin{align}
x &= \frac{4}{\frac{1}{\sqrt{3}}} \\
&= 4 \times \sqrt{3} = 4\sqrt{3}
\end{align}
これは先の節で確認した通りの値となっていますね。

弧度法とラジアン

弧度法です。弧度法は、角度を円弧の長さと半径で求める方法のことです。もっというと半径の長さと弧の長さの比です。

f:id:detamamoru:20191115213035p:plain
図10: radian
弧度法で求めた角度の値をラジアンといい、単位がradと表します。単位は省略されることが多いです。
弧の長さが半径と同じになる扇形の中心角を1radとします。1radをπ倍すると180°になります。

cosθはx、sinθはy

単位円において、cosθはxに、sinθはyに対応します。

f:id:detamamoru:20191116091433p:plain
図11: cosθをx、sinθをy

Voice changerを作りたくて(2): とりあえず音声信号入力したれや

出田 守です。
このシリーズは、ほとんど何もわからない状況からVoice changerを作る過程の記録です。

環境(私も含めて)

[私について]
算数・数学: 苦手で避けてきた(後悔中)。おそらく中学2年生で止まっている。
プログラミング: 努力中
[開発環境 - PC0]
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.39.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

音声信号入力

マイク買いました

音声信号入力用のマイクを買っちゃいました。めちゃ安いやつ。
iBUFFALO マイクロフォン ミニクリップ ブラック BSHSM03BK

コーデックの種類

以下を参照。今回はwavファイルを対象にするつもりです。
音声コーデックの種類と違い(MP3・AAC・WMA・WAV・Vorbis・AC3・FLAC等)【フォーマット】 | AviUtlの易しい使い方

Audacityで録音

Audacity 2.3.2で録音し、wavファイルとして書き出します。今はこのwavファイルを読み込んで、解析しようと思います。
いずれは、リアルタイムにしたいと思います。つまり、音声入力 -> 解析 -> 加工 -> 再構築 -> 音声出力をファイルを書き出さずにやりたいです。

rodio 0.9.0

音声再生用のクレートです。今回は外部クレートを使ってwavファイルをデコードしてもらったりしますが、いつか自力でデコードしてみたいですね。
crates.io - rodio


wavを再生するは、以下のようにするとうまくいきました。他にもplay_rawメソッドとかplay_onceメソッドとかあるんですが、実行するとコンピュータのわけわからんとこから「ボシュッ」て聞こえるだけでした。

# Cargo.toml
...
[dependencies]
rodio = "0.9.0"
// main.rs
use std::fs::File;
use std::io;

fn main() {
    let device = rodio::default_output_device().unwrap();
    let sink = rodio::Sink::new(&device);

    let file = File::open("sample_audio/sample1.wav").unwrap();
    let source = rodio::Decoder::new(io::BufReader::new(file)).unwrap();
    sink.append(source);

    sink.sleep_until_end();
}

最初にデフォルトで使用されている出力デバイスを指定します。次にデバイスをSink型で作成します。Sink型は音声出力用のハンドルです。
次にwavファイルをオープンしてBufReaderとしてDecoderに渡します。このメソッドの戻り値はIteratorトレイトを継承するSampleトレイトをItemに持つSourceトレイトが実装されたイテレータです。

pub trait Source: Iterator where
Self::Item: Sample, {
    ...
}

plotlib 0.4.0

plotlibはグラフ描画用のクレートです。svgファイルで出力されます。

crates.io - plotlib

グラフも見れた方が良いと思いいろんなクレート(gnuplotなど)を試しましたが、Windows環境ではplotlibがやっとこさうまくいきました。他のクレートでもやり方が間違ってなければうまくいくのかもしれません。
plotlib 0.4.0での線グラフの書き方は以下のようにするとうまくいきました。

use plotlib::page::Page;
use plotlib::view::ContinuousView;
use plotlib::style::Line;

fn main() {
    let mut style = plotlib::line::Style::new();
    let l1 = plotlib::line::Line::new(&[(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)]).style(style.colour("#000000"));
    let v = ContinuousView::new().add(&l1);
    Page::single(&v).save("line.svg").expect("saving svg");
}

スタイルを指定し、色を設定しないと肝心のデータ部分が表示されませんでした。おそらくデフォルトが背景色と同じ白なのかもしれません。

rodioで取得したサンプルをグラフに描画するプログラムは以下のように書きました。

use std::fs::File;
use std::io;
use rodio::source::Source;
use plotlib::page::Page;
use plotlib::view::ContinuousView;
use plotlib::style::Line;
use failure::Error;

fn draw_spectrum(data: &Vec<(f64, f64)>, outpath: &str) -> Result<(), Error> {
    let mut style = plotlib::line::Style::new();
    let li = plotlib::line::Line::new(&data)
        .style(style.colour("#000000"));
    let v = ContinuousView::new().add(&li);
    Page::single(&v).save(outpath)?;
    Ok(())
}

fn main() {
    let device = rodio::default_output_device().unwrap();
    let sink = rodio::Sink::new(&device);

    let file = File::open("sample_audio/sample1.wav").unwrap();
    let source  = rodio::Decoder::new(io::BufReader::new(file)).unwrap().buffered();

    let data: Vec<(f64, f64)> = source.clone().enumerate().map(|(i, x)| (i as f64, x as f64)).collect();
    draw_spectrum(&data, "line.svg").expect("Failed draw graph");

    println!("{}", source.sample_rate());

    sink.append(source.clone());
    sink.sleep_until_end();
}

下の画像はsvgファイルをpngファイルに変換して載せています。

f:id:detamamoru:20191114183408p:plain
line.png
これで、どのような波形なのか確認することができます。
めちゃ適当に思いつくままに作っているのですが、果たしてゴールにたどり着けるのでしょうか。(笑)

Voice changerを作りたくて(1): 音ってなんやねん

出田 守です。
このシリーズは、ほとんど何もわからない状況からVoice changerを作る過程の記録です。

環境(私も含めて)

[私について]
算数・数学: 苦手で避けてきた(後悔中)。おそらく中学2年生で止まっている。
プログラミング: 努力中
[開発環境 - PC0]
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.39.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

音とは

もしかしたら習ったかもしれませんが、今まで真剣に音について考えたことがなかったので調べてみました。
音とは物体の振動です。それらの振動を空気や水を伝って感じることで私たちは音を認識します。音は振動を起こす音源があってこそ発生します。音を構成する音の3要素というのがあります。

音の大きさ

音の大きさとは、空気圧の変化量によって決まります。これを音圧と言います。音の大きい小さいは音圧で決まります。圧力の単位としてはPa(パスカル)があります。ただし、Paで表記した場合桁数が多くなりすぎ、このまま扱えば分かりにくいため、一般的にdB(デシベル)という相対的で分かりやすい単位を用います。音の場合、dBは人が聞こえる20μPaを基準として0dBとします。

音の高さ

音の高さとは、1秒間に振動する回数で、これを周波数と言います。周波数の単位はHz(ヘルツ)です。1秒間に1回振動すれば1Hzとなります。人は20Hz~20,000Hzまで聞こえると言われています。

音の音色

同じ音圧、同じ周波数でも振動の形が違えば人間は音の違いを認識します。これらは周辺の環境や音源などから違いが生まれます。

サンプリング

音はアナログで連続した量を表します。この信号をコンピュータで扱うためにサンプリングとよばれる処理を実行します。サンプリングはある時間間隔で、その時点での振幅を読み取ります。それらをリストで保持します。各値のことをサンプルと言います。
これにより、音はメモリ内では周波数とサンプルというリストで表現することができます。これをサンプリング周波数と言いHzを単位とします。例えば1秒間に44,100個のサンプルを読み取る場合は44,100Hzとなります。サンプリング周波数が大きくなるほどサンプル数が多くなり音質も良くなります。一般的に多く用いられているサンプリング周波数は44,100Hz、48,000Hz, 96,000Hzです。

チャンネル

調べた感じだと、1ch毎にスピーカの数に対応しているようです。例えば2chなら左と右に一つずつ、6chなら贅沢に頭の周りに6つスピーカに囲まれて臨場感を味わえるということですねー。ただ、サラウンドは3ch以上を表しており、5.1chとか7.2.2chとかいう表記されます。ドットセパレータで表し、左からスピーカ数、ウーハ数、上からのスピーカ数だそうです。