Raspberry Pi Pico WとPicorubyでLチカ
ダウンロード
R2P2のGitHubのReleasesのリンクから最新版をダウンロード。R2P2-FLASH
から始まるやつはWやWHが付かない通常のRaspberry Pi Pico用、R2P2_W-FLASH
はWやWH用。それぞれgzとzipがあるがどちらでもよい。解凍するとR2P2_W-FLASH_MSC-0.2.1-20240209-5145384.uf2
のような名前のファイルがある。
uf2書き込み
Raspberry Pi Pico WHのBOOTSELボタンを押しながらUSBケーブルを繋ぐ。INDEX.HTM
などを含むフォルダが出てきたら成功。
このフォルダに先ほどの.uf2ファイルをドロップする。コピーされて勝手にExplorerウィンドウが閉じてまた開く。以下のようにbin
, home
などが出てくればOK。
Tera Term設定
Tera Term をダウンロードして起動する。現在の最新版は5.2。大昔からある安定したソフトなのでバージョンは気にしなくて多分大丈夫。
「File」→「New Connection」を選び、「Serial」を選択。選択肢がいくつかあるので適当に試す。今回はCOM9だった。
正しくRaspberry Pi Picoと繋がれば以下のような画面になる。
Lチカ(本体内蔵LED)
$>
のプロンプトでirb
を入力してirbを起動する。Raspberry Pi Pico WやWHの場合は以下を入力すると本体のLEDが光る。
led = CYW43::GPIO.new(CYW43::GPIO::LED_PIN) led.write 1
Lチカ(外部LED)
Pico Wのピン配置は以下のような感じ。
とりあえずGP5と隣のGNDにLEDを刺す。LEDを使うときは抵抗を入れる必要があるが、ここで使っているのは秋月電子で買った抵抗内蔵のLED。配線の手間が省けるしたいして高くないので(10個で200円)良い感じ。
irbで以下を入力すると光らせることができる。
led = GPIO.new(5, GPIO::OUT) irb> led.write 1
Arduino Nano互換品を動かす
Arduino Nanoの正規品は秋月電子で見ると1個3560円。Amazonで3個で1999円という激安品があったので買ってみた。しかもピンはんだ付け済み。
https://www.amazon.co.jp/gp/product/B0CQXCCWZT/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&th=1
ただし互換品は正規品と同じ設定ではスケッチを書き込めないことがある。ここを参考にして試したところ、全く同じ手順ではないけどうまくいった。
ジェネリック『Arduino nano』を動かしてみた | ゲームで楽しく学ぶプログラミング教室
上の参考サイトでは以下のことをやっていた。
- ドライバインストール
- ボード: Arduino Nano
- プロセッサ: ATmega168
- シリアルポート: COM8
今回買ったボードはドライバは入れなくても認識した。ただ認識しないケーブルがあったので、ケーブルの相性はありそう。 USB-SERIAL CH340 (COM8) が認識したボード。
書き込みの設定は以下でできた。
- ボード: Arduino Nano
- プロセッサ: ATmega 328P (Old Bootloader)
- シリアルポート: COM8 (デバイスマネージャーで出てきたものにする)
適当にLチカで試す。
const int led = 11; void setup() { Serial.begin(9600); Serial.println("hello"); pinMode(led, OUTPUT); } void loop() { digitalWrite(led, HIGH); delay(1000); digitalWrite(led, LOW); delay(1000); }
Rubyで大きなwavファイルを読む (未完)
訳あってwaveファイルの処理をする必要があったのでRubyを使ってやってみた。やりたいのはwavファイルをNumo::NArrayの形式で読むこと。また、使用したsample.wavはかなり大きい(2時間分、約1.2GB)。
「ruby wave file」あたりでググるとだいたい以下の2つのgemが出てくる。
- jstrait/wavefile:
gem install wavefile
- shokai/ruby-wav-file:
gem instlal wav-file
両方を試してみたが、結論から言うとどちらもPythonに比べてかなり時間がかかるため結構つらい。以下詳細。
参考: Pythonの場合
本題のRubyの前に、PythonでwavファイルをNumPyのndarrayで読むには以下のようにする。動作も速く、1.0~1.5秒ほど。
最後にsum()
で合計を求めているのは何らかの遅延評価的なもの(読み込んだように見えて実際には読んでおらず、必要になったときにはじめて読む)がないことを確認するため。
#!/usr/bin/env python import wave import numpy as np with wave.open("sample.wav") as wf: nframes = wf.getnframes() frames = wf.readframes(nframes) data = np.frombuffer(frames, dtype=np.int16) print(np.sum(data))
wavefileの場合
wavファイルを読むコードはだいたい以下のようになる。each_buffer
の中身が空なのでファイルを読むだけで何もしていない。これだけでも1分7秒かかった。NArrayに変換するとさらに時間がかかるだろう。
#!/usr/bin/env ruby require "wavefile" WaveFile::Reader.new("sample.wav"){|wr| wr.each_buffer{|buf| } }
wav-fileの場合
こちらのgemの場合は以下のようになる。readDataChunk()
までなら約1秒ほどだが、バイナリデータを取得するためにunpack()
まで行うと約30秒かかる。
#!/usr/bin/env ruby require "wav-file" f = open("sample.wav") format = WavFile::readFormat(f) dataChunk = WavFile::readDataChunk(f) f.close wavs = dataChunk.data.unpack("s*")
OpenCVとNumo::NArrayのバインディング
OpenCVは行列を表すクラスを独自に持っていますが(cv::Mat
クラス)、Pythonバインディングではこれをnumpyのndarrayに置き換えています。opencvrはcv::Mat
をRubyにバインドさせたものを使っていましたが、試しにNumo::NArrayにバインドさせてみました(opencvrとは何か、とかOpenCVの行列ライブラリの話の詳細は以前に書いたこの記事を参照)。
具体的にはopencvr内で以下の2つを行います。
- OpenCVの関数の戻り値が
cv::Mat
のときは、その戻り値をNumo::NArrayに変換してRuby側に返す - OpenCVの関数の引数が
cv::Mat
のときは、Ruby側から受け取った引数はNumo::NArrayであると想定し、それをcv::Mat
に変換してからC++の関数を呼び出す
試しに実装しただけなので不完全なところはあるでしょうが一応動きました。以下サンプル。
元画像
読み込みと行列の情報の表示
imread()
の戻り値がNumo::UInt8になっています。
#!/usr/bin/env ruby require 'numo/narray' require_relative '../opencvr/cv2' img = CV2::imread("sample1.jpg") p img.class p img.shape p img.size
# 実行結果 $ ./test.rb Numo::UInt8 [533, 800, 3] 1279200
減色処理
Numo::NArrayのAPIで画像を加工してみます。画素値を整数値で割ると小数点以下が切り捨てられ、また同じ値を掛けると値が離散的になり、結果として減色されます(もっと良いアルゴリズムがあるだろうけど)。
また、hstack()
で行列を結合させることで画像の結合も可能です。
#!/usr/bin/env ruby require 'numo/narray' require_relative '../opencvr/cv2' img = CV2::imread("sample1.jpg") img2 = img / 32 * 32 img3 = Numo::NArray::hstack([img, img2]) CV2::imwrite("subtract.jpg", img3)
画像をグレースケールにしてヒストグラムを作成
画像読み込み時にIMREAD_GRAYSCALE
を指定するとグレースケールとして読み込み、結果は2次元UInt8行列になります。0~255がそれぞれ何ピクセルあるかヒストグラムで表示します。Numo::GSLとNumo::Gnuplotを使ったヒストグラムの作成はこちらの記事を参考にしました。
#!/usr/bin/env ruby require 'numo/narray' require_relative '../opencvr/cv2' require 'numo/gnuplot' require 'numo/gsl' def gen_hist(img, fname) h = Numo::GSL::Histogram.new(256) h.set_ranges_uniform(0, 255) img_flat = img.flatten img_flat.each do |x| h.increment x end freq = h.bin center_of_range = h.range.to_a.each_cons(2).map{|a,b| (a+b)/2.0} Numo.gnuplot do set terminal: :png set output: fname set style:[:fill, :solid] unset :key plot center_of_range, freq, w: :boxes end end img_gray = CV2::imread("sample1.jpg", CV2::IMREAD_GRAYSCALE) gen_hist(img_gray, "hist-rb.png")
なお、Pythonならもっと短いコードで同じような結果が得られます。Rubyももっと短くしたいところですが。
#!/usr/bin/env python import cv2 import matplotlib.pyplot as plt img = cv2.imread("sample1.jpg", cv2.IMREAD_GRAYSCALE) img2 = img.flatten() plt.hist(img2, bins=256) plt.savefig('hist-py.png')
ベタ塗り画像に文字を書く
今度はimread()
で画像ファイルを読み込むのではなく、Numo::NArrayのAPIで行列データを作成します。カラー画像を作成するには3次元のUInt8型の行列を作ります。幅800, 高さ600ピクセルの画像なら(600, 800, 3)です。このうち青成分のみ255にすれば青ベタ塗りの画像ができるので、それにputText()
で文字を書きこみます。なおOpenCVの画像データはRGBではなくBGRなので、青成分は0番です。
#!/usr/bin/env ruby require 'numo/narray' require_relative '../opencvr/cv2' img = Numo::UInt8.zeros(600, 800, 3) img[0..-1, 0..-1, 0] = 255 CV2::putText(img, "Hello OpenCV with Numo::NArray", [100, 300], CV2::FONT_HERSHEY_DUPLEX, 1, [255, 255, 255]) CV2::imwrite("out.png", img)
ちなみに
画像をNumo::NArrayで取得するだけならすでにmagroというライブラリがあるのでそっちを使った方が良いです。あと単なる減色処理や画像の結合についてももっと良い既存のライブラリがあるはずです。この程度の処理にOpenCVを使うのはオーバーキルですね。