Processing 3.3 と NyARToolkit 5.0.5 で AR の練習

# 動機
長らく、Processing 1.5.1 と NyARToolkit で AR を楽しんできました。
少し自由な時間ができたので… さすがにもう、最新の環境に乗り移ろうと決心しました。最新の NyARToolkit は 3.0.2 以降、従来のマーカに加えて、NFT（Natural Feature Tracking; 自然特徴点追跡）もできるので、そのあたりも練習してみました。

# 環境
* Windows 10
* [Processing](https://processing.org/) 3.3
* [NyARToolkit for Processing](https://github.com/nyatla/NyARToolkit-for-Processing/) 5.0.5

# やったこと
* ビデオキャプチャのテスト
* 最もシンプルな AR
* OBJ 形式の 3DCG モデルで AR
* マルチ NFT（自然特徴点追跡）による AR

# コード

解説は主にコード中のコメントのみで恐縮です。

## ビデオキャプチャのテスト

まずは、Processing 標準の video ライブラリでビデオキャプチャのテストです。
Processing 3 では、video ライブラリは自分でインストールするようです。Processing IDE の「ライブラリの追加...」で The Processing Foundation の video ライブラリをインストールしました。

Processing 1.5.1 時代に比べて（特に Windows 環境では）とても容易になりました。

```java:cam_test.pde
// cam_test.pde
// Install "video" library via "Add Library" tool of Processing IDE.
// Connect a Web camera to your PC.
// Run this sketch once.
// Find a camera device number with "size=640x480,fps=30" from the list.
// * Rewrite "x" with the device number you use.
// Run this sketch again, and confirm the video is shown.
// Coded by Mitsuteru Kokubun, 2017/02/20

import processing.video.*;               // using "video" library

Capture cam;                             // declaration of Capture object

// setup() function is executed once
void setup() {
  size(640, 480);                        // setting size of window
  String[] cameras = Capture.list();     // getting available camera devices
  printArray(cameras);                   // listing available camera devices
  cam = new Capture(this, cameras[12]);  // * Rewrite [x] with the device number you use.
  cam.start();                           // starting capture
}

// draw() function is exectuted repeatedly
void draw() {
  if(cam.available() == false) {         // if camera is not available
    return;                              // do nothing and return
  }
  cam.read();                            // capturing image
  image(cam, 0, 0);                      // displaying captured image
}
```

## 最もシンプルな AR

[NyARToolkit for Processing の GitHub リポジトリ](https://github.com/nyatla/NyARToolkit-for-Processing/)の指示通りに、nyar4psg ライブラリをインストールしました。
カメラのキャリブレーションパラメータもデフォルトの "camera_para.dat" を使いました。
また、NyID マーカを使っています（[こちらのサイト](http://sixwish.jp/AR/Marker/idMarker/)で作成）。
実質20行くらいでできました。

![ar_box.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/38aed228-049d-7098-8e85-fca82e740919.jpeg)


```java:ar_box.pde
// ar_box.pde
// A simple AR demo using NyARToolkit and Proceccing 3.
// Install "video" library via "Add Library" tool of Processing IDE.
// Install "nyar4psg" library according to the URL below.
//   -> https://github.com/nyatla/NyARToolkit-for-Processing/
// Connect a Web camera to your PC.
// * Rewrite "x" with the camera device number you use.
// Place "camera_para.dat" in "data" folder of this sketch.
//   - "camera_para.dat" is included within ./libraries/nyar4psg/data
// Print NyID markers on paper. ("NyId_000-005.pdf")
// Run this sketch, and check that an orange transparent box is superimposed on the marker.
// Coded by Mitsuteru Kokubun, 2017/02/20

import processing.video.*;               // using "video" library
import jp.nyatla.nyar4psg.*;             // using "nyar4psg" library

Capture cam;                             // declaration of Capture object
MultiMarker mm;                          // declaration of MultiMarker object

void setup() {
  // window and camera settings
  size(640, 480, P3D);                   // setting size of window with P3D mode
  String[] cameras = Capture.list();     // getting available camera devices
  printArray(cameras);                   // listing available camera devices
  cam = new Capture(this, cameras[12]);  // * Rewrite [x] with the device number you use.
  cam.start();                           // starting capture
  // NyARToolkit settings
  mm = new MultiMarker(this,             // initial settings of NyARToolkit
             width,                      // width of the input image
             height,                     // height of the input image
             "camera_para.dat",          // camera calibration parameter file
             NyAR4PsgConfig.CONFIG_PSG); // configuration for Processing
  mm.addNyIdMarker(0, 80);               // adding NyId marker(ID, marker_width[mm])
}

void draw() {
  // video capture
  if(cam.available() == false) {         // if camera is not available
    return;                              // do nothing and return
  }
  cam.read();                            // capturing image
  // Start of AR process
  mm.detect(cam);                        // detecting marker within captured image
  mm.drawBackground(cam);                // drawing captured image on background
  if(mm.isExist(0) == false) {           // if marker[0] is not exist within the image
    return;                              // do nothing and return
  }
  mm.beginTransform(0);                  // starting coordinate projection based on marker[0]
    translate(0, 0, 40);                 // move the origin 40 mm in the Z axis direction
    fill(255, 165, 0, 127);              // fill color(R, G, B, opacity)
    box(80);                             // draw a 80 mm square box
  mm.endTransform();                     // ending coordinate projection
  // End of AR process
}
```


## OBJ 形式の 3DCG モデルで AR

3DCG のモデルを読むには、Processing 1.5.1 時代は、[saitoobjloader](https://code.google.com/archive/p/saitoobjloader/downloads) や [MQOLoader](http://www.hyde-ysd.com/reco-memo/mqoview.html) などを使わせていただいてきました。
今回、saitoobjloader を試したところ、Processing 3.3 では動きませんでした。
少し調べたら、Processing 標準の PShape で OBJ 形式のモデルが読めることが分かったので、試したら、簡単にできました。実質30行くらいです。
OBJ 形式の 3DCG モデル（rocket.obj, rocket.mtl, rocket.png）は、Processing のサンプル「LoadDisplayObj.pde」で使われているものを使いました。Processing 本体のフォルダ内の、modes\java\examples\Basics\Shape\LoadDisplayOBJ\data の中にあります。

![ar_obj.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/bc361cf5-f728-8470-ed72-e833e86d22f0.jpeg)


```java:ar_obj.pde
// ar_obj.pde
// A 3DCG (*.obj) AR demo using NyARToolkit and Proceccing 3.
// Install "video" library via "Add Library" tool of the Processing IDE.
// Install "nyar4psg" library according to the URL below.
// Connect a Web camera to your PC.
// * Rewrite "x" with the camera device number you use.
// Place "camera_para.dat" in "data" folder of this sketch.
//   - "camera_para.dat" is included within ./libraries/nyar4psg/data
// Place "rocket.obj", "rocket.mtl", and "rocket.png" in "data" folder of this sketch.
//   - "rocket.*" are included within ./Processing_3.x.x/
//       modes/java/exmples/Basics/Shape/LoadDisplayOBJ/data
// Print NyID markers on paper. ("NyId_000-005.pdf")
// Run this sketch, and check that a rocket of 3DCG is superimposed on the marker.
// Coded by Mitsuteru Kokubun, 2017/02/20

import processing.video.*;               // using "video" library
import jp.nyatla.nyar4psg.*;             // using "nyar4psg" library

Capture cam;                             // declaration of Capture object
MultiMarker mm;                          // declaration of MultiMarker object
PShape obj;                              // declaration of PShape object
float ry;                                // variable for rotation of 3DCG model

void setup() {
  // window and camera settings
  size(640, 480, P3D);                   // setting size of window with P3D mode
  String[] cameras = Capture.list();     // getting available camera devices
  printArray(cameras);                   // listing available camera devices
  cam = new Capture(this, cameras[12]);  // * Rewrite [x] with the device number you use.
  cam.start();                           // starting capture
  // NyARToolkit settings
  mm = new MultiMarker(this,             // initial settings of NyARToolkit
             width,                      // width of the input image
             height,                     // height of the input image
             "camera_para.dat",          // camera calibration parameter file
             NyAR4PsgConfig.CONFIG_PSG); // configuration for Processing
  mm.addNyIdMarker(0, 80);               // adding NyId marker(ID, marker_width[mm])
  // PShape setting
  obj = loadShape("rocket.obj");         // loading a 3DCG model named "rocket.obj"
}

void draw() {
  // video capture
  if(cam.available() == false) {         // if camera is not available
    return;                              // do nothing and return
  }
  cam.read();                            // capturing image
  // Start of AR process
  mm.detect(cam);                        // detecting marker within captured image
  mm.drawBackground(cam);                // drawing captured image on background
  if(mm.isExist(0) == false) {           // if marker[0] is not exist within the image
    return;                              // do nothing and return
  }
  mm.beginTransform(0);                  // starting coordinate projection based on marker[0]
    lights();                            // adding lights in the 3D scene
    scale(0.3);                          // adjusting the size of the 3DCG model
    translate(0, 0, 80);                 // adjusting the position of the 3DCG model
    rotateX(PI/2);                       // rotating the 3DCG model 90 degrees around the X axis
    rotateY(ry);                         // rotating the 3DCG model ry radians around the Y axis
    shape(obj);                          // displaying the 3DCG model
  mm.endTransform();                     // ending coordinate projection
  // End of AR process
  ry += 0.1;                             // changing rotation angle
}
```

## マルチ NFT（自然特徴点追跡）による AR

最後に NFT も練習してみましした。せっかくなので、適当に写真を撮ってそれを NFT のターゲットに、重畳表示させる CG モデルも Blender を使って自作しました。

以下はターゲット用に撮影した写真です。
![IMG_0177.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/44138b22-5579-bf6b-5e56-507b5eaae328.jpeg)

NyARToolkit のサンプルとして付属する NftFileGenerator を使って画像から特徴点を抽出し、特徴点ファイル（*.fset, *.fset3, *.iset の三つのファイル）を出力します。
![nftgen_savefeatureset.PNG](https://qiita-image-store.s3.amazonaws.com/0/157570/fffad013-bcb4-88cf-18bb-f71043962f15.png)

Blender で適当に CG を作りました。
![blender_time.PNG](https://qiita-image-store.s3.amazonaws.com/0/157570/8806cc28-2802-f4b8-c683-0780b1b90aa2.png)

完成です。
![nft_clock.JPG](https://qiita-image-store.s3.amazonaws.com/0/157570/a959aa92-8657-8cd6-afd4-c440a2f1e70d.jpeg)


```java:ar_nft.pde
// ar_nft.pde
// An NFT(Natural Feature Tracking) AR demo using NyARToolkit and Proceccing 3.
// Install "video" library via "Add Library" tool of the Processing IDE.
// Install "nyar4psg" library according to the URL below.
// Connect a Web camera to your PC.
// * Rewrite "x" with the device number you use.
// Place "camera_para.dat" in "data" folder of this sketch.
//   - "camera_para.dat" is included within ./libraries/nyar4psg/data
// Place "drop.{obj, mtl}" and "time.{obj, mtl}" in "data" folder of this sketch.
//   - These obj models are made by using some modeling software, e.g. Blender.
// Place "faucet.{fset,fset3, iset}" and "time.{fset,fset3, iset}" in "data" folder of this sketch.
// Print "faucet.jpg" and "clock.jpg" on paper.
// Run this sketch, and check that the 3DCGs are superimposed on the marker.
// Coded by Mitsuteru Kokubun, 2017/02/20

import processing.video.*;               // using "video" library
import jp.nyatla.nyar4psg.*;             // using "nyar4psg" library

Capture cam;                             // declaration of Capture object
MultiNft nft;                            // declaration of MultiNft object
PShape[] obj;                            // declaration of PShape array object

void setup() {
  // window and camera settings
  size(640, 480, P3D);                   // setting size of window with P3D mode
  String[] cameras = Capture.list();     // getting available camera devices
  printArray(cameras);                   // listing available camera devices
  cam = new Capture(this, cameras[0]);   // * Rewrite [x] with the device number you use.
  cam.start();                           // starting capture
  // NyARToolkit settings
  nft = new MultiNft(this,               // initial settings of NyARToolkit
             width,                      // width of the input image
             height,                     // height of the input image
             "camera_para.dat",          // camera calibration parameter file
             NyAR4PsgConfig.CONFIG_PSG); // configuration for Processing
  nft.addNftTarget("faucet", 160);       // adding NFT target[0] (name, target_width[mm])
  nft.addNftTarget("clock", 160);        // adding NFT target[1] (name, target_width[mm])
  // PShape setting
  obj = new PShape[2];
  obj[0] = loadShape("drop.obj");        // loading a 3DCG model named "drop.obj"
  obj[1] = loadShape("time.obj");        // loading a 3DCG model named "time.obj"
}

void draw() {
  // video capture
  if(cam.available() == false) {         // if camera is not available
    return;                              // do nothing and return
  }
  cam.read();                            // capturing image
  // Start of AR process
  nft.detect(cam);                       // detecting marker within captured image
  nft.drawBackground(cam);               // drawing captured image on background
  for (int i=0; i<2; i++) {              // repeating for two NFT targets
    if(nft.isExist(i) == false) {        // if target[i] is not exist within the image
      continue;                          // do nothing and find next target
    }
    nft.beginTransform(i);               // starting coordinate projection based on target[i]
      lights();                          // adding lights in the 3D scene
      translate(-80, 40, 0);             // adjusting the position of the 3DCG model
      rotateY(PI);                       // rotating the 3DCG model 180 degrees around the Y axis 
      shape(obj[i]);                     // displaying the 3DCG model
    nft.endTransform();                  // ending coordinate projection
  }
  // End of AR process
}
```


# 所感と今後

移行して大正解でした。
たいていの私の用途は初期のプロトタイピングのため、通常のマーカーを使っていてもなんの問題もないのですが、NFT のほうがやっぱり楽しいですね。
引き続きいろいろ遊んでみたいと思います。