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機

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

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

のどちらでもよいとする．またPCが特にWindowsの場合は，[Bonjour for Windows](https://support.apple.com/kb/DL999?viewlocale=ja_JP&locale=ja_JP)をインストールしておくこととする．これは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には今回使用する１台だけが存在すると仮定する．


# 初期設定

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

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

1. RasobianをMicroSDにインストール．
    * RASPBIAN STRETCH WITH DESKTOP (Version:November 2017)を使用．
        * [JAISTのミラー](http://ftp.jaist.ac.jp/pub/raspberrypi/raspbian/images/)から2017-11-29-raspbian-stretch.zipを取得．
    * 実際にはWindowsで作業したので，MicroSDのパーティションを全部消して（「コンピュータの管理」内の「ディスクの管理」より），[SD Formatter](https://www.sdcard.org/jp/downloads/formatter_4/)でフォーマットしてから，[Win32 Disk Imager](https://ja.osdn.net/projects/sfnet_win32diskimager/releases/)で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の場合，

* [Windows 10 Fall Creators Updateで追加されたsshクライアント](https://blogs.msdn.microsoft.com/powershell/2017/12/15/using-the-openssh-beta-in-windows-10-fall-creators-update-and-windows-server-1709/)
* [putty](http://www.putty.org/)

などを使うことができるが，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](https://github.com/kleinee/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://raspberrypi.local:8888/ にアクセスすると，パスワードを入力する画面が表示されるはずである．接続できない場合，webブラウザのプロキシ設定も確認すること．

# OpenCV

[ここ](https://qiita.com/PonDad/items/9fbdf4d694f825dd1b6e)を参考にソースからビルドする（だいたいコンパイルに約２時間かかる．MicroSDのスピード次第？）．ただしgcc-6の場合はエラーが出るので，途中で`cmake/OpenCVPCHSupport.cmake`に

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

を入れる点に注意（参考：[OpenCV Issue #6517](https://github.com/opencv/opencv/issues/6517), [OpenLightingProject Issue #1125](https://github.com/OpenLightingProject/ola/issues/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のインストールにはそれなりの時間がかかる（１時間くらい）．

# 動作確認

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

```python3:test.ipynb
import numpy as np
import cv2
import matplotlib.pyplot as plt

print(cv2.__version__)
```

# 保存

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

* 参考：http://www.raspberry-projects.com/pi/pi-operating-systems/win32diskimager