- 投稿日:2020-10-25T21:21:06+09:00
OKI AE2100 & Node-REDでローコードIoTしてみた。その4実践編 2
Node-REDでローコードIoT実践編 2
MQTT Pub/Subはじめに
本記事はOKIの「AIエッジコンピューター」と呼ばれる AE2100 というゲートウェイ製品にNode-REDを載せて、ローコードIoTのプラットフォームにしようという記事の その4 Node-REDでローコードIoT実践編 2 MQTT Pub/Sub です。
その1 PythonでRS-485圧力センサーシミュレータ準備編
その2 DockerでローコードIoTプラットフォーム構築編
その3 Node-REDでローコードIoT実践編 1 ダッシュボード作成
その4 Node-REDでローコードIoT実践編 2 MQTT Pub/Sub (本記事)前編では RS-485入力の圧力値をNode-RED でローカルに作ったダッシュボードにリアルタイム表示しました。
もうそこまでで十分なわけですが、いちおうIoT関連の記事なので、ネットワークを利用するところまでやらせてください!こんなことやります
本編で行うのは最後の 4 MQTT Pub/Sub になります。
RS-485で受信した圧力値をクラウドに飛ばしてIoTらしいアプリに仕上げます。
使用するネットワークプロトコルはMQTTです。MQTT
Node-RED 経験者なら、ほぼ間違いなくMQTTを使ったことがあるでしょうが、ここではMQTT初心者向けに少しだけ解説します。
MQTTとは、パブリッシュ/サブスクライブ型のメッセージキューです。
サーバーであるMQTTブローカーを仲介者として、複数のクライアントがサブスクライブ(購読)とパブリッシュ(発行)に分かれ、トピック名をキーワードとしてメッセージの送受信を行います。1つのクライアントがパブリッシュとサブスクライブの両方を行うこともあります。
Node-RED ならば、MQTT、Node-RED 両方の初心者でも、設定だけで簡単に MQTT を試すことができます。なおMQTTブローカーには、test.mosquitto.org を使わせていただきます。
Node-RED フロー作成
Node-RED のフローエディタ構成を再掲しました。
ワークスペース内のフローがこれから作成するフローです。
流量制限ノード
まず上図のフローの左下に追加した "1 data/10 sec" ノードですが、これは毎秒受信する圧力センサーのデータを10秒に1回に流量制限するものです。
無料で使わせていただくMQTTブローカーに毎秒データを送りつけるのは気が引けるので、遠慮して10秒に1回にします。これをワークスペースに置いて、ダブルクリックして以下のように設定して完了します。
このように流量の制限が設定だけで簡単にできてしまいます。MQTTパブリッシュノード
次に、MQTTブローカーにセンサーデータをパブリッシュするノードを設定します。
パレットからこの mqtt out ノードを掴んで、ワークスペースにドロップします。
ダブルクリックして設定します。
まずは鉛筆アイコンをクリックして、MQTTブローカーの設定画面に移行します。下の右にある設定画面が開きます。
名前はとりあえず test.mosquitto.org としておきましょう。
サーバは今回は test.mosquitto.org と指定する必要があります。
ポートはデフォルトの1883でOKです。
クライアント名は、何でも良いのですが、ここでは AE2100 とします。
以上3つ設定したら、更新をクリックして初めの設定画面に戻ります。トピック名はMQTTブローカー内でユニークである必要があります。
今回は ae2100/pressure とすればOKでしょう。
QoSは 1 にして、少なくとも1回はMQTTブローカーにデータが届くまで(複数回届く可能性があります)パブリッシュするようにします。mqtt out の設定が完了したら、フローエディタの右上の デプロイ をクリックして保存・実行しましょう。
下図のように test.mosquitto.org ノードが 接続済 になったでしょうか?ここでは 接続済 にならなくてもOKです。後ほど別の方法をご紹介します。
次の確認にすすみましょう。フローの改造
RS485ノードと test.mosquitto.org ノードが接続済みになったので、1秒ごとにRS-485で入力される圧力データが10秒ごとにMQTTブローカーに送信されているはずです。
MQTTサブスクライブしてそのデータを受信してみましょう。
一度、MQTTブローカーにサブスクライブしておくと、以降パブリッシュがある度に、MQTTブローカーがパブリッシュされたデータを転送してくれます。本来、サブスクライブは別クライアントで行うべきでしょうが、今回は Node-RED での説明を兼ねて、今まで使ってきたフローを2つに分けて、上の図のMQTTクライアント 1と2を Node-RED の1つのワークスペース上に実装します。
上のフローはセンサーデータを収集してクラウドにアップロードするエッジゲートウェイを想定しています。
RS485ノードからの圧力データはそのままダッシュボードに表示せず、MQTTブローカーにパブリッシュするだけにします。
そして下のフローは、エッジからのデータを受信してダッシュボード表示するWebアプリケーションを想定しています。
MQTTブローカーから転送された圧力データを受信してダッシュボードに表示します。作業内容としては mqtt in ノードを新規に追加して、gauge と chart ノードをこちらにつなぎ替えるだけです。
ダッシュボードのプロットが10秒に1回になれば、MQTTサブスクライブが確認できるはずです。
MQTTサブスクライブノード
それではmqtt in ノードを追加・設定しましょう。
パレットから下図のノードをワークスペースにおいて、ダブルクリックです。
サーバにはすでに test.mosquitto.org:1883 と入っているはずです。
(違う場合、ドロップダウンメニューで選択してください。)
トピック名にはパブリッシュ側の mqtt out ノードに設定した ae2100/pressure を入力します。
出力はデフォルトの自動判定のままにして、文字列として受信するようにします。
名前は test.mosquitto.org とでもしておきましょう。最後に、RS485ノードに接続している gauge と chart のワイヤーを削除して、以下のように今追加した mqtt in ノードの右に置き、接続し直します。
以上で設定は終わりです。デプロイ をクリックして、保存・実行してみてください。
ダッシュボードに10秒に1回、圧力値がプロットされるはずです!test.mosquitto.org に接続できない!
実はこの無料サーバーは時々つながらない事があります。
このサーバーはMQTTブローカーのリファレンス実装を公開している mosquitto.org により運用されているのですが、我々MQTTクライアント側がテストのために使用する目的だけでなく、MQTTブローカー自体をテストする目的でも使われており、不安定になったり、ダウンすることがあるそうです。運悪くちょうどダウンしている場合、2,3日待ってトライしてみてください。
いや、今すぐ試してみたい!場合、このサービスはいかがでしょうか。
毎日パスワードを変更する必要がありますが、登録なしに無料で使えます。
もしくは node-red-contrib-mqtt-broker ライブラリを追加して、Node-RED上でMQTTブローカーを立ち上げる方法もあります。さいごに
AE2100 によるローコードIoT開発、いかがだったでしょうか。
今回のサンプルアプリはローコードどころか、ノーコードでできてしまいました。インタラクティブにアクセスする手段がSSHクライアントかXサーバー経由くらいしかないAE2100の使い方として、他のマシンでコンテナ内にアプリを作って、AE2100にそれをデプロイして実行するのが通常だと思います。
しかしNode-REDが動くと、AE2100で直接開発しても全く苦にならず、特に今回のシリアル通信のようなAE2100のハードウェアを使う場合、AE2100で直接開発することになるので、Node-RED は大きな助けになるはずです。
確かにNode-REDはメモリ食いだし、処理速度も速いとは言えないのですが、1秒くらいのレスポンスなら十分ではないでしょうか。
(まずNode-REDでぱぱっと作ってみて、処理速度が足りなさそうだったら、Node.jsやC言語で作り直す、というやり方もあるでしょう。)これで4編にわたる記事もようやく終わりになります。
こんなに長くなるとは思わなかったのですが、AE2100 のユーザーのみなさんに少しでもお役に立てば幸いです。
最後の最後にお約束ですが、本記事に関してOKIは何も関知しないので、記事の内容に関してOKIに問い合わせることがないようにお願いいたします。
- 投稿日:2020-10-25T17:42:36+09:00
俺的RxSwiftまとめ④(disposebagとは?)
この記事は、俺的RxSwiftまとめ①,俺的RxSwiftまとめ②,俺的RxSwiftまとめ③(Observableとは何か?)の続きです。
始め方は
subscribe
/終わらせ方はdispose bag
Observable
の特徴として、「3つのイベント(next/error/completed
)を持ち、subscribeすることでそれらの要素を取り扱えるようになる」というものがあった。
始め方はsubscribe
があるが、終わらせ方はどのようにすれば良いのか?それは、下記の3通りが考えられる。
error
イベントが流れてくるcompleted
イベントが流れてくるdisposebag
(ゴミ袋)にobservarble
を追加する今回は、3番目の
dispose bag
について詳しく記す。
disposebag
とは何なのか?
observable
をまとめて破棄するためのゴミ袋なぜ必要なのか?
observable
の中には、completed
やerror
を流したくないものがある(ex:〇〇)。その場合は、observable
に備わっているdispose
メソッドを用いて、明示的にobservable
を完了させる(≒廃棄する)必要がある。(完了させないと、永遠にsubscribe
状態になり、メモリリークの原因となってしまうため。)その際、
observable
個別にdispose
メソッドを使う代わりに、管理コストを抑えるためにobservable
をまとめて入れるdisposebag
をインスタンスのプロパティとして持たせておいて、インスタンス破棄のタイミングで全てのobservable
をdispose
することができる(ViewController
のプロパティとして持たせるパターンをよく見かける)
- 投稿日:2020-10-25T17:04:45+09:00
Flutter系の記事のまとめ
まとめ
- 自分のFlutter関連の記事を整理しました
- 自分が一番の読者なのでw
Bitfinexアプリ系
実際のアプリを通して1通りの機能を整理
- Flutter/DartでBitfinexのレンディングアプリを作る(Part1 インストールと画面作成/画面遷移)
- Flutter/DartでBitfinexのレンディングアプリを作る(Part2 stateful/データの永続化)
- Flutterの"Widget of the Week"がめっちゃ勉強になったので、まとめてみた
- Flutter/DartでBitfinexのレンディングアプリを作る(Part3 グラフ描画/ファイル分割/設計書/アイコン)
- Flutter/DartでBitfinexのレンディングアプリを作る(Part4 バックグラウンド/mbaas/テスト)
リリース系
- Flutterで作ったアプリをAndroidとiOSとWebに同時にリリースする(Android編)
- Flutterで作ったアプリをAndroidとiOSとWebに同時にリリースする(Webアプリ編)
- Flutterで作ったアプリをAndroidとiOSとWebに同時にリリースする(iOS編)
Widget系
その他系
- 投稿日:2020-10-25T16:03:06+09:00
俺的RxSwiftまとめ③(Observableとは何か?)
この記事は、俺的RxSwiftまとめ①,俺的RxSwiftまとめ②の続きです。
Observableとは何か?
observable
,observable sequence
,sequence
,stream
これらは全部同じ意味を持つ言葉で、イベントの流れである。(RxSwiftでは、sequence
ということが使われることが多い)
(引用)Everything is a sequenceなのだ。
ここでいうイベントとは、数値や自身で定義したクラスのインスタンス、タップジェスチャーなどが含まれる。
このイベントは、以下の
Enum
で定義されている。Event.swiftpublic enum Event<Element> { case next(Element) case error(Error) case completed }それぞれの
case
の場合どうなるか、Observable(sequence)
下記を表すマーブルダイアグラムとともに見ると、
next
の場合は、Observable
は次のイベントを放出する。また、error/completed
が呼ばれるまで、イベントを放出し続ける。
error
の場合はError
を放出し、observable
はイベントの放出をストップする。
completed
の場合は、observable
が停止し、イベントを放出をストップする。どうやってObservableを用いて処理をするか?
Observable
から放出されるイベントを使用するためには、subscribe
する必要がある。(Observable
はSubscribe
されるまで、イベントを放出したり、operator
での処理を行わない)
この際、イベントタイプごとに、handler
を追加することができる。
また、next
だったらElement
,error
だったらエラーインスタンスをhandler
に渡すことができる。例
下図のマーブルダイアグラムに示されたObservableについて考える
Example.swiftlet one = "1" let two = "2" let three = "3" let observable = Observable.of(one, two, three) observable.subscribe { event in print(event) }出力next(1) next(2) next(3) completed
subscribe
すると、イベントが順次扱えるようになる。
それをnext
が3回流れ、最後にobservable
の停止を意味するcompleted
が流れてくる。
- 投稿日:2020-10-25T16:03:06+09:00
俺的RxSwiftまとめ③(Observableとは何か? - その1)
この記事は、俺的RxSwiftまとめ①,俺的RxSwiftまとめ②の続きです。
Observableとは何か?
observable
,observable sequence
,sequence
,stream
これらは全部同じ意味を持つ言葉で、イベントの流れである。(RxSwiftでは、sequence
ということが使われることが多い)
(引用)Everything is a sequenceなのだ。
ここでいうイベントとは、数値や自身で定義したクラスのインスタンス、タップジェスチャーなどが含まれる。
このイベントは、以下の
Enum
で定義されている。Event.swiftpublic enum Event<Element> { case next(Element) case error(Error) case completed }それぞれの
case
の場合どうなるか、Observable(sequence)
下記を表すマーブルダイアグラムとともに見ると、
next
の場合は、Observable
は次のイベントを放出する。また、error/completed
が呼ばれるまで、イベントを放出し続ける。
error
の場合はError
を放出し、observable
はイベントの放出をストップする。
completed
の場合は、observable
が停止し、イベントを放出をストップする。どうやってObservableを用いて処理をするか?
Observable
から放出されるイベントを使用するためには、subscribe
する必要がある。(Observable
はSubscribe
されるまで、イベントを放出したり、operator
での処理を行わない)
この際、イベントタイプごとに、handler
を追加することができる。
また、next
だったらElement
,error
だったらエラーインスタンスをhandler
に渡すことができる。例
下図のマーブルダイアグラムに示されたObservableについて考える
Example.swiftlet one = "1" let two = "2" let three = "3" let observable = Observable.of(one, two, three) observable.subscribe { event in print(event) }出力next(1) next(2) next(3) completed
subscribe
すると、イベントが順次扱えるようになる。
それをnext
が3回流れ、最後にobservable
の停止を意味するcompleted
が流れてくる。
- 投稿日:2020-10-25T16:03:06+09:00
俺的RxSwiftまとめ③(Observableとは何か? )
この記事は、俺的RxSwiftまとめ①,俺的RxSwiftまとめ②の続きです。
Observableとは何か?
observable
,observable sequence
,sequence
,stream
これらは全部同じ意味を持つ言葉で、イベントの流れである。(RxSwiftでは、sequence
ということが使われることが多い)
(引用)Everything is a sequenceなのだ。
ここでいうイベントとは、数値や自身で定義したクラスのインスタンス、タップジェスチャーなどが含まれる。
このイベントは、以下の
Enum
で定義されている。Event.swiftpublic enum Event<Element> { case next(Element) case error(Error) case completed }それぞれの
case
の場合どうなるか、Observable(sequence)
下記を表すマーブルダイアグラムとともに見ると、
next
の場合は、Observable
は次のイベントを放出する。また、error/completed
が呼ばれるまで、イベントを放出し続ける。
error
の場合はError
を放出し、observable
はイベントの放出をストップする。
completed
の場合は、observable
が停止し、イベントを放出をストップする。どうやってObservableを用いて処理をするか?
Observable
から放出されるイベントを使用するためには、subscribe
する必要がある。(Observable
はSubscribe
されるまで、イベントを放出したり、operator
での処理を行わない)
この際、イベントタイプごとに、handler
を追加することができる。
また、next
だったらElement
,error
だったらエラーインスタンスをhandler
に渡すことができる。例
下図のマーブルダイアグラムに示されたObservableについて考える
Example.swiftlet one = "1" let two = "2" let three = "3" let observable = Observable.of(one, two, three) observable.subscribe { event in print(event) }出力next(1) next(2) next(3) completed
subscribe
すると、イベントが順次扱えるようになる。
それをnext
が3回流れ、最後にobservable
の停止を意味するcompleted
が流れてくる。
- 投稿日:2020-10-25T14:34:38+09:00
Core ML Toolsでレイヤーを編集する方法
Core ML Toolsを使ってモデルを変換していると、レイヤーを追加して間に計算処理を入れたり、レイヤーの一部をカットしたくなることがありますが、本稿はその方法を説明します。
Core ML Toolsは、ドキュメントも少なくネット上にも情報が少ないため、ソースコードを読みながら調べるしかないのですが、レイヤーを編集する作業はCore MLへの変換ではよくあるユースケースだと思いますので、この記事が役に立てばと思います。
モデルケース:出力の前にreshapeを入れて、出力のshapeを変更する
モデルケースとして、モデルの出力のshapeが(19)のものを、reshapeを追加して(1,1,19)にしてみます。
次元を追加しているだけなのでほとんど意味がないですが、単純なケースにしたいのでこうしています。
使用するモデルは、1桁の数字2つを与えると、それらを足した結果を予想(分類)するものを使います。分類結果は19個の配列になります。これは、1桁の数字を足すと0〜18で合計19種類の分類問題になるためです。
図にするとこんな感じです。
このモデルは以前の記事で使ったものと同じです。前回の記事は出力にラベル名まで与えていましたが、今回は単に19個の確率そのまま出力します。
Kerasで作った簡単なモデルをCoreMLを使ってiOSで動作させる
手順
それでは早速、Reshapeレイヤーを追加します。
Google Colaboratory上で作業をします。なお、今回の作業の完全なコードはこちらにあります。
https://gist.github.com/TokyoYoshida/2fada34313385d63b666253490b5f3f41.Kerasのモデルを読み込む
Kerasで作ったモデルを使用します。モデルの作成部分は、前回の記事にあるので省略します。
notebookfrom keras.models import load_model keras_model = load_model('my_model.h5')2.CoreMLに変換する
CoreMLに変換します。今回はラベルを貼らないのでシンプルにそのまま変換しています。
notebookfrom coremltools.converters import keras as converter mlmodel = converter.convert(keras_model)3.ビルダーで読み込む
Core ML ToolsのNeuralNetworkBuilderに読み込みます。
notebookimport coremltools spec = coremltools.utils.load_spec(coreml_model_path) builder = coremltools.models.neural_network.NeuralNetworkBuilder(spec=spec)モデルの情報を表示してみます。
ここから、モデルの出力はoutput1という名前で、shapeは19であることがわかります。notebookspec.description.output # 出力結果 # [name: "output1" # type { # multiArrayType { # shape: 19 # dataType: DOUBLE # } # } # ]レイヤーの情報を表示します。
notebookbuilder.layers # 出力 # ['dense_4', # 'dense_4__activation__', # 'dense_5', # 'dense_5__activation__', # 'dense_6', # 'activation_18']今回の対象は、出力レイヤーであるactivation_18です。このレイヤーの出力に対してreshapeをかけます。
4.Rehapeレイヤーを追加する
NeuralNetworkBuilderにはadd_reshapeというメソッドがあり、reshapeレイヤーを追加できます。
ここで注意点なのですが、builderを使うと直感的にはもとのモデルを表すbuilderにそのままレイヤーを追加できるような気がしていまいますが、この方法だとうまくいきません。(2020/10/27追記、元のモデルを表すbuilderにレイヤーを追加できることがわかりましたので訂正致します)例えば、先程builderに読み込んだモデルにそのままReshapeレイヤーを追加してみます。
notebookreshape = builder.add_reshape(name='Reshape', input_name='activation_18', output_name='output', target_shape=(1,1,19), mode=0)これをXcodeで読み込むとこんなエラーがでます。
XcodeのエラーThere was a problem decoding this CoreML document validator error: Layer 'Reshape' consumes an input named 'activation_18' which is not present in this network.Python上でCoreMLの推論を実行しようとするとこんなエラーになります。
PythonのエラーRuntimeError: Error compiling model: "Error reading protobuf spec. validator error: Layer 'Reshape' consumes an input named 'activation_18' which is not present in this network.".ネットで探してもなかなかわからず苦労したのですが、きっと同じことで苦労する人がいるのではと思います。
このエラーが出る理由は、input_name='activation_18'
としているところです。ここには元のモデルでbuilder.spec.description
で表示したinputもしくはoutputの名前を指定する必要があります。
対処法ですが、NeuralNetworkBuilderを使って、レイヤーが1つだけの完結したモデルを作り、それを元のモデルに追加するようにします。
(2020/10/27修正)
add_reshapeの引数input_nameが元のモデルのoutput_nameと一致していれば、うまくレイヤーをつなぎこむことができます。モデルにreshapeレイヤーを追加します。
inputは、元のモデルのoutput、つまりoutput1
を指定します。これは新しいモデルの入力であるinput2
のような気がしましたが、それだと元のモデルのoutputとreshapeレイヤーへの入力の紐付けがうまくいかないようです。
outputは、新しいモデルのoutputであるoutput2
を指定します。
target_shapeは今回変換したいshapeである(1,1,19)を指定します。notebookbuilder.add_reshape(name='Reshape', input_name='output1', output_name='output2', target_shape=(1,1,19), mode=0)これで、reshapeを追加したモデルができました。
レイヤー情報を確認します。
notebookbuilder.layers # 出力 # ['dense_4', # 'dense_4__activation__', # 'dense_5', # 'dense_5__activation__', # 'dense_6', # 'activation_18', # 'Reshape']5.モデルの出力情報を変更する
モデルの最後にreshapeレイヤーがついたわけですが、それだけだとモデルの最後のレイヤーが差し替わっただけであり、モデル全体としての出力情報は変わっていません。
試しに出力情報を確認すると、期待したshapeである(1,1,19)ではなく(19)のままであることがわかります。
notebookspec.description.output # [name: "output1" # type { # multiArrayType { # shape: 19 # dataType: DOUBLE # } # } # ]モデルの出力情報の変更は、これまたクセが強くて
builder.spec.description.output[0]
に対して代入しようとするとエラーになります。そのため、popしたりaddしたりしながら指定してきます。notebook// 出力情報を1つ削除する。今回は出力情報は1つしかなかったので、出力情報が消える builder.spec.description.output.pop() // 出力情報を1つ追加する builder.spec.description.output.add() // 出力情報の属性を指定する output = builder.spec.description.output[0] output.name = "output2" output.type.multiArrayType.dataType = coremltools.proto.FeatureTypes_pb2.ArrayFeatureType.ArrayDataType.Value('DOUBLE') // shape情報として(1,1,19)を設定する output.type.multiArrayType.shape.append(1) output.type.multiArrayType.shape.append(1) output.type.multiArrayType.shape.append(19)(補足)
上の例では、pop()で消してadd()で追加することでクリーンな状態にしてから情報を追加していましたが、もともと存在するoutputの属性値だけを直接書き換えることはできます。例えば、shapeを書き換えるのは次のようにします。
shapeを書き換える例builder.spec.description.output[0].type.multiArrayType.shape[0] = 100(補足終わり)
出力情報を確認すると、うまく設定できていることがわかります。
notebookbuilder.spec.description.output # [name: "output2" # type { # multiArrayType { # shape: 1 # shape: 1 # shape: 19 # dataType: DOUBLE # } # } # ]6.モデルの出力結果をJupyter Notebookで確認する
モデルの出力結果をMac上で動作しているJupyter Notebookで確認します。
なぜMacでJupyter Notebook?という話なんですが、Google ColaboratoryはバックエンドがLinuxなのでCoreMLの推論はできず、モデルが正しく動作しているかの確認ができません。Xcodeで推論してもよいのですが、Xcodeはおかしなモデルを読むとエラーを出してくれることもありますが、ビルド時に次のエラーを吐いていきなり落ちることもあります。
XcodeのエラーCommand CoreMLModelCompile failed with a nonzero exit codeこうしたエラーが出たときは、Jupyter Notebookで実行することでエラーの詳細の情報を確認すると良いです。
ということで、Jupyter Notebookを起動します。
こちらに完全なコードがあります。
https://gist.github.com/TokyoYoshida/632b4c8070aa6c937539e4ae261a2740モデルへの入力として[2,3]を与えて推論を実行してみます。
JupyterNoteBookcoreml_model_path= "my_model_with_builder.mlmodel" import coremltools spec = coremltools.utils.load_spec(coreml_model_path) builder = coremltools.models.neural_network.NeuralNetworkBuilder(spec=spec) mlmodel = coremltools.models.MLModel(spec) mlmodel.predict({'input1': np.array([2.0,3.0])}) # {'output2': array([[[2.56040733e-21, 7.25779588e-15, 1.99342376e-10, 1.11184195e-09, # 5.92091055e-05, 9.99939799e-01, 9.72097268e-07, 1.13292452e-14, # 4.43997455e-23, 5.00404492e-33, 0.00000000e+00, 0.00000000e+00, # 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, # 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]]])}今回のモデルは2つの要素を足し算してくれるモデルなので、5番目の要素の確率がほぼ1になっていることから正しく推論できていることがわかります。
また、出力されたarrayが
[[[確率の入った19個の配列]]]
というとなっていることから、shapeが(1,1,19)と意図通りになっていることが確認できます。7.モデルの出力結果をXcode & アプリで確認する
最後に、モデルの出力結果をXcodeに載せてアプリで確認します。
完全なコードはこちらです。
https://github.com/TokyoYoshida/CoreMLSimpleTestViewController.swiftlet model = my_model_with_builder() let inputArray = try! MLMultiArray([2,3]) let inputToModel = my_model_with_builderInput(input1: inputArray) if let prediction = try? model.prediction(input: inputToModel) { print(prediction.output2) try! print(prediction.output2.reshaped(to: [19])) } // # Double 1 x 1 x 19 array // # Double 19 vector // # [2.422891379885771e-21,7.01752566646674e-15,1.959301054732521e-10,1.089580203839091e-09,5.933549255132675e-05,0.9999396800994873,9.530076567898504e-07,1.087061586308846e-14,4.16250629238845e-23,4.617410135639087e-33,1.401298464324817e-45,1.401298464324817e-45,1.401298464324817e-45,1.401298464324817e-45,1.401298464324817e-45,1.401298464324817e-45,0,0,0] //こちらも意図通りになっていることが確認できます。
確率の数字が微妙に違うかもしれませんが、これはモデルを訓練しなおしたためなので気にしないで下さい。番外編 レイヤーを削除する方法
動作確認はしていませんが、レイヤーを削除する方法も書いておきます。
レイヤーを削除する。
notebooklayers = builder.spec.neuralNetwork.layers # レイヤーの後ろから2番めを取得(activation_18) item = layers[-2] # その要素を削除 layers.remove(item)レイヤーの情報を表示すると、削除されていることがわかります。
notebookfor layer in layers: print(layer.name) # dense_4 # dense_4__activation__ # dense_5 # dense_5__activation__ # dense_6 # Reshape最後に
NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshidaTwitterでも発信しています。
https://twitter.com/jugemjugemjugem
- 投稿日:2020-10-25T14:22:36+09:00
【Swift 】通知を出す方法について
通知を許可しているか確認
通知を出す場合、事前に通知を許可しているか確認する必要があります。
// 通知許可の取得 UNUserNotificationCenter.current().requestAuthorization( options: [.alert, .sound, .badge]){ (granted, _) in if granted{ UNUserNotificationCenter.current().delegate = self } }指定時間経過で通知を出す
○秒後に実行させる場合、
UNTimeIntervalNotificationTrigger(timeInterval: , repeats:)
で通知を作成させる必要があります。let content = UNMutableNotificationContent() content.sound = UNNotificationSound.default content.title = "タイトル" content.subtitle = "サブタイトル" content.body = "内容" // 指定時間後に実行 let timer = 10 // 通知リクエストを作成 let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(timer), repeats: false) let identifier = NSUUID().uuidString let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger) // 通知リクエストを登録 UNUserNotificationCenter.current().add(request){ (error : Error?) in if let error = error { print(error.localizedDescription) } }指定時刻になったときに通知を出す
指定時刻に実行させる場合、
UNCalendarNotificationTrigger(dateMatching: , repeats:)
で通知を作成させる必要があります。let content = UNMutableNotificationContent() content.sound = UNNotificationSound.default content.title = "タイトル" content.subtitle = "サブタイトル" content.body = "内容" // 通知時刻を指定 let date = Date() let newDate = Date(timeInterval: 60, since: date) let component = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: newDate) // リクエストを作成 let trigger = UNCalendarNotificationTrigger(dateMatching: component, repeats: false) let identifier = NSUUID().uuidString let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger) // 通知リクエストを登録 UNUserNotificationCenter.current().add(request){ (error : Error?) in if let error = error { print(error.localizedDescription) } }
- 投稿日:2020-10-25T04:03:26+09:00
VisionKitの文書スキャナーは、コンビニのマルチコピー機のスキャナーの代わりに使えるか
iPhoneのカメラで紙の文書をスキャンして、普通のスキャナーの代わりにネット上の書類のやりとりに使えるのでしょうか? 試してみました。
AppleのVisionKitを使えば、文書をスキャンできます。
斜めに撮っても、コンピュータービジョンでまっすぐな文書に直してくれます。つかいかた
import VisionKitlet documentCameraViewController = VNDocumentCameraViewController() documentCameraViewController.delegate = self present(documentCameraViewController, animated: true)ドキュメントスキャナーにプリセットされているSaveボタンで写真ライブラリに保存するコードはこちら。
Info.PlistのPrivacy Camera Additional Usage Descriptionを設定しておくことを忘れずに。// Saveボタンが押された時に呼ばれるデリゲートメソッド func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) { let image = scan.imageOfPage(at: 0) UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) }コンビニのスキャナーとの比較
1、VisionKit
2、コンビニのスキャナー
コンビニの方が圧倒的に綺麗でした。
Visionで文字読み取りする前に紙から真っ直ぐにして取り込むのとかには使えそうです。
?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-10-25T04:03:26+09:00
iPhone(VisionKit)の文書スキャナーは、コンビニのマルチコピー機のスキャナーの代わりに使えるか
iPhoneのカメラで紙の文書をスキャンして、コピー機のスキャナーの代わりに書類のやりとりに使えるのでしょうか? 試してみました。
AppleのVisionKitを使えば、文書をスキャンできます。
斜めに撮っても、コンピュータービジョンでまっすぐな文書に直してくれます。つかいかた
import VisionKitlet documentCameraViewController = VNDocumentCameraViewController() documentCameraViewController.delegate = self present(documentCameraViewController, animated: true)ドキュメントスキャナーにプリセットされているSaveボタンで写真ライブラリに保存するコードはこちら。
Info.PlistのPrivacy Camera Additional Usage Descriptionを設定しておくことを忘れずに。// Saveボタンが押された時に呼ばれるデリゲートメソッド func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) { let image = scan.imageOfPage(at: 0) UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) }コンビニのスキャナーとの比較
1、VisionKit
2、コンビニのスキャナー
コンビニの方が圧倒的に綺麗でした。
蓋をして光を当てているので、シワが全然ない。VisionKitは、Visionで文字認識する前に、紙から取り込むのとかには使えそうです。
?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-10-25T00:32:03+09:00
iOSアプリ開発:タイマーアプリ(9.プログレスバーの色をカスタマイズする)
記事
タイマーアプリを作成するためのポイントを複数記事に分けて掲載しています。
この記事では、プログレスバーに自作のグラデーションカラーを適用し、時間経過とともに色相を変化させる方法について掲載します。環境
- OS: macOS 10.15.7 (Catalina)
- エディタ: Xcode 12.1
- 言語: Swift
- 主な使用ライブラリ: SwiftUI
Gitリポジトリ
以下のGitリポジトリのURLからサンプルコードをご覧いただけます。
https://github.com/msnsk/Qiita_Timer.git手順
- プログレスバーをどういう色にするか検討する
- ProgressBarView に変化する色相(Hue)の数値を格納するプロパティを作成する
- ProgressBarView にグラデーションを生成するメソッドを作成する
- プログレスバーの Circle() の .stroke モディファイアの色の引数にメソッドを追加する
- ProgressBarView に .onReceive モディファイアを追加し、 プログレスバーの色を常に変化させる
1. プログレスバーをどういう色にするか検討する
プログレスバーの色を美しく表現するにはどうすれば良いかを考えます。これは人によって好みが別れるかもしれません。私の場合、は以下の2点が重要だと考えました。
一つは、単色ではなくグラデーションで表示すること。
もう一つは、時間経過とともに色が変化すること。
この2つを実装して、アプリのビジュアル的な印象を向上させます。2. ProgressBarView に変化する色相(Hue)の数値を格納するプロパティを作成する
グラデーションを作るには最低 2 色が必要です。この2色を自作していきます。
Swift では、色の指定方法はいくつかあります。それは一般的な色の規格とも合致しています。例えば、RGB、CMYK、HSB などです。
簡単に説明すると、RGB は Red、Green、Blue の光の三原色をそれぞれどの程度配合するかで色を表現するものです。3 色全て 100% なら白、 0% なら黒になります。
CMYK は色の三原色 + K:Black で、RGB とは逆に 3 色全て 100% なら黒、0% なら白になります。インクジェットプリンタを想像するとわかりやすいかと思います。
そして、HSB は、色相(Hue)、彩度(Saturation)、明度(Brightness)で色を表します。Hue は何色かを示し、Saturation は色味がどの程度か(0ならモノトーン)、Brightness は白の割合で 100% なら白なので色が濁りません。
最終的に色を常に変化させることを考えると、HSB ならば、Hue の値のみを変化させ、Saturation と Brightness を固定しておけば調整しやすそうですので、ここでは HSB での色指定を採用します。
それでは ProgressBarView に色相(Hue)を格納するプロパティを2色分追加します。それぞれ customHueA、customHueB とします。
HSB での色指定の場合、Color() は以下のような引数が求められます。それぞれデータ型は CGFloat 型で、最小値が 0.0、最大値が 1.0 と定められています。
Color(hue:, saturation:, brightness:)
そのため、新規に追加する Hue のプロパティの値の初期値として、ひとまず 0.5 と 0.3 をそれぞれ代入しておきます。0.5 が青緑色、 0.3 が黄緑色で、比較的近い色味にしておきます。
例えば 0.0 と 0.5 だと色相が真逆になって最終的にグラデーションにしたときに中間が濁った色になりがちです。そのため、ここでは色相の差を 0.2 にとどめておきました。ちなみに Hue 0.0 と Hue 1.0 では一周回って同じ色になります。
ProgressBarViewstruct ProgressBarView: View { @EnvironmentObject var timeManager: TimeManager @State var costomHueA = 0.5 @State var costomHueB = 0.3 var body: some View { //(省略) } }3. ProgressBarView にグラデーションを生成するメソッドを作成する
次に、Hue の値を格納した2つのプロパティから、円形プログレスバーにふさわしいグラデーションを作成するメソッドを作成していきます。
メソッドの名前は makeGradientColor としました。
引数は Double 型のデータをとる引数 hueA と hueB です。ここに先に作成したプロパティを入れる予定です。
そして、戻り値のデータ型は、円すい状グラデーションの AngularGradient です。ちなみに、Swift のグラデーションは他にも、線形グラデーション(LinearGradient)や放射状グラデーション(RadialGradient)があります。円をぐるりと一周する方向でグラデーションがかかるのが AngularGradient です。
func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient {}次に makeGradientColor メソッドの {} の中を記述していきます。まず、メソッドの {} 内の最初にグラデーションに必要な2色を作るプロパティを用意します。
ColorA は引数 hueA の値から色を作ります。ここには ProgressBarView のプロパティ costomHueA が入る予定です。同様に、ColorB は引数 hueB の値から色を作ります。こちらには costomHueB が入る予定です。
ColorA も ColorB も saturation と brightness は同じ数値を入れます。再度はやや高め、明度はかなり高めにしています。
func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient { let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9) let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9) }そして最後に、作った2色から円錐型グラデーションを作ります。AngularGradient 構造体のインスタンスを作成するわけですが、引数 gradient には イニシャライザ .init() を入れ、さらにその引数には Array でグラデーションの開始から終了までの色を入れます。
ここで以下のようにすると、円の始点と終端の重なるところで、くっきり色が分かれてしまいます。
[colorA, colorB]
ですので、以下のように、colorB からまた colorA へグラデーションをかけるようにします。
[colorA, colorB, colorA]
もっと細かく中間にも色を置いて調整することもできます。例えば以下のような具合です。ここでは2色でのグラデーションにとどめておきます。
[colorA, colorB, colorC, colorD, colorA]
func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient { let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9) let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9) let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)) return gradient }メソッドは body{} の下に記述します。
ProgressBarViewstruct ProgressBarView: View { @EnvironmentObject var timeManager: TimeManager @State var costomHueA = 1.0 @State var costomHueB = 0.5 var body: some View { //(省略) } func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient { let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9) let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9) let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)) return gradient } }4. プログレスバーの Circle() の .stroke モディファイアの色の引数にメソッドを追加する
これまでプログレスバーの円の色は .stroke モディファイアの引数で単色のシアンを指定していました。
.stroke(Color(.cyan), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))ここで、さきほど作ったメソッドを Color(.cyan) の代わりに引数として入れます。メソッドの引数 hueA と hueB には先に用意していたプロパティ costomHueA と costomHueB をそれぞれ入れます。
.stroke(self.makeGradientColor(hueA: costomHueA, hueB: costomHueB), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))コード全体としては以下のようになります。
ProgressBarViewstruct ProgressBarView: View { @EnvironmentObject var timeManager: TimeManager @State var costomHueA = 1.0 @State var costomHueB = 0.5 var body: some View { ZStack { //(背景用の円省略) //プログレスバー用の円 Circle() .trim(from: 0, to: CGFloat(self.timeManager.duration / self.timeManager.maxValue)) //メソッドで生成したグラデーションカラーを適用 .stroke(self.makeGradientColor(hueA: costomHueA, hueB: costomHueB), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round)) .scaledToFit() //輪郭線の開始位置を12時の方向にする .rotationEffect(Angle(degrees: -90)) .padding(15) } } func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient { let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9) let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9) let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)) return gradient } }5. ProgressBarView に .onReceive モディファイアを追加し、 プログレスバーの色を常に変化させる
手順4で、プログレスバーの色をプロパティの色相から指定する形でグラデーション表示にすることができました。
さらに、onReceive モディファイアで、TimeManager クラスの timer プロパティ(Timer.publish() メソッド)の発動をトリガーにして、毎 0.05 秒ごとに色相のプロパティ costomHueA と costomHueB の値を変化させて、プログレスバーの色が常に変化するようにしていきます。
0.05 秒ごとに色相の値を 0.005 ずつ足していきます。もっとゆったり色を変化させたい場合は 0.001、もっとコロコロ色が変わる方が良ければ 0.01 くらいが良いかと思います。これ以上数値を上げると、色がただチカチカして目が痛くなりそうな動きになってしまいます。
また、色相は1.0が最大値ですので、1.0になったら、0.0に戻すように if 文で記述します。
.onReceive(timeManager.timer) { _ in self.costomHueA += 0.005 if self.costomHueA >= 1.0 { self.costomHueA = 0.0 }これを costomHueB の分も同様に記述します。コード全体では以下のようになります。
ProgressBarViewstruct ProgressBarView: View { @EnvironmentObject var timeManager: TimeManager @State var costomHueA = 0.5 @State var costomHueB = 0.3 var body: some View { ZStack { //背景用の円 Circle() .stroke(Color(.darkGray), style: StrokeStyle(lineWidth: 20)) .scaledToFit() .padding(15) //プログレスバー用の円 Circle() .trim(from: 0, to: CGFloat(self.timeManager.duration / self.timeManager.maxValue)) //メソッドで生成したグラデーションカラーを適用 .stroke(self.makeGradientColor(hueA: costomHueA, hueB: costomHueB), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round)) .scaledToFit() //輪郭線の開始位置を12時の方向にする .rotationEffect(Angle(degrees: -90)) .padding(15) } //毎0.05秒ごとに発動 .onReceive(timeManager.timer) { _ in self.costomHueA += 0.005 if self.costomHueA >= 1.0 { self.costomHueA = 0.0 } self.costomHueB += 0.005 if self.costomHueB >= 1.0 { self.costomHueB = 0.0 } } } func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient { let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9) let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9) let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)) return gradient } }これで美しいグラデーションで、かつ常に色相が変化する円形プログレスバーができました。