Python
OpenCV
RaspberryPi
Jupyter

HeadlessなRaspberry Pi 3でJupyter notebookを自動起動させてOpenCVを使うまで

目標設定

HeadlessなRaspberry Pi3 Model Bを想定し,以下を実現することを目指す.

  • Jupyter notebookが自動起動し,リモートPCからの接続を受け付ける.
    • LANなしでPCと1対1で繋いでも,LANを介して繋いでもJupyterに接続できる.
  • PythonからOpenCVを使うことができる.

想定する環境

  1. RPi3 : Raspberry Pi3本体
  2. PC : RPi3を操作するためのWindows / macOS / Linux機

の2つのデバイスが同一ネットワークに接続されているとする.より具体的には

  1. RPi3とPCがハブを介して接続している
  2. RPi3とPCが有線で1:1接続されている

のどちらでもよいとする.またPCが特にWindowsの場合は,Bonjour for Windowsをインストールしておくこととする.これはRPi3のIPアドレスが不明の場合でも,raspberrypi.localという名前でアクセスできるようにするため(Raspbianのavahiデーモンが実現している機能).

制限事項

以下の点はあえて解決しないままとする.

  • セキュリティについては必要最低限のまま(外部公開は想定しない).
  • インストールがすべて終わった状態であればPCと1:1接続した際にインターネット接続がなくてもOKだが,インストール中はパッケージやソースコードをダウンロードするためにインターネット接続が必要.最初からPCと1:1接続する場合は,PC側でNATしてあげる必要がある.
    • Windowsなら「インターネット接続の共有(ICS)」機能を使う.例えばPCが無線LANでインターネットに接続されているなら,無線インターフェースで「インターネット接続の共有」を有効にし,その際に「ホームネットワーク接続」としてRPiと接続している有線インタフェースを指定する.この設定を行うと「ホームネットワーク接続」で有効にしたインタフェースでDHCPサーバが動き始めるので,一度RPiとのLANケーブルを抜き差しすればRPiにDHCPでアドレスが割り当てられる.インストール後にインターネット接続の共有を解除してもよいし,このまま使い続けてもよい.
  • RPi3を無線LANで接続しない.
    • 無線LANのパスワードなどを設定する必要があるので,今回はパス.
  • LANに複数台のRaspberry Piが存在する場合,raspberrypi.localがどのデバイスになるかは予測しがたい.LANには今回使用する1台だけが存在すると仮定する.

初期設定

以下ではOSとしてRaspbianを新規インストール,つまりディスクイメージをSDカードに書きこむところからスタートすることとする.

RaspbianのインストールとSSHアクセスの有効化

  1. RasobianをMicroSDにインストール.
    • RASPBIAN STRETCH WITH DESKTOP (Version:November 2017)を使用.
    • 実際にはWindowsで作業したので,MicroSDのパーティションを全部消して(「コンピュータの管理」内の「ディスクの管理」より),SD Formatterでフォーマットしてから,Win32 Disk ImagerでOSイメージを書き込んだ.
  2. MicroSDをPCでマウントし,第1パーティション(FAT32.ラベルは「boot」.Windowsなら「boot」というボリューム名のついたドライブに見える)にsshという名前の空のファイルを作る.
  3. RPiを起動

SSH接続

上記のようにbootパーティションにsshというファイルが存在すると,ブート時にsshデーモンが起動する.そこでPCから

> ssh pi@raspberrypi.local

としてssh接続をする(デフォルトのユーザpiの初期パスワードはraspberry).なおPCがWindowsの場合,

などを使うことができるが,BoW環境のsshでは接続できない(/etc/resolv.confにDNSサーバが直書きされているからmDNSで名前解決できない?).

そのほかの設定

  1. 以下のインストール作業にはそれなりの時間がかかるので,tmuxをインストールしておいて適宜detouchできるようにする.
  2. 必要に応じてlv, vimもインストールしておく.

Jupyter

インストール

pipでインストールする.

$ sudo apt-get update 
$ sudo apt-get upgrade 
$ sudo apt-get install python3 python3-dev build-essential python-pip 
$ sudo pip3 install --upgrade pip 
$ sudo pip3 install jupyter 

設定

JNSを参考にJupyterの設定ファイル(~/.jupyter/jupyter_notebook_config.py)を作る以下のスクリプトを作成して実行する.ただしパスワードは別途設定することにしてここでは書き込まない.

configure_jupyter.sh
#!/bin/bash
# script name:     configure_jupyter.sh
# last modified:   2017/03/05
# sudo:            no

if [ $(id -u) = 0 ]
then
   echo "to be run as $(logname)"
   exit 1
fi

# generate config and create notebook directory
# if notebook directory exists, we keep it (-p)
# if configuration file exeists, we overwrite it (-y)

jupyter notebook -y --generate-config
cd $home
mkdir -p notebooks  

target=~/.jupyter/jupyter_notebook_config.py

# set up dictionary of changes for jupyter_config.py
declare -A arr
app='c.NotebookApp' 
arr+=(["$app.open_browser"]="$app.open_browser = False")
arr+=(["$app.ip"]="$app.ip ='*'")
arr+=(["$app.port"]="$app.port = 8888")
arr+=(["$app.enable_mathjax"]="$app.enable_mathjax = True")
arr+=(["$app.notebook_dir"]="$app.notebook_dir = '/home/$(logname)/notebooks'")
#arr+=(["$app.password"]="$app.password =\
#'sha1:81f7fe53c0e5:0c1ddb2ff6bb83d83639c29e4cf6b4cb30ff7617'")

# apply changes to jupyter_notebook_config.py

# change or append
for key in ${!arr[@]};do
    if grep -qF $key ${target}; then
        # key found -> replace line
        sed -i "/${key}/c ${arr[${key}]}" $target
    else
        # key not found -> append line
        echo "${arr[${key}]}" >> $target
    fi
done

続いてパスワードを設定する.パスワードを設定しないと,毎回ランダムに決まるトークンを入力するように促されるのでかえって面倒.

$ jupyter notebook password
Enter password:
Verify password:
[NotebookPasswordApp] Wrote hashed password to /home/pi/.jupyter/jupyter_notebook_config.json

自動起動

Jupyterが自動起動するように設定(参考にしたサイト:https://qiita.com/mt08/items/edb9bcaeaa416d3e5319 ).

/etc/systemd/system/jupyter.service
[Unit]
Description=Jupyter notebook

[Service]
Type=simple
PIDFile=/var/run/jupyter-notebook.pid
ExecStart=/usr/local/bin/jupyter notebook
User=pi
Group=pi
WorkingDirectory=/home/pi/notebooks
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl start jupyter
sudo systemctl enable jupyter
sudo systemctl status jupyter

接続テスト

ここまででwebブラウザから http://rapsberrypi.local:8888/ にアクセスすると,パスワードを入力する画面が表示されるはずである.接続できない場合,webブラウザのプロキシ設定も確認すること.

OpenCV

ここを参考にソースからビルドする(だいたいコンパイルに約2時間かかる.MicroSDのスピード次第?).ただしgcc-6の場合はエラーが出るので,途中でcmake/OpenCVPCHSupport.cmake

-    SET(_PCH_isystem_prefix "-isystem")
+    SET(_PCH_isystem_prefix "-I")

を入れる点に注意(参考:OpenCV Issue #6517, OpenLightingProject Issue #1125).

build_opencv.sh
sudo apt-get install -y build-essential cmake pkg-config
sudo apt-get install -y libjpeg-dev libtiff5-dev libjasper-dev libpng-dev
sudo apt-get install -y libatlas-base-dev gfortran libeigen3-dev
sudo apt-get install -y libgtk2.0-dev
sudo apt-get install -y libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libxvidcore-dev libx264-dev
wget -O - http://github.com/opencv/opencv/archive/3.4.0.zip > opencv.zip
unzip opencv.zip
cd ~/opencv-3.4.0/

cat<<EOD | patch -p1
--- opencv-3.4.0.orig/cmake/OpenCVPCHSupport.cmake   2017-12-31 15:29:51.550167481 +0000
+++ opencv-3.4.0/cmake/OpenCVPCHSupport.cmake        2017-12-31 15:30:00.500101155 +0000
@@ -24,7 +24,7 @@
     ENDIF()

     SET(_PCH_include_prefix "-I")
-    SET(_PCH_isystem_prefix "-isystem")
+    SET(_PCH_isystem_prefix "-I")
     SET(_PCH_define_prefix "-D")

 ELSEIF(CMAKE_GENERATOR MATCHES "^Visual.*$")
EOD

mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D INSTALL_PYTHON_EXAMPLES=ON \
-D BUILD_EXAMPLES=ON ..
make -j4
sudo make install
sudo ldconfig

そのほかのパッケージ

$ sudo pip3 install matplotlib scipy pandas scikit-learn scikit-image plotly

pandasのインストールにはそれなりの時間がかかる(1時間くらい).

動作確認

webブラウザで http://raspberrypi.local:8888/ にアクセスして,新しいnotebookを作る.以下を実行できればOK.

test.ipynb
import numpy as np
import cv2
import matplotlib.pyplot as plt

print(cv2.__version__)

保存

この時点でのMicroSDをイメージファイルとして保存しておくと,同じことを他のRPiで行いたいときに便利かもしれない.Win32 Disk Imagerを使う場合,MicroSDに2つのパーティションがあるのでどちらのドライブを選択すれば…と迷うが,どちらでもよい.Read Only Allocated Partitionsにはチェックを入れること(そうしないと微妙にサイズが異なるMicroSDに書き込めなくなるというか,そういうことが無いように最初からやや小さめのパーティションを作っておくべきだし,バックアップするときもそれを反映させなさい,ということか?).