我は神龍……どんな平面でも一つだけタッチスクリーン化してやろう

こんにちは。あつし([@Anaakikutsushit](https://twitter.com/Anaakikutsushit))です。

[株式会社モフ](http://moff.jp/)さんから「どんな壁でもタッチスクリーンにできるシステムが欲しいんだけど、お願いできない？」とお声がけ頂きまして、その開発にあたっての備忘録として本記事を書きました。技術情報の公開自体は許可を頂いております。

壁面への映像の投影、およびタッチ処理を実現するためのOSはWindowsの想定です。

すなわち、**プロジェクターで映し出したWindows PCの画面を直接タッチして操作可能にする**ことを想定しています。

開発は **Windows10、C++、およびC#** で行いました。やってた感じではMacでも出来そうです（未検証）。

# 任意の壁をタッチ操作可能にするシステムを作ろう

## タッチ方式についての前提知識

### そもそもタッチスクリーンを実現するには

平面をタッチスクリーン化するためには、少なくともタッチした点の座標を特定することが必要となります。
タッチ座標を特定するために、古今東西さまざまな動作原理が利用されてきました。

[タッチパネル - 動作原理別（Wikipedia）](https://ja.wikipedia.org/wiki/%E3%82%BF%E3%83%83%E3%83%81%E3%83%91%E3%83%8D%E3%83%AB#%E5%8B%95%E4%BD%9C%E5%8E%9F%E7%90%86%E5%88%A5)

身近なところではニンテンドーDSやWiiUゲームパッドでは抵抗膜方式（感圧式）を、スマホやタブレットでは静電容量方式を採用しています。

### 今回採用する方式

しかし、今回目指すシステムは**表面の素材に左右されずタッチスクリーン化する**ことが要件となってきますので、上記の2つの方式はどちらも採用できません。

今回の開発では赤外線方式によるタッチスクリーン化を採用しました。

### ちなみに：こんなプロダクトも

専用のペン型デバイスをPCにBluetooth接続することでタッチスクリーン化を実現するプロダクトもあります。

[画面がデジタルボードに変身！書き込み、保存、共有、遠隔会議も『GoTouch』](https://www.makuake.com/project/gotouch/)

これはかなりいい感じのプロダクトなのですが、対応可能なサイズは80インチまでとのこと。今回のプロジェクトでは**タッチスクリーン化したい壁面が100インチ級**になる可能性があったため、見送りとなりました。

### 先駆者の方々

赤外線方式を利用したタッチスクリーンシステムの開発については幾人かの知見があり、参考にさせて頂きました。

使用機材はいずれも本記事とは異なりますが、システム構築の概念は共通したものです。似たようなシステムを開発する場合に参考になるかと思います。

[赤外線カメラを使用した壁タッチセンサー](https://qiita.com/aq_tsurumai/items/10907f786890f46f8037)

[ウォールタッチパネルを５千円ぐらいで作りたい-その１](http://denkikei.com/2018/07/walltouch01/)

## システムの概要

今回は[LiDAR](https://ja.wikipedia.org/wiki/LIDAR)と呼ばれる距離測定センサーを使ってタッチ座標を検出します。

![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336249/c6dfb70d-383c-6b20-618e-298467f85d1a.jpeg)


（下記Amazon商品ページより引用）

製品としては[RPLiDAR社のA1M8](http://www.slamtec.com/en/lidar/a1)を使用しました。
[Amazon等で購入することができます](https://www.amazon.co.jp/dp/B07J9HSWJ6/)。

こちらのLiDARは、上部の丸い部分の側面の窓からレーザーを照射し、距離を測定する仕組みとなっています。

丸い部分が回転することによって360°全方向の距離をリアルタイムに測定可能にしているというわけですね。

測定結果のイメージは上記公式サイトで確認できます。

その他、LiDARの詳細については[公式のサポートベージ](http://www.slamtec.com/en/Support#rplidar-a-series)で確認可能です。

今回のシステムではその機能を利用して、次の図のようにシステムを組み上げていきます。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336249/933e25da-1b4b-bdfa-d4d2-ff4015e1d91c.png)


- LiDARを横倒しにして、壁と平行な平面を検出するようにする
  - このとき、検出平面をなるべく壁に近づけることで精度がよくなります（壁に触れていないのにタッチ判定されることが少なくなる）
  - 別に壁じゃなくても大丈夫です。床でも天井でも、デスクの上でも。平面に平行な面を検出するようにLiDARを設置するとこだけがポイント。
- 検出平面内に指が入るとLiDARでその位置を特定できるので、特定した指の位置に対してクリック処理を起こす
- 図では「検出平面の横幅に対して中心」かつ「検出平面の上方」にLiDARを設置していますが、この設置場所もどこでも大丈夫です。
  - 私が開発したときは、この位置に置くと座標の計算がやりやすかったというだけです（計算式は後述）。
  - ただ上記型番のLiDARは、～10cmの近距離検出がニガテっぽいです。検出平面内に置くのは避けたほうがいいかも？

## 必要な機材

- LiDAR
  - 上記機種でなくてもイケると思います。"360 lidar sensor"などのキーワードで検索すると、類似商品がたくさん見つかります。
- ケーブル類
  - 上記A1M8の場合、インターフェースがmicro USB-Bになるので、必要に応じて[USB-Aに変換するケーブル](https://www.amazon.co.jp/dp/B07G6X2LJ8/)も用意してください。
  - USBケーブルは伝送距離の上限が5mと規格で定められていますが、リピーターケーブルと呼ばれる長距離用の延長ケーブルを利用することで規格以上の距離を接続することが出来ます。壁とPCの間が離れているときに使ってください。
  - 参考：[USBアクティブリピーターケーブル特集](https://www.sanwa.co.jp/seihin_joho/repeatercable/index.html)

# センサーを使ってみる

## センサーを接続する

本項は[公式の開発マニュアル](http://bucket.download.slamtec.com/e680b4e2d99c4349c019553820904f28c7e6ec32/LM108_SLAMTEC_rplidarkit_usermaunal_A1M8_v1.0_en.pdf)に準じます。

まずはUSB接続したLiDARをPC側で検出可能にするドライバをインストールしてください。

ドライバへのリンクはマニュアル上に記載されていますが、本記事執筆時点ではリンク切れ。[こちらのリンク](https://jp.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers)からドライバを入手しましょう。

PCに接続するとセンサー部分がグルグルと回り始めます。しかしLiDARが回っていてもPCで認識できていない場合もあります。しっかりとデバイス一覧にLiDARが載っていることを確認しましょう。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">ドライバのインストールが正しくできていれば、コントロールパネルからデバイス一覧を確認したときにSiliconLabsの名前でデバイスを確認できるはずです。私の環境ではポート番号はCOM3でした。 <a href="https://t.co/DcjTJHDeFe">pic.twitter.com/DcjTJHDeFe</a></p>&mdash; あつし🎮eスポーツフェスタ学びエリア (@Anaakikutsushit) <a href="https://twitter.com/Anaakikutsushit/status/1194073591832166401?ref_src=twsrc%5Etfw">November 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## SDKをダウンロードしてビルドする

[公式のGitHub](https://github.com/slamtec/rplidar_sdk)からSDKを入手できます。

SDKはVisual Studio 2010でしかビルドできない仕様になっているのですが、有志の方がVisual Studio 2017や2019でもビルド出来るように修正してくれました。

現在だと2010は入手しづらいので、当該バージョンのVisual Studioを持っていない方はこちらの修正を適用してください。

[[apps][fix] Make examples compilable in Visual Studios 2017 and 2019](https://github.com/topin89/rplidar_sdk/commit/c70c3098c5ce4dbdf5d7a1f5780e05ad9dbea758)

あとはVisual Studioでソリューションを開き、ビルドできればOKです。

## デモンストレーション用のアプリを使う

本項はタッチスクリーン開発とは無関係です。しかし、LiDARがどのように動作しているかのイメージを掴むために有用です。

`output`ディレクトリ以下に生成されたアプリ`frame_grabber`を起動して、実際にLiDARを使ってみましょう。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">ビルドが正常に完了していれば、outputディレクトリ以下にサンプルアプリが出来上がっています。これを使ってみましょう。<a href="https://t.co/riHNtK1F2P">https://t.co/riHNtK1F2P</a></p>&mdash; あつし🎮eスポーツフェスタ学びエリア (@Anaakikutsushit) <a href="https://twitter.com/Anaakikutsushit/status/1194128540075089922?ref_src=twsrc%5Etfw">November 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

各種初期設定を済ませてアプリを実行してみると、下記のような測定データがリアルタイムに表示されると思います。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">スキャン結果がこれ…………いや～～わからね～～～～！ｗｗｗｗ<br>元々部屋全体の形をスキャンするのが目的のデバイスなので、机の上だけをスキャンするのはちょっと狭すぎたようです。近すぎるオブジェクトの認識に難がある模様。<br>しかし認識出来ないことはないみたいなので一安心ですね <a href="https://t.co/UPGWGH8O86">pic.twitter.com/UPGWGH8O86</a></p>&mdash; あつし🎮eスポーツフェスタ学びエリア (@Anaakikutsushit) <a href="https://twitter.com/Anaakikutsushit/status/1194133188949372929?ref_src=twsrc%5Etfw">November 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

また、`output`以下にある別のアプリ`ultra_simple`を使ってみると、どのような形式でLiDARがデータを取得しているのかをより良くイメージできるでしょう。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">ultra_simple.exeを実行すると「LIDARのデータはこんな感じで出てくるんだな～」と直感的に判断できると思います。<br>このサンプルコードを利用して欲しい部分だけ取得できないか改造してみましょう <a href="https://t.co/4AVbwWWPXI">pic.twitter.com/4AVbwWWPXI</a></p>&mdash; あつし🎮eスポーツフェスタ学びエリア (@Anaakikutsushit) <a href="https://twitter.com/Anaakikutsushit/status/1194149446579146752?ref_src=twsrc%5Etfw">November 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

`ultra_simple`では次のデータを取得できていることがわかります。

- 角度
- 距離
- 測定品質

LiDARの動作イメージが掴めたら、早速開発に取り掛かっていきましょう。

# センサーの値を利用するプログラムを書く

## C++側のコードを書く

先ほど利用した`ultra_simple`などは元々C++で開発されています。

しかし、筆者はC++に不慣れなため、新規のコードはC#で書いて、既存のC++のコードと連携させることにしました。

C++のコードをC#から利用するには、C++上でC#から呼び出すためのコードを書き、DLL形式で出力し、出力したDLLをC#で読み込むという手順が必要になります。

詳しくは下記の参考資料を読んでみてください。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">Unityと外部C++プロジェクトの連携についてはこの辺りを読んで勉強します<a href="https://t.co/E5ZOlbrUVh">https://t.co/E5ZOlbrUVh</a><a href="https://t.co/7I71FEdeMd">https://t.co/7I71FEdeMd</a><a href="https://t.co/tmd9wF9bQz">https://t.co/tmd9wF9bQz</a><a href="https://t.co/9TYjPXI0Oe">https://t.co/9TYjPXI0Oe</a></p>&mdash; あつし🎮eスポーツフェスタ学びエリア (@Anaakikutsushit) <a href="https://twitter.com/Anaakikutsushit/status/1200254497131397120?ref_src=twsrc%5Etfw">November 29, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

上記ではUnityの話をしていますが、UnityもC#で動くらしいんで同じことです（たぶん）

そういうわけでまずはC++側……つまり`ultra_simple`のコードから必要な部分だけを切り出して、C#側で使えるように準備していきます。

具体的には、下記の処理をする関数をDLLとしてエクスポートし、C#から利用しました。

- 使用するLiDARの個数を定義する
- LiDARを初期化する
- LiDARの回転をスタートさせる
- LiDARから測定データを読み出す
- LiDARの回転をストップして終了させる

DLLの中身がちゃんと出来ているかな？と確認するには、[Dependencies](https://github.com/lucasg/Dependencies)というアプリを利用すると便利です。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">今度はDLLにエクスポートした関数が呼べない呼べない……と思っていたら、 __stdcall を付けて宣言した関数にはアンダースコアが付くという仕様のせいだったみたいです。トラップが多すぎない？<br>上の define_scanner_num には __stdcall が付いていないのでDependenciesで見てもアンダースコアがない <a href="https://t.co/HFt0pRz4ns">pic.twitter.com/HFt0pRz4ns</a></p>&mdash; あつし🎮eスポーツフェスタ学びエリア (@Anaakikutsushit) <a href="https://twitter.com/Anaakikutsushit/status/1206074142803423232?ref_src=twsrc%5Etfw">December 15, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

なんだか上手く利用できないな？ということがあったら、上記ツイートのように原因を特定するのに役立ててください。

## C#側のコードを書く

### C++から値を受け取る

C#側ではGUIを作って、各種パラメータを入力し、測定開始・終了が出来るようなデザインにしました。
その辺はいい感じに作ってください（丸投げ）

ところで、C#とC++の間でデータをやり取りするにあたって型の変換が発生しますよね。

その辺りはちゃんと対応した型を双方指定する必要があります。「C++ C# マーシャリング」などのキーワードで検索してみると詳しい情報を得られるかと思います。

### 受け取った値をモニター上の座標に変換する

本項はコーディングというよりも数学の話になります。

LiDARが取得するデータは二次元極座標で表される角度と距離のデータ。

しかし、PC上でクリックイベントを発生させるためには、モニター左上を原点とした二次元直交座標で指定しなければなりません。

その変換については、タッチスクリーン化したいスクリーンの縦横の幅と、LiDARからスクリーンまでの距離を既知のものとして、下図の計算で求めることが出来ます。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336249/894ebc05-1640-9332-3fca-59870b51d11c.png)

LiDARをスクリーンの上に置くか下に置くかで式が変わってきますが、その条件はGUIから入力します。

ちなみに：この図は[GeoGebra](https://www.geogebra.org/?lang=ja)で作成しました。「条件内であれば各点がどの場所にあっても計算結果が破綻しないな」ということを実際に点QやLiDARの位置をグリグリ動かしながら確かめられます。大変助かりました。

また、数式の検証にあたってトゥーンさん（[@Toon_Scarlet](https://twitter.com/Toon_Scarlet)）にご協力頂きました。ありがとうございました。

### 特定の座標にクリックイベントを発生させる

下記記事を参考にしました。

[C#で指定した位置をクリックする](http://whoopsidaisies.hatenablog.com/entry/2013/03/22/142031)

「指定座標にマウスカーソルを移動させる関数」と、「任意のマウスイベントを発生させる関数」の2つを組み合わせることで、壁のタッチ位置にクリックイベントを発生させるロジックを組んでいます。

今回のプロジェクトでは、「ポイントはしているけどクリックはしていない」という状態はありません。

LiDARが検出したら常にクリックする⇔LiDARが検出していなければクリックしない、という2状態のみです。

ただしそれだと指が壁に触れている限りクリックを連打してしまうことになるので、2回以上クリックしたい場合は壁から指を離したあとでもう一度触れる必要があるという仕様にしました。

# 完成

## 開発にあたってつまずいた部分など

あとは作ったものをテストしつついい感じに調整していけば完成となります。

ソースコードの類はテストと修正が容易なのですが、物理的な機器に関しては問題点の洗い出しが少々面倒でした。

ちゃんとPCに接続しているのにデバイスが認識されなかったり、ケーブルの角度や向きをいじってみたら何とか認識される……みたいなことも結構あってツラかったです。

LiDARを複数接続する場合はやっぱりUSBハブがあると安心かなと思って、ちゃんとAC電源からセルフパワーで供給するものを購入して使ってみたりしたんですが、それでも動作は不安定になってしまいましたね。

可能な限りUSBハブは使わずに直接PCに接続するのが安定させるためのコツみたいです。

公式サポートへの問い合わせでもその回答でしたし、本番でもハブを使わないことで安定して動作してくれました。

## 本システムの採用実績

かなり汎用性の高いシステムですので、モフさんの方で今後さまざまなプロジェクトに使っていって頂けることでしょう😊これからの展開に注目です。

もちろん、私個人としても何かしらのプロジェクトに使っていきたいですね💪

### 富士通「CLIP-LIVE」

富士通さんの「CLIP-LIVE」において本システムを採用して頂きました。

実際に動いてるところはこんな感じ↓

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">SNS解禁許可が出た！<br>戦況分析・可視化システム「CLIP-LIVE」のタッチスクリーンシステム部分をお手伝いさせていただきました！<br>連鎖部分のクリップが自動で左右のボードに展開していき、壁に直接タッチするとその部分のムービーを見られる！<br>みんな東京eスポーツフェスタ絶対に来てくれよな！ <a href="https://t.co/Q5n321OOjZ">pic.twitter.com/Q5n321OOjZ</a></p>&mdash; あつし🎮eスポーツフェスタ学びエリア (@Anaakikutsushit) <a href="https://twitter.com/Anaakikutsushit/status/1215792274040188928?ref_src=twsrc%5Etfw">January 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

マウスクリックでインタラクションする、プロジェクター投影用のコンテンツを別で作っておいて、私のタッチスクリーンシステムと組み合わせて頂いた形ですね。

## 謝辞

最後に、本システムの開発にあたって[株式会社 猫とロボット社](https://www.nekotorobot.com/)代表の[飯田尚宏さんに技術的なアドバイスを頂きました](https://profile.coconala.com/users/13768)。大変助かりました。この場を借りてお礼申し上げます。

その他、参考にさせて頂いた各記事にもお世話になりました。ありがとうございました。