ハードウェア技術者のスキルアップ日誌

某家電メーカーの技術者がスキルアップのために勉強したことを記録するブログです

Keras - 学習済みモデルを流用する

KerasではImageNetのデータセットで学習した画像分類のモデルが準備されており、ダウンロードして使用することができます。
Applications - Keras Documentation

Mobilenetの学習済みモデルを使おうとしたときに少し手こずったところがあったので備忘録として使い方を書いておきます。

 

学習済みモデルのダウンロード方法

サンプルとしてMobilenetのモデルをダウンロードするコードを載せます。
keras.applicationsの下に様々なモデルがあるので、使いたいモデルを指定すればOKです。VGG16であれば、applications.vgg16.VGG16()となります。

from keras import applications

model = applications.mobilenet.MobileNet(input_shape=None, alpha=1.0,
depth_multiplier=1, dropout=1e-3,
include_top=True, weights='imagenet',
input_tensor=None, pooling=None, classes=1000)

また、引数の意味をいくつか抜粋して説明します。

  • weights : 重みを乱数とするか(None)、
                   imagenetで学習したものとするか('imagenet')
  • include_top : ネットワークに全結合層を含むかどうか
          Trueだと全結合層付きのモデル、Falseだと全結合層なしのモデル
  • pooling : 特徴量抽出後のPooling Layerの指定 (include_topがFalseの時に有効)
         - None : 畳み込み層
                   - 'avg' : global average pooling
                   - 'max' : global max pooling
  • classes : 何クラス分類のモデルとするか(include_topがTrue, weightがNoneの時)

詳細はこちら:Applications - Keras Documentation

 

次にモデルの使い方を含めた具体的なサンプルコードを乗せておきます。

①分類器として使用

ImageNetで学習したモデルを用い、1000クラスの画像分類を行う最もシンプルな例です。モデルは全結合層付きのものを使用します。

import tensorflow as tf
from tensorflow import keras
import numpy as np
from keras.applications import mobilenet

#モデルのロード model = mobilenet.MobileNet(input_shape=None, alpha=1.0, depth_multiplier=1,
dropout=1e-3, include_top=True, weights='imagenet',
input_tensor=None, pooling=None, classes=1000) #model.summary()
#画像の前処理 height = 224 width = 224 img_pil = keras.preprocessing.image.load_img("./dog.jpg", target_size=(height, width)) img = keras.preprocessing.image.img_to_array(img_pil) img = img[tf.newaxis, ...] img_preprocessed = mobilenet.preprocess_input(img)

#推論実行
predict = model.predict(img_preprocessed) #出力クラスのtop5を表示 result = mobilenet.decode_predictions(predict, top=5) print(result)

 

②特徴抽出器として利用

全結合層前の特徴量を抽出する方法です。分類クラス数をカスタマイズしたり、ベースモデルの一部のみを追加学習するなど転移学習をしたい場合はこの方法を使います。

全結合層は不要なので引数のinclude_topはFalseとします。

import tensorflow as tf
from tensorflow import keras
import numpy as np
from keras.applications import mobilenet

#モデルのロード model = mobilenet.MobileNet(input_shape=None, alpha=1.0, depth_multiplier=1,
dropout=1e-3, include_top=False, weights='imagenet',
input_tensor=None, pooling='avg', classes=1000) #model.summary()
#画像の前処理 height = 224 width = 224 img_pil = keras.preprocessing.image.load_img("./dog.jpg", target_size=(height, width)) img = keras.preprocessing.image.img_to_array(img_pil) img = img[tf.newaxis, ...] img_preprocessed = mobilenet.preprocess_input(img)
#特徴量抽出 predict = model.predict(img_preprocessed) print(predict.shape) #(1,1024)

転移学習の具体的な手順はこちらのサイトをご参照下さい。
https://note.nkmk.me/python-tensorflow-keras-transfer-learning-fine-tuning/

 

参考サイト

TensorFlow, KerasでVGG16などの学習済みモデルを利用 | note.nkmk.me

Tensorflowの基本の使い方②

前回はTensorflowを使うにあたっての考え方をを記事にしました。

masaeng.hatenablog.com

今回はGithubに上がっていたソースコードをベースに、具体的にDeep Learningの学習、推論をするときの基本的な記述方法について書いていきたいと思います。

①ネットワークの記述

まずは使用するDeep Learningのネットワークを構築します。
今回はMNIST(手書き文字認識)のサンプルで使われるネットワークを題材にします。

Githubのコードを見ると関数の中で記述しているケースが多いようです。

def create_model(input):
    #畳み込み+max pooling
    x_1 = tf.reshape(input, [-1, 28, 28, 1])
    k_0 = tf.Variable(tf.truncated_normal([4, 4, 1, 10], mean=0.0, stddev=0.1))
    x_2 = tf.nn.conv2d(x_1, k_0, strides=[1, 3, 3, 1], padding='VALID')
    x_3 = tf.nn.relu(x_2)
    x_4 = tf.nn.max_pool(x_3, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID')

    #全結合
    x_5 = tf.reshape(x_4, [-1, 160])
    w_1 = tf.Variable(tf.zeros([160, 40]))
    b_1 = tf.Variable([0.1] * 40)
    x_6 = tf.matmul(x_5, w_1) + b_1
    x_7 = tf.nn.relu(x_6)
    w_2 = tf.Variable(tf.zeros([40, 10]))
    b_2 = tf.Variable([0.1] * 10)
    x_8 = tf.matmul(x_7, w_2) + b_2

    #ソフトマックスで確率表現
    output = tf.nn.softmax(x_8)
    return output

 

②学習と学習結果の保存

①で作成した関数を使ってグラフを構築します。
加えて、学習時にはlossとoptimizerを設定し、sess_run()で学習を行います。
学習終了後は重みをckpt形式で保存します。

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

#グラフの構築
g = tf.Graph()
with g.as_default():
    x = tf.placeholder(tf.float32, name='x')
    output = model_create(x)

    labels = tf.placeholder(tf.float32, name='labels')

    #損失関数:交差エントロピー, 最適化:Adam
    loss = -tf.reduce_sum(labels * tf.log(output))
    optimizer = tf.train.AdamOptimizer().minimize(loss)

#重み保存用
saver = tf.train.Saver()

# 学習の実行
with tf.Session(graph=g) as sess:
    sess.run(tf.global_variables_initializer())

    for i in range(NUM_TRAIN):
        batch = mnist.train.next_batch(BATCH_SIZE)
        inout = {x: batch[0], labels: batch[1]}
        _, loss_value = sess.run((train, loss), feed_dict=inout))

        if i % OUTPUT_BY == 0:
            print(loss_value)

    #学習結果の保存
    saver.save(sess, "model.ckpt")

 

➂学習結果の読み出しと推論

②で学習した結果を読み出して推論を実行します。
学習後、引き続き推論を行う場合には結果の読み出しは不要です。

学習済みモデルを読み出す前にはモデルを構築しておくことに注意が必要。

#グラフの構築
g = tf.Graph()
with g.as_default():
    x = tf.placeholder(tf.float32, name='x')
    output = model_create(x)

#重み読み出し用
saver = tf.train.Saver()

# 推論の実行
with tf.Session(graph=g) as sess:
    #学習結果の読み出し
    saver.restore(sess, "model.ckpt")

    result = sess.run(output, feed_dict={x:test_img})
    print(result)

ちなみに、ckpt保存時に生成される.metaファイルにはモデルの構造が含まれており、tf.train.import_meta_graph()で.metaファイルをロードすれば、事前にモデルを構築しておく必要がないようです。

参考サイト:TensorFlow で学習したモデルのグラフを `tf.train.import_meta_graph` でロードする - Qiita

 

まとめ

Tensorflowを使ったDeep Learningの学習、推論のコードの書き方をまとめました。
基本となる型のようなものができましたので、コードを自作する際にはこれをベースに修正を加えていけばいろいろ応用が利くと思います。

 

参考サイト

これらのサイトを参考にさせていただきました。ありがとうございました!

https://qiita.com/41semicolon/items/d159662385a6d72ee195
https://tensorflow.classcat.com/2018/04/20/tensorflow-programmers-guide-low-level-intro/
https://www.atmarkit.co.jp/ait/articles/1804/24/news134.html
https://qiita.com/cfiken/items/bcdd7eb945c5c3b2bb5f

 

 

Tensorflowの基本の使い方①

前回はKerasの使い方を整理しました。

masaeng.hatenablog.com

イチからネットワークを作るときはKerasで問題ありません。
しかし、Tensorflowで作られたオープンソースのモデルを修正したい場合、Kerasだけの知識だけでは思い通りのことができないことが多くありました。
そこでTensorflowの低位APIの使い方も勉強していこうと思います。

 

Tensorflowを扱う上での基本の考え方

Tensorflowは処理順にコードを書いていくのではなく、まず初めに計算内容を記述した「計算グラフ」を定義してそのあとに計算処理を実行するという使い方をします。
この考え方が分かっていない状態では公開されているコードを読んでも理解が十分できませんでした。しっかり抑えておきましょう。

計算グラフとは数値と演算の組み合わせでどのような計算をするかを示したものです。一例を以下に示します。これは非常に単純な例ですが、Deep Learningで使用するLayerも計算グラフで表すことができます。

    f:id:masashi_k:20200718230529p:plain

計算グラフは計算処理の内容を表すだけでこれだけでは計算ができません。
計算を実行するときにはセッションというものを用意します。
この時、実際に計算を行うハードウェア(CPU、GPUなど)を指定することも可能。
セッション内で計算するグラフを指定し、計算処理を実行します。

      f:id:masashi_k:20200718231449p:plain

 

グラフの構成要素

グラフがどのような要素で出来ているかを詳しく見ていきます。

数値の型

数値を入れることができる型が3種類あります。数値は1次元に限らず、多次元配列(テンソル)を入れることが可能です。

  1. 定数 tf.constant(value, dtype, shape, name)
     計算の中で変更されない値に使用します。
  2. 変数 tf.Variable(inital_value, name, dtype, expected_shape, constraint)
     グラフの計算の過程で変化する値に使用します。
     (例えば、学習時に更新される重みパラメータなど)
     変数はセッションの最初で初期化する必要があります。
      tf.variables_initializer()
      tf.global_variables_initializer()
  3. プレースホルダー tf.placeholder(dtype, shape, name)}
     データが格納される入れ物でグラフ構築時には値が入っていない。
     セッション実行時に引数feed_dictに値を指定することでその値をグラフに渡す。
演算 tf.Operation()

多次元配列(テンソル)の演算を行います。
具体的には足し算、行列積などの数値計算です。

tensorflowで使用できる演算は公式ドキュメントに記載されています。
https://www.tensorflow.org/api_docs/python/tf/math
https://www.tensorflow.org/api_docs/python/tf/linalg

 

グラフの作り方

上で述べた数値と演算を使ってグラフを作っていきます。

tf.Graph()を準備し、その中で計算したい処理内容を記述します。
ここではグラフの定義を明示的に行っていますが、上の2行はなくても問題ありません。
Tensorflowはデフォルトでグラフが一つ用意されており、何も指定しなければそこにグラフを作っていきます。
下のサンプル中のas_defaultメソッドはこのグラフをデフォルトグラフにするという意味になります。

g = tf.Graph()
    with g.as_default():
        x = tf.constant(1, name="x")
        y = tf.constant(2, name="y")

        z = tf.add(x, y)

 

一方、複数のグラフをそれぞれ定義して使い分けるときは以下のようにします。
それぞれのグラフについてas_default()をつける必要があるので注意

g_1 = tf.Graph()
with g_1.as_default():
  # Operations created in this scope will be added to `g_1`.
  c = tf.constant("Node in g_1")
 
g_2 = tf.Graph()
with g_2.as_default():
  # Operations created in this scope will be added to `g_2`.
  d = tf.constant("Node in g_2")

 

計算の実行

最後に定義した計算グラフを実行します。
tf.Sessonでセッションを構築し、その中でSession.run()を実行することで計算結果を得ることができます。

sess.run()の引数(以下のサンプルではz)は計算結果を得たいテンソルを指定します。
計算グラフの再集団の結果だけではなく、計算の途中のノードでも取り出せます。

グラフの中にplaceholderがある場合は、sess.run()の引数feed_dictでplaceholderに入れる値を渡します。

# グラフを定義 
x = tf.constant(1, name="x")
y = tf.placeholder(tf.int32, name="y")
z = tf.add(x, y)

# セッション構築、実行
with tf.Session() as sess:
    output = sess.run(z, feed_dict={y:3})

 

上記のサンプルでは明示的にグラフの定義をしておらず、デフォルトグラフをセッションで実行しています。複数のグラフを使う場合にはtf.Session()の引数でグラフを指定します。

g_1 = tf.Graph()
with g_1.as_default():
  # Operations created in this scope will be added to `g_1`.
  c = tf.constant("Node in g_1")
 
  # Sessions created in this scope will run operations from `g_1`.
  sess_1 = tf.Session()
 
g_2 = tf.Graph()
with g_2.as_default():
  # Operations created in this scope will be added to `g_2`.
  d = tf.constant("Node in g_2")
 
# Alternatively, you can pass a graph when constructing a `tf.Session`:
# `sess_2` will run operations from `g_2`.
sess_2 = tf.Session(graph=g_2)

 

まとめ

Tensorflowを使う上での基本の考えをまとめてきました。次回はTensorflowでDeep Learningを実行するサンプルを作って、流れを把握できるようにしていきたいと思います。

Kerasの基本の使い方

これまでいくつかのDeep Learningを用いた物体検知のモデルを試してきました。
公開されているPretrained modelを用いて、画像の入力をUSBカメラから取得した画像に、出力を入力画像に検知結果を表すバウンディングボックスを重ねたものにインターフェースを置き換えるという実装をしています。
この作業はフレームワークの知識がなくとも、Python, OpenCVの知識があれば何とか実現できたため、これまでTensorflowの基本がよくわかっていないままでした。

そこで、Tensorflowのチュートリアルを一通りやってみて改めてTensorflowの使い方を整理しておこうと思います。
今回はTensorflowの高水準ライブラリであるKerasの使い方をまとめます。

Tensorflowのチュートリアル
https://www.tensorflow.org/tutorials/keras/classification?hl=ja

 

Kerasとは?

公式ドキュメントには以下の記述があります。https://keras.io/ja/

Kerasは,Pythonで書かれた,TensorFlowまたはCNTKTheano上で実行可能な高水準のニューラルネットワークライブラリです. Kerasは,迅速な実験を可能にすることに重点を置いて開発されました.  

実装量が少なくて済むように開発されているライブラリのため、スクラッチからモデルを作って学習、推論をするにはKerasを使うのがいいと思います。
TensorflowチュートリアルではTensorflowの低水準APIの解説が全くありません。
高度なことをしなければKerasで十分ということでしょう。
次からはチュートリアルからコードを抜粋して見ていきます。

 

ライブラリのインポートとデータセットのダウンロード

mnist, CIFAR100をはじめ、Deep Learningでよく使われるデータセットをKerasのライブラリを使ってダウンロードすることができます。

詳細はこちら。
https://keras.io/ja/datasets/#fashion-mnist

# TensorFlow と tf.keras のインポート
import tensorflow as tf
from tensorflow import keras

# データセットのダウンロード fashion_mnist = keras.datasets.fashion_mnist (train_images, train_labels), (test_images, test_labels)
   = fashion_mnist.load_data()

 

モデルの構築

モデルを作成するにはkeras.Sequential()関数を使います。
この関数の中で前から順にlayerを重ねていきます。
活性化関数は引数のactivationで指定します。

layerの種類、引数はこちらをご参照ください。
https://keras.io/ja/layers/core/
活性化関数の詳細はこちら。
https://keras.io/ja/activations/

model = keras.Sequential([
    keras.layers.Flatten(input_shape=(28, 28)), #Flatten:2次元配列を1次元配列に展開
    keras.layers.Dense(128, activation='relu'),     #Dense:全結合層, activationで活性化関数を指定
    keras.layers.Dense(10, activation='softmax')
])

 

モデルのコンパイル

model.compile()で学習時に必要な項目の設定を行います。
最適化、ロス関数、評価関数について設定できる選択肢は以下のサイトで確認できます。

最適化:https://keras.io/ja/optimizers/
ロス関数:https://keras.io/ja/losses/
評価関数:https://keras.io/ja/metrics/

model.compile(optimizer='adam',         #optimizer:モデルの更新方法
      loss='sparse_categorical_crossentropy', #loss:損失関数
      metrics=['accuracy'])           #metrics:学習のステップごとに確認する値

 

モデルのトレーニン

学習はmodel.fit()で行います。
ここでは学習画像、それに対応した教師ラベル、エポック数を指定しています。

model.fit(train_images, train_labels, epochs=5)  #epoch:同じ学習データセットを何回回すか

 

正解率の評価

model.evaluate()でテストデータの評価を行います。

test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)  #verbose:ログ出力モード

 

学習したモデルを使って推論

model.predict()で学習モデルを使って推論します。evaluateはモデルの損失、評価値を返すのに対し、predictは予測結果を返します。

predictions = model.predict(test_images)

 

モデルの保存と読み出し

重み、モデルの保存形式はckptとhdf5形式があります。
ckptはモデルと重みを、hdf5はそれらに加えてオプティマイザなどモデルの設定を含めて保存できます。

モデルを保存(ckpt)
model.fit()  #学習後
model.save_weights('./checkpoints/my_checkpoint') #手動で保存

#--------------------------------------------------------
# callback関数ModelCheckpointをfitの引数に設定すれば
# 学習中の途中経過の重みを自動で保存できる
cp_callback = tf.keras.callbacks.ModelCheckpoint(
    checkpoint_path, verbose=1, save_weights_only=True,  period=5)
    # 重みを5エポックごとに保存

model = create_model()
model.fit(train_images, train_labels,
          epochs = 50, callbacks = [cp_callback],
          validation_data = (test_images,test_labels),
          verbose=0)

モデルを読み出し(ckpt)
# loadする前にモデルを準備する必要がある
model = create_model() model.load_weights(checkpoint_path)
 
モデルを保存(hdf5)

hdf5形式の場合、model.save_weight()ではなくmodel.save()を使用します。

model.fit() #学習後
model.save('my_model.h5')

 

モデルを読み出し(hdf5)
# 重みとオプティマイザを含む全く同じモデルを再作成
new_model = keras.models.load_model('my_model.h5)

 

 まとめ

Kerasの基本的な使い方をまとめてきました。
次回はTensorflowの低水準APIについて整理していきたいと思います。

【電子工作】ー 踏切の遮断機を作ってみた

これまで学んだラズパイの動かし方を応用して、子どもと遊ぶための踏切の遮断機を作ってみました。

仕上がりはこんな感じです。子どもと一緒に作ったら結構喜んでくれました!


【電子工作】ラズパイで踏切の遮断機を作ってみた

 この記事では簡単に作り方をご紹介したいと思います。

 

回路構成

f:id:masashi_k:20200712025913p:plain

LED制御、ステッピングモーター制御、スピーカーから成ります。
ステッピングモーターはこちらの記事で作った回路をそのまま使っています。

masaeng.hatenablog.com

段ボールで作った踏切に赤色LEDを取り付け、ステッピングモーターに遮断機のバーをつけます。

 

制御シーケンス

ソフトウェアでの制御手順は以下の通りです。
 ①GPIOの初期化
 ②踏切音再生開始(リピート再生)
 ③LED点滅を繰り返す
 ④(数秒後)ステッピングモーターを制御してバーを下ろす
 ⑤所定の時間になったらLED点滅と踏切音の再生を止め、バーを元に戻す

それでは1つずつ見ていきましょう。

①GPIOの初期化

今回使用するGPIOポートの使用モード、初期値を設定します。

import RPi.GPIO as GPIO

# GPIOピンの定義
L_IN1 = 20
L_IN2 = 21
R_IN1 = 12
R_IN2 = 16

AIN1 = 2
AIN2 = 3
BIN1 = 4
BIN2 = 14
STBY = 15

# GPIOピンの設定
GPIO.setmode(GPIO.BCM)
GPIO.setup(L_IN1, GPIO.OUT)
GPIO.setup(L_IN2, GPIO.OUT)
GPIO.setup(R_IN1, GPIO.OUT)
GPIO.setup(R_IN2, GPIO.OUT)
GPIO.setup(AIN1, GPIO.OUT)
GPIO.setup(AIN2, GPIO.OUT)
GPIO.setup(BIN1, GPIO.OUT)
GPIO.setup(BIN2, GPIO.OUT)
GPIO.setup(STBY, GPIO.OUT)


# スタンバイ状態にする
GPIO.output(L_IN1,0)
GPIO.output(L_IN2,0)
GPIO.output(R_IN1,0)
GPIO.output(R_IN2,0)
#モータードライバースタンバイ解除
GPIO.output(STBY,1)

 

②踏切音再生(リピート再生)

まず、事前準備としてラズパイのスピーカー出力を有効にします。
手順は以下の通りです。

1.$sudo raspi-config
2.「7 Advanced Option」を選択
3.「A4 Audio」を選択
4.「1 Force 3.5mm (‘headphone’) jack」を選択

参考サイト:https://tomosoft.jp/design/?p=33680

次にpythonのコードからmp3ファイルを再生するためにpygameというモジュールを使います。コードは以下の通りです。pygame.mixer.music.playの引数は再生回数を指定します。-1とするとループ再生になります。

import pygame.mixer

#mp3再生
pygame.mixer.init()
pygame.mixer.music.load("./fumikiri.mp3")
pygame.mixer.music.play(-1)

踏切の音はこちらのフリー素材を使わせていただきました。
https://dova-s.jp/se/play769.html

 
③LED点滅を繰り返す

これはただのLチカですね。while文で繰り返します。

import time

while True:
        GPIO.output(L_IN1,1)
        GPIO.output(L_IN2,0)
        GPIO.output(R_IN1,1)
        GPIO.output(R_IN2,0)
        time.sleep(0.2)
        GPIO.output(L_IN1,0)
        GPIO.output(L_IN2,1)
        GPIO.output(R_IN1,0)
        GPIO.output(R_IN2,1)
        time.sleep(0.2)
 
④(数秒後)ステッピングモーターを制御してバーを下ろす

LEDの点滅が始まって数秒後にバーを下ろします。モーターの回転中もLEDの点滅が止まらないようにモーターの回転は別スレッドで動かすようにします。

pythonでマルチスレッド処理をするにはthreadingというモジュールを使用します。threading.Tread()で別スレッドで動作させたい関数を指定します。targetは関数名を指定、argsはその関数に渡す引数を指定。argsは型がtupleなので、引数が1個でも(A, )という形にして渡します。thread.start()でそのスレッドをスタートさせます。
参考サイト:https://qiita.com/castaneai/items/9cc33817419896667f34

バーを下ろす処理はLED点滅開始後、5秒後に行います。これは1回だけでいいのでflagで初めての実施かをチェックしています。

import threading
f = 0.1 # 回転速度[rps] 1秒に何回転するか
s_angle = 5.625 # ステップ角[deg]
step_360 = 2048 # 1回転のステップ数
unit_step = step_360 * s_angle / 360 # ステップ角当たりのステップ数
wait = (1/f)*(s_angle/360)/unit_step # 1ステップの待ち時間
start_time = time.time() flag = 0 thread = threading.Thread(target=backward, args=(int(90 / s_angle * unit_step),)) while True: if(((time.time() - start_time) > 5 ) and (flag == 0)): thread.start() flag = 1

  #以下③のLチカの処理

 

⑤所定の時間になったらLED点滅と踏切音の再生を止め、バーを元に戻す

一定時間経過後、mainのwhileループから抜けられるようにtry分の中に入れます。キーボードからの強制終了時と時間経過後、whileループをbreakで抜けたときに終了処理を行います。

終了処理ではLEDをすべて消す、mp3の再生を止める、モーターを元に戻すの3つの処理を行います。

f = 0.1 # 回転速度[rps] 1秒に何回転するか
s_angle = 5.625 # ステップ角[deg]
step_360 = 2048 # 1回転のステップ数
unit_step = step_360 * s_angle / 360 # ステップ角当たりのステップ数
wait = (1/f)*(s_angle/360)/unit_step # 1ステップの待ち時間


try
: while True:      #mainのwhileループ(➂、➃) if(time.time() - start_time) > args.wait : break except KeyboardInterrupt: GPIO.output(L_IN1,0) GPIO.output(L_IN2,0) GPIO.output(R_IN1,0) GPIO.output(R_IN2,0) pygame.mixer.music.stop() time.sleep(0.5) forward(int(90 / s_angle * unit_step)) else: GPIO.output(L_IN1,0) GPIO.output(L_IN2,0) GPIO.output(R_IN1,0) GPIO.output(R_IN2,0) pygame.mixer.music.stop() time.sleep(0.5) forward(int(90 / s_angle * unit_step))

まとめ

GPIO制御によるLED点灯とステッピングモーター制御を応用して踏切の遮断機を作ってみました。まだまだ単純な制御しかやっていないので、もう少し高度なことを含んだネタを考えていきたいと思います。

参考サイト

https://tomosoft.jp/design/?p=33680
https://qiita.com/Nyanpy/items/cb4ea8dc4dc01fe56918#1pygame
https://qiita.com/castaneai/items/9cc33817419896667f34

Python - フォルダ内の複数のファイルに対して同じ処理を実行

以前、シェルスクリプトでフォルダ内の複数ファイルに対して同じ処理を実行する方法について記事を書きました。

masaeng.hatenablog.com

今回は、Pythonコードで同じことを行う方法についてまとめたいと思います。

サンプルコード

以下は引数で指定したディレクトリパス以下のファイルに対して任意の処理をするPythonスクリプト(sample.py)です。パスの指定の仕方を3通り記載していますので、必要に応じて選択してください。

import glob
import re
import os
import argparse


def main():
    # parserを作成
    parser = argparse.ArgumentParser()
    parser.add_argument('dir_path', help='designate dir path')
    args = parser.parse_args()

    # ワイルドカードで条件を満たすパスの文字列を指定
    pathlist = glob.glob(args.dir_path + '/*.text')

    # python3.5以降 サブディレクトリも検索
    # **の部分があらゆる中間ディレクトリに対応
    pathlist = glob.glob(args.dir_path + '/**/*.text', recursive=True)

    # ファイル名のみを取得
    pathlist = [os.path.basename(p) for p in glob.glob(args.dir_path + '/**', recursive=True) if os.path.isfile(p)]

    for path in pathlist:
        ##ここで何か処理
        print(path)

if __name__ == '__main__':
    main()


実行例(カレントディレクトリ以下のファイルを指定)
 $ python sample.py ./

 

参考サイト

こちらを参考にさせていただきました。ありがとうございます。

Pythonで条件を満たすパスの一覧を再帰的に取得するglobの使い方 | note.nkmk.me

python — 特定のディレクトリ内のファイルを反復処理する方法

ラズパイでステッピングモーターを動かす

前回、ラズパイでDCモーターを動かしてみました。

masaeng.hatenablog.com

DCモーターでは回転角度を制御できないため、今回はステッピングモーターを制御することに挑戦しました。事前に他の方がどのように制御されているかを調べたところ、ライブラリを使用している例が多い印象でした。具体的な制御手順を理解するため、ライブラリを使わずにやってみたいと思います!

ステッピングモーターとは?

直流のパルス電圧を印加して駆動するモーターで、DCモーターとは異なり、正確に回転を制御できます。例えば90度回したい、30度回したいといったことが容易にできます。

内部構成は以下のようになっています。周囲のコイルに電流を流すことでコイルがS極となり、中央の磁石のN極を近づけます。電流を流すコイルをL1, L2, L3, L4, L1・・・と変えることで、中央の磁石が回転します。

駆動方法はいくつかの手法がありますが、安定的に駆動できると言われる二相励磁を用います。電流を流す区間を1ステップ分、隣のコイルと重複させます。

      

f:id:masashi_k:20200623225754p:plain f:id:masashi_k:20200623225820p:plain

        画像引用:https://dotstud.io/blog/stepper-motor-nefrybt-control/

回路設計

 今回準備した部品はこちらです。
  ステッピングモーター28BYJ-48:http://akizukidenshi.com/catalog/g/gP-13256/
  モータードライバー TB6612FNG:http://akizukidenshi.com/catalog/g/gK-11219/
TB6612は本来DCモーター用のドライバーですが、ステッピングモーターにも使用できました。(DCモーター用に買ったものを流用するため、動かし方を調べました)

モーターの配線図はこんな感じ。3番端子(赤)には電源5Vを入力します。二相励磁で動かすためには2-A, 5-B, 4-A', 1-B'に対して右図のようにパルス電圧を印加する必要があります。

f:id:masashi_k:20200623230828p:plain  f:id:masashi_k:20200623231019p:plain

画像引用:http://akizukidenshi.com/download/ds/akizuki/28byj-48.pdf


電圧を供給するのはモータードライバーTB6612です。ドライバーモジュールの回路図は以下左図のようになっており、AIN1,2, BIN1,2が入力、A01,A02,B01,B02が出力です。ドライバーの真理値表で注目すべきところは赤枠で囲んだところです。OUT1,2がH/LまたはL/Hとなっているところを二相励磁のパルスに当てはめます。

f:id:masashi_k:20200623231439p:plain f:id:masashi_k:20200623231611p:plain

       画像引用:http://akizukidenshi.com/download/ds/akizuki/AE-TB6612.pdf

 

モーター端子、即ちTB6612の出力端子を上図の二相励磁の方式で動かそうとするとTB6612の入力は右のようにH/Lを制御すればよいことになります。これら入力AIN1,2,BIN1,2をラズパイのGPIOポートに接続して制御します。
 f:id:masashi_k:20200623232231p:plain

最終的に回路図は以下のようになります。モーターには乾電池で作ったDC5Vを入力。モータードライバーのPWM端子はVCC(3.3V)と接続しています。

f:id:masashi_k:20200623232620p:plain

こんな感じで回路を組みました。

f:id:masashi_k:20200623234101j:plain

ソースコード

Pythonでコードを書いていきます。回転角と回転方向を引数として与えるようにします。

まずはライブラリのインポートとGPIOピンの指定です。上の回路図に合わせて変数にGPIO番号を入力します。

import argparse
import time
import RPi.GPIO as GPIO
AIN1 = 2 AIN2 = 3 BIN1 = 4 BIN2 = 14 STBY = 15

 

ここで、回転速度を指定します。
ステップ角とは回転角の最小単位、ステップとは二相励磁の図における、縦の点線の区間1つ分です。モーターの回転速度(1秒に何回転するか)から1ステップの待ち時間を計算しています。ステップ角と1回転のステップ数はモーターの仕様です。購入サイトで確認できます。

f = 0.1 # 回転速度[rps] 1秒に何回転するか
s_angle = 5.625 # ステップ角[deg]
step_360 = 2048 # 1回転のステップ数
unit_step = step_360 * s_angle / 360 # ステップ角当たりのステップ数
wait = (1/f)*(s_angle/360)/unit_step # 1ステップの待ち時間

 

main関数です。
引数として回転角度と回転方向(0:時計回り、1:反時計回り)を受け取ります。
GPIOの初期設定後、回転方向が時計回りならforward()を、反時計回りならbackward()を実行して、停止処理を行います。forward/backward関数の引数はステップ数のため、回転角度からステップ数に変換して渡します。モーターに電圧をかけたままだと熱くなるため、最後にモータードライバーをスタンバイモードに入れます。

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('angle', type=float, help='rotation angle') 
    parser.add_argument('--dir', default=0, choices=["0","1"], help='rotate direction, 0=CW, 1=CCW')
    args = parser.parse_args()

    #GPIO初期化
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(AIN1, GPIO.OUT)
    GPIO.setup(AIN2, GPIO.OUT)
    GPIO.setup(BIN1, GPIO.OUT)
    GPIO.setup(BIN2, GPIO.OUT)
    GPIO.setup(STBY, GPIO.OUT)

    #モータードライバースタンバイ解除
    GPIO.output(STBY,1)

    if args.dir == "0":
        forward(int(args.angle / s_angle * unit_step))
    else :
        backward(int(args.angle / s_angle * unit_step))

    #停止処理
    print("stop!!")
    GPIO.output(AIN1,0)
    GPIO.output(AIN2,0)
    GPIO.output(BIN1,0)
    GPIO.output(BIN2,0)
    GPIO.output(STBY,0)   

 

時計回りの回転の場合は、各ポートの状態が左から右に遷移するようにGPIOを制御します。二相励磁の場合は4ステップ周期で同じ状態となるため、step/4回分だけforループを回します。

f:id:masashi_k:20200623225820p:plain

def forward(step):
    print("move forward")
    for i in range(step/4): # stepが4周期のため4で割る
        GPIO.output(AIN1,1)
        GPIO.output(AIN2,0)
        GPIO.output(BIN1,0)
        GPIO.output(BIN2,1)
        time.sleep(wait)

        GPIO.output(AIN1,1)
        GPIO.output(AIN2,0)
        GPIO.output(BIN1,1)
        GPIO.output(BIN2,0)
        time.sleep(wait)

        GPIO.output(AIN1,0)
        GPIO.output(AIN2,1)
        GPIO.output(BIN1,1)
        GPIO.output(BIN2,0)
        time.sleep(wait)

        GPIO.output(AIN1,0)
        GPIO.output(AIN2,1)
        GPIO.output(BIN1,0)
        GPIO.output(BIN2,1)
        time.sleep(wait)

def backward(step): print("move backward") for i in range(step/4): # stepが4周期のため4で割る GPIO.output(AIN1,0) GPIO.output(AIN2,1) GPIO.output(BIN1,0) GPIO.output(BIN2,1) time.sleep(wait) GPIO.output(AIN1,0) GPIO.output(AIN2,1) GPIO.output(BIN1,1) GPIO.output(BIN2,0) time.sleep(wait) GPIO.output(AIN1,1) GPIO.output(AIN2,0) GPIO.output(BIN1,1) GPIO.output(BIN2,0) time.sleep(wait) GPIO.output(AIN1,1) GPIO.output(AIN2,0) GPIO.output(BIN1,0) GPIO.output(BIN2,1) time.sleep(wait)

動作確認

こちらのソースコードで実際にモーターが回っている様子です。角度と回転方向の指定を変えることで、自在にモーターの回転が制御できています。


ラズパイ/TB6612でステッピングモーターを動かしてみた

まとめ

ライブラリを使わずにラズパイのGPIO制御でステッピングモーターを動かすことができました。動作原理を元に制御を考え、その結果、うまく動くと理解が正しかったことが確認でき、非常に勉強になると感じました。これが自分で手を動かす強みですね。

次回はステッピングモーターを使って何か作ってみたいと思います。