WindowsPCにつないだWebカメラをリモートカメラにする方法＆つまづきポイント

　備忘録代わりに。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/206a52da-26dc-82d2-17f1-92d9c4fca4c6.png)

　最終的にはこんなことをしたいです。

# レベル1 ローカルで確認


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/94cca88b-b843-70eb-c9e3-00576126a7c3.png)

　まずはリモートであることは忘れてローカルで確認を行います。最終的には Web サーバーとして立ち上げたいので、Django でサーバーを立ち上げます。

　プロジェクトファイルのディレクトリ構成はこんな感じになります。

```
remote_camera
│  db.sqlite3
│  manage.py
│
├─app
│  │  admin.py
│  │  apps.py
│  │  models.py
│  │  tests.py
│  │  urls.py
│  │  views.py
│  │  __init__.py
│  │
│  └─migrations
│          __init__.py
│
└─remote_camera
        asgi.py
        settings.py
        urls.py
        wsgi.py
        __init__.py
```

　プロジェクトフォルダの方の `remote_camera/urls.py`

```python:urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('app.urls')),
]
```

　アプリケーションフォルダの `app/urls.py`

```python:urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.video_feed, name='video_feed'),
]
```

 `views.py`

```python:views.py
import cv2
from django.http import StreamingHttpResponse

# Create your views here.

def gen(camera):
    while True:
        success, image = camera.read()
        
        # カメラからの読み取りが成功したかどうかを確認
        if not success:
            print("Failed to read from camera.")
            continue

        # 画像が空でないか確認
        if image is None or image.size == 0:
            print("Received an empty frame!")
            continue

        ret, jpeg = cv2.imencode('.jpg', image)

        # エンコードが成功したかどうかを確認
        if not ret:
            print("Failed to encode image.")
            continue

        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n\r\n')


def video_feed(request):
    return StreamingHttpResponse(gen(cv2.VideoCapture(0,cv2.CAP_DSHOW)),
                                 content_type='multipart/x-mixed-replace; boundary=frame')
```

　この段階で、`python manage.py runserver` でサーバーを起動させると、`http://127.0.0.1:8000/` でカメラのリアルタイム画像が表示されるはずです。

## ！カメラの起動がやたら遅い場合

　`cv2.VideoCapture(0)` だけだとカメラの起動が遅くて使い物にならない場合があります。そういう場合、私の環境では `cv2.VideoCapture(0,cv2.CAP_DSHOW)` という引数をつけることで上手くいきました。理由はわかりませんが、こうしたら上手くいったという報告だけ。


## ！サーバーが起動できない場合

　マイグレートしていない可能性があるので、`python manage.py migrate` をしましょう。

# レベル2 ローカルエリア内でリモートアクセス

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/877a5960-d21e-596e-cdf8-685e804644f6.png)

　こういう状態です。例えば、同じ無線LANを共有しているような場合です。近いんだからリモートする意味ないだろと言われそうですが、広い家やオフィスなどで別の部屋の様子を確認したい場合には、このレベルでも普通に役に立つかもしれません。

　ローカルサーバーを起動している場合、`ローカルIP:8000` で他のPCからもアクセスできます。

　ローカルIPはコマンドプロンプト `ipconfig` を打つことにより調べることができます。普通の環境だったら、`192.168.xxx.xxx` のような形になっているはずです。

## ！アクセスできないときその1

```python
python manage.py runserver 0.0.0.0:8000
```

　実は `runserver` はデフォルトの引数では外部からアクセスできないようになっています。`0.0.0.0:8000` にバインドすることによって、外部のブラウザからアクセスできるようになります。（もちろん、ポート番号を任意のものに変えてもOKです）

## ！アクセスできないときその2

　ファイアウォールによって色々弾かれている可能性があります。

### Python が弾かれているとき

　Python が「許可されたアプリ」になっていない可能性があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/8c94a1e8-3273-bdc3-7f05-11b0cb11f717.png)

　変更の仕方については「Python ファイアウォール 許可」あたりで検索すれば出てくるかと思われます。

### TCP が弾かれているとき

　ファイアウォールの個別規則で TCP の 8000 ポートを許可する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/35bc1006-10d5-16a1-74d6-94e2ebc47bcd.png)

　こんな感じで追加していきましょう。詳しい手順は「ファイアウォール TCP 許可」あたりで検索すれば以下略。

　まあ、ここら辺の設定は**ファイアウォールを無効化する**という力技で解決（解決していない）（推奨されません）することもできますが……。

# レベル2 インターネットを超えて確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/9c4a87a3-af40-f402-62ef-22b666dae2c9.png)

　このレベルになると普通にリモートカメラになります。便利ですが、映像データを全世界に向けて通信することになるので、セキュリティ対策は必須です。


## ポート開放の手順　※VPN接続をする場合は後で不要になります

　通常、外部からのアクセスはルーターで弾かれるので、ルーターへのアクセスをローカルマシンに転送するような処理が必要になります。ポートフォワーディングとかポート開放とか呼ばれるアレです。ポート開放の処理はルーターの機種等の個人の環境によって違いすぎるので、詳細は各自検索してください。

　ポート開放が済んだ状態だと、`グローバルIP:8000`（これも `ipconfig` で調べることができます）にアクセスするとリモートカメラの画像が中継されるはずです。この段階で、**偶然このグローバルIPを引き当てた人からは映像が丸見えになります**。確率は極めて低いとはいえ、映ってはいけないものが映ってないか注意してください。

 　ポート開放の手順については、どのルーターを使っているかとかの環境の違いが大きすぎるので、普遍性のある手順は書けません。とりあえず私の環境（YAMAHA NVR-500）だと、

  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/aee4f945-991a-4a8e-acbd-ff6db092c85e.png)

　このような設定になります。ポート `80` 番の `tcp` プロトコルを追加すると、上の図でいう5番目の項目が追加されます。「使用ホストIPアドレス」が、Web サーバーを起動するパソコンのローカルIP になります。

## ここら辺のつまづきポイント

　色々ある気がしますが、とりあえず

- ルーターからサーバーのPCにポート転送できているか？
- サーバーのPCのファイアウォールは、TCPの転送を受け入れるようになっているか？

　という点に注目しながらデバッグしていくしかないと思います。

# レベル3 DDNS

　なお、このグローバルIPは時々変わります（貴重なリソースをみんなで必要な時だけ使い回しているため）。変わらないプラン（高い）もありますが、普通の人は変わるプランだと思います。

　これでは、その都度その都度正しいIPを指定しないとリモートアクセスできないという、極めて不便なサーバーになります。そのため、固定されたURLでこのサーバーにアクセスできるようにするべく、世の中にはDDNSというサービスがあります。例えば、`mydomain` というURLをDDNSに登録したら、常に正しいグローバルIPに変換してくれるようになります。詳しい導入の手順については、ルーターのメーカー等によって対応が違いますが、だいたい、メーカーが自前でこのサービスを用意してくれていることが多いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/db88795d-3d58-c354-c988-a87f03f05600.png)

　このような手順により、どこからでも、いつでも同じようにアクセスできるリモートカメラとなりました。ただし、このURLを特定されたら全世界に映像が垂れ流しという点は変わりません。

# レベル4 セキュリティ

　安心して使うためには、自分や信頼できるユーザーだけが映像にアクセスできるようにする必要があります。そのための対策として、

- ログイン認証を設ける
- VPNでアクセスできるようにする

　などがあります。

## VPN接続を出来る場合

　VPN接続が可能なルーターの場合は、そちらの方が安全なので、その手順を説明します。グローバルIPが固定（またはDDNSにより擬似的に固定）されている場合、VPN接続によってあたかもそのLANの中にいるかのようにふるまうことができます。この方法を使う場合はポート開放すらも必要ありません（インターネットを超えていないので）。
 
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/9882c186-a6ef-6d15-64dc-a2139506a834.png)

　図にまとめるとこんな感じ。情報量おおくてもうわかんねえな！

## ここら辺のつまづきポイント

 　VPN接続も、ポート開放と同じく、ルーターの機種などによって大きく左右されるので、普遍的な手順は示せませんが、個人的につまづいたポイントを上げます。
  
  Windows環境から接続しようとする場合、接続先や共有鍵は合っているはずなのに、「VPNに接続できません。リモートコンピューターと最初にネゴシエートするときに、セキュリティ層で処理エラーが検出されたため、L2TP接続に失敗しました。」

　というエラーが発生しました。
 
　以下の2つによって解決しました。

### ルーターの認証方式を変える

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/0b9e0e94-886b-cee4-2b32-85004019da47.png)

　ルーター（私の環境では NVR-500）のデフォルト方式だと上手くいかないようで、以下のように設定を変える必要がありました。

- 認証アルゴリズムを「HMAC-SHA」に
- 暗号アルゴリズムを「AES-CBC」に
- PPP認証方式を「CHAP-PAP」に

　さすがに、それぞれの技術仕様や、なぜ上手くいったかまではわかりません……。

 ### VPN接続のセキュリティを変える

 　コントロールパネルをいじる必要があります。

  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/cc408d40-6c80-c7f6-d49a-cf340fcad534.png)

　ここから、

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/f38de8b9-9199-c2fe-3790-8c2548f826cf.png)

　ここにとび、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/5d437ff1-4578-6cbc-fb0d-67d996c45639.png)

　VPN 接続のプロパティに。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/85dda3a5-1ac8-a895-ab43-811c23994cb4.png)

　このあたりのプロトコルを許可（デフォルトだとオフになっているはずです）。

 ### ちなみに

　なお、Macからの接続だとこんな設定しないでも上手くいきました。