Mac のターミナル・Node.js でのカメラ画像取得： imagesnap とnode-webcam（ #GWアドベントカレンダー 5/4 ）

こちらは、下記の #GWアドベントカレンダー の 6日目の記事です。

●日数分だけ記事を書く！（ @youtoy ） | GWアドベントカレンダー
　https://gw-advent.9wick.com/calendars/2020/69

## はじめに
この記事に書いた内容をやろうとしたきっかけは、以下のハンズオンに参加したことです。
もう少し説明を書くと、このときはラズパイで USBカメラを利用してコマンドでカメラ画像を取得していたのですが、カメラ画像取得を node.js で行ったり、Mac の内蔵のカメラでラズパイ上でやったのと似たようなことを行ったりできないかが気になったためです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">今日はこちらです。<br><br>●（オンライン）ハンズオン - ねこ様見守りカメラを作ろう！ - connpass<br>　<a href="https://t.co/haAsiLIlXj">https://t.co/haAsiLIlXj</a><a href="https://twitter.com/hashtag/nekocierge?src=hash&amp;ref_src=twsrc%5Etfw">#nekocierge</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1257168344798511104?ref_src=twsrc%5Etfw">May 4, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

以下で、試した内容を備忘録も兼ねて書いていこうと思います。

## Mac のターミナル上で 内蔵カメラの画像を取得
Mac のターミナル上でコマンドを使ってカメラ画像の取得ができないか調べました。

いろいろとググってみた結果、自分の今の Mac の環境で手軽に実現する方法は [Homebrew](https://brew.sh/index_ja) を使って「[imagesnap](https://github.com/rharder/imagesnap)」を利用するのが良さそうでした。

●rharder/imagesnap: Capture Images from the Command Line
　https://github.com/rharder/imagesnap

### imagesnap の導入
まず、imagesnap を導入します。
自分は Homebrew をセッティング済みだったため、以下のコマンドを実行するだけで導入は完了しました。

```bash
$ brew install imagesnap
```

この後、実際にカメラ画像を取得してみます。

### カメラ画像の取得（オプションなし）
[公式の情報](https://github.com/rharder/imagesnap)を見ると、シンプルな画像の取得と、出力する画像のファイル名を指定するコマンドは、それぞれ以下のとおりでした。

```bash
# シンプルなコマンドで画像取得
$ imagesnap
# 出力する画像のファイル名を指定しての画像取得
$ imagesnap output.jpg
```

自分の環境で上記を試すと、ターミナルを実行しているフォルダに画像が出力されたのですが、意図通りの画像を取得できませんでした。

その原因は仮想カメラの [CamTwist](http://camtwiststudio.com/) を導入していたためです。
上記のコマンドで取得できた画像は以下のようなものになっており、内蔵カメラより優先して CamTwist が使われたようでした。
![test.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ff928c77-c4d2-3e09-ea8a-9c361bd9f5c2.jpeg)

### カメラ画像の取得（特定のカメラデバイスを指定）
公式のサイトを見直すと、複数のカメラがある場合にリストを得るコマンドと、カメラを指定するコマンドが書かれていました。

```bash
# カメラのリストを得る
$ imagesnap -l
# カメラを指定して画像を取得する
$ imagesnap -d 【カメラ名】
```

出力を確認すると「FaceTime HD Camera」という名称で指定すれば良さそうなことが分かったため、以下のコマンドを実行しました。
「FaceTime HD Camera」の名称が半角スペースを含むため、コマンドではダブルクォーテーションで囲んで実行しました。

```bash
$ imagesnap -d "FaceTime HD Camera" output.jpg
```

上記を実行するとコマンドを実行したフォルダ内に 1280x720 のサイズの画像が出力されましたが、画像の中身が黒い領域ばかりのおかしなデータになっていました。

### カメラ画像の取得（カメラの初期化待ちを加える）

そして、imagesnap についての記事をググったところ、[とある日本語の記事](https://news.mynavi.jp/article/osxhack-237/)で以下の記載を見つけました。

> しかし、カメラの初期化には多少の時間が必要なようで、デフォルトの状態では真っ黒なJPEGが生成されてしまう。これを避けるためには、「-w」オプションで撮影を遅延させればいい(単位は秒、小数点可)。試したところ、0.2秒(-w 0.2)あたりから薄暗く映るようになり、0.7秒(-w 0.7)も経てばまずまずの明るさで撮影できる。1秒(-w 1)を指定しておけば確実だろう。なお、一度確認すればデバイス名を表示する必要はないので、ついでに「-q」オプションで出力を止めてしまおう。

これに従ってコマンドの内容をさらに修正し、以下を実行することで無事に Mac の内蔵カメラで画像を取得できました。

```bash
$ imagesnap -d "FaceTime HD Camera" -w 1 output.jpg
```

以下が実際に取得した画像です（※ 少しクリッピング・リサイズをしています）。
![output.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/45c8455c-72dd-b2d3-e513-147920273a3f.jpeg)

### 様々なオプション

なおコマンドでヘルプを見ることもでき、他にも今回使っていないオプションが複数ありました。

```bash
$ imagesnap -h
#【途中省略】
  -h          This help message
  -v          Verbose mode
  -l          List available video devices
  -t x.xx     Take a picture every x.xx seconds
  -q          Quiet mode. Do not output any text
  -w x.xx     Warmup. Delay snapshot x.xx seconds after turning on camera
  -d device   Use named video device
```

## Node.js でのカメラ画像取得
### 試すものの選定
Node.js のパッケージでカメラを利用するものを探してみて、見つかったものの中の 1つに以下がありました。

●node-webcam - npm
　https://www.npmjs.com/package/node-webcam

そして、上記のページ内でちょうど以下のような記載もあったので、こちらを試してみることにしました。
> Mac OSX
> 　\#Mac OSX relies on imagesnap
> 　\#Repo https://github.com/rharder/imagesnap
> 　\#Avaliable through brew
> 
> 　brew install imagesnap

なお、Linux の場合は「fswebcam」を利用し、Windows の場合は「CommandCam（exe は同梱）」を利用する形のようです。

### 導入してサンプルを動かしてみる
とりあえず、以下のコマンドを実行します。

```bash
$ npm install node-webcam
```

その後、適当な名前で JavaScript のファイルを作成し、公式ページにある以下のプログラムをそのまま実行してみました（なんだか、やたらと改行が多いサンプルだったので、それは適宜削りました）。

```JavaScript
var NodeWebcam = require( "node-webcam" );

//Default options
var opts = {
    //Picture related
    width: 1280,
    height: 720,
    quality: 100,

    //Delay in seconds to take shot
    //if the platform supports miliseconds
    //use a float (0.1)
    //Currently only on windows
    delay: 0,

     //Save shots in memory
    saveShots: true,

    // [jpeg, png] support varies
    // Webcam.OutputTypes
    output: "jpeg",

    //Which camera to use
    //Use Webcam.list() for results
    //false for default device
    device: false,

    // [location, buffer, base64]
    // Webcam.CallbackReturnTypes
    callbackReturn: "location",

    //Logging
    verbose: false
};

//Creates webcam instance
var Webcam = NodeWebcam.create( opts );

//Will automatically append location output type
Webcam.capture( "test_picture", function( err, data ) {} );

//Also available for quick use
NodeWebcam.capture( "test_picture", opts, function( err, data ) {
});

//Get list of cameras
Webcam.list( function( list ) {
    //Use another device
    var anotherCam = NodeWebcam.create( { device: list[ 0 ] } );
});

//Return type with base 64 image
var opts = {
    callbackReturn: "base64"
};

NodeWebcam.capture( "test_picture", opts, function( err, data ) {
    var image = "<img src='" + data + "'>";
});
```

そして、内容に手を加えずに上記を実行すると「`test_picture.jpg`」というファイルは出力されたのですが、予想通り imagesnap を直接使った場合と同じで CamTwist から取得した画像が出力されていました。

### オプションに変更を加える
カメラの指定は以下が関係しそうなので、GitHub上のソースコードで以下の指定に関係しそうな部分を見てみました。

```JavaScript
    //Which camera to use
    //Use Webcam.list() for results
    //false for default device
    device: false,
```

そうすると、「`/node-webcam/blob/master/src/webcams/ImageSnapWebcam.js`」の 72行目あたりに以下の部分がありました。
これを見ると、カメラのデバイス名をそのまま指定してやれば良さそうです。
<img width="440" alt="ImageSnapWebcam_js.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/bb3a1fdf-3621-aa30-288b-3f0148849e09.jpeg">

そこで、サンプルのソースコード中の「`device: false`」の部分を「`device: "FaceTime HD Camera"`」に変更して、再度プログラムを実行しました。
その結果、無事に以下の画像を得ることができました（サンプルを動かした時と同じく、コマンドを実行したフォルダ内に「test_picture.jpg」というファイル名で出力されました）。
![test_picture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/55a41698-4721-fec6-8edb-df68ba99b0ec.jpeg)
なお、imagesnap を直接ターミナル上で使った時はカメラの初期化待ちのディレイを 1秒いれる必要がありそうでしたが、JavaScript のプログラムを実行したときのオプションではデフォルトの「`delay: 0`」で問題なく動作しました（プログラムを実行してから画像が出力されるまでは、ディレイが入ったような待ちが入った気もしますが）。

念のため、最終的に動作させてソースコードを掲載しておきます。

```JavaScript
var NodeWebcam = require( "node-webcam" );

//Default options
var opts = {
    //Picture related
    width: 1280,
    height: 720,
    quality: 100,

    //Delay in seconds to take shot
    //if the platform supports miliseconds
    //use a float (0.1)
    //Currently only on windows
    delay: 0,

     //Save shots in memory
    saveShots: true,

    // [jpeg, png] support varies
    // Webcam.OutputTypes
    output: "jpeg",

    //Which camera to use
    //Use Webcam.list() for results
    //false for default device
    device: "FaceTime HD Camera",

    // [location, buffer, base64]
    // Webcam.CallbackReturnTypes
    callbackReturn: "location",

    //Logging
    verbose: false
};

//Creates webcam instance
var Webcam = NodeWebcam.create( opts );

//Will automatically append location output type
Webcam.capture( "test_picture", function( err, data ) {} );

//Also available for quick use
NodeWebcam.capture( "test_picture", opts, function( err, data ) {
});

//Get list of cameras
Webcam.list( function( list ) {
    //Use another device
    var anotherCam = NodeWebcam.create( { device: list[ 0 ] } );
});

//Return type with base 64 image
var opts = {
    callbackReturn: "base64"
};

NodeWebcam.capture( "test_picture", opts, function( err, data ) {
    var image = "<img src='" + data + "'>";
});
```

## おわりに
今回、Mac のターミナル上で Mac内蔵のカメラから画像を取得する方法と、それを利用した Node.js のパッケージを利用して画像を取得する方法とを試しました。

今後、さらに取得した画像を単に出力するだけでなく、取得した画像を処理に用いる JavaScript のプログラムを作ったりしてみたいと思います。