ONVIF対応カメラとJetson nanoで人物追跡AIカメラを作る

# できたこと

AIで人間を認識して、自動追尾するカメラを作りました。
動画がうまく編集できなかったので、静止画で...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/c3041f00-6032-db1d-b459-f08819277ae6.png)
立つと...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/a375dfb2-9ada-a703-6958-3092625de748.png)
追いかけ...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/1866f647-431a-3a9d-bb0b-f32d498aab2a.png)
座ると...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/231f0a09-4adb-971f-c13e-12bf897644f9.png)
また追いかけ...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/f7e05783-9bee-51ab-08cd-e878acfa7ddf.png)
常に画面の中心にとらえようと、自動的にパン・チルトを行います。

# 必要なもの

1. [GENBOLT GB102H](http://www.genbolt.com/jp/index.php?g=&m=article&a=index&id=80&cid=15) = ONVIF対応PTカメラ。[Amazon](https://www.amazon.co.jp/dp/B07GSS6NYY)で3,600円くらい。
1. [Jetson nano](https://www.nvidia.com/ja-jp/autonomous-machines/embedded-systems/jetson-nano/) = CUDA対応オンボードコンピュータ。Amazonで16,000円くらい。2GB版だと7,000円弱。
1. [Yolov3-tiny](https://pjreddie.com/darknet/yolo/) = 物体認識AI
1. [ONVIFライブラリとサンプルプログラム](https://github.com/agsh/onvif.git) = PTZカメラを動かすためのライブラリ
1. [VLC](https://www.videolan.org/vlc/index.ja.html) = カメラからのRTSP出力を表示します

# 手順

## 1. カメラの設定

### 1-1. カメラに電源とイーサケーブルをつなぐ

カメラの取説にしたがって、電源(USB-Micro)とイーサケーブルを接続します。

### 1-2. カメラのIPアドレスを知る

専用のスマホアプリを入れてカメラに接続します。
設定(歯車アイコン) → システム情報 を表示すると
カメラに割り振られたのIPアドレスがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/cbc60420-a19c-9773-06c7-d7824fc5dd05.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/2160c733-92f7-f9e7-59ac-d6b7ca568059.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/2d5c1ab8-420e-33e2-ff46-311994bd8a7d.png)

### 1-3. カメラの設定をする

```http://{カメラのIPアドレス}``` をブラウザで開くと設定ができます
デフォルトのユーザ名は ```admin```
デフォルトのパスワードは ```admin```
※ この記事ではこのまま進めますが、セキュリティ上変更したほうがよいです

ビデオ出力の設定をします。
Second StreamをYoloに入力するので、
Second Streamの Maximum frame rate を 6fps に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/09cf74a0-1eec-a0ae-6f1f-20863c5afdba.png)

ONVIFでカメラを制御するので、ONVIFがOnになっていることを確かめます。
また、ポート番号(8080)も確認しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/cb77b7d3-cdad-0d33-257a-96ca20fd3353.png)

## 2. Yoloのインストール

darknetをクローンします

```shell
$ sudo apt install git
$ git clone https://github.com/AlexeyAB/darknet.git
```

CUDAを有効にしてビルドします

```shell
$ cd darknet
$ vi Makefile
#GPU CUDNN OEPNCVの設定を有効にする
GPU=1
CUDNN=1
OPENCV=1

$ make
```

weightsをダウンロードします

```shell
$ wget https://pjreddie.com/media/files/yolov3.weights
$ wget https://pjreddie.com/media/files/yolov3-tiny.weights
```

Yoloを実行してみます

```shell
$ ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
```

このように認識されれば、Yoloは正しくセットアップされています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/4cc28c54-bf11-5421-7ca3-8cb0d4c13ba9.png)

## 3. Yoloにカメラの画を流し、物体認識させる

```shell
$ ./darknet detector demo cfg/coco.data cfg/yolov3-tiny.cfg yolov3-tiny.weights rtsp://admin:admin@192.168.0.100/2
```

```admin:admin``` の部分はパスワードを変えた場合には、そのパスワードに変更してください
```192.168.0.100``` の部分は **GB102H** のIPアドレスに変更してください

## 4. Jetson nanoからカメラのパン・チルト(PT)操作をする

### 4-1. ライブラリとサンプルアプリのクローン

[ONVIFライブラリとサンプルプログラム](https://github.com/agsh/onvif.git)をクローンします。
nodejsのプロジェクトなので、必要なライブラリを ```npm install``` でインストールします。

```shell
$ git clone https://github.com/agsh/onvif.git
$ cd onvif
$ npm install
```

### 4-2. コード変更(アドレス設定など)

カメラのIPアドレス、もし変えていたらポート番号、ユーザ名、パスワードを修正します。

```shell
$ vi example3.js
------------------------------------
var HOSTNAME = '192.168.0.100',
PORT = 8080,
USERNAME = 'admin',
PASSWORD = 'admin',
STOP_DELAY_MS = 50;
------------------------------------
```

### 4-2. 実行

```shell
$ node example3.js
```

上下左右のカーソルキーを押すと、カメラが動く様子がわかると思います。

## 5. 物体認識結果をもとにカメラのパン・チルト(PT)操作をする (完成)

```example3.js``` をベースに改造をします。

```shell
$ cp example3.js example_yolo.js
```

### 5-1. 必要なライブラリをインストール

```JSONStream``` をインストールします

```shell
$ npm install JSONStream --save
```

### 5-2. ライブラリのrequireと、Yoloのhost/portの追加

```shell
$ vi example_yolo.js
```

```javascript
const request = require('request')
const JSONStream = require('JSONStream') // ★ これ

// ONVIF
var HOSTNAME = '192.168.0.100',
PORT = 8080,
USERNAME = 'admin',
PASSWORD = 'admin',
STOP_DELAY_MS = 50;

// YOLO
var YOLO_HOST = '127.0.0.1' // ★ これ
var YOLO_PORT = 5000        // ★ これ
```

### 5-3. キーボード操作関数呼び出しをコメントアウト、Yolo操作関数呼び出しを追加

```javascript
cam_obj.getStreamUri({
	protocol : 'RTSP'
},	// Completion callback function
	// This callback is executed once we have a StreamUri
	function (err, stream, xml) {
		if (err) {
			console.log(err);
			return;
		} else {
			console.log('------------------------------');
			console.log('Host: ' + HOSTNAME + ' Port: ' + PORT);
			console.log('Stream: = ' + stream.uri);
			console.log('------------------------------');

			// start processing the keyboard
			//read_and_process_keyboard(); // ★ この行をコメントアウト

			// start processing the Yolo
			read_and_process_yolo(); // ★ この行を追加
		}
	}
);
```

### 5-2. Yolo操作関数本体

Yoloに接続すると、認識結果がJsonとして出力されるので、
pipeでストリーム出力し、JSONStreamにたべさせて、パースします。

パース結果をもとに、パン・チルトコマンドを発行してカメラを動かします。

```javascript
function read_and_process_yolo() {
	console.log('yolo : ' + YOLO_HOST + " : " + YOLO_PORT)
	const stream = request('http://' + YOLO_HOST + ':' + YOLO_PORT).pipe(JSONStream.parse('$*'))
	var cnt = 0;
	stream.on('data', (data) => {
		cnt = (cnt + 1) % 4;
		var objs = data.value.objects
		objs.forEach(v => {
			if (v.name === 'person') {
				const xadj = 0.05
				const yadj = 0.05
				var cx = v.relative_coordinates.center_x;
				var cy = v.relative_coordinates.center_y;
				console.log('cx : ' + cx + ' / cy : ' + cy)
				if ((0 == cnt % 2) && (cx < 0.5 - xadj)) move(-1, 0, 0, 'left');
				if ((0 == cnt % 2) && (0.5 + xadj < cx)) move( 1, 0, 0, 'right');
				if ((0 == cnt)     && (cy < 0.6 - yadj)) move( 0, 1, 0, 'up');
				if ((0 == cnt)     && (0.6 + yadj < cy)) move( 0,-1, 0, 'down');
			}
		}); // end of objs.forEach
	}); // end of stream.on
}
```

### 5-3. Yoloの実行

認識結果をJson形式で出力させるために ```-json_port 5000``` を引数に追加します。
また、Yoloの出力する画像が不要な場合には ```-dont_show``` をつけます。

```shell
$ ./darknet detector demo cfg/coco.data cfg/yolov3-tiny.cfg yolov3-tiny.weights rtsp://admin:admin@192.168.0.100/2 -json_port 5000 -dont_show
```

### 5-4. ```example_yolo.js``` の実行

```shell
$ node example_yolo.js
```

カメラが自分の方向に自動的にパン・チルトしてくれれば成功です

### 5-5. VLCで画像を観察

VLCを起動し、メディア >> ネットワークストリームを開く を選択し
URLに ```rtsp://admin:admin@192.168.0.100/1``` を入力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/01bc1be9-0359-bbe0-27a5-d373527b3325.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102797/f19369bf-276f-0c1d-da89-f092cfa9c5f1.png)

以上です