ラインレーザーを使った3Dスキャナー


はじめに

3Dプリンターを使っているうちに3次元のモデルを出力するだけではなく入力してみたいと思うようになりました.入手が容易な部品で3Dスキャナーが作れそうだったので自作してみました.

3Dスキャナーの原理

今回作成するラインレーザーを使った3Dスキャナーの仕組みを説明します.基本的には,下の図のように少し離して設置されたレーザーモジュールとカメラを用いて三角測量の原理で測定します.対象物に当たったレーザーの位置をカメラで撮影した画像から検出することで,対象物の表面がレーザーモジュールからどれだけ離れているかを計算します.物体の位置がAとBの場合に応じてカメラの画像中のレーザースポットの位置が変化します.この図ではレーザースポットは点(0次元)ですが,実際はラインレーザーを使うので1次元(線)上の情報が一度の撮影で取得できます.そして,レーザーをスキャンさせて測定を繰り返せば2次元(平面)上の距離情報が取得できます.このようにして得られた情報はポイントクラウドと呼ばれ,そこからポリゴンを生成することができます.

以下ではより具体的なアルゴリズムを説明しますが,計算が簡単になるようにベクトルを使って空間上の座標を表現します.まず下の図のように,カメラが座標原点(0, 0, 0)に(0, 0, 1)の方向(Z軸の正方向)を向いて置かれているとします.ラインレーザーモジュールがカメラからd離れたX軸上の点qに置かれているとします.原点からZ方向にf離れた地点に仮想的なスクリーン(仮想イメージプレーン)があると考えて,座標空間上の点はその位置とカメラ原点を結ぶ線と仮想イメージプレーンの交点の(x, y)座標の点として撮影した画像上で得られるとします.このようなカメラのモデルはピンホールカメラモデルと呼ばれ,fは焦点距離(focal length)と呼ばれます.

焦点距離f,ラインレーザー面の法線ベクトルn,ラインレーザー面に含まれる点q=(-d, 0, 0)の値は既知であるとします.アルゴリズムの入力はレーザーが対象物に反射した輝点を仮想イメージプレーン上に射影した点r=(x, y, f),出力はレーザーが対象物に当たった点pとします.点pはカメラ原点(0, 0, 0)と仮想イメージプレーン上の画素点rを結ぶ線上のどこかに存在するので,この関係はスカラー変数lを使って以下のように書けます:

p = l r

また点pはラインレーザー面上に存在するので次の式も成り立ちます:

n ⋅ (p - q) = 0

上の2つの式を解くと,

n ⋅ (l r - q) = 0
l nr = nq
l = (nq) / (nr)
p = ((nq) / (nr)) r

となり,レーザーが当たっている対象物の3次元空間上の位置を求めることができます.

3Dスキャナーの原理や数式については,こちらのページからダウンロードできるPDFの資料(3D Scanning for Personal 3D Printing: Build Your Own Desktop 3D Scanner)が参考になります.

作成した3Dスキャナーと動作例

完成した3Dスキャナーと回路図,配線図を下記に示します.Raspberry Pi 4にステッピングモーターを接続してレーザーモジュールを回転させ,800万画素のRaspberry Pi Camera Module V2で画像を撮影します.3Dプリンターで部品を作成して三脚に固定しました.

完成した3Dスキャナー
回路図
配線図

回路について

Raspberry PiのGPIOに接続してステッピングモーターやラインレーザーモジュールを制御するコントローラーを作りました.最初はユニポーラ型のステッピングモーター(28BYJ-48)を使うためにULN2003を使いましたが,その後バイポーラ型のステッピングモーター(TS3166N913)を使うためにA4988を後付けしました.A4988は1/16のマイクロステッピング動作をさせています.なおレーザーモジュール(5mW)は3.3Vを与えた場合に消費電流が10mA程度だったため,GPIOのポートで直接駆動しても問題ないと思います.

ソフトウェアと初期調整

スキャン用のソフトはC++で実装しました.スキャン結果のポイントクラウドをバイナリPCDフォーマットで出力します.ここにコードを置いておきます.

このソフトは以下の5つのモードがありコマンドラインで指定します:

capture
単純に写真を撮影します.
calibrate
後述するカメラキャリブレーションを行います.
adjust
インタラクティブな調整モードに入ります.モーターを指定した角度だけ回転させたりレーザーをON/OFFするなどの操作を手動で行えます.
scan
指定した範囲の角度をスキャンします.
compensate
後述するステッピングモーターの位置精度を補正するためのデータを取得します.

スキャンを始める前にいくつかのパラメータを求めておきます.まずこのスキャナーを平らな壁から1m離して設置します.長さがちょうど1mのもの(メジャーなど)を撮影して画像上でのピクセル単位での長さを測定することで,焦点距離fを求めます.またレーザーとカメラ間の距離dも定規で測定します.これらの値はプログラムの起動時に読み込まれる設定ファイル(XML形式)に保存しておきます.最後に,adjustモードでレーザーが画面中央に来るようにモーターを回転させて,その位置を回転角度の原点とします.

動作例

下の写真は3Dスキャンの例です.木工用ボンド,透明なガラス容器入りのニス,反射しやすい金属製の変換コネクタ,光を吸収しやすい黒色のカメラを段ボールの上にのせてスキャンしました.外乱を抑えるためスキャン中は部屋を暗くします.スキャン時間は,1ラインあたり1~2秒程度です.スキャン結果を見れば分かるように,レーザーの反射をカメラで撮影して測定するという光学的な性質上,黒色の物体や透過・反射しやすい物体の測定は苦手です.

スキャン対象物
スキャン中の様子
スキャン結果

設計と実装

実際に動かしてみるとうまくいかないことがあり何度も作り直しました.以下に設計や実装上の細かい点を列挙します.

3Dスキャナーの方式

3Dスキャナーの方式には以下のものが考えられましたが,ここではハードウェアがシンプルで比較的頑健そうに思われた三角測量に基づく光切断方式を採用しました.

フォトグラメトリ
これはデジカメで多数の方向から撮影した画像のみからソフトウェアで3Dモデルを構築する方法で,特別なハードウェアは必要ありません.体験版のソフトを試してみましたが,対象物によりうまくいくケースもあれば全然駄目なケースもあり,パラメータを調整しても安定して成功させることが難しかったのでこの方法はあきらめました.
三角測量
これは最初に説明したように,対象物に当てたレーザー光等を少し離して設置したカメラで撮影して,三角測量に基づき距離を計算する方法です.プロジェクターでパターンを投影して2次元上の距離情報を一度に取得する構造化光方式や,ラインレーザーで1次元上の距離情報を一度に取得する光切断方式があります.
Time-of-Flightセンサー
Time-of-Flight (ToF)方式の距離センサーが最近手軽に入手できます.これはレーザー光等を対象物に当てて反射して返ってきた時間を計測して距離を知るものです.原理的にはこのセンサーで対象物をスキャンすれば3Dモデルを得られますが,一度に0次元上の距離情報しか得られないのでスキャン速度が現実的ではありません.

3Dスキャナーの構造

ラインレーザーを使った三角測量に基づく3Dスキャナーにもいくつかの構造が考えられます.FreeLSSなどのオープンソースの3Dスキャナーでよく見るのはターンテーブルに対象物を置き,レーザーとカメラは固定する方法です.しかしこの構造は対象物のサイズが小さいものに限定されてしまい,また回転させた場合にレーザーの死角になる箇所があると正確な形状を測定できないという問題があります.そこで今回は,こちらのプロジェクトのようなレーザーを動かして対象物の2次元上の距離情報を取得する構造にしました.

アームの構造

上記のように,今回は対象物は固定してレーザーを動かす構造にしました.最初に作成したバージョンでは,アクリルで作ったアーム(4x10x400mm)にレーザーとカメラを固定してステッピングモーターで回転させましたが剛性が不足して失敗でした.

次に,もう少し太めのヒノキ材で作ったアーム(12x12x910mm)を試しました.モーターの負担を減らすためにカウンターウェイトを取り付けて,さらにボールベアリングを2つ使った軸受けを作りましたが,力のモーメントが大きく精密な動作が困難でした.

最終的にはアームをさらに太い木材(19x19x50mm)にし,アーム自体は固定してレーザーのみを回転させるという構造に変更しました.この方法はとても良くて,モーターの負荷が軽くメカがシンプルになり信頼性が向上しました.

アーム自体を回転させる方式
レーザーのみを回転させる方式

ステッピングモーターの位置精度

レーザー回転用のステッピングモーターには最初は28BYJ-48というギアを内蔵した安価に出回っているモーターを使いました.これは5Vで動くのでRaspberry Piから電源供給できて,分解能も高い(ハーフステップ動作で1回転4096ステップ)という利点があります.しかしながら位置精度が非常に悪く4ステップの周期でかなり角度がばらつくことが分かりました.下図(左)のスキャン結果は平らな壁面をスキャンしたものです.この原因として,こちらのページで説明されているようにユニポーラー型のモーターで2組のクローポールがあるため,その間にズレがあることが考えられます.位置精度の問題以外にも,ギアを使用しているためバックラッシュが大きく,機械的なガタツキがあって精密な動作には向いていないモーターだと分かりました.

改良後は,TS3166N913というバイポーラ型のモーターとドライバICのA4988を使いました.このモーターは1回転400ステップですが,A4988を1/16のマイクロステッピングで使うので1回転6400ステップの解像度が得られます.マイクロステッピングを使った場合の位置精度が心配でしたが,下図(中)のスキャン結果のように16ステップ周期で変動が見られました.回転のズレを測定してソフト上で補正したところ下図(右)のようになりましたが,やや動作が不安定でうまく補正されない場合もあります.

4096ステップユニポーラ駆動
6400ステップバイポーラ駆動(補正なし)
6400ステップバイポーラ駆動(補正あり)

ラインレーザーの直線性

測定結果に誤差が出る原因の一つがラインレーザーの直線性でした.ラインレーザーの光線はほとんど直線ですが,今回使用したレーザーは1mほどの距離に投影した場合に若干弓なりに曲がった形状となりました.これはソフトウェアで補正するのはかなり面倒です.安物のレーザーモジュールではなく建築用などのちゃんとしたものを使うべきかもしれません.今回は,レーザーモジュールの先端に取り付けられているレーザー形状を変えるレンズと本体の間にテープを挟んでレーザーが当たる角度を微調整して,できるだけ直線になるようにしました.

Raspberry Piのカメラ

大昔に購入した初代Raspberry Pi Model A (メモリ256MB)が余っていたので最初はこれを使おうとしましたが,Raspberry Pi Camera Module V2をOpenCVから最大解像度で使おうとしたところメモリが足りなかったので,最新のRaspberry Pi 4を購入しました.メモリ不足は解消されましたが,OpenCVのVideoCaptureで3280x2464のカメラ画像を取得すると画像が乱れるという問題が起こりました.OpenCVのAPIを使うことはあきらめてV4L2のAPIを使うことでようやく正常な画像が取得できるようになりました.なお画像が乱れる問題はV4L2でも起こり,これは3280x2464の解像度を指定しても実際は3296x2464の画像が返されることが原因でした.

部屋を暗くしてレーザーを照射した状態で画像を取得したところ,露出が足らずにレーザー光がほとんど写らないという問題が起きました.V4L2では今回使用したカメラの露出制御が細かく行えないことが分かりました.最終的には,raspistillコマンドなどでも使われている低レベルAPIであるMMALを使って露出を制御できるようになりました.具体的には,analog_gainとdigital_gainを指定することで細かく露出を決めることができます.MMALに関する情報は少なかったのですが,基本的な仕組みなどはpicameraというPython用ライブラリのドキュメントが参考になりました.結局OpenCVのAPIは使わなかったのでRaspberry Pi 4を購入する必要はなかったかもしれませんが,コンパイル時間が速くなり開発効率が上がったのでよかったと思います.

カメラキャリブレーション

一般的にカメラのレンズを通して撮影した画像は歪みが生じるため,測定目的で画像を使う場合には補正が必要になります.幸いOpenCVにはカメラキャリブレーション用の関数が用意されているのでそれを使いました.キャリブレーション時にはチェスボードパターンを複数枚撮影して使用します.最初は印刷したパターンを段ボールに張り付けて使用しましたが撮影時に微妙に歪んでしまうので,丈夫な厚いプラスチック板に接着して撮影しました.

サブピクセル単位でのレーザースポット位置の同定

赤色レーザーを使用しているため,カメラで撮影した画像から赤の輝度が最も高い点を求めて仮想イメージプレーン上での座標を求めます.しかし単純に輝度が最大の点を求めるだけではカメラの解像度と同じ分解能しか得られないため,輝度で重み付けた平均値を計算してサブピクセル単位でレーザースポットの位置を求めます.

レーザースポット位置の計算

スキャン精度

3Dスキャナーの仕様としての精度(分解能)は重要ですが,この値は対象物の大きさ(小さい対象物には近づけば精度は上がる)やカメラとレーザーの距離(離せばZ方向精度が上がる)によって変化します.今回作成した3Dスキャナーは1m離れた対象物を測定するという条件では,X方向で0.98mm,Y方向で0.38mm,Z方向で0.79mmの精度が理論上得られます.

終わりに

今回3Dスキャナーを作成し,ポイントクラウドを得ることはできました.しかしながら色々と試した結果,どのような対象物に対しても高い信頼性でスキャンできる実用的な3Dスキャナーを作るのは容易ではないという結論に至りました.理由として,ステッピングモーターの位置精度やレーザーの直線度といったハードウェアの精度により誤差が生じるという問題がありますが,そもそも黒色の物体や光を反射しやすい物体はスキャンが難しいという根本的な問題があります.構造化光方式を使ったIntelのRealSense SR305のような安価なデプスカメラが入手できるようになっているので,そのような市販製品を使えばハードウェアの問題で悩む可能性は減ると思います.しかしながら,市販の高価な3Dスキャナーでも対象物によってはスキャンが難しく,うまくスキャンできる対象物しかスキャンできないという問題は3Dスキャナーで共通しているようです.とりあえず,3Dスキャナーを実際に作ってみて色々と知識を得られたのはよかったと思います.


[戻る]
2020-02 制作
2020-03-15 ページ作成
T. Nakagawa