Raspberry Pi・Arduino・電子工作

2021年7月11日 (日)

ケーブルクリップやらRaspberry Pi 4用ケース等を買ってみた

いろいろ買いました。

Img_0520

百均で買ったものと、Amazonから届いたものが入り混じってますが、順に紹介を。

Img_0521

これは、百均で買った結束バンド。

これ、たまに使うんですが、とうとう使い切ったので購入。それだけです。

Img_0522

で、これはケーブルクリップ。買う予定はなかったのですが、店頭で見て思わず購入。

Img_0528

と、いうのも、せっかくディスプレイアームを購入してスッキリしたはずの机の上が、このぴょんぴょんした充電ケーブル類で台無し感があったので、何とかしてやりたいとずっと思っていたところです。

Img_0531

が、こんな感じに、ディスプレイの下面に張り付けちゃえばすっきりするじゃん、と思った次第。

実際、」結構スッキリしました。

Img_0530

使うときは、こんな勢いで引っ張ればヨシ!

Img_0523

で、お次はこれ。

スマホの画面を拡大するっていう、虫眼鏡なやつです。

Img_0527

まあ、要するにこうやって……

Img_0526

スマホの画面が拡大……

とは、なかなかいきませんね。

このレンズ、中央以外はぼやけて、まるで見えません。

Img_0524

もっとも、これを買った目的は、こういう使い方をするためなんですが。

今度、会社でRaspberry Pi電子工作+Pythonな講座をやるので、それをリモート(Zoom)で流そうと思ったら、拡大しないとダメそうだなと思って買ってきたやつです。

中央に持ってくることさえ忘れなければ、どうにか使えそうです。

Img_0532

とまあ、百均の品が前座を務めてくれましたが、いよいよここで本命登場。

端にあった小さな段ボール風の箱の中身は、Raspberry Pi 4用のケースです。これだけ、Amazonでの購入品です。

Img_0533

といっても、上下をアクリル板で囲むだけの簡単なやつ。

見た目は簡素ですが、作るのはこの通り、大変そうです。

このケースにしたのは、側面に隙があった方が、何かと便利そうだったから、というだけの理由です。

早速、組み立て開始。

Img_0534

ところがこのケースの説明文、めちゃくちゃ不親切です。

1コマ目からいきなりアクリル板の表面のフィルムをはがしてから、4本のねじを立ててくれ、プリーズ!

って、書いてあるんですが……

Img_0535

その台座のねじを置こうにも、どう見てもそのままではつきません。

その下から、ねじを通さなきゃいけない模様。

ですが、このアクリル板と台座ネジの間には、どの大きさのねじを使うのかが全く書かれていないので、

Img_0536

この中から、探すしかありません。なんという、不親切な……

Img_0537

で、分かったのは、この組み合わせで使えばいいということ。

Raspberry Piの台座部分のねじは上の細いやつ、ケース上下面をつなぐ長い台座のネジには、太いねじを使う模様。

これに気づけって、素人には無理です、絶対。

Img_0538

で、あっさりした説明書とは裏腹に、実際にはまず普通のネジを穴に通して、

Img_0539

台座となるこのねじを締め付けます。

Img_0540

これを4本分、繰り返します。これでようやく、台座部分が完成。

Img_0541

で、Raspberry Pi 4を載せて、ナットで締めると、

Img_0542

その外側に、長い台座ネジを取り付け。

Img_0543

一方、上面の方には、ファンを取り付けます。

Img_0545

で、あとはこれを外側の4本のねじに止めれば

Img_0546

と、その前に、カメラのフレキシブルケーブルを取り付けます。

Img_0547

で、上面を取り付けます。

これで、ケースは完成。

Img_0548

なお、CPUのヒートシンクとファンの間は、こんな関係です。

なんだかちょっと、ファンの位置がずれてますね。

Img_0544

ちなみに、ヒートシンクが2種類ついてました。

Raspberry Pi 4用と、3B+用がついてました。

が、すでにヒートシンクがついていたため、これは使わずにとっておきます。

Img_0549

で、このケースにカメラを両面テープで固定したのち、以前、3COINSという300円均一の店で買ったスマホスタンドに取り付けます。

こんなことして、何をするのかといえば……

実はこれを使って、Raspberry Pi 4でmediapipeを動かして、人の姿勢を「数値化」してみようと思ってまして。

Img_0551

で、MediaPipeのコードを少し書き換えて、関節の一部の数値をCSVファイルとして出力するやつを作りました。

Img_0552

ちょっと見難いですが、椅子に座っている私を読み込んでおります。

数値もちゃんと、出力してました。

こいつを使って、ちょっと会社で遊んで(?)みようかと企んでます。

ということで、久々の百均 & Raspberry Piケースネタは以上です。

また何かあれば(姿勢推定の話も含め)、このブログで報告します。

Raspberry Pi 4 4B-64GB(技適マーク入)MicroSDHCカード64G/Raspbianシステムプリインストール/カードリーダ /5.1V/3A Type-C スイッチ付電源/MicroHDMI-to-HDMIケーブルライン/三つヒートシンク/簡単に取り付けケース/日本語取扱説明書/24ヶ月保証

2021年7月 2日 (金)

Raspberry Pi 4でWindows 11が動く!?

非公式、非推奨、非サポートな方法であることを承知の上で、こちらをご覧ください。

Raspberry Pi 4にARM版 Windows 11をインストールしてみる。 | Miya-Gadget

なんと、Raspberry Pi 4の4GBモデルで、Windows 11のARM版を動かしてしまったというお話。 

動画もありました。

本当に動いてますね。思ったより、スムーズです。やはり、UIがスマホに近づいたからでしょうか?

ただし、こういう無理やりハードな使い方は、たいていは実用性がないというのが落ちです。さすがにアプリケーションを使うのには不便なのではないかと。

話題としては、面白いですけどね。

しかし、Raspberry Pi 4でも動くくらいのOSなら、Surface Go 1でも動かせるようにならなかったのか……

昔は、手のひらサイズの端末でWindowsが動かせることがとてもワクワクしましたけど、今はスマホがあるためか、わざわざ小型の端末でWindowsを動かそうという気持ちにはなれないですよね。

携帯機は、AndroidやiOSで十分。キータイプが多い用途には、12~15インチサイズのノート型PCで。そういう時代です。


Microsoft Windows 10 Home April 2018 Update適用 32bit/64bit 日本語版【最新】|オンラインコード版

2021年6月27日 (日)

Jetson Nanoの固定カメラで人の動きを分析する「動線分析」をやらせてみた

Jetson Nanoでやってみたかったことの一つに、人の「動線分析」があります。

お店や工場の中で、ここはよく通る、ここはあまり人が通らないという人の動きを視覚化する、その動きを分析することでお店の商品配列や工場内の配置を変えるというのを「動線分析」と言います。

例えば、こんな感じのやつ。

Jetdetect5

こういう人が歩く場所の動画から、

Jetdetect6

こんな感じに、人が通ったところほどに色が付き、特に人が滞留しやすい場所ほど赤く表示させる(ヒートマップ化)というやつを、このJetson Nanoにやらせてみました。

(ちなみに上の画像は、Jetson Nanoの”/usr/share/visionworks/sourses/data”の中にあったデモ用の動画”pedestrians.mp4”を使って作ってます)

なお、この人検出 → ヒートマップ化するというコードが、全然ネット上に公開されていないんです。

やっぱり、動線分析って商売になるからでしょうかね。いくらググっても、動線分析のソフトやベンダーばかりが引っ掛かって見つかりません。

悔しいので、なんとか試行錯誤して無理矢理作りましたので、公開します。

あ、これ以降は、下記サイトで紹介した「Jetson Nano 2GB版+USBカメラでの物体検出」を実行済みであることが前提です。

Jetson Nano 2GBを購入しました: EeePCの軌跡

それじゃ、順番に解説。

流れとしては、1.Jetson Nanoで人検出データを作成(csvファイル) → 2.PCにてヒートマップ化 の2つです。

リアルタイムに、ヒートマップ表示できるわけではありません。また、Jetson Nanoだけで完結できませんでした。

その辺りは予め、ご了承ください。

【1.人検出データ】

Jetson Nanoで人の位置を検出させます。

そのために前回使ったDockerを使って、物体検出させて、”人”として認識されたボックスの中心座標を、CSVファイルとして出力させます。

その作業ディレクトリを作成。

ホームディレクトリの直下に「heatmap」というディレクトリを作りました。

$ mkdir heatmap

でその中に、以下のコードをエディターで書きこみ、「detect.py」という名前で保存しておきます。


import jetson.inference
import jetson.utils

net = jetson.inference.detectNet("ssd-mobilenet-v2"threshold=0.5)
camera = jetson.utils.videoSource("/dev/video0")      # '/dev/video0' for V4L2
display = jetson.utils.videoOutput("display://0"# 'my_video.mp4' for file

frg=0

with open("./xydetect.csv","w"as f:
    while display.IsStreaming():
        img = camera.Capture()
        if frg==0:
            jetson.utils.saveImageRGBA('test.png',img,img.width,img.height)
            frg=1
        detections = net.Detect(img)
        display.Render(img)
        display.SetStatus("Object Detection | Network {:.0f} FPS".format(net.GetNetworkFPS()))
        for detect in detections:
            #print(detect.ClassID,",",detect.Confidence,",",detect.Center)
            datad = str(detect.ClassID) + ","+str(detect.Confidence) + "," + str(detect.Center)
            data2 = str(detect.Center).replace("(","").replace(")","") + "\n"
            print(datad)
            if detect.ClassID==1:
                f.write(data2)

かなり無理やり感たっぷりなコードです。ご了承ください。

その後に、以下のコマンドでDockerを起動。

$ docker/run.sh --volume ~/heatmap:/heatmap

このコマンドで、先ほど作った「heatmap」というディレクトリがDocker内でもマウントされて、先ほどのプログラムコードにアクセスできます。

いや、最初、これが全然わからなくて、苦労しました。なにせ、Dockerというもの自体が初めてなもので。

で、sudoのパスワードを打ち込むと、Dockerに入ります。

そこで、すかさずディレクトリを移動。

$ cd /heatmap

と入力して、先ほど作ったディレクトリに移動します。

ここで、

$ python3 detect.py

と実行すると、USBカメラからの画像から物体を検出するコードが走り出します。

Img_0510

こんな感じです。

しばらく、うろうろします。

止めるときは、実行したターミナルで「Ctrl+C」を押します。

で、Jetson Nano内でファイルマネージャーを開き、「ホームフォルダ」の「heatmap」の中を覗くと、「test.png」「xydetect.csv」という2つのファイルができているかと思います。

test.png」・・・USBカメラの1枚目の画像

Jetdetect01

xydetect.csv」・・・人の動きの中心座標

Jetdetect2

この2つのファイルを、どうにかしてPCへ持っていきます。

私はTeratermのSSH_SCPの転送を使いましたが、USBメモリーを挿して持って行ってもOKです。

【2.PCにてヒートマップ化】

すべてJetson Nano上で完結したかったのですが、Jetson Nanoって、どういうわけかOpenCVがうまく使えないんですよね(入って入るようですが、メモリー不足のようなメッセージが出て動作せず)。

おまけにpipコマンドまで省かれているので、もう手も足も出ません。

というわけで、データ取得後から先は、PCで作業します。

うちのPCには、Python 3.7.9、ライブラリとしては、OpenCV、numpy、matplotlibが入ってます。まずは、この辺りを予め入れておいてください。

そこで、以下のコード「heatmap_detect.py」を適当なフォルダに入れておきます。


import numpy as np
import matplotlib.pyplot as plt
import csv
import math
import cv2

#画像サイズを入力
xmax = 1280
ymax = 720

x = []
y = []

with open("./xydetect.csv","r"as f:
    reader = csv.reader(f)
    for line in reader:

        x.append(math.floor(float(line[0])))
        y.append(math.floor(float(line[1])))

# 無理やり画像サイズを入れる
x.append(0)
x.append(xmax)
y.append(0)
y.append(ymax)

# numpy形式に変換
x = np.array(x)
y = np.array(y)

heatmap, xedges, yedges = np.histogram2d(y, x, bins=10)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]

heatmap = cv2.resize(heatmap, (xmax,ymax))

heatmapshow = None
heatmapshow = cv2.normalize(heatmap, heatmapshow, alpha=0beta=255norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
heatmapshow = cv2.applyColorMap(heatmapshow, cv2.COLORMAP_JET)
cv2.imshow("Heatmap", heatmapshow)
cv2.waitKey(0)

img1 = cv2.imread('test.png')

img1 = cv2.resize(img1,(xmax,ymax))

alpha = 0.5
blended = cv2.addWeighted(img1, alpha, heatmapshow, 1 - alpha, 0)

cv2.imwrite('image.png',blended)

# 結果を表示する。
plt.imshow(cv2.cvtColor(blended, cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.show()

このコードと同じ場所に、先ほどの「test.png」「xydetect.csv」を入れておきます。

そういえば、うちのカメラは「1280×720」の解像度なので、コード中のxmax、ymaxを上のようにしてますが、そこは「test.png」の解像度に合わせておいてください。

で、あとはこれを走らせます。Windows PowerShellあたりで上のコードの入ったフォルダに入った後、

$ python heatmap_detect.py

と実行するだけ。

以下のような画像(image.png)が出力されるはずです。

Jetdetect4

ちょっと見づらいですが、写真とヒートマップが重なってます。

入り口付近と、その少し横の引き戸の辺りにピークがありますね。この両方を往復していたため、こういうデータが得られたわけですが。

なお、先ほどの人検出したCSVファイルをプロットしてみると、こんな感じでした。

Jetdetect3

部屋が狭いので、ほとんど横しか動けてませんね。多少、しゃがんだりはしたのですが、もうちょっと派手に動けば、よかったですかね?

このため、ヒートマップもほぼ真横にしか色がついてません。

これが公園や、あるいは食品売り場のような、人が縦横にうろうろする場所に設置すれば、もっと面白い絵が取れるんでしょうけど。

ともかく、これで一応、目的のものは作れました。

なお、「detect.py」の5行目、


camera = jetson.utils.videoSource("/dev/video0"


camera = jetson.utils.videoSource("./pedestrians.mp4"

とすると、動画ファイルを読み込んでくれます。

また、24行目にあるif文


            if detect.ClassID==1:

のClassIDを


            if detect.ClassID==3:

1から3に変えると、「人」ではなく、「車」を検出するコードに早変わりです。

Jetdetect7

試しに、うちの隣の駐車場を写してみましたが……あまり物体検出精度が高くなくて、微妙なヒートマップができました。

正面から撮れば、もうちょっと色が付いたであろうと思われます。

こんな感じに、いろいろと使えそうです。

Raspberry Pi 4あたりと変わらないくらいの廉価なJetson Nano 2GBですが、こんなものが作れてしまうとは……正直、驚いてます。

とりあえず私はこれを使って、会社内の工場や会議場内の人の動線分析をやろうかと企んでます。

これと、以前に紹介した「Raspberry Pi 4でmediapipeを動かしてみた: EeePCの軌跡」も使えば、様々な人の動きがデータ化できます。

こうなったら、会社中をRaspberry PiとJetson Nanoだらけにしてやろうかと。

少々、使い勝手の悪いコードですが、使ってみたいという方はご自由に。


NVIDIA Jetson Nano 2GB 開発者キット

2021年6月21日 (月)

Jetson Nano 2GBを購入しました

先日、NVIDIAの人と打ち合わせる機会があって、そのNVIDIAの方の話を聞いていたら、なんだか急にJetsonが欲しくなって、思わず買ってしまいました。

Img_0444

といっても、Nanoの2GBという、Jetsonでも一番安いやつ。お値段は、7千円弱。Raspberry Pi 4とはいい勝負くらいの価格です。

Img_0448

開封すると、出てきました。2GB版Jetson Nanoの開発キット。

Img_0449

4GB版と比べると、いろいろなものが省かれてます。まず、USB-Aの端子が3つしかないし、AC電源コネクターもなし。その代わり、USB-Cコネクターがついてます。なお、その名の通り、メモリーが2GBしかありません。

もっとも、必要最小限なものはあるので、十分かと。

で、早速いじろうかと思ったのですが。

そういえばこれ、モニターどうしようか?となりまして。

Img_0445

しょうがないので、デスクトップ機で使っていたあの23インチモニターを復活させました。

う……せっかく作ったスペースが、丸つぶれに。

Img_0446

何よりも気になったのは、このUSB-HDDと干渉すること。

あと1センチ高ければ干渉しないというのに、ほんのわずかガチ当たりです。仕方がないので、机を少しスライドするなどして、モニターを無理矢理収めます。

Img_0447

いろいろと配置を変えて、どうにかすっきりさせました。

さて、この先少し、備忘録も兼ねて、Jetson Nanoのセットアップから、物体検出を動かすまでをまとめます。

以下、ちょっと長いので、簡単に目次をつけてみました。

準備

Img_0450_20210620142801

他ではあまり書かれてませんが、Jetson Nanoだけでは動きません。

まず、Jetson Nanoって、Wi-Fiがありません。

うちでは、そこらへんに転がっていたWi-Fiドングルをぶっ挿しておきました。

他にもキーボード、マウス、そしてモニター、画像認識的なことをやらせるには、さらにUSBカメラも必要です。USBが3つしかないので、キーボード、マウスは一体型のドングルを使うやつがいいですね。

また、電源にはUSB-C PD対応電源も必要。

上の画像の左端にUSB-Cコネクターがついてますが、ここに最低でも5V 3A(15W)を供給しなきゃいけないようです。

私はこの間買った、Aukey製の20WのUSB-C電源を使いました。最近はPD対応電源も安いし、iPhone/Androidスマホの急速充電もできるので、一つは持っておいた方が便利かと。

SDカードですが、UHS-I対応のmicroSDXC 64GB以上が推奨。ですが、私は東芝製32GBを使ってます。

環境インストール

(1) OS (JetPack)のインストール

まず、microSDカードにOSを入れます。

「JetPack」という、Ubuntu + 必要環境があらかじめ収められたイメージファイルを落として使うのが便利です。

このあたりは、Raspberry Piよりは親切です。

ダウンロード元は、以下。

Jetson Download Center | NVIDIA Developer

ですが、ここでひとつ注意が。

ここで「JETSON NANO DEVELOPER KITS」の「For Jetson Nano 2GB Developer Kit:」の下にある「Download the SD Card Image」を押してしまいそうになりますが、そこではなく、下にスクロールして現れる「> Jetson Nano 2GB Developer Kit SD Card Image 4.5」というのをクリックします。

普通にクリックすると最新のバージョンが4.5.1が入手できるのに対し、以下のリンク先を見る限りでは、どうやら4.5を選ばないといけない模様('21/6/21現在)。

jetson-inference/aux-docker.md at master · dusty-nv/jetson-inference · GitHub

ここを見ると、JetPack 4.5、4.4.1、4.4の3種類しか書かれてません。

いや、もしかしたら4.5.1でもいけたのかもしれませんが、やり直しとなると、とんでもなくめんどくさいことになるので、ここは無難に4.5にしておきました。

Jetnano01

なお、すごく時間がかかりました。3時間以上かかりましたかね。

ダウンロードしたZipファイルを展開し、イメージファイルを取り出します。

で、このイメージファイルを、以下のサイトから入手した「balenaEtcher」というソフトでSDカードに書き込みます。

balenaEtcher - Flash OS images to SD cards & USB drives

この時、間違ってもOSのドライブや外付けの記憶デバイスなどを選択しないように注意しましょう(一度やらかしました)。

書き込みが終わったら、Jetson Nano本体に挿し込みます。

なお、バカでかいヒートシンクの裏側辺りに、SDカードスロットがあります。そこにカチッとはめればOK。

(2) Dockerなどの導入

物体認識、物体検出を動かすための、Dockerイメージを導入します。

やり方は、先に載せたリンク「jetson-inference/aux-docker.md at master · dusty-nv/jetson-inference · GitHub 」にも載ってますが、ターミナルを開いて、以下のようなコマンドを打ち込みます。

$ git clone --recursive https://github.com/dusty-nv/jetson-inference
$ cd jetson-inference
$ docker/run.sh

最初のgit cloneに時間がかかりますが、しばらく頑張って待ちます。

なお、この「cd jetson-inferene」と「docker/run.sh」は、電源を入れなおすたびに実行が必要です(この環境を使いたい場合)。

これでとりあえず、Jetson Nano 2GB版を動作できるところまで来ました。

テスト動作(物体検出)

さて、いよいよ動作です。

USBカメラをつけた状態で、以下を実行します。

ターミナルでは「jetson-inference」というディレクトリにいると思うので、その状態で

$ cd build/aarch64/bin

と入力。

「ls」と打ち込むと、たくさんコードらしきものが入ってますが、物体検出を動かします。

動かし方は、簡単。ここで、以下のコマンド

$ ./detectnet.py /dev/video0

を入力し、Enterキー。

Img_0457

初回実行だけ、なにかを構築しているみたいで、えらく時間がかかります。が、

Jetnano02

そのうち、こんな風に物体検出の画面が出てきます。

私が「person」として認識されてますね。

Jetnano03

カメラをメインPCに向けると、見事「laptop」と「mouse」で認識されてます。

Jetnano05

なお、この状態のSurface Goも「laptop」時々「book」と認識されました。

Jetnano04

iPhone 7 Plusとダイソー製Bluetoothスピーカー。

iPhoneの「cell phone」はともかく、スピーカーの「suitcase」はなんやねん?

Jetnano06

という具合に、結構遊べます。

速度はだいたい、20fps出てますね。エッジ端末でこの速度はなかなかすごい。

ちなみにこの物体検出のアルゴリズムはSSD(Single Shot multibox Detect)+mobileNetだそうです。いわゆるYoloよりはちょっと劣る感じかなぁと。

Img_0465

そうそう、これを動かしてる最中に、このバカでかいヒートシンクに触れると、めちゃくちゃ熱いです。

多分ですが、60度以上はありそう。

安定動作には、ファンは必須でしょうね。

とまあ、お手軽Jetson Nano 2GBを入手して、お手軽に物体検出をさせてみました。

以前は、ここまで到達するだけでも一苦労だったんですけどね……楽になりました。

問題は、これをどう応用するか、なんですけどね。


NVIDIA Jetson Nano 2GB 開発者キット

2021年6月 6日 (日)

Raspberry Pi 4でmediapipeを動かしてみた

今年の1月に、姿勢推定の仕組みである「mediapipe」を使って遊んでみました。

高精度な手足、身体の姿勢推定API「MediaPipe」を使って体の姿勢を数値化させてみる: EeePCの軌跡

が、ちょっと思うところがあり、Raspberry Pi 4で動かせないかと調べていると、そのやり方が出てきました。

と、いうわけで、そのプロセスをまとめておきます。

なお、Raspberry Pi 4上でmediapipeを動かすにはOSを64ビット版にする必要があり、Raspberry Pi OSの64ビット版か、Ubuntu辺りをインストールする必要があります。

ここでは、64ビット版Raspberry Pi OSを入れます。

まず、下記サイトにアクセスします。

Index of /raspios_arm64/images

Raspi6400

あまり新しいバージョンはだめらしいので、ここでは「2020-08-24」を選びました。

ゲットしたいバージョンのフォルダーをクリックすると、その中に一つ「zip」の拡張子のがあるので、それをダウンロード。

で、その圧縮ファイルを展開すると、なかからimgファイルが出てきます。

続いて、下記のサイトから、Raspberry Pi Imagerをダウンロードしておきます。

Raspberry Pi OS – Raspberry Pi

ちょっと下の方に「Download for Windows」というのがあるので、それをクリックすると、Raspberry Pi Imagerが得られます。

インストーラーがあるので、それをインストールし、起動。

「CHOOSE OS」をクリックすると、こんな選択画面が出ますが、

Raspi6401

これをずっと下までスクロールし、一番下にある「Use custom」を選択。

Raspi6402

するとファイル選択画面が出てくるので、先ほどダウンロードしたイメージファイルを選択します。

Raspi6403

で、StrageはSDHC CARDを選択するんですが、

Img_0434

一度、痛い目にあってますからね。またUSB HDDを消されないように、抜いておきました。

Raspi6404

ドキドキしながら、書き込みを待ちます。

Raspi6405

無事、終了しました。

Img_0436

で、これを8インチのアナログディスプレイにつないで起動。

5インチのやつでは、画面表示しませんでした。仕方なく、最初は8インチで。

最初に、パスワードの設定や言語選択、Wi-Fiの設定などをした後、解像度も一番低いやつに変えておきました。

で、リブートすると、いよいよmediapipeのインストールです。

やり方は、以下のサイトを参照。

GitHub - PINTO0309/mediapipe-bin: MediaPipe Python Wheel installer for RaspberryPi OS aarch64, Ubuntu aarch64, Debian aarch64 and Jetson Nano.

ここの「2.Install」の項目から忠実に実行しました。

あ、Installには2つの項目があって、Raspberry Pi OS 64bit BrusterとUbuntu 20.04 aarch64がありますが、ここでは64bit Brusterの方を実行。

で、「3.Sample」にある通り、サンプルコードをgitで入手し、「python3 sample_hand.py」を実行。

Img_0437

動きました。先のページにある通り、だいたい8fpsくらいで動きますね。

Img_0438

ちなみに、「python3 sample_pose.py」を動かすと、上のように身体の骨格推定も動きます。こちらは、だいたい5fpsくらいでした。

まあまあの速度ですね。Raspberry Piであることを思えば、悪くありません。

Img_0439

せっかくなんで、5インチの方でも動かしてみました。

ちょっと狭いですが、かなりコンパクトになりました。ただ、なぜかWi-Fiが動きません。電力不足か?

これのよくある使い方ですが、これを工場や作業場に持ち込んで、細かい作業姿勢や手の動きを分析させてみたいなぁと思ってます。

そうなると、小型のRaspberry Piが便利なんですよね。

ただ、実際の動きと画像が1秒ほどずれるのが気がかり。ちゃんと推定できているので、問題はないんですが、画面を見ながら動くとちょっと戸惑いそうです。

Raspberry Pi 3B+ばかり使ってて、しばらく4の方はほったらかしでしたが、これを機にRaspberry Pi 4も使ってみましょうかね。

2021年5月 8日 (土)

ダイソーのBluetoothスピーカーとmicroHDMI変換コネクター買ってみた

ダイソーネタも、久々ですね。よく利用はしているんですが、あまりこちらのネタになるものはないので。

Img_0372

購入したのはこちら、HDMI→microHDMI変換ケーブルと、Bluetoothスピーカー。

なお、スピーカーの方は550円(税込み)です。

Img_0373

なんとなく、スピーカーの方が気になるので、こっちからレビュー。

Img_0374

裏面はこの通り、スイッチに、充電用microUSBコネクター、メディア用のmicroSDスロットとUSBコネクター。

Img_0375

上面は、こんな感じに4つのスイッチが搭載。

Img_0385

ぺらっとした説明書を見ると、こんな感じです。「M」はメディアの切り替えボタンで、プラスとマイナスは音量や曲送り、逆くの字のボタンは再生です。音量以外は、直接メディアを挿したときに機能するやつですね。

Img_4446

Bluetoothスピーカーなので、iPhone辺りと接続してみました。

ちょっとぼやぁ……とした音ですね。まあ、500円ですから。

ガンガン聞くには物足りないですが、動画の音くらいならこれでもOKではないかと。

ところで、説明書には「電話に出る」というのがあったのですが、マイクはなさそうです。スピーカーのみ。

ところで、結構前に買った、Anker製Bluetoothスピーカーですが。

 AnkerのBluetoothスピーカー”Anker SoundCore mini”購入: EeePCの軌跡

今、これはどうなっているのかというと、実は会社に置いてあります。

というのも、突発的に入ったリモート会議(Zoom、Teams)の時にこれ、とても便利なんです。

一応、マイクがついており、2、3人程度なら声もちゃんと拾ってくれるので、重宝してます。しかも、USBではなくてイヤホンジャックで接続できるのが手軽さの理由。

もっといいリモート会議用のスピーカーはもちろん職場にもあるんですが、わりと取り合い気味。そんなわけで、これを会社に持ち込んでいるんです。

もちろん、メーカーの使用想定外です。おすすめはしませんが。

で、話をこっちのBluetoothスピーカーに戻すと、これが手元にないので、ちょっと不便。iPhoneのスピーカーではちょっと聞きづらいというのもあって、これを買った次第です。

てことで、Bluetoothスピーカーとして使えればいいので、機能上は問題ないのですが、メディアもちょっと試してみました。

Img_0386

使ったのは、この3種類。容量がバラバラですが、microSDが32GBと256GB、USBが128GBです。

これらの中に、iTunesで買った音楽(.m4a)と、普通の音声ファイル(.mp3)を入れて再生。

Img_0387

microSDはこんな感じに、端子側を上向きにしてから挿入。カチッとはめます。

Img_0388

USBの方は、身もふたもないですね。

なお、メディアを挿した状態で電源を入れると、Bluetoothではなく、メディアモードで起動し、いきなり中の音楽を再生し始めます。

といっても、.mp3はいけましたが、.m4aはダメでした。

なお、256GBのmicroSDでもOK。先ほどの3種類、全て読めます。

さて続いて、microHDMIコネクターの方です。

Img_0380

これを買った理由はこいつ。Raspberry Pi 4です。

こいつのコネクターが標準HDMIではないので、ちょっと不便なことに。

以前、こいつ用に買った5インチモニターに変換ケーブルがあったはずなのですが、どこかに行ってしまいまして……

でもまあ、もはや国民一人に一台のRaspberry Pi(!?)ですから、こういうものもダイソーで売られるのが当然かと。

Img_0378

さっそく、使ってみます。

Img_0376

使うのはこのモニター。以前、うちのX1用に買った8インチのVGAモニター、LCD-8000Vです。

 自作デジタル8ピン-VGA変換ケーブル+Century LCD-8000Vでシャープ X1F起動成功!: EeePCの軌跡

デスクトップ機ではなくなったため、Raspberry Pi用のモニターって今、これを使うしかないんですよね。わざわざ23インチを出すのは面倒です。

Img_0377

こいつに、HDMI→VGA変換コネクターをつけて、その先にmicroHDMI変換コネクターをつけます。

大丈夫かな、そんなにつけて。

Img_0384

なお、コネクターの形状が広いため、HDMI0にはUSB-Cケーブルと干渉するため挿さらず。

仕方なく、HDMI1に挿します。

Img_0381

さっそく、電源オン。

なんだか、特に赤色が怪しげ。

Img_0382

ですが、無事に起動。

800×480という解像度ですが、起動しました。

Img_0383

変換コネクターを挟み過ぎたため、ちょっと文字が読みにくいですね。

が、許容範囲。ターミナル画面だともうちょっと見えるので、これなら問題なく使えます。

久しぶりにダイソーにてデジタルガジェットを購入しました。

ららぽーとにある3Coinsが便利で、そちらをよく使ってましたが、ダイソーもなかなかです。目が離せませんね。

Anker Soundcore mini (コンパクト Bluetoothスピーカー)【15時間連続再生 / 内蔵マイク搭載/microSDカード & FMラジオ対応】(ブラック)

2021年5月 6日 (木)

Raspberry Piでピザ窯の温度制御

秋葉原に現れたピザ屋台で、Raspberry Piを用いたピザ窯の温度制御をやっているという記事を発見。

ラズパイでピザ窯制御——秋葉原にIoTピザ屋台「おとめし」が登場 | fabcross

これによれば、このピザ屋台「おとめし」では、Raspberry Piを用いてこのピザ窯の温度制御を行っているとのこと。

焼くのは、直径20cm程度の、一人で食べきれるサイズのピザ。これを、窯内2か所の熱電対で計測した温度をもとに、Raspberry Piにて窯の温度を制御しているようです。

いずれは、音声対応、ベルトコンベヤーのオートメーション化などを考えているとのこと。

なお、単なるピザ屋台ではなく、電子工作の普及にも貢献しようと考えているようで、実際にここのRaspberry Piの回路はむき出しで公開されている模様です。なかなか、秋葉原らしいです。

ただ……とても素晴らしい屋台なのですが、非常に細かい突っ込みが一つ。

それは、この仕組みそのものを「IoT」と称しているのですが、見る限り、IoT要素が全然ないんです。

IoTとは、「Internet of Things」、つまり、モノのインターネットという意味。ですが、これがネットに接続されている雰囲気が全然ない。

電子工作としては、素晴らしいです。なにせこの熱い窯と、熱に弱いコンピューターボードとの組み合わせを実現しているんですから。

それゆえに、IoTという言葉への違和感がちょっと……いや、ほんとに細かい話なんですけどね。

もしかすると、温度制御の情報が逐一ネット上で公開されていたり、あるいはRaspberry Piはエッジ側で、クラウド上に温度制御のコードが置かれていて、そこに送信して制御してるってことなのかもしれませんが。

Raspberry Piを使ったこの手の応用の話は、なかなかユニークなものが多くて面白いですよね。こちらもそろそろ何か、考えたいものです。

LABISTS Raspberry Pi 4 4GB キット(技適マーク入)MicroSDHCカード32G/Raspbianシステムプリインストール/カードリーダ /5.1V/3A Type-C スイッチ付電源/MicroHDMI-to-HDMIケーブルライン/三つヒートシンク/簡単に取り付けケース/日本語取扱説明書(4GB RAM)

2021年3月14日 (日)

Keyestudio CCS811という二酸化炭素濃度センサーを使ってみた

今、まさにコロナ禍です。

このため職場では、部屋の換気をよくやります。

が、気温がまだ低い時期でもあるので、換気が終わればすぐに閉めたいところ。

その換気の目安に、この二酸化炭素濃度センサーが使えそうという話を聞いたので、さっそく買ってみました。

Img_0283

Keyestudio CCS811搭載のKS0457というセンサーです。

Amazonで1499円。ほかにもっと安いのがあったんですけど、Primeマーク付きじゃないところからはなるべく買わないようにしたら、これが最安値でした。

Img_0284

本当はRaspberry Piで使いたかったんですが、I2Cながらやや特殊な接続の仕方(通常のI2Cなら4本で済むところが、5本接続)だったので、Arduinoに接続して使用することに。

で、あとは下記のサイトに従って、使えるようにします。

 [電子工作]Arduinoで二酸化炭素濃度測定器を作ってみた(Vol.1)|ホッタ|note

まず、Arudinoとこのセンサーを接続。メス-メスのジャンパー線で、それぞれ

  センサー  …  Arduino

・GND …  GND

・VCC  …  5V

・SDA  …  A4

・SCL  …  A5

・WAKE  …  GND

に接続します。

続いて、メーカーの提供するコードとライブラリを以下からダウンロード。

Dropbox - KS0457 keyestudio CCS811 Carbon Dioxide Air Quality Sensor - 日常をシンプルに

得られたZipファイルを展開し、ライブラリフォルダにある「CCS811」というフォルダを丸ごとArduinoIDEのライブラリ用フォルダ(たいていはドキュメントの中の「Arduino」-「libraries」というフォルダ)に入れます。

で、ArduinoIDEを開き、以下のコードをコンパイルします。


#include <CCS811.h>

/*
 * IIC address default 0x5A, the address becomes 0x5B if the ADDR_SEL is soldered.
 */
//CCS811 sensor(&Wire, /*IIC_ADDRESS=*/0x5A);
CCS811 sensor;

void setup(void)
{
    Serial.begin(115200);
    /*Wait for the chip to be initialized completely, and then exit*/
    while(sensor.begin() != 0){
        Serial.println("failed to init chip, please check if the chip connection is fine");
        delay(1000);
    }
    /**
     * @brief Set measurement cycle
     * @param cycle:in typedef enum{
     *                  eClosed,      //Idle (Measurements are disabled in this mode)
     *                  eCycle_1s,    //Constant power mode, IAQ measurement every second
     *                  eCycle_10s,   //Pulse heating mode IAQ measurement every 10 seconds
     *                  eCycle_60s,   //Low power pulse heating mode IAQ measurement every 60 seconds
     *                  eCycle_250ms  //Constant power mode, sensor measurement every 250ms
     *                  }eCycle_t;
     */
    sensor.setMeasCycle(sensor.eCycle_250ms);
}
void loop() {
  delay(1000);
    if(sensor.checkDataReady() == true){
        //Serial.print("CO2: ");
        Serial.println(sensor.getCO2PPM());
        //Serial.print(" ppm, TVOC: ");
        //Serial.print(sensor.getTVOCPPB());
        //Serial.println(" ppb");
        
    } else {
        Serial.println("Data is not ready!");
    }
    /*!
     * @brief Set baseline
     * @param get from getBaseline.ino
     */
    sensor.writeBaseLine(0x847B);
    //delay cannot be less than measurement cycle
    //delay(1000);
}

実は元コードはCO2濃度とTVOC(総揮発性有機化合物濃度)の2種類を出力できるようになってますが、二酸化炭素濃度値しかいらないので、コメントアウトしてます。

これをArduinoIDEでArduinoに流し込んでおきます。

一方、Raspberry Pi側には、以下のコードを入れます。

co2sens_ar.py


import serial
import datetime

ser = serial.Serial('/dev/ttyACM0'115200)
date_file = datetime.datetime.now()

while True:
    with open("/home/pi/co2_{0:%Y%m%d%H%M%S}.csv".format(date_file),"a"as f:
        now = datetime.datetime.now()
        String_data = ser.readline().decode('utf-8').rstrip()
        print(now," , ",String_data)
        print(now," , ",String_data, file=f)
        f.flush()

ser.close()

シリアルポート(USB)から値を読むだけなので、簡単なコードです。

なお、二酸化炭素濃度値の前に日付・時刻をつけるようにしてます。

これを使う前に、「pip3 install pyserial」を実行しておいてください。

Img_0285

あとはRaspberry PiのUSBにArduinoを接続し、上の「co2sens_ar.py」を実行します。

> python3 co2sens_ar.py

すると、以下のようにターミナル上に日付と二酸化炭素濃度が表示されるはずです。

Co2sens01

※上はWindowsのTeraterm上

なお、このセンサーの下限値は400ppmだそうです。つまり、400ppm以下は測定不可。

実はこれを一晩ほど動かしてみましたが。

Co2sens02

新室内の二酸化炭素濃度が2260ほどまで上昇。

えっ!?ちょっと待って、これほんと!?

ちなみに、労働衛生上の基準値は1000ppm以下です。これを倍ほど上回る値をたたき出してます。

が、ここで外からRaspberry Piにアクセスできなくなってしまったため、一旦、Raspberry Piを再起動して、もう一度値を取り直してみたら……

なぜかですね……400ppmからスタートしました。

それから何度かプログラムを起動してみたんですが、どうやらこれRaspberry PiのコードがArduinoにシリアル接続するたびに「400ppm」からスタートしてるみたいです。

ううん、なんか怪しいなぁ……

でも、息を吹きかけると一時的に1000ppm程度まで上昇し、その後は下がるという挙動を示すため、確かに二酸化炭素の濃度を即手はしているような気がします。

プログラムコードを読むと、Arduino側はシリアル接続したときに値を出力している節があります。つまり、シリアル接続するまではLEDがちかちかしているものの、値を取得していなさそう……ていう理解で、いいのかな?

まあ、ともかく、換気の目安として使えるかどうかが問題。二酸化炭素濃度を正確に測定することは、今回の狙いではないので。

ということで一度、部屋を閉め切ってしばらく二酸化炭素濃度を上昇させたのちに、部屋の窓を開けて一気に換気して、値がどう動くかを調べてみました。

Img_0286

1時間ほど寝そべって過ごした後、こんな勢いで部屋の窓を開けました。ちょっと雨が降り気味な日でしたが、構わず開けてます。

で、その時のグラフが以下。

Co2sens03

ちょうどかくっと下がっているところがありますが、そこが換気した時間。

一気に二酸化炭素濃度は400程度まで下がるんですが……なぜかですね、2度ほど900ppmまで上昇しました。

何なのでしょう?ノイズでしょうか、それとも私の息でも降りかかったのか、あるいは外の排気ガスか何かでも拾ったんでしょうか……?

なんだかこのセンサー、ちょっと怪しい。

と思ったら、スイッチサイエンスのこんな記事を発見。

空気品質を測定し、記録する - AmbientでIoTをはじめよう

これを読むと、どうやらCSS811ってセンサーは、エージング(慣らし運転)というのをやらないといけない。

おまけに最初の20分は不安定らしいので、開始から20分の値はあてにしちゃだめだということのようです。

なんてこった……ということで、まず48時間のエージングというのを実施。

あまり意味はないですが、その48時間(正確には48時間と28分)の変化をグラフ化してみると、こんな感じに。

 

Ccs811_04

※ データが多すぎるので、ここだけGoogleスプレッドシートを使ってます

分かりにくいですが、最大6000ppm近くまで上昇してます。

労働安全衛生法での事務所の基準値は5000ppm以下(空調設備により調整可能ならば、1000ppm以下)とされているので、それを上回る値が出てますが……ただ、その時間が昼間の15時半ごろで、これを設置した部屋には誰もいないはずの時間。ちょっと怪しいです。

まあ、ともかく48時間のエージングを終えたので、測定を再開してみた。

Ccs811_03

30分ほど経過し、安定しているはずの時間での値は、大体600くらいになりました。

で、そのまま一晩動かした結果

Co220210312

こんな感じです。安定してますね。

ただ、逆に言うと安定しすぎ。

部屋を閉め切っているので、もうちょっと濃度が上昇してもおかしくはないかなぁと思うのですが。

とまあ、信用してよいのか悪いのか、よく分からないセンサーですね。

しばらく、様子見です。


KEYESTUDIO DC 5V CCS811 CO2 二酸化炭素 TVOC 大気質 センサー モジュール for Arduino アルドゥイーノ アルディーノ 電子工作

2021年3月 3日 (水)

4つのカメラを取り付け可能にするアダプタ「Raspberry Pi用マルチカメラアダプタ」

こんな製品があるとは、つい最近まで知りませんでしたね。

Raspberry Pi用マルチカメラアダプタ V2.2 - スイッチサイエンス

Raspberry Pi用マルチカメラアダプタという製品。バージョンが2.2ということは、既に存在していた製品だったようです。

要するに、4つのPiCameraを取り付け可能なHAT基板です。ただし、4つ同時にカメラが使えるというわけではないようです。あくまでも切り替えが可能というだけらしいですね。

使い方としては、標準、望遠、広角、赤外線を切り替える等でしょうか。あるいは前後左右をタイミングで切り替えて監視するという用途ならいけそう。たいしてケーブル長さがないカメラなので、視点切り替えとして使うには難がありそう。

無茶は承知で言いますが、同時使用が可能なら面白い画像が撮れそうですね。2眼カメラによる深度推定ができれば、色々使えそうなんですが、残念ながら同時に一つです。

こんな感じのRaspberry Pi関連の隠れた珍製品が、まだあるかもしれませんね。注意せねば。

LABISTS Raspberry Pi カメラモジュール V2 ソニーIMX219PQ CMOS画像センサ 8メガピクセル 4B、3B 、3B、2Bに対応可能

2021年2月27日 (土)

Raspberry Pi × 画像認識 で「後出しじゃんけん機」作ってみた

久しぶりの、Raspberry Piネタです。

今日は、どちらかというと「教育用」なネタです。

Raspberry Piと画像認識を応用した「後出しじゃんけん機」なるものを作ってみました。

なんじゃそら?という名前ですが、簡単に言うと、

(1) グー、チョキ、パー をラズパイカメラで撮影して記録 (各30枚づつくらい)

(2) 上の写真を用いて深層学習(CNN)を実施

(3) できたモデルを使い、カメラで撮影した「手」を推論する

(4) 推論結果に対し、勝つ手の画像を表示する

……という、Raspberry Piを使った一種のAIです。

これ、要するに、身近なもので画像認識AIを体感しよう!って趣旨の工作&プログラムです。

通常なら、Raspberry PiとPCを組み合わせるところですが、この両者の行き来が煩わしいので、学習用のデータ取りから学習、そして推論までを、すべてRaspberry Pi上でできるようにしてます。

まず、準備ですが、以下の3つのコードをコピペして持って行ってください。

「1_camera.py」


import glob
import time
import os
import io

# for Raspberry Pi
import RPi.GPIO as GPIO
import picamera

GPIO.cleanup()

#for Raspberry Pi
GPIO.setmode(GPIO.BCM)
port1 = 17 # gu
port2 = 27 # choki
port3 = 22 # pa

GPIO.setup(port1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(port2, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(port3, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

count = 1
flag = 0

# File remove
file_list = glob.glob('./data/0_gu/*')
for file in file_list:
    os.remove(file)
file_list = glob.glob('./1_choki/*')
for file in file_list:
    os.remove(file)
file_list = glob.glob('./data/2_pa/*')
for file in file_list:
    os.remove(file)

print('Ready!')

try:
    while True:
        sc = str(count)
        ssc = sc.zfill(4)
        #GPIOの17,27,22がオンになったら、画像を取り込んで認識を開始
        if GPIO.input(port1):
            label = '0_gu'
            flag = 1
        elif GPIO.input(port2):
            label = '1_choki'
            flag = 1
        elif GPIO.input(port3):
            label = '2_pa'
            flag = 1
            
        if flag ==1 :
            print(ssc + ':' + label)
            with picamera.PiCamera() as camera:
                    camera.resolution = (12896)
                    camera.start_preview()
                    camera.capture('./data/'+label+'/'+label+ssc+'.jpg')
            count +=1
            flag = 0

        time.sleep(0.01)

except KeyboardInterrupt:
    GPIO.cleanup()

「2_train.py」


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

import keras
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten
from keras.layers import Conv2D,MaxPooling2D
from keras.preprocessing.image import array_to_img,img_to_array,load_img
from keras import backend as K
from sklearn.model_selection import train_test_split
from keras.models import load_model
from keras.callbacks import ModelCheckpoint

# ======= hypter param =====
batch_size = 10
epochs = 50
# ==========================

path=os.getcwd()+'/data/'

class_count = 0
folder_list=os.listdir(path)

for folder in folder_list:

  class_count = class_count+1

NUM_CLASSES = class_count
IMAGE_SIZE = 28

# Loss
def plot_history_loss(fit):
    # Plot the loss in the history
    axL.plot(fit.history['loss'],label="loss for training")
    axL.plot(fit.history['val_loss'],label="loss for validation")
    axL.set_title('model loss')
    axL.set_xlabel('epoch')
    axL.set_ylabel('loss')
    axL.legend(bbox_to_anchor=(10), loc='lower right'borderaxespad=1fontsize=10)

# Accurascy
def plot_history_acc(fit):
    # Plot the loss in the history
    axR.plot(fit.history['acc'],label="loss for training")
    axR.plot(fit.history['val_acc'],label="loss for validation")
    axR.set_title('model accuracy')
    axR.set_xlabel('epoch')
    axR.set_ylabel('accuracy')
    axR.legend(bbox_to_anchor=(11), loc='upper right'borderaxespad=1fontsize=10)

if __name__ == '__main__':

    count=0
    folder_list = sorted(os.listdir(path))

    train_image = []
    train_label = []
    test_image = []
    test_label = []
    X = []
    Y = []

    label = 'label.txt'
    
    f = open(label, 'w')
    for folder in folder_list:
        subfolder = os.path.join(path,folder)
        file_list = sorted(os.listdir(subfolder))

        filemax = 0

        i = 0

        for file in file_list:

            i = i + 1

            img = img_to_array(load_img('./data/' + folder + '/' + file,target_size=(28,28)))
            X.append(img)
            Y.append(count)
        
        label_name = folder + ' ' + str(count) + '\n'
        f.write(label_name)

        count +=1

    X = np.asarray(X)
    Y = np.asarray(Y)
    X = X.astype('float32')
    X = X / 255.0

    Y = np_utils.to_categorical(Y, NUM_CLASSES)

    train_image, test_image, train_label, test_label = train_test_split(X,Y,test_size=0.20)
    
    f.close()
    print(u'画像読み込み終了')

    input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3)

    model = Sequential()
    model.add(Conv2D(32,kernel_size=(3,3),
                     activation='relu',
                     padding='same'
                     input_shape=input_shape))
    model.add(MaxPooling2D(pool_size=(2,2)))

    model.add(Conv2D(64, (3,3), activation='relu'padding='same'))
    model.add(MaxPooling2D(pool_size=(2,2)))

    model.add(Flatten())
    model.add(Dense(512activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(NUM_CLASSES, activation='softmax'))

    model.compile(loss=keras.losses.categorical_crossentropy,
                  optimizer=keras.optimizers.Adadelta(),
                  metrics=['accuracy']
                  )

    chkpt = './model_28.h5'
    cp_cb = ModelCheckpoint(filepath = chkpt, monitor='val_loss'verbose=1
                            save_best_only=Truemode='auto')

    history = model.fit(train_image, train_label,
              batch_size=batch_size,
              epochs=epochs,
              verbose=1,
              validation_data=(test_image, test_label),
              callbacks=[cp_cb],
              )

    model.summary()

    score = model.evaluate(test_image, test_label, verbose=0)

    fig, (axL, axR) = plt.subplots(ncols=2figsize=(10,4))

    plot_history_loss(history)
    plot_history_acc(history)

    fig.savefig('./loss_acc.png')
    plt.close()

「3_atdashi.py」


#!/usr/bin/env python

import os
import sys
import numpy as np
import tensorflow as tf

import keras
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten
from keras.layers import Conv2D,MaxPooling2D
from keras.preprocessing.image import array_to_img,img_to_array,load_img
from keras import backend as K
from sklearn.model_selection import train_test_split
from keras.models import load_model
import time

import RPi.GPIO as GPIO
import picamera

i = 0
label_name = []

label = 'label.txt'

f = open(FLAGS.label,'r')
for line in f:
  line = line.rstrip()
  l = line.rstrip()
  label_name.append(l)
  i = i + 1

NUM_CLASSES = i
IMAGE_SIZE = 28

if __name__ == '__main__':
    test_image = []
    
    # model read
    model = load_model('./model_28.h5')
    model.summary()
    
    # for Raspberry Pi

    GPIO.cleanup()

    #for Raspberry Pi
    GPIO.setmode(GPIO.BCM)
    port1 = 24 # switch

    GPIO.setup(port1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

    print('Ready!')
    try:
        while True:
            if GPIO.input(port1):
                with picamera.PiCamera() as camera:
                    camera.resolution = (12896)
                    camera.start_preview()
                    camera.capture('./tmp.jpg')
                
                img = img_to_array(load_img('./tmp.jpg' , target_size=(28,28)))
                test_image.append(img)
                test_image = np.asarray(test_image)
                test_image = test_image.astype('float32')
                test_image = test_image / 255.0
    
                predictions = model.predict_classes(test_image)
    
                print(label_name[predictions[0]], u'です。')
                
                test_image = []
            
            time.sleep(0.01)

    except KeyboardInterrupt:
        GPIO.cleanup()

事前に、TensorFlow、Keras、numpyなどのライブラリをpip3コマンドで取り込んでおいてください。

(なお、TensorFlowは1.14、kerasは2.2.4 を推奨)

で、Raspberry Piの回路図は以下の通り。4つのタクトスイッチを使います。

Atdasi01

ちょっとわかりにくいですが、各タクトスイッチにつながる線で、黒は「GPIO24」、青は「GPIO17」、黄は「GPIO27」、緑は「GPIO22」、そして赤は「3.3V」につながってます。

あ、Raspberry Pi用のカメラも当然、つないでおきます。

で、先の3つのプログラムコードを、一つのフォルダに入れます。

Atdashi02

また、同じフォルダ内に「data」という名前のフォルダを作り

Atdashi03

その下に、上のような「グー」「チョキ」「パー」に当たる3種類のフォルダを作っておきます。

Img_0159

で、我が家のRaspberry Piで組んでみました。

Img_0164

こんな感じに、4つのタクトスイッチとつないでます。

なお、カメラは5インチ液晶の裏に両面テープで張り付けておきました。

これで、準備完了です。

(1) グー、チョキ、パー をラズパイカメラで撮影して記録 (各30枚づつくらい)

まず、自分の手を使って、「グー」「チョキ」「パー」の手を集めます。

Raspberry Pi上でターミナルを開き、

> python3 1_camera.py

と入力し、実行。

ターミナル上に「Ready!」と表示されたら、準備完了です。撮影を開始します。

Raspberry Piのカメラの前で「グー」「チョキ」「パー」の3種類の手を構えて、先の回路図にあるボタン「グー」「チョキ」「パー」で、手にあったものを選んで押します。

Img_0162

すると以下のような感じに、各フォルダ内に画像がたまっていきます。

Atdashi04

各ラベルごとに、20~30枚づつくらい撮影してください。

なお、合計が100枚を超えると、Raspberry Pi 3B+ではメモリーが飛ぶっぽいです(うちは飛びました)。

(2) 上の写真を用いて深層学習(CNN)を実施

(1)で教師データができたので、学習を実行します。同じフォルダ内で、

> python3 2_train.py

と実行します。

デフォルトでは、50エポックほど流れます。大体5分くらいで終わります。

なお、この50エポック内の最良のモデルのみが保存される仕組みになっているため、学習が終わればそのまま(3)へと移行します。

が、その前に、学習の状況を確認しておきましょう。

作業フォルダー中に、「loss_acc.png」という画像ファイルができているはずです。

Loss_acc

その名の通り、Loss(損失)値とAccuracy(精度)値のグラフが出てきます。Loss値は0に近いほど、Accuracyは1に近いほど、高精度なモデルができている目安になります。

が、よく見るとどちらも2色あります。これは、学習用データでの検証値(Train)と、評価用データでの検証値(Val)となります。

簡単に言うと、上の画像のようにLoss、Accuracy共に、この両者がほぼ同じところに収束していれば、学習としてはまずまずです。

これがTrainとValとが離れていると、いわゆる”過学習”と呼ばれる状態になります。その場合は汎用性が落ちているので、パラメータを変更するなり、(1)からやり直すなりして、再学習する必要が出てきます。

(例えば、batchの値を10→20に変えてみる 等)

単純に、何も変えずにもう一度実行するだけでよくなることもあります。何度実行しても改善されないときのみ、パラメータをいじるか、あるいは写真の撮り直しを実行してみてください。

グラフを確かめてから、(3)へ行きます。

(3) できたモデルを使い、カメラで撮影した「手」を推論する

ここでは、「3_atodashi.py」を使います。

> python3 3_atodashi.py

と実行したのち、

Img_0163

こんな感じに、カメラの前で手を構えます。

もし、写真のようにチョキを出しているときに、プロンプト上で

 > 2_choki

と表示されたら、推論成功です。

グーやパーだと勘違いしていたら、学習に失敗している可能性があります。

(1)か、(2)あたりからやり直してください。

あるいは、手が近すぎる or 遠すぎることもあります。何度か実験した経験での話ですが、ちょっと動かしてみると、精度が上がる距離がどこかにあることが多いです。

さて、画像認識としてはここまでで終了ですが、これではまだ「後出しじゃんけん機」ではありませんね。

(4) 推論結果に対し、勝つ手の画像を表示する

Raspberry Piに後出しじゃんけんをさせるために、「3_atodashi.py」を以下のコードに置き換えます。


#!/usr/bin/env python

import os
import sys
import numpy as np
import tensorflow as tf
import cv2

import keras
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten
from keras.layers import Conv2D,MaxPooling2D
from keras.preprocessing.image import array_to_img,img_to_array,load_img
from keras import backend as K
from sklearn.model_selection import train_test_split
from keras.models import load_model
import time

import RPi.GPIO as GPIO
import picamera

i = 0
label_name = []

label = 'label.txt'

f = open(label,'r')
for line in f:
  line = line.rstrip()
  l = line.rstrip()
  label_name.append(l)
  i = i + 1

NUM_CLASSES = i
IMAGE_SIZE = 28

if __name__ == '__main__':
    test_image = []
    
    # model read
    model = load_model('model_28.h5')
    model.summary()
    
    # for Raspberry Pi

    GPIO.cleanup()

    #for Raspberry Pi
    GPIO.setmode(GPIO.BCM)
    port1 = 24 # switch

    GPIO.setup(port1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

    print('Ready!')
    try:
        while True:
            if GPIO.input(port1):
                with picamera.PiCamera() as camera:
                    camera.resolution = (12896)
                    camera.start_preview()
                    camera.capture('./tmp.jpg')
                
                img = img_to_array(load_img('./tmp.jpg' , target_size=(28,28)))
                test_image.append(img)
                test_image = np.asarray(test_image)
                test_image = test_image.astype('float32')
                test_image = test_image / 255.0
    
                predictions = model.predict_classes(test_image)
    
                print(label_name[predictions[0]], u'です。')

                #後出し処理
                if predictions[0] == 0# 「グー」だった時
                    reac = "pa.png" # 「パー」を返す
                elif predictions[0] == 1# 「チョキ」だった時
                    reac = "gu.png" # 「グー」を返す
                else# 残り、すなわち「パー」だった時
                    reac = "choki.png" # 「チョキ」を返す

                img = cv2.imread(reac)
                cv2.imshow("reaction",img)
                key = cv2.waitKey(1000
                cv2.destroyAllWindows()
                
                test_image = []
            
            time.sleep(0.01)

    except KeyboardInterrupt:
        GPIO.cleanup()

「#後出し処理」というコメント行がある後ろ当たりに、その後出しじゃんけんの処理が追加されてます。

また、画像表示にはOpenCVを用いてますので、pip3コマンドでOpenCVをインストールしておいてください。

> pip3 install python-opencv

で、このほかに画像が3枚、つまり、グー、チョキ、パーの画像をそろえる必要があります。

私はいらすとやで落としましたが、なんでもいいです。

Atodashi05

こんな感じに「グー」「チョキ」「パー」それぞれ「gu.png」「choki.png」「pa.png」として、Raspberry Pi上のプログラムコードの入った同じフォルダに入れておいてください。

で、この状態で(3)と同様に動かすと、推論の直後に、Raspberry Piが勝つと思う手を表示してきます。

Img_0205

こんな具合です。

(3)でそこそこの精度が出るモデルであれば、正しい「勝ち手」を出してくれるはずですね。

はい、以上が「後出しじゃんけん機」を作るまで、です。

(2)で使う「Train.py」という学習用コードを読めばわかる通り、28×28の画像で学習、推論させてます。この解像度じゃないと、Raspberry Piでは学習できません。

かなり低い解像度ですが、案外この解像度でも、じゃんけんくらいは認識できるようです。

もっとも、一筋縄ではいかないところもありますね。背景に依存しちゃったり、あるいは過学習で現実の手をうまく認識しなかったり……その辺りは、トライアンドエラーで実験してみるのがよいかと思います。

今回入れてませんが、Grad‐Camを使ったりして、モデルの確からしさを調べるのもいいですね。実際、会社にある同じコードでは、Grad-Camを組み込んでます。

どちらかというと、画像認識の理屈よりも、それを実際に使ってみてノウハウっぽいものを学ぶというのが狙いのプログラムコード。やってみると、思ったよりも精度は出ることもあるし、思い通りにいかないこともあります。

もちろん、ここに書いただけでは不十分な話が多いです。パラメータまで含めたら、一介のブログ記事では書ききれないほどいろいろあります。

このほか、精度におけるカメラと手の距離の依存性が高いので、HC-SR04などの距離センサーと組み合わせて撮影させてみるなど、電子工作の題材にもぴったりですね。

とまあ、我ながらうまい仕掛けを作ったものだと思っていたんですが。

これを作り上げた後に、「人気ブロガーからあげ先生のとにかく楽しいAI自作教室」(日経BP)という本にも、同じようにじゃんけんの手を学習させるというコードがあることが判明。

で、Kindle版で購入してみてみたんですが……あちらはRaspberry Pi上ではなく、しかも画像セットはあらかじめ準備されたものを使用、かつ、Google Colab上で動かすというものでした。いやはや、幸いにもこちらのやってることとは、かぶってませんでしたね。

まあ、似たようなことは皆さん、考えるものですね。

ちなみにこの本、画像だけでなく、自然言語など、幅広いカテゴリーの話が載っていたりと、なかなか面白い本です。買って損はありません。

著者のからあげ氏のブログは、Raspberry Pi関係のコードではとてもお世話になってますね。私もよく、参考にさせてもらってます。

以上、「画像認識を体で覚える」仕組みを作ってみた、というお話でした。


人気ブロガーからあげ先生のとにかく楽しいAI自作教室

より以前の記事一覧

当ブログ内検索

スポンサード リンク

ブログ村

無料ブログはココログ