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が出てくる。

両方を試してみたが、結論から言うとどちらも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::MatRubyにバインドさせたものを使っていましたが、試しにNumo::NArrayにバインドさせてみました(opencvrとは何か、とかOpenCVの行列ライブラリの話の詳細は以前に書いたこの記事を参照)。

具体的にはopencvr内で以下の2つを行います。

  • OpenCVの関数の戻り値がcv::Matのときは、その戻り値をNumo::NArrayに変換してRuby側に返す
  • OpenCVの関数の引数がcv::Matのときは、Ruby側から受け取った引数はNumo::NArrayであると想定し、それをcv::Matに変換してからC++の関数を呼び出す

試しに実装しただけなので不完全なところはあるでしょうが一応動きました。以下サンプル。

元画像

f:id:wagavulin:20220105051019j:plain

読み込みと行列の情報の表示

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)

f:id:wagavulin:20220105051049j:plain

画像をグレースケールにしてヒストグラムを作成

画像読み込み時に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")

f:id:wagavulin:20220105051755p:plain

なお、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')

f:id:wagavulin:20220105052427p:plain

ベタ塗り画像に文字を書く

今度は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)

f:id:wagavulin:20220105193950p:plain

ちなみに

画像をNumo::NArrayで取得するだけならすでにmagroというライブラリがあるのでそっちを使った方が良いです。あと単なる減色処理や画像の結合についてももっと良い既存のライブラリがあるはずです。この程度の処理にOpenCVを使うのはオーバーキルですね。

ThinkPad X1 Carbon Gen5 (2017モデル) で4K 60Hz出力

ThinkPad X1 Carbon Gen5 (2017モデル) にはHDMI端子があるが4K出力では30Hzになる。ゲームをするのでなければそんなに気にならない、かと思いきや、実際に使ってみるとマウス移動のカクカク感が結構気になる。

とここで「USB Type C→HDMI変換ケーブルを使えば60Hz出力が可能」という情報を見たのでやってみた。使ったのはAnker PowerExpand+ USB-C & HDMI 変換アダプタ 【4K (60Hz) 対応】というやつで、Amazonで2000円だった。

試したところ無事4K 60Hzで動作した。これで快適になった。

PC本体のHDMIに繋げた場合(30Hzまでしか選択できない) f:id:wagavulin:20220103001025p:plain

Type C→HDMI変換ケーブルを経由した場合(60Hzまで選択できる) f:id:wagavulin:20220103001132p:plain