MFT2019に展示した3Dスキャナ

3D Sensor Advent Calendar 2019の12日目の記事です
空いていたので勝手ながらお邪魔します
https://qiita.com/advent-calendar/2019/3d-sensor

BLK-360は素晴らしくて欲しい3Dスキャナですが、お値段が200万円越えです
3Dスキャンを使った定期的な仕事の収入がなければローンで人生詰むレベルですので、
仕方なく、LIDAR-Lite v3を使用して自作しました

YouTubeなど調べると、すでに作っていらっしゃる方がいます
https://www.youtube.com/watch?v=gCpCGkwwy8I

今回は、カメラも搭載し、ポイントクラウドに色を付けたり、
オフラインでsHDRIを生成する機能も付けました

ほとんどが公開されているプログラムを部分的に改造しただけなのですが、
手間取ったところだけメモしますので、ご参考になれば幸いです

#####1. 構成
#####2. Arduino
・LIDAR-Lite v3
・Servo
・カメラスイッチ
#####3. Unity
・UniRX / serial controller（測定結果、方位の入手、カメラのスイッチ）
・Webcam Texture / capturing color
・カメラのスイッチ
・（オフライン）point cloud importer / player
    
#1. 構成<p>
装置は下の図のように構成しました<p>
![MFT2019.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103446/74c49204-5a2b-4161-9255-43a3b55932ca.png)<p>
Arduinoを使い、yawとpitchを変更するservoを制御し、Lidar lite v3で距離を測定しました
測定付近の色と周辺画像を撮影するカメラのシャッターもArduinoが制御しました
距離と測定した方位はUnityに取り込みました
アセットのUniRXを用い、Arduinoとシリアル通信しました
距離測定したときの方位の中心付近の画像から色を平均化して取得しました
HDRI画像はオフラインで生成しました
装置の外観は下の写真のようになりました
動くメカの自作が初めてだったので、今後、修行していこうと思います
![3dscanner.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103446/85ad9900-bddb-d359-fd51-98c2f5b49ebc.jpeg)

<p>
#2. Arduino <p>
####- LIDAR-Lite v3 
gitHubからダウンロードしたスケッチを改変しました 
https://github.com/garmin/LIDARLite_Arduino_Library 
その際、一つ忘れていたのは、configureという測定モードの変更です
ビックサイトの天井は高いので、最大距離まで測定する必要があったのですが、
気づかないで、0のdefaultにしていました

```
  /*
    configure(int configuration, char lidarliteAddress)
    Selects one of several preset configurations.
    Parameters
    ----------------------------------------------------------------------------
    configuration:  Default 0.
      0: Default mode, balanced performance.
      1: Short range, high speed. Uses 0x1d maximum acquisition count.
      2: Default range, higher speed short range. Turns on quick termination
          detection for faster measurements at short range (with decreased
          accuracy)
      3: Maximum range. Uses 0xff maximum acquisition count.
      4: High sensitivity detection. Overrides default valid measurement detection
          algorithm, and uses a threshold value for high sensitivity and noise.
      5: Low sensitivity detection. Overrides default valid measurement detection
          algorithm, and uses a threshold value for low sensitivity and noise.
    lidarliteAddress: Default 0x62. Fill in new address here if changed. See
      operating manual for instructions.
  */

  myLidarLite.configure(3); // Change this number to try out alternate configurations

```
https://github.com/garmin/LIDARLite_Arduino_Library/blob/master/examples/v3/GetDistanceI2c/GetDistanceI2c.ino 
<p>
Lidarが取得した値は、シリアル通信で出力し、Unityが受け取ります
<p>
####- Servo
こちらのページを参考に、機種を選定したり、スケッチを改変しました
http://nomolk.hatenablog.com/entry/2017/04/09/222415
今回はEMAX ES08MAIIを使用しました
この時に勘違いしていたのが、myservo.read()です
現在の角度を読み取る命令だと思っていたのですが、
調べたところ、直前にmyservo.write()で送った角度が返されます
なので、電源投入後の復帰時や、手で向きを変えてしまった後などは、
現在の角度と直前の指示角度が違ってしまい、狙った制御が難しくなります
http://www.musashinodenpa.com/arduino/ref/index.php?f=1&pos=2079
<p>
距離を測定した方位をシリアル通信で出力し、Unityが受け取ります<p>

####- カメラのスイッチ<p>
デジタルアウトを1pin割り当てました
カメラの都合でそのままデジタルアウトを接続しても動作しなかったので、
リレーを経由させました
<p>

#3. Unity<P>
今回は2018.4.0f1を使用しました<p>
####- UniRX / serial controller（測定結果、方位の入手、カメラのスイッチ）<p>
こちらを参考に、スクリプトを改変しました
http://nn-hokuson.hatenablog.com/entry/2017/09/12/192024<p>

```
    void Start () 
    {
        this.serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One); 

        try
        {
            this.serial.Open();
            Scheduler.ThreadPool.Schedule (() => ReadData ()).AddTo(this);
        } 
```

```
    public void ReadData()
    {
        while (this.isLoop)
        {
            string message = this.serial.ReadLine();
			
			string[] data =  message.Split('\t');
			
			Dist1 = float.Parse(data[2]);
			Pitch1 = float.Parse(data[0]);
			PlaneYaw = float.Parse(data[1]);
			photoFlag = int.Parse(data[3]);
        }
    }
```
取得した結果はcsvで保存しました
https://high-programmer.com/2017/12/10/unity-savedata-otherfile/

Point Cloud Free Viewer用に、ファイルのヘッダを下記のように変更しました

		textSave("COFF");      
		textSave("25000 50000 0");
		textSave("0 0 0 0 0 0 255");

		origtextSave("R Yaw Pitch");   

####- Webcam Texture / capturing color<p>
こちらを参考に、スクリプトに機能を追加しました
本当は距離に応じて色を取得する場所を変えるべきでした
http://nn-hokuson.hatenablog.com/entry/2017/08/09/192813<p>

```
		webcamtex.GetPixels32 (colors);
		int width = webcamtex.width;
		int height = webcamtex.height;
		probeIndex = webcamtex.width * webcamtex.height / 2 + webcamtex.width / 2 - 10;
		Color c = colors[probeIndex];
```

```
			var _obj = (GameObject)Instantiate (obj1, new Vector3(PclX, PclY, PclZ), Quaternion.Euler(-90 + Pitch1, 90-PlaneYaw, 0));
			_obj.GetComponent<Renderer>().material.color = new Color(c.r, c.g, c.b, c.a);
			Debug.Log( (PlaneYaw).ToString()+","+(PclX).ToString()+","+(PclY).ToString()+","+(PclZ).ToString()+","+(int)(255*c.r)+","+(int)(255*c.g)+","+(int)(255*c.b));
```

####- point cloud importer / player<p>
こちらを参考に、オフラインで、取得したポイントクラウドを出力しました
http://www.pointcloud.jp/blog_n30/

<p>
結果はこちらのようになりました
<blockquote class="twitter-tweet" lang="ja"><p>ビックサイト内のキャプチャー結果</p>&mdash; aya:Tinkering-man:100%UnOfficial (@nakano_muramoto) <a href="https://twitter.com/nakano_muramoto/status/1157677842215211008">2019年 8月 4日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

天井の照明が複数の距離で折り返って取得された結果になりました
ポストフィルタの検討が必要です
また、ビックサイトの中は灰色がほとんどなので、色づけの効果が分かりにくかったです

##今後の展開
メカの修正や出力結果のポストフィルタなどたくさん残されていますが、
自作するのであれば、キャプチャー時間をさらに短縮するか、
分解能をさらに上げるか、有意性を考えてみたいです