読者です 読者をやめる 読者になる 読者になる

RaspberryPiでUSBメモリを挿したことをユーザプログラムから認識する

RaspberryPi python Ubuntu コンピュータ 開発 電子工作

RaspberryPiでUSBメモリを指したことを確認する

つい先日名古屋ではこういうイベントがありました。

geekbar.doorkeeper.jp

発表もいろいろあったので自分ももう少し作品作りをしなければという気持ちになりました。ということで次回のイベント(?)に向けて自分も作ってみることにしました。(過去の焼き直しではありますが)

今回のエントリーでは、仮面ライダーWのダブルドライバーをRaspberryPiで実現に向けたものにしようと思います。昔作ったものはPCベースだったため、ベルト化(小型化)が難しかったのですが、今回はRaspberryPiでの実装ですのでバックル化も可能になると思います。最終ターゲットは腰に巻けるようにすることになります。

Wドライバーは以下のようなものです。

Amazon CAPTCHA

簡単にいうとUSBメモリを挿し、挿したものの種類を判別して音を鳴らすというものになります。Windows版については以下を参照してください。該当の組み合わせであれば変身音がなるというものです。

www.slideshare.net

USBメモリを抜き差しを検出する。

ネットを色々と調べてみるとLinuxではudevを使用することでデバイスの接続を確認することができるようです。

itpro.nikkeibp.co.jp

これによれば

udev(userspace device management)とは,カーネルがパソコンへの接続を検出したデバイスに対して,動的に「デバイス・ファイル」を作成して割り当てるための仕組みです。 Linuxは,システムに存在するあらゆるリソースをファイルとして扱うという特徴を持っています。

事前にlsusbを使用してみる

とりあえずudevを使用する前に、接続しようとする、USBメモリの認識を確認しておきます。USBの接続状況を確認するのは恒例のlsusbコマンドになります。

【接続前の状態】

$ lsusb
Bus 001 Device 005: ID 045e:075d Microsoft Corp. LifeCam Cinema
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

【接続後の状態】

$ lsusb
Bus 001 Device 006: ID 090c:1000 Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.) Flash Drive
Bus 001 Device 005: ID 045e:075d Microsoft Corp. LifeCam Cinema
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

以下のエントリーが追加されたUSBメモリの情報になります。

Bus 001 Device 006: ID 090c:1000 Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.) Flash Drive

IDの後につづいている値がVenderIDとProductID(ModelID)になります。もう少し固有の値がほしいのですが、このコマンドではここまでしか表示できませんでした。(後からわかったのですが-vで詳細がでますので、それで詳細をみてもいいかもしれません。)

lsusbコマンドでは認識されたUSBデバイスの状況のみが確認できるため、抜き差しのタイミングでの検知はできません。

udevを使用してみる

次にudevのコマンドインターフェースであるudevadmを使ってUSBデバイスの挿抜状態をモニタリングしてみたいと思います。

udevadmのモニタリングモードでは、KERNEL側の情報とUDEV側の情報をモニタリングできます。イベントとしては接続時のaddイベント、取り外し時のremoveイベントなどが見れます。このあたりはデバイスのクラスに依存するのでその他もありますが、詳細はドキュメントに記載されています。

実際に実行してみます。

$ udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

KERNEL[1219.983660] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4 (usb)
KERNEL[1219.986476] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0 (usb)
KERNEL[1219.997083] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0 (scsi)
KERNEL[1219.997701] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/scsi_host/host0 (scsi_host)
UDEV  [1220.020340] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4 (usb)
UDEV  [1220.027171] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0 (usb)
UDEV  [1220.031344] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0 (scsi)
UDEV  [1220.035723] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/scsi_host/host0 (scsi_host)
KERNEL[1221.487617] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0 (scsi)
KERNEL[1221.487880] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0 (scsi)
KERNEL[1221.488153] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0 (scsi_disk)
KERNEL[1221.488541] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0 (scsi_device)
KERNEL[1221.490362] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0 (bsg)
UDEV  [1221.491079] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0 (scsi)
KERNEL[1221.493208] add      /devices/virtual/bdi/8:0 (bdi)
UDEV  [1221.496802] add      /devices/virtual/bdi/8:0 (bdi)
KERNEL[1221.504379] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
KERNEL[1221.505020] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1 (block)
UDEV  [1221.505249] add      /module/sg (module)
KERNEL[1221.505405] add      /module/sg (module)
UDEV  [1221.505879] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0 (scsi)
KERNEL[1221.506146] add      /class/scsi_generic (class)
KERNEL[1221.506620] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_generic/sg0 (scsi_generic)
UDEV  [1221.507040] add      /class/scsi_generic (class)
UDEV  [1221.512906] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0 (scsi_device)
UDEV  [1221.513143] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0 (scsi_disk)
UDEV  [1221.516441] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0 (bsg)
UDEV  [1221.525971] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_generic/sg0 (scsi_generic)
UDEV  [1221.589504] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
UDEV  [1221.663788] add      /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1 (block)
KERNEL[1242.032388] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0 (bsg)
KERNEL[1242.032573] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_generic/sg0 (scsi_generic)
KERNEL[1242.032678] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0 (scsi_device)
KERNEL[1242.040388] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0 (scsi_disk)
UDEV  [1242.040618] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0 (bsg)
KERNEL[1242.040797] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1 (block)
KERNEL[1242.040961] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
KERNEL[1242.041102] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0 (scsi)
UDEV  [1242.047234] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_generic/sg0 (scsi_generic)
UDEV  [1242.049023] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1 (block)
UDEV  [1242.051848] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0 (scsi_device)
UDEV  [1242.053386] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0 (scsi_disk)
UDEV  [1242.063650] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
UDEV  [1242.070194] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0/0:0:0:0 (scsi)
KERNEL[1242.074278] remove   /devices/virtual/bdi/8:0 (bdi)
KERNEL[1242.074545] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0 (scsi)
KERNEL[1242.075798] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/scsi_host/host0 (scsi_host)
UDEV  [1242.076003] remove   /devices/virtual/bdi/8:0 (bdi)
KERNEL[1242.076136] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0 (scsi)
KERNEL[1242.076318] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0 (usb)
UDEV  [1242.078805] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/target0:0:0 (scsi)
UDEV  [1242.079279] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0/scsi_host/host0 (scsi_host)
KERNEL[1242.079514] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4 (usb)
UDEV  [1242.085069] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/host0 (scsi)
UDEV  [1242.089120] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0 (usb)
UDEV  [1242.099753] remove   /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4 (usb)
KERNEL[1246.060791] add      /devices/w1_bus_master1/00-980000000000 (w1)
UDEV  [1246.064646] add      /devices/w1_bus_master1/00-980000000000 (w1)

一応、これでUSBメモリを挿抜ときの状態を確認できるようです。これをユーザープログラム上から検出できれば目的はほぼ達成となります。

udevでデバイスの挿抜状態を検出する方法としては、pythonpyudevというモジュールがあるのでこれを使用することにします。

pyudevとは

pyudev – pure Python libudev binding

https://pyudev.readthedocs.io/en/latest/

libudevの情報をpythonで取得できるようにしたモジュールになります。

pyudevをインストールする

パッケージのインストール方法に従ってモジュールのインストールをしてみます。

$ pip install pyudev
Downloading/unpacking pyudev
  Downloading pyudev-0.21.0.tar.gz (89kB): 89kB downloaded
  Running setup.py (path:/tmp/pip-build-jZcUeV/pyudev/setup.py) egg_info for package pyudev

    warning: no previously-included files matching '*.py' found under directory 'tests/.hypothesis'
    warning: no files found matching '*.c' under directory 'reproducers'
    warning: no files found matching '*.py' under directory 'reproducers'
Requirement already satisfied (use --upgrade to upgrade): six in /usr/lib/python2.7/dist-packages (from pyudev)
Installing collected packages: pyudev
  Running setup.py install for pyudev
    error: could not create '/usr/local/lib/python2.7/dist-packages/pyudev': Permission denied
    Complete output from command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-jZcUeV/pyudev/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-24vQet-record/install-record.txt --single-version-externally-managed --compile:
    running install

running build

running build_py

creating build

creating build/lib.linux-armv7l-2.7

creating build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/_qt_base.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/pyqt4.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/version.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/core.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/__init__.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/_util.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/discover.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/glib.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/_compat.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/pyqt5.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/monitor.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/wx.py -> build/lib.linux-armv7l-2.7/pyudev

copying src/pyudev/pyside.py -> build/lib.linux-armv7l-2.7/pyudev

creating build/lib.linux-armv7l-2.7/pyudev/device

copying src/pyudev/device/_device.py -> build/lib.linux-armv7l-2.7/pyudev/device

copying src/pyudev/device/_errors.py -> build/lib.linux-armv7l-2.7/pyudev/device

copying src/pyudev/device/__init__.py -> build/lib.linux-armv7l-2.7/pyudev/device

creating build/lib.linux-armv7l-2.7/pyudev/_ctypeslib

copying src/pyudev/_ctypeslib/libc.py -> build/lib.linux-armv7l-2.7/pyudev/_ctypeslib

copying src/pyudev/_ctypeslib/libudev.py -> build/lib.linux-armv7l-2.7/pyudev/_ctypeslib

copying src/pyudev/_ctypeslib/_errorcheckers.py -> build/lib.linux-armv7l-2.7/pyudev/_ctypeslib

copying src/pyudev/_ctypeslib/__init__.py -> build/lib.linux-armv7l-2.7/pyudev/_ctypeslib

copying src/pyudev/_ctypeslib/utils.py -> build/lib.linux-armv7l-2.7/pyudev/_ctypeslib

creating build/lib.linux-armv7l-2.7/pyudev/_os

copying src/pyudev/_os/pipe.py -> build/lib.linux-armv7l-2.7/pyudev/_os

copying src/pyudev/_os/poll.py -> build/lib.linux-armv7l-2.7/pyudev/_os

copying src/pyudev/_os/__init__.py -> build/lib.linux-armv7l-2.7/pyudev/_os

running install_lib

creating /usr/local/lib/python2.7/dist-packages/pyudev

error: could not create '/usr/local/lib/python2.7/dist-packages/pyudev': Permission denied

----------------------------------------
Cleaning up...
Command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-jZcUeV/pyudev/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-24vQet-record/install-record.txt --single-version-externally-managed --compile failed with error code 1 in /tmp/pip-build-jZcUeV/pyudev
Storing debug log for failure in /home/pi/.pip/pip.log

あれ?エラーです。内容としてはディレクトリが作成できない感じのメッセージなので、一般ユーザでは権限がないからなのかなと思います。 sudoつきでpipを行えば大丈夫そうなんですが、今回はapt-getでインストールしてみることにします。

一応、パッケージがあるか確認してみます。

$ apt-cache search pyudev
python-pyudev - Python bindings for libudev
python3-pyudev - Python3 bindings for libudev

aptのパッケージではVersion2系と3系があるようですので、今回は2系を入れます。(そろそろ3への以降も考えたいですが。)

$ sudo apt-get install python-pyudev
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  ax25-node libax25 libllvm3.7 openbsd-inetd
これを削除するには 'apt-get autoremove' を利用してください。
提案パッケージ:
  python-qt4 python-pyside.qtcore
以下のパッケージが新たにインストールされます:
  python-pyudev
アップグレード: 0 個、新規インストール: 1 個、削除: 0 個、保留: 161 個。
31.6 kB のアーカイブを取得する必要があります。
この操作後に追加で 193 kB のディスク容量が消費されます。
取得:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-pyudev all 0.16.1-2 [31.6 kB]
31.6 kB を 1秒 で取得しました (29.4 kB/s)
以前に未選択のパッケージ python-pyudev を選択しています。
(データベースを読み込んでいます ... 現在 130447 個のファイルとディレクトリがインストールされています。)
.../python-pyudev_0.16.1-2_all.deb を展開する準備をしています ...
python-pyudev (0.16.1-2) を展開しています...
python-pyudev (0.16.1-2) を設定しています ...

無事にインストールできました。

念のためテストしてみると…

$ python
Python 2.7.9 (default, Sep 17 2016, 20:26:04)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyudev
>>>

エラーがでていないのでインストールは無事に終わったようです。 ドキュメントに出ているサンプルを実行させてみます。

以下のサンプルは認識されているデバイスの一覧を表示させるものになります。

【sample.py】

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

import pyudev
context = pyudev.Context()
for device in context.list_devices():
    print(device)

では、実行してみます。

$ python sample.py
Device(u'/sys/devices/armv7_cortex_a7')
Device(u'/sys/devices/breakpoint')
Device(u'/sys/devices/platform/alarmtimer')
Device(u'/sys/devices/platform/clocks')
Device(u'/sys/devices/platform/clocks/clocks:clock@0')
Device(u'/sys/devices/platform/clocks/clocks:clock@1')
Device(u'/sys/devices/platform/clocks/clocks:clock@2')
Device(u'/sys/devices/platform/clocks/clocks:clock@3')
Device(u'/sys/devices/platform/clocks/clocks:clock@4')
Device(u'/sys/devices/platform/clocks/clocks:clock@5')
Device(u'/sys/devices/platform/clocks/clocks:clock@6')
Device(u'/sys/devices/platform/lirc_rpi')
(略)

udevで認識されているデバイスが大量に出てきます。 あとは、USBメモリを指したことがモニタできれば次のステップにすすめます。 udevadmのモニタ機能と同じものができればよいということになります。

メモリの細かい判別を行うために必要なパラメータとしては

  • Action(挿した抜いたなどのイベントの種類)
  • ID_MODEL_ID(デバイスのModelID)
  • ID_MODEL_FROM_DATABASE(ModelIDから得られる製品のモデル名)
  • ID_VENDOR_ID(デバイスのVentorID)
  • ID_VENDOR_FROM_DATABASE(VentorIDから得られるベンダー(会社)名)
  • ID_SERIAL_SHORT(製品の固有値の短縮形式)
  • ID_SERIAL(製品の固有値 短縮形にメーカー名、モデル名などが入っていることがあります)

以上だと思います。 ではモニタリングを行うプログラムは以下のようになります。

【pyudev_event.py】

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

import pyudev

context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='usb')
monitor.start()

for device in iter(monitor.poll, None):
  print(device.action)
  print('MODEL  : {0} -> {1}'.format(device.get('ID_MODEL_ID'), device.get('ID_MODEL_FROM_DATABASE')))
  print('VENDOR : {0} -> {1}'.format(device.get('ID_VENDOR_ID'), device.get('ID_VENDOR_FROM_DATABASE')))
  print('SERIAL : {0} -> {1}'.format(device.get('ID_SERIAL_SHORT'), device.get('ID_SERIAL')))
  print('---')

pyudevのMonitorオブジェクトを生成し、Contextを監視する様にしたのがサンプルとの大きな違いです。pyudev.Monitor.from_netlink(context) と実行しているnetlinkに関してですがman pageによれば

netlink はカーネルモジュールとユーザー空間のプロセス間で 情報をやりとりするために用いられる。

とのことですので、pyudevはこれを経由して動的にデバイス情報を得ていることになります。

実行して、USBメモリを何本か挿抜してみます。(面倒なのでシリアル値はそのままにしました…)

$ python pyudev_event.py
add
ID_MODEL : 0098 -> None
ID_VENDOR : 0411 -> BUFFALO INC. (formerly MelCo., Inc.)
SERIAL : A000000000095409 -> BUFFALO_SiliconHardDisk_A000000000095409
---
add
ID_MODEL : None -> None
ID_VENDOR : None -> BUFFALO INC. (formerly MelCo., Inc.)
SERIAL : None -> None
---
remove
ID_MODEL : None -> None
ID_VENDOR : None -> BUFFALO INC. (formerly MelCo., Inc.)
SERIAL : None -> None
---
remove
ID_MODEL : 0098 -> None
ID_VENDOR : 0411 -> BUFFALO INC. (formerly MelCo., Inc.)
SERIAL : A000000000095409 -> BUFFALO_SiliconHardDisk_A000000000095409
---
add
ID_MODEL : 1000 -> Flash Drive
ID_VENDOR : 090c -> Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.)
SERIAL : None -> SMI_Corporation_USB_DISK
---
add
ID_MODEL : None -> Flash Drive
ID_VENDOR : None -> Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.)
SERIAL : None -> None
---
remove
ID_MODEL : None -> Flash Drive
ID_VENDOR : None -> Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.)
SERIAL : None -> None
---
remove
ID_MODEL : 1000 -> Flash Drive
ID_VENDOR : 090c -> Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.)
SERIAL : None -> SMI_Corporation_USB_DISK
---
add
ID_MODEL : 5583 -> None
ID_VENDOR : 0781 -> SanDisk Corp.
SERIAL : 4C531001520507123272 -> SanDisk_Ultra_Fit_4C531001520507123272
---
add
ID_MODEL : None -> None
ID_VENDOR : None -> SanDisk Corp.
SERIAL : None -> None
---
remove
ID_MODEL : None -> None
ID_VENDOR : None -> SanDisk Corp.
SERIAL : None -> None
---
remove
ID_MODEL : 5583 -> None
ID_VENDOR : 0781 -> SanDisk Corp.
SERIAL : 4C531001520507123272 -> SanDisk_Ultra_Fit_4C531001520507123272
---
^CTraceback (most recent call last):
  File "pyudev_sample.py", line 8, in <module>
    for device in iter(monitor.poll, None):
  File "/usr/lib/python2.7/dist-packages/pyudev/monitor.py", line 340, in poll
    rlist, _, _ = select.select([self], [], [], timeout)
KeyboardInterrupt

今回の例では3メーカーのUSBメモリを挿してみたのですが、一回抜き差しをするごとに2回のイベントが表示されていました。また、USBメモリ固有値であるとされるID_SERIAL_SHORT'ID_SERIALが必ずしも入っていないことがわかりました。このあたりはメーカーのポリシーなのかもしれません。(価格が安いからとかそういう感じでもないようでした)

方針としては、値が取得できるときには判別にそれを使用し、無いときにはVenderIDとModelIDを使うことで代用したいと思います。

これでUSBの挿抜の認識の準備はできました。あともう少しですが今回はここまでにします。

おわりに

udevをつかったデバイス検出の部分と、pythonからのudev情報の呼び出し部分を作成の実験を行ってみました。後もう少しですが、次は組み合わせと音を鳴らす部分の処理を追加してプロトタイプを仕上げたいと思います。

やっているうちにもっと違う方法もあるのかなと思い始めたのですが、気が向いたらそれも試したいと考えています。

Node.jsでもOpenCVしてみる

JavaScript node.js opencv コンピュータ 開発 電子工作 RaspberryPi

Node.jsでもOpenCVしてみる

少し前のエントリではPythonを使用してOpenCVで顔認識をしていたんですが、モノによってはnode.jsでも同様なことができるかなと思いますので 実験をやってみました。

node.jsでもopencvのモジュールがあります。ただ、OpenCVは2系であることに注意です。

www.npmjs.com

github.com

以前のエントリでRaspberryPiにインストールしたのは2系ですので問題なく動作します。(version 2.4.9)

uepon.hatenadiary.com

opencvモジュールのインストール

node.jsのインストールに関しては

uepon.hatenadiary.com

こちらで取り上げましたのでRaspbianのオリジナルのNode.jsではないことに注意です。npmopencvをインストールすればOKです。

$ npm install opencv
-
> opencv@6.0.0 install /home/pi/node_opencv/node_modules/opencv
> node-pre-gyp install --fallback-to-build

make: Entering directory '/home/pi/node_opencv/node_modules/opencv/build'
  CXX(target) Release/obj.target/opencv/src/init.o
  CXX(target) Release/obj.target/opencv/src/Matrix.o
  CXX(target) Release/obj.target/opencv/src/OpenCV.o
  CXX(target) Release/obj.target/opencv/src/CascadeClassifierWrap.o
  CXX(target) Release/obj.target/opencv/src/Contours.o
  CXX(target) Release/obj.target/opencv/src/Point.o
  CXX(target) Release/obj.target/opencv/src/VideoCaptureWrap.o
  CXX(target) Release/obj.target/opencv/src/CamShift.o
  CXX(target) Release/obj.target/opencv/src/HighGUI.o
  CXX(target) Release/obj.target/opencv/src/FaceRecognizer.o
  CXX(target) Release/obj.target/opencv/src/Features2d.o
  CXX(target) Release/obj.target/opencv/src/BackgroundSubtractor.o
  CXX(target) Release/obj.target/opencv/src/Constants.o
  CXX(target) Release/obj.target/opencv/src/Calib3D.o
  CXX(target) Release/obj.target/opencv/src/ImgProc.o
  CXX(target) Release/obj.target/opencv/src/Stereo.o
  CXX(target) Release/obj.target/opencv/src/LDAWrap.o
  SOLINK_MODULE(target) Release/obj.target/opencv.node
  COPY Release/opencv.node
  COPY /home/pi/node_opencv/node_modules/opencv/build/opencv/v6.0.0/Release/node-v46-linux-arm/opencv.node
  TOUCH Release/obj.target/action_after_build.stamp
  CXX(target) Release/obj.target/test_nativemat/test/nativemat.o
  SOLINK_MODULE(target) Release/obj.target/test_nativemat.node
  COPY Release/test_nativemat.node
make: Leaving directory '/home/pi/node_opencv/node_modules/opencv/build'
opencv@6.0.0 node_modules/opencv
tqq buffers@0.1.1
tqq nan@2.6.1
mqq istanbul@0.4.5 (abbrev@1.0.9, async@1.5.2, wordwrap@1.0.0, nopt@3.0.6, esprima@2.7.3, once@1.4.0, supports-color@3.2.3, which@1.2.14, mkdirp@0.5.1, resolve@1.1.7, glob@5.0.15, js-yaml@3.8.3, escodegen@1.8.1, handlebars@4.0.6)

比較的長い時間がかかります。(RaspberryPi2では10分ちょっとかかっています) これが終わるといよいよOpenCVの世界に入れます。

静止画像の顔認識

まずは静止画に含まれる顔認識を行ってみます。 認識をする画像はinput.jpg解析に成功した場合には、 顔と思われる部分に赤色の四角形を描画し、output.jpgとして画像を出力します。

若干書きぶりが異なりますが、なんとなくは同じ様です。oencvパッケージに含まれるサンプルを参考にしているので分類機の読み込みが異なるようです。

CascadeClassifierdetectMultiScaleでの呼び出しではなく、detectObjectで引数での呼び出しとなっています。

OpenCVサンプルフォルダ】 github.com

【face-detect.js】

var cv = require('opencv');

var RED = [0, 0, 255];
var THICKNESS = 2;

cv.readImage("./input.jpg", function(err, im){
  if (err) throw err;
  if (im.width() < 1 || im.height() < 1) throw new Error('error:画像が不正の様です。');

  im.detectObject("./haarcascade_frontalface_alt.xml", {}, function(err, faces){
    for (var i=0;i<faces.length; i++){
      var face = faces[i]
      im.rectangle([face.x, face.y], [face.width, face.height], RED, THICKNESS);
    }
    console.log('イメージを保存しました');
    im.save('./output.jpg');
  });
})

実行のためには、あらかじめプログラムと同じディレクトリにhaarcascade_frontalface_alt.xmlなど分類機設定のファイルがないとエラーになりますので注意してください。

実行させると…

$ node.js face-detect.js

【input画像】 f:id:ueponx:20170410232450j:plain ライセンス: CC0 Public DomainCreative Commons — CC0 1.0 Universal

【output画像】 f:id:ueponx:20170410232511j:plain

Webカメラの画像の取り込み

カメラ画像の取り込みに関しては基本的にpythonと違いはありません。 OpenCVGUIを使えますが、node.js経由でも同様にGUIを使用することができます。

【camera.js】

var cv = require('opencv');

try {
  var camera = new cv.VideoCapture(0);
  var window = new cv.NamedWindow('Video', 0)

  setInterval(function() {
    camera.read(function(err, im) {
      if (err) throw err;
      console.log(im.size())
      if (im.size()[0] > 0 && im.size()[1] > 0){
        window.show(im);
      }
      window.blockingWaitKey(0, 50);
    });
  }, 20);
  
} catch (e){
  console.log("Couldn't start camera:", e)
}

実行させると…

$ node.js camera.js

【動作画像】 f:id:ueponx:20170410232605j:plain

多少のアラームは発生するのですが問題なく動作しました。使用したWebカメラMicrosoftのLifeCamになります。(以前の動作までに少し時間がかかる挙動に関係するのだと思います)あとpythonからの呼び出しとは異なってデフォルト解像度は320*240ぽいですね。

Webカメラで顔認識を行う

あとはこれら2つを組み合わせていきます。

【face-detect-cam.js】

var cv = require('opencv');

var RED = [0, 0, 255];
var THICKNESS = 2;

try {
  var camera = new cv.VideoCapture(0);
  var window = new cv.NamedWindow('Video', 0);

  camera.setWidth(480);
  camera.setHeight(320);

  setInterval(function() {
    camera.read(function(err, im) {
      if (err) throw err;

      if (im.size()[0] > 0 && im.size()[1] > 0){
        im.detectObject("./haarcascade_frontalface_alt.xml", {}, function(err, faces){
          for (var i=0;i<faces.length; i++){
            var face = faces[i]
            im.rectangle([face.x, face.y], [face.width, face.height], RED, THICKNESS);
          }
          window.show(im);
        });
        window.blockingWaitKey(0, 50);
      }
    });
  }, 500);

} catch (e){
  console.log("Couldn't start camera:", e)
}

実行させると…

$ node.js face-detect-cam.js

【動作画像】

f:id:ueponx:20170411094557j:plain

このコードでは動作に関してはかなり遅いと思います。遅延が3秒ほどありそうでした。

今回はsetInterval()を使って周期処理を行っているのですが、直感的には whileでループを回せばいいのかなと思ったんですが、うまくいきません。 javascriptsleep()とかwait()が無いことに起因してるかなと思います。

あと、setInterval()の周期のパラメータを小さくすると結構な確率でSegmentation faultが発生するので500msec以上にしたほうがいいかなと思います。

終わりに

一応、node.jsでOpenCVを使ったプログラムを組んでみました。今後そういうこともあるかなとは思います。RaspberryPi2では少しパワー不足という印象がありますが、ホストが一般的なPCならもっといいパフォーマンスをだせるのかなと思います。

これが限界なのかなと色々おもっていたのですが、Xを使ってリモートで接続していることに気が付きました。

ダイレクトにモニタに接続するとsetInterval()の周期のパラメータを250程度まで小さくしてもSegmentation faultもほとんど発生せず、遅延も2秒程度まで追い込めました。

ちなみにファイルに一度保存したほうが早いのではないかと思って、以下のコードを書いてみましたが、全然早くはならず、逆に処理が追いつかずSegmentation faultの確率が上がったように感じます。こちらはディスプレイの直接接続でもSegmentation faultが発生するようでした。

var cv = require('opencv');

var RED = [0, 0, 255];
var THICKNESS = 2;

try {
  var camera = new cv.VideoCapture(0);
  var window = new cv.NamedWindow('Video', 0);

  camera.setWidth(480);
  camera.setHeight(320);

  setInterval(function() {
    camera.read(function(err, im) {
      if (err) throw err;

      if (im.size()[0] > 0 && im.size()[1] > 0){
        im.detectObject("./haarcascade_frontalface_alt.xml", {}, function(err, faces){
          for (var i=0;i<faces.length; i++){
            var face = faces[i]
            im.rectangle([face.x, face.y], [face.width, face.height], RED, THICKNESS);
          }
          im.save('./tmp.jpg');
        });
        cv.readImage("./tmp.jpg", function(err2, im2){
          if (err2) throw err2;
          
          if (im2.size()[0] > 0 && im2.size()[1] > 0){
            window.show(im2);
          }
        });
        window.blockingWaitKey(0, 50);
      }
    });
  }, 500);

} catch (e){
  console.log("Couldn't start camera:", e)
}

RaspberryPiでL-02Cを使ってSORACOM Airに接続する

RaspberryPi コンピュータ サービス

RaspberryPiでL-02Cを使ってSORACOM Airに接続する

仕事が落ち着いたのでいろいろやりたいことをぼちぼちと行っています。

昨年、会社の移転に伴い、廃棄物品の中にL-02Cがあったのでいただくことにしました。(一応、ご自由にどうぞということだったので)

docomo L-02C レッド(R) データ通信専用機種

L-02Cはdocomoさんの商品ですが、実際はLGさんの商品になります。

f:id:ueponx:20170408115404j:plain

今回はSORACOM AirのSIMを使って接続を行っていこうと思います。SIMは2016年のSoftwareDesign5月号の付録についていたものをアクティベートしていたのでそれを使う予定でした。ただ、SIMサイズが合わなかったので以下のアダプタで対応しました。

安!

f:id:ueponx:20170408115336j:plain

こんな感じで装着します。

今回は以下のRaspberryPiの環境で行っています。

$ uname -a
Linux raspberrypi 4.4.34-v7+ #930 SMP Wed Nov 23 15:20:41 GMT 2016 armv7l GNU/Linux

また、以下の記事を参考にしています。

www.mana-cat.com

SORACOM AirのSIM設定を事前にしておく。

今回はアクティベート済みのSIMなのですが、念のためSORACOM Airのユーザーコンソールで確認しておきます。

soracom.jp

ユーザーコンソールへ

f:id:ueponx:20170408111602j:plain

ユーザーコンソールでSIMの状況を確認

f:id:ueponx:20170408111653j:plain

大丈夫そうですね。

L-02CをRaspberryPiに接続する

L-02Cが意外と大きいのでその他のUSB機器とコネクタの干渉をしてしまうので延長ケーブルを経由したほうが無難です。

USBに指すとdmesgの結果が以下のように変化しました。ネットで出ている情報ではL-02CはCDROMドライブとして認識されるということだったのですが、今回はCDROMにはなっていませんでした。なぜ?

L-02Cはドライバーなどを本体に持っている機器のため、そのまま接続するとCDROMドライブとして見え、ドライバインストール後はモデムとしてみえるタイプのデバイスの様です。

$ dmesg |tail
[ 6026.677568] option 1-1.4:1.0: GSM modem (1-port) converter detected
[ 6026.677996] usb 1-1.4: GSM modem (1-port) converter now attached to ttyUSB0
[ 6026.678756] option 1-1.4:1.1: GSM modem (1-port) converter detected
[ 6026.679132] usb 1-1.4: GSM modem (1-port) converter now attached to ttyUSB1
[ 6026.679870] option 1-1.4:1.2: GSM modem (1-port) converter detected
[ 6026.680318] usb 1-1.4: GSM modem (1-port) converter now attached to ttyUSB2
[ 6026.681146] option 1-1.4:1.3: GSM modem (1-port) converter detected
[ 6026.681537] usb 1-1.4: GSM modem (1-port) converter now attached to ttyUSB3
[ 6027.411662] w1_master_driver w1_bus_master1: Family 0 for 00.3e0000000000.a1 is not registered.
[ 6086.671765] w1_master_driver w1_bus_master1: Family 0 for 00.be0000000000.2d is not registered.

たまたま何でしょうか?あるいはOSのバージョンアップなどの要因でしょうか?会社で使っているときの設定でなにかあったのでしょうか。

正解はUSBシリアルとして認識されることなので問題はないのですが…。 念のためlsusbコマンドでも確認します。

【接続前】

$ lsusb
Bus 001 Device 005: ID 045e:075d Microsoft Corp. LifeCam Cinema
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

【接続後】

$ lsusb
Bus 001 Device 006: ID 1004:61dd LG Electronics, Inc.
Bus 001 Device 005: ID 045e:075d Microsoft Corp. LifeCam Cinema
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Bus 001 Device 006: ID 1004:61dd LG Electronics, Inc.のエントリが追加されているので正常に認識されているようです。

wvdialejectをパッケージをインストール

あとはUSBモデムのダイヤラーであるwvdialパッケージをインストールします。 今回はCDROMとして認識されていないので必要ないのですがejectパッケージは念のためインストールします。

$ sudo apt-get update
$ sudo apt-get install wvdial eject
()
Success!  You can run "wvdial" to connect to the internet.
  (You can also change your configuration by editing /etc/wvdial.conf)
()

CDROMとして認識されているようであれば sudo eject sr0を実行してください。

$ sudo modprobe usbserial vendor=0x1004 product=0x618f
$ sudo eject sr0
$ sudo chmod 666 /dev/ttyUSB*

これでインストールは完了しました。

wvdialの接続設定を行う

インストール時にも表示されましたが

Success! You can run “wvdial” to connect to the internet. (You can also change your configuration by editing /etc/wvdial.conf)

とのことですので/etc/wvdial.confの設定を行います。

$ sudo vim /etc/wvdial.conf

【インストール後のオリジナル設定ファイル】

[Dialer Defaults]
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Modem Type = Analog Modem
Baud = 9600
New PPPD = yes
Modem = /dev/ttyUSB2
ISDN = 0
; Phone = <Target Phone Number>
; Password = <Your Password>
; Username = <Your Login Name>

【変更後】

[Dialer Defaults]
Init1 = ATZ
Init2 = ATH
Init3 = AT+CGDCONT=1,"IP","soracom.io"
Init4 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Dial Attempts = 3
Stupid Mode = 1
Modem Type = Analog Modem
Dial Command = ATD
New PPPD = yes
APN = soracom.io
Modem = /dev/ttyUSB2
Baud = 460800
ISDN = 0
Phone = *99***1#
Username = sora
Password = sora
Carrier Check = no

内容は初期化するためのATコマンドやダイアル先、スピード、認証、APNなどになります。SORACOM Airはほぼ同じ設定になるかと思います。

SORACOM Airで接続

すべての設定が終わったので後は接続テストになります。以下で実行することになりますがコンソールが占領されるので&をつけるか、もう一つコンソールを起動してください。自分はコンソールもう一つ起動しています。

$ sudo wvdial
--> WvDial: Internet dialer version 1.61
--> Initializing modem.
--> Sending: ATZ
ATZ
OK
--> Sending: ATH
ATH
OK
--> Sending: AT+CGDCONT=1,"IP","soracom.io"
AT+CGDCONT=1,"IP","soracom.io"
OK
--> Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
OK
--> Modem initialized.
--> Sending: ATD*99***1#
--> Waiting for carrier.
ATD*99***1#
CONNECT
--> Carrier detected.  Starting PPP immediately.
--> Starting pppd at Sat Apr  8 10:04:58 2017
--> Pid of pppd: 29671
--> Using interface ppp0
--> pppd: ー[01]黐・5
--> pppd: ー[01]黐・5
--> pppd: ー[01]黐・5
--> pppd: ー[01]黐・5
--> pppd: ー[01]黐・5
--> pppd: ー[01]黐・5
--> local  IP address ???.???.???.???
--> pppd: ー[01]黐・5
--> remote IP address ???.???.???.???
--> pppd: ー[01]黐・5
--> primary   DNS address ???.???.???.???
--> pppd: ー[01]黐・5
--> secondary DNS address ???.???.???.???
--> pppd: ー[01]黐・5

接続されるとpppのインターフェースにIPアドレスDNSなどが設定されてネットワーク接続ができるようになります。

接続状況の確認

ipコマンドで確認するとppp0インターフェースが追加されていることがわかります。

【接続前のネットワークインターフェース】

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    (略)
       valid_lft forever preferred_lft forever
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    (略)
       valid_lft forever preferred_lft forever

【接続後のネットワークインターフェース】

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    (略)
       valid_lft forever preferred_lft forever
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    (略)
       valid_lft forever preferred_lft forever
4: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 3
    link/ppp
    inet ???.???.???.??? peer ???.???.???.???/?? scope global ppp0
       valid_lft forever preferred_lft forever

4番目のエントリにppp0インターフェースが追加されています。

疎通確認

ローカルIPに対してのPING実行を行う

???.???.???.???の部分は接続後のipコマンドから調べて置き換えてください。

$ ping ???.???.???.???
PING ???.???.???.??? (???.???.???.???) 56(84) bytes of data.
64 bytes from ???.???.???.???: icmp_seq=1 ttl=64 time=0.169 ms
64 bytes from ???.???.???.???: icmp_seq=2 ttl=64 time=0.098 ms
64 bytes from ???.???.???.???: icmp_seq=3 ttl=64 time=0.115 ms
64 bytes from ???.???.???.???: icmp_seq=4 ttl=64 time=0.112 ms
64 bytes from ???.???.???.???: icmp_seq=5 ttl=64 time=0.114 ms
^C
--- ???.???.???.??? ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 3999ms
rtt min/avg/max/mdev = 0.098/0.121/0.169/0.027 ms

ローカル側のpingも成功しています。

ppp0インターフェースを経由してのPING実行

googleDNSpingを飛ばしてみます。

$ ping -I ppp0 8.8.8.8
PING 8.8.8.8 (8.8.8.8) from ???.???.???.??? ppp0: 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=55 time=68.2 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=55 time=70.8 ms
^C
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 68.234/69.549/70.865/1.341 ms

無事に接続できました。

終わりに

折角アクティベートしていたSORACOM AirのSIMがやっと活きました。作品のデモを行う展示会などではWiFiやBulethoothが入らない事があるので通信がうまくいかないことも多いのですが、USBモデムとSORACOM Airがあることで回避策を一つ追加して保険がかけられるようになるのかなと思って期待しています。

RaspberryPiでOpenCVを使って顔認識を行ってみる

python コンピュータ 電子工作 開発 opencv

RaspberryPiでOpenCVを使って顔認識を行ってみる

ようやくやりたかったOpenCVを使った顔認識を行ってみます。MicrosoftさんやIBMさんのサービスなどのWebサービスで顔認識ができるのでそれほど必須というわけでは無いのですが、ネットワークサービスが使えない場合や、回数制限などがあって頻繁にWebAPIを叩けない場合にはローカルの処理でもそこそこ似たような動作ができると助かる局面はあると思います。

例えば、顔認識はローカルのOpenCVで行って、顔が検知できたらWebAPIにアクセスすることで表情の解析を行うというような場合です。以前作成した「ヒミツのくまちゃん」 では、周期的にMicrosoftEmotionAPIを使用しているので、APIの回数制限のため長期間は動作できないことになります。そこで顔を見つけるまではローカル処理でそれなりに対応することで、WebAPIへのアクセス回数を減らすことができるようになります。

【参考】 uepon.hatenadiary.com

以前の記事でOpenCVをインストールしていることを前提にします。

uepon.hatenadiary.com

顔認識を行う。

かなり行われていることなのでネットの情報もかなり充実しています。自分も他のかたのブログなどを参考させていただきます。

自分はこちらを参考にさせていただきました。その為、今回のエントリーでは自分がやったことのメモという扱いになります。

famirror.hateblo.jp

顔認識の流れ

顔認識では大まかに以下のような処理を行います。

  1. 分類器の読み込み
  2. 画像読み込み
  3. BGR2GRAY変換(グレースケールへの変換)
  4. 顔検出処理

ここで分類器という言葉がでています。OpenCVを使った顔認識では「カスケード分類器」というものを使用して顔と非顔の分別を行う様です。

分類器とは?

カスケード型分類器 — opencv 2.2 (r4295) documentation

う~。

自分なりの解釈としては

正しいオブジェクトと間違ったオブジェクトから特徴量を学習して作った分類器から 拡大や回転などの変換をかけて一致するかしないかを検出する方法

分類器を作成するのは荷が重いのでOpenCVにデフォルトでついている分類器を使用することにします。ただし、パッケージのインストールをつかっているので分類器は別途ソースコードについているサンプルから取ってくる必要があります。

OpenCVソースコードGitHubにありますが、分類器は以下のパスにあります。

github.com

今回はhaarcascade_frontalface_default.xmlを使うことにしました。これをプログラムを置くパスにダウンロードしておきます。分類器は20あまりもあるので、ほかのものを選んでもいいのかも。

画像ファイルに含まれる顔を認識する

では、画像ファイルから顔認識をしてみます。見つかった場合には顔として認識されたエリアを赤い四角で囲みファイルとして出力をします。

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

import cv2

faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
face = faceCascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=2, minSize=(10, 10))

if len(face) > 0:
    for rect in face:
        cv2.rectangle(img, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (0, 0,255), thickness=2)
else:
    print "no face"

cv2.imwrite('detected.jpg', img)

サンプルとしてはネットを探してでてきものを使ってみます。オリジナルは以下になります。

f:id:ueponx:20170406160132j:plain

ライセンス: CC0 Public DomainCreative Commons — CC0 1.0 Universal

これをプログラムに処理させると以下のような画像が出力されます。

f:id:ueponx:20170406160603j:plain

顔認識の精度は微妙ではありますが認識は行われたようです。デフォルトなんでこんなもんでしょう。ちなみに別の分類器を使えばこんな感じにもなります。(haarcascade_frontalface_alt.xml

f:id:ueponx:20170406160841j:plain

コードをみる

分類器の読み込み

faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

OpenCVのCascadeClassifier()メソッドを使用して分類器の読み込みを行います。引数に与えるXMLファイルが分類器に相当します。

画像読み込み

img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)

OpenCVのimread()メソッドを使用して画像の読み込みを行います。 特定のファイルではなくプログラムの引数で与えるのであれば

args = sys.argv
img = cv2.imread(args[1], cv2.IMREAD_COLOR)

としてもいいかと思います。

BGR2GRAY変換(グレースケール変換)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

OpenCVのcvtColor()メソッドを使用して画像のグレースケール変換を行います。

顔検出処理

face = faceCascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=2, minSize=(10, 10))

ここまできてやっと顔検出のを行います。 第2引数の1.2がscaleFactorであり、分類器の精度と処理スピードに影響するパラメータとなります。以下のリンクに解説がありました。

answers.opencv.org

数値を小さくすると検出するが上がるのですが、誤検知も増え、処理時間も増えると考えていいみたいです。画像の解像度が高いと処理時間が数秒になるのでWebCameraを使うときには調整が必要そうです。(WebCameraの解像度を下げればいいのですけど。)

今回のサンプルでは処理が終わるのに5秒ぐらいかかっていました。もう少しゆるい制限にしたほうが現実的だと思います。

WebCameraの映像に含まれる顔を認識する

今度はこれをWebCameraで行ってみたいと思います。

前回エントリではcapture = cv.CaptureFromCAM(0)って感じでキャプチャデバイスの設定していましたが今回はcapture = cv2.VideoCapture(0)って感じでキャプチャデバイスを取得しています。後者のほうがより一般的なキャプチャデバイスへの対応ができるのかなと思います。(スピードはあんまり変わりません)

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

faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')

capture = cv2.VideoCapture(0) # カメラセット
# 画像サイズの指定
ret = capture.set(3, 480)
ret = capture.set(4, 320)

i = 0
while True:
    start = time.clock() # 開始時刻
    ret, image = capture.read() # 画像を取得する作業
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    face = faceCascade.detectMultiScale(gray_image, scaleFactor=1.3, minNeighbors=2, minSize=(30, 30))

    if len(face) > 0:
        for rect in face:
            cv2.rectangle(image, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (0, 0,255), thickness=2)

    get_image_time = int((time.clock()-start)*1000) # 処理時間計測
    # 1フレーム取得するのにかかった時間を表示
    cv2.putText( image, str(get_image_time)+"ms", (10,10), 1, 1, (0,255,0))

    cv2.imshow("Camera Test",image)
    # キーが押されたら保存・終了
    if cv2.waitKey(10) == 32: # 32:[Space]
        cv2.imwrite(str(i)+".jpg",image)
        i+=1
        print("Save Image..."+str(i)+".jpg")
    elif cv2.waitKey(10) == 27: # 27:Esc
        capture.release()
        cv2.destroyAllWindows()
        break

処理時間の測定をしているのはパラメータの変更の効果をみるためになります。 また、スペースを押すとその時点での認識状況の保存ができるようになっています。

パラメータに関しては

scaleFactor=1.3, minNeighbors=2, minSize=(30, 30)

としました。RaspberryPi2では秒間2-3回程度の認識ができるようにしています。 パラメータで一番効果があったのはscaleFactor=1.3でした。1.2あたりでは処理時間が倍くらいかかってしまいました。また、キャプチャの解像度を下げるのも効果があります。ただし、キャプチャの解像度を下げると認識率がわるくなるのである程度にしておいたほうが無難です。デフォルトの640*480のサイズで動かすと認識に1.6秒ほどかかりました。

以下のような認識ができました。

f:id:ueponx:20170406210903j:plain

f:id:ueponx:20170406211019j:plain

おわりに

かなり昔から度々やろうと思っていたことがやっとできました。

最近ではWebAPIを使えば比較的簡単に同じことはできますが、通信制限がある場合には、OpenCVとWebAPIを組み合わせるなどのことも考えていければいいかなと思っています。

あと、RaspberryPiではやはりパワー不足は否めませんので、Xの機能をいっその事削除したり、PCを使って動かしたほうがいいかなと思います。別の処理系であってもpythonなのでそのままコードが使用できるのはいいですね。

RaspberryPiにPhantomJSをインストールする

JavaScript コンピュータ 電子工作 開発 RaspberryPi

RaspberryPiにPhantomJSをインストールする

昔作ったPhantomJSのプログラムをRaspberryPiでも実行したいと思ったのですが、RaspberryPiのパッケージがなく、 ビルドにも数時間かかるということだったので諦めていました。そろそろ時間もできたのでインストールすることにしました。

PhantomJS | PhantomJS

ネットを探すと既にビルドしているものを公開している方がいらっしゃったので、そちらを使うことにしました。 自分でビルドするつもりでしたが、さすがに5時間とかかかると言われると厳しいです。

以下のサイトを参考にさせていただきました。ビルドしたバイナリも公開していただいているので大変ありがたいです。(バージョンは2.1.1なので新しいです!)

https://mecrazy.net/ja/2016/06/06/raspberry-pi%E7%94%A8%E3%81%ABphantomjs-2-1-1%E3%82%92%E3%83%93%E3%83%AB%E3%83%89%E3%81%97%E3%81%A6%E3%81%BF%E3%81%BE%E3%81%97%E3%81%9F/

他にも

www.miyakawa.link

github.com

こちらのサイトもあるんですが、少しバージョンが古かったので前者にしています。

インストールする

ビルドされたバイナリは以下にありますのでgitを使って取得します。

github.com

ビルド時に必要なパッケージも事前にインストールしていますが念のためということで。

$ sudo apt-get install build-essential g++ flex bison gperf ruby perl libsqlite3-dev libfontconfig1-dev libicu-dev libfreetype6 libssl-dev libpng-dev libjpeg-dev
$ git clone https://github.com/mecrazy/phantomjs-binaries.git
$ chmod 755 phantomjs-2.1.1-linux-armhf
$ sudo apt-get install fonts-ipafont
$ sudo ln -sf `pwd`/bin/phantomjs-2.1.1-linux-armhf /usr/local/bin/phantomjs

cloneしたディレクトリにあるbinディレクトリにバイナリがあります。phantomjsもあります(多分x86なバイナリのリンク?)が、今回使用するのはarm版のphantomjs-2.1.1-linux-armhfになります。これを/usr/local/bin/シンボリックリンクを作成します。これで準備完了です。

以下のようにコマンドを実行するとバージョン出ます。これでOKです。

$ phantomjs --version
2.1.1

サンプルっぽいものを実行してみる

PhantomJSはWebkitベースのHeadlessブラウザなので、googleのトップページにアクセスしてスクリーンキャプチャをしてみます。

//sample.js
var page = require('webpage').create();
page.open('https://google.com', function(status) {
  console.log("Status: " + status);
  if(status === "success") {
    //screen capture
    page.render('google.png');
  }
  phantom.exit();
});

実行すると、画面上にURLアクセスしたステータスを表示して終了します。 実行ディレクトリにgoogle.pngができていれば成功です。

$ phantomjs sample.js
Status: success
$ ls
google.png  sample.js

google.pngは以下のような画像になります。(フォントをインストールしてないと文字化けしますので注意です。)

f:id:ueponx:20170316133617p:plain

簡単な説明

ソースの一行目でブラウザオブジェクトを作成し

var page = require('webpage').create(); // オブジェクト生成

指定されたURL(https://google.com)にアクセスし、アクセスに成功したら画面イメージをキャプチャします。

page.open('https://google.com', function(status) { // URLへアクセス
  console.log("Status: " + status); // 実行のステータスをコンソール上に表示
  if(status === "success") { //アクセスに成功したら
    //screen capture
    page.render('google.png'); //スクリーンキャプチャする
  }
  phantom.exit(); //実行終了
});

説明になってないような気もしますが、サンプルなので…

おわりに

公開されたバイナリがあって圧倒的感謝・・・・!

http://cdn-ak.f.st-hatena.com/images/fotolife/g/gotocc/20101123/20101123074729.jpg

RaspberryPiでOpenCVを使ってみる【つまづき編】

RaspberryPi python コンピュータ 開発 opencv

RaspberryPiでOpenCVを使ってみる【つまづき編】

f:id:ueponx:20170226143214j:plain

やっと、OpenCVに手をだす感じになってきたのですが、即挫折させられるという運の無さ。 急いでいる人は以下の手順でOKです。

$ sudo apt-get install libopencv-dev
$ sudo apt-get install python-opencv
$ sudo apt-get install libgl1-mesa-dri
$ x11vnc -storepasswd
$ sudo mkdir /home/pi/.config/autostart/
$ sudo vim /home/pi/.config/autostart/x11vnc.desktop (設定ファイルの中身は下を参照)
$ sudo vi /boot/config.txt(設定ファイルの中身は下を参照)
$ sudo raspi-config(設定後再起動)

インストール作業

以前のエントリでも、opnecvの開発パッケージ、pythonopencvモジュールの2つはインストールしていますので、あえてやらなくてもいいのかなとは思いますがバージョンアップもあるかなということで行いました。

$ sudo apt-get install libopencv-dev
$ sudo apt-get install python-opencv

一ヶ月ほど前に入れたにも関わらず、バージョンアップが行われていたので時間がかかりました。

インストールを行ったOpenCVのバージョンの確認方法

インストールするとバージョンがあとからわからなくなることがあるのですが以下のファイルにバージョンが記載されています。

$ less /usr/include/opencv2/core/version.hpp
(略)

/*
  definition of the current version of OpenCV
  Usefull to test in user programs
*/

#ifndef __OPENCV_VERSION_HPP__
#define __OPENCV_VERSION_HPP__

#define CV_VERSION_EPOCH    2
#define CV_VERSION_MAJOR    4

#define CV_VERSION_EPOCH    2
#define CV_VERSION_MAJOR    4
#define CV_VERSION_MINOR    9
#define CV_VERSION_REVISION 1

(略)

/* old  style version constants*/
#define CV_MAJOR_VERSION    CV_VERSION_EPOCH
#define CV_MINOR_VERSION    CV_VERSION_MAJOR
#define CV_SUBMINOR_VERSION CV_VERSION_MINOR

#endif

この時点(2017/02/25)でのインストールではversionは2.4.9になっていたようです。

インストールでは難なく完了するので後はサンプルを動かすだけです。以前のエントリーでも画面表示は行っていませんでしたがUSBカメラからのキャプチャは行っていましたので、どちらかというとGUIっぽい表示にすることがメインのチェックとなります。

OpenCVのサイトに行けばサンプルも結構あるのでそれを編集しています。

カメラ画像をX上で表示する使用するサンプルcamera.py

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

import cv2.cv as cv
import time

cv.NamedWindow("camera", 1)

capture = cv.CaptureFromCAM(0)

# 画像サイズの指定
cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_WIDTH,320)
cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_HEIGHT,240)

while True:
    img = cv.QueryFrame(capture)
    cv.ShowImage("camera", img)
    if cv.WaitKey(10) > 0:
        break
cv.DestroyAllWindows()

「カチャカチャカチャ・・・ッターンッ!!」これで出来上がったぜいぇーいとなるはずだったんです。

実行時エラーがでました。

$ python camera.py
Xlib:  extension "RANDR" missing on display ":11.0".

(camera:31765): GdkGLExt-WARNING **: Window system doesn't support OpenGL.

お前の環境だとOpenGLに対応してね~から実行は無理というものでした。オゥフ。 これまでは画像のキャプチャのみを行っていたので表示系まではやっていなかったので気がついてイなかったみたいです。というかXを使っていなかったというのが正しいw。

今回はコンソール(Teraterm)の代わりにWindowsにデフォルトでインストールされている【リモートデスクトップ接続】を使っていました。これが原因なんだろうなあと思って、気が重くなりました。個人的には経験からLinux系の設定のツラミはほぼほとんどがX関係っていうことが多くキツイ目にあっていたので。

といっても、GUIの無い画像処理はありえないと思うので解決を模索します。

早速エラーメッセージ関係でググってみると以下の記事にあたりました。

Raspberry Pi • View topic - Enable OpenGL on Raspbian Jessie for OpenCV

内容としては「OpenGL系の対応ライブラリが入っていないのでインストールせよ」というものでした。 では早速インストールしてみます。

$ sudo apt-get install libgl1-mesa-dri

動いてくれ頼む!

$ python camera.py
Xlib:  extension "RANDR" missing on display ":11.0".

(camera:31765): GdkGLExt-WARNING **: Window system doesn't support OpenGL.

変わっていねえ…(一部の数値とか変わるみたいですが、キャプチャ忘れました)

作戦変更へ

どうも以前入れたVNCサーバであるTightVNCserverがX接続すると別のディスプレイ接続環境を生成して、接続を行うという特徴があるというものだったので、純粋にハード的なXではなくソフトウエア上で動いている(表現がおかしいが)Xということだったようです。

X環境は基本設定をしないと共有されるはずですが、それがTightVNCserverにはなかったので若干不思議ではあったのですが、なんとなく理解できました。

そこでRaspberryPiのVNCサーバを

tightvncserver から x11vncに変更することにします。

X11vnc - ArchWiki

一応、ぐぐってみるとと実績があるようでした。

機能がバッティングしていることも考えてアンインストールしてからインストールを行っています。

$ sudo apt-get remove tightvncserver
$ sudo apt-get install x11vnc

インストールは全くトラブルなく終了しました。

x11vncの設定

以下のサイトが詳しく乗っていたので参考にしました。

www.1ft-seabass.jp

流れとしては

  • vnc接続用のパスワード設定
  • vncserverの起動
  • vncviewerからの接続

となります。

それが完了したら

*自動起動を設定

を行います。

vnc接続用のパスワード設定

vncではユーザという考え方以外にもvnc用のパスワードが存在しています。そのパスワードを作成することになります。

$ x11vnc -storepasswd
Enter VNC password:
Verify password:
Write password to /home/pi/.vnc/passwd?  [y]/n y
Password written to: /home/pi/.vnc/passwd

このコマンドで/home/pi/.vnc/passwdにパスワード情報が生成されます。このパスがパスワードファイルのデフォルト場所のようです。

vncserverの起動

後はVNCサーバを起動しますがテストであれば

$ x11vnc -usepw

の実行でいいかなと思います。オプションの-usepwは最初に設定したパスワードを使用するという宣言になります。

起動が正常にできて、このままずーっとVNCサーバを起動状態にする場合には以下の様に実行します。

$ x11vnc -usepw -forever

今回こそはとおもったんですが、失敗しました。

エラーメッセージの内容としては「VNCからXのディスプレイ:0に接続できないです」(意訳)というものでした。

ここからが苦痛でした。

エラーの内容からするとXがどうも立ち上がっていない様子。 しかし、HDMIを接続するとXは立ち上がりログインもできる。

そこで実験としてraspi-configコマンドでCLI環境とし、HDMIを接続しない状態でターミナルを2つ立ち上げて、一つでstartxを起動すると無事に起動しました。(WindowManegerはない状態ではあります。)そのあとにVNCServerを起動するとエラーは発生せず5900ポートで接続してほしい旨のメッセージがでました。

ここで仮説を立ててみました。 HDMIモニターを繋いでいるときにはXが起動している、RaspberryPiはHDMIを繋いでいないとコンポジット側に接続を替えるという仕様だったようなきがするので、強制的にHDMI出力をするモードに させてしまえばXは勝手に起動してくれる?と思い、/boot/config.txtを編集することを試してみました。

$ sudo vim /boot/config.txt

ファイル中ほどにある以下の部分のhdmi_force_hotplug=1のコメント化を解除します。

/boot/config.txtの変更前

(略)
# uncomment if hdmi display is not detected and composite is being output
# hdmi_force_hotplug=1
(略)

/boot/config.txtの変更後

(略)
# uncomment if hdmi display is not detected and composite is being output
hdmi_force_hotplug=1
(略)

ようやくこれでXは自動で起動してくれる様になりました。(これでほぼ1日潰れました) 後は前述のコマンドでVNCServerを起動します。

$ x11vnc -usepw
26/02/2017 13:29:15 -usepw: found /home/pi/.vnc/passwd
26/02/2017 13:29:15 x11vnc version: 0.9.13 lastmod: 2011-08-10  pid: 5984
26/02/2017 13:29:15 XOpenDisplay("") failed.
26/02/2017 13:29:15 Trying again with XAUTHLOCALHOSTNAME=localhost ...
26/02/2017 13:29:15

(略)

The VNC desktop is:      raspberrypi:0
PORT=5900

(略)

そしてリモートデスクトップ接続からRaspberryPiに接続、xrdp経由でVNCに接続となります。 注意点としては

  • 接続後のモジュール選択を【vnc-any】
  • IPには接続する【RaspberryPiのIPアドレス
  • portにはVNCServerの起動時に指定された【ポート番号(初期値は5900)】

を設定することになります。

ログイン画面のダイアログは以下の様になります。

f:id:ueponx:20170226132624j:plain

ここまできたら後は、目的のサンプルプログラムを実行してみます。

f:id:ueponx:20170226133643j:plain

うまく実行でき、やっと第一歩が踏み出せました。

後は自動起動の設定となります。

自動起動設定

まず、/home/pi/.config/autostart/というディレクトリを作成し、そのフォルダの中にx11vnc.desktopというファイルを作成します。

$ sudo mkdir /home/pi/.config/autostart/
$ sudo vim /home/pi/.config/autostart/x11vnc.desktop

x11vnc.desktop

[Desktop Entry]
Encoding=UTF-8
Type=Application
Name=X11VNC
Comment=
Exec=x11vnc -forever -display :0 -rfbauth /home/pi/.vnc/passwd
StartupNotify=false
Terminal=false
Hidden=false

続いてはraspi-configの設定をします。前述の設定ではログインしないと自動設定が行われないので自動でログインする設定にします。セキュリティとしては微妙ですが今回は目を瞑ります。

raspi-configを起動して【3. Boot Options】を選択

f:id:ueponx:20170226140556j:plain

画面が切り替わったら【B1. Desktop / CLI】を選択

f:id:ueponx:20170226140610j:plain

画面が切り替わったら【B4. Desktop Autologin Desktop GUI. automatically logged in as ‘pi’ user】を選択

f:id:ueponx:20170226140627j:plain

設定が終わったら再起動すると、リモートデスクトップから即ログインができるようになっています。

おまけ

リモートデスクトップ接続の画面が狭くなりますが、先程編集した/boot/config.txtに解像度のパラメータもありますので、これを変更することで広くすることができます。

sudo vi /boot/config.txt

/boot/config.txtの変更前

(略)
# framebuffer_width=1280
# framebuffer_height=720
(略)

/boot/config.txtの変更後

(略)
framebuffer_width=1280
framebuffer_height=720
(略)

これで1280*720の解像度で起動が行われます。

f:id:ueponx:20170226143214j:plain

おわりに

思った以上につまずいてしまって泣けましたが、これでもう障壁はないかなと思う(と思いたい)ので、引き続きOpenCVもやっていきたいと思います。

StartupWeekend豊橋に行ってきました

勉強会

StartupWeekend豊橋に行ってきました

2/17~19に開催されたStartupWeekend豊橋に参加してきました。 色々と噂を聞いているStartupWeekendだったのでかなりの不安がありました。

swtoyohashi.doorkeeper.jp

Startup Weekend (以降SWと略 )は、週末の3日間を利用してアイデアを形にするための方法論を学びながら実践する、スタートアップ体験イベントです。このワークショップは2009年に米国で始まり、これまで全世界700都市で1,500回以上にわたって実施されてきました。Startup Weekendは初日の夜、参加者のアイデアの発表から始まります。そしてハスラーハッカー・デザイナーでチームを組み、3日目の午後までに必要最小限のビジネスモデルを一気に作り上げます。ハスラーは顧客開発を、ハッカーは機能の開発を、デザイナーは使いやすいデザインを担当します。

一日目

仕事を早退して豊橋に向かいました。名古屋から豊橋まではそんなに遠く無いだろうと思っていたんですが、意外とありましたね。電車居眠りをしながら豊橋に到着。早めについたので会場近くをぶらぶらしようと思っていたら道を間違って会場と違う方向に。 そんなこんなありながら、会場には時間通りに到着。今回は初めての参加だったので、かなり挙動不審気味に会場に入りました。一応、ハッカーとして参加です。

会場はトライアルビレッジさん。 個人的にはかなり好きな雰囲気のコワーキングスペースです。

trialvillage.net

f:id:ueponx:20170221235754j:plain

f:id:ueponx:20170221235845j:plain

初日は交流会・ピッチ・チーム形成・作業開始でした。基本、仕事はボッチでやるほうが捗るタイプなので初対面の人と喋るときは挙動不審だったかなと思います。初回のピッチでは2つのキーワードを使ってグループでアイディアを練るというものでした。自分たちが選んだのは「ペンギン」と「e-sports」でした。

流石にかなりキーワードとしては難しかったので、ペンギンが動くことをe-sportsにするようなことを言っていたのですが、苦し紛れで「けっきょく南極大冒険」のネタを出して終わる失態に終わりました。 (個人的には「夢大陸アドベンチャー」のほうが好きです。)

その後、個人のアイデアピッチをいうのですが、イケアイデア(イケてるアイデア)になることもなく他の方のアイデアに賛同してチームを結成。チームのアイデアはダイエットをしたらその分お金がもらえるというようなものでした。ダイエットは継続しにくいので、何らかのベネフィットがあれば継続できるだろうというものだったので、自分のようなデブに優しい企画だなと思って参加しました(そんな意図でなかったらチームのみなさんすみません。)

このビジネスに関してアンケートを取らないとなあということでGoogleフォームでアンケートをとるかなと思っていたところ、有料サービスのほうがいいっすよという助言があり一件あたり50円で100件分をやってみました。1.5時間で速攻でデータが取れるのはさすがにカルチャーショック。社内研修ではこういうときは、統計サイトを見ましょうという感じなんですが、こうなると予想の域が多くなってしまう印象でした。少しのコストで欲しい時に短時間でこういうことができるのは時代はこっちに向かってるのかなと思えるし、お金って偉大だなという気持ちにもなりました。

いろいろ話あっている間に終電近くになってしまったので、名古屋へ帰宅。正直今から思えばこの選択は体力的な問題でNGでした。

二日目

終電近くだったので24時ごろ家に帰宅し、7時ぐらいに起床。一時間かけて会場へ。正直イベントを頑張るなら近くに宿泊するか、最寄りの会場のイベントに出ましょう。

今日のメインの作業は顧客開発ということになります。アンケートでもいいんですが、さすがに懐にはダメージがあるので、屋外で通りゆく人に話しかけてヒアリングへ。 豊橋駅の近辺ということで人通りもかなりあり、ヒアリングもいけそうな感じですが…心折れた。終わってからの感想ですが、駅の近くで人に話を聞くのは向きません。それは駅に基本経由地で、目的地への移動したいからかもしれません。(話を聞く経緯を話す暇すらありません。)

その後、折れた心でテニスクラブにいってヒアリングにいきましたがここではみんな話を聞いてくれました。目的地についている人に話を聞くとうまくいくのではないでしょうか。(仮説)

15人ほど話をきいて会場へ。(この時点でかなり疲れていた。)2チームに分かれていたので30名ほどの意見はもらえていたと思います。

ここまでの検討から、ダイエットからボディービルダー企画へ方針変更。ボディビルダーの運動の効果や怪我を防止するというようなフィットネスウェアです。具体的にはパンプアップすると色が変わって、運動効果を可視化して見えるという感じのものになります。(企画を考えながら頭の中にアドンとサムソンが飛んでました。)

その後、筋肉を鍛えている人が多そうだということで、豊橋武道館でヒアリングへ。ベンチプレスやマシーンをつかっている数人に話を聞くことができました。

みんな基本寡黙にトレーニングしていました。その中でなんとか意見をもらえました。

  • 「そこそこトレーニングしている人は効果がある程度わかるので需要はちょっと少ないかもしれない」
  • 「見せるために筋肉を鍛えるようなボディビルをやっている人ならそこまで追い込むので考えられる」

というような話でした。なるほど。

大体の目処がたったので2日目終了。この時点でテンションは結構落ちていましたが、なんとなく目処が立ってる気になっていたので、持ちこたえることはできました。(話に聞いていたのは「これか!」って感じ)

疲れたときにはブラックサンダーです。

f:id:ueponx:20170222000015j:plain

さすがに歩き回ったり話あったりしていたので非常に疲れましたが、この日は会場の近くで宿泊することにしていたので少し楽でした。同じイベントに来ていた方と相部屋で宿泊。

最終日

相部屋の人と「仮面ライダーエグゼイド」で気合を入れて会場へ。

最終日はピッチにむけてのラストスパートなんですが、今回はフィットネスウェアのプロトタイプができていたのでみんなそっちに目が向いしまっていたような気がします。

↓パンプアップすると色が変わるサポータのプロトタイプ

f:id:ueponx:20170222000543j:plain

コーチ陣にはいいアドバイスももらって、ブラッシュアップしていくのはかなり楽しかったです。やっぱりピッチは練習ですね。たくさん練習していてもうまくいかないので、練習しすぎるぐらいのほうがいいと思います。後もっと、コーチ陣に話しを聞いてもらったほうがよかったような気もします。

実際のピッチでは審査員の方にかなりインパクトは与えられたんですが、商品の説明が今ひとつだったようで結果は思った風にはなりませんでした。 でも、終わった後は充実感を得られたような気がします。

これまでも会社でこういう研修はありましたが、今回のほうがやり遂げた感と、充足感が半端ないと思います。終わった後のドリンクも美味しかったです。

いろいろありましたが、慣れによるテクニックもある感じなのかもなという気もしたので何回かはチャレンジしてみたいですね。

コーチ陣の方々、審査員の方々、運営の方々、そしてファシリテーターのマツモトさん、本当にありがとうございました。あと今回のイベントで同じチームの方々本当に有難うございました。今度お会いするときはもう少し成長している…と思いますのでまた楽しみましょう!

終わってみて

会社の似たような研修はそれなりにあるんですが、同じようなことをやっているはずなのになぜここまで気持ちに違いがあるのかなと考えてみました。

  • スピード感
  • 上下関係の無さ
  • なんでもあり
  • イデアより行動

この4つが大きいのかなという印象です。会社に戻って他の誰か(やる気があるひとに限る)に行ったほうがいいよと勧めたいです。

「次こそはもっといいのやろう!」という気持ちで結びたいと思います。

日曜日の中日新聞東三河版)に載っていたみたいです。

f:id:ueponx:20170222000140j:plain