前回では、とりあず、Numpy を用いて、バイナリのポイントクラウドデータを読み込み、それを表示する簡単な例を紹介しました。
ただし、実際に実行してみるとわかりますが、前回入力したデータは、あくまで、x,y,z 座標値のみをセットして、ただ表示しただけですので、データによってはきちんと表示できない場合がでると思います。
1. ポイントに色をつける
その原因の1つは、背景が白であるのに対して、ポイント自体も白く表示されていたために、物体が表示されていることがわからないということが考えられます。そこで、とりあえず、背景との区別がつくように、灰色( RGB 形式で [0.5, 0.5, 0.5] )を設定してみます。
pcd0.paint_uniform_color([0.5, 0.5, 0.5])
灰色でみえるようになったでしょうか?
2. z 値の違いでグラデーションをつける
ちなみに、さきほどの場合は、すべてのポイントを同じ RGB にしましたが、奥行き( z 値)の違いを色で表現したい場合もあると思いますので、その方法を紹介します。
まず、先に p0 に nan 値がある可能性があるため、次のようにして nan 値を取り除いた p1 をつくります。
p1 = p0[~np.isnan(p0).any(axis=1)]
次に z 値の最小値と、最大値を求めます。
c_min, c_max = np.min(p1[:,2]), np.max(p1[:,2])
そして、z 値が最大のときは灰色( 0.5 )、最小のときは黒( 0 )となるようなデータ c_data をつくり、Open3D のプロパティcolors に
Vector3d 形式でセットします。
pcd0.colors = o3d.utility.Vector3dVector(
[np.ones(3) * 0.5 * (z - c_min)/(c_max - c_min) for z in p1[:,2]])
z 方向の違いによるグラデーション表示ができたでしょうか?
3. ポイントの法線を設定する
法線は、一般的には、三角ポリゴンで構成されたメッシュデータの場合、各面の表と裏の方向を示したり、そのポリゴンを形作る3 点について、法線を設定することで、その面をレンダリング表示する際、光源との角度の違いにより陰影などをつけるために使用されます。ですので、これが正しく設定されていないと、変な見え方になります。
しかし、ポイントクラウド自体は単なる点であり、メッシュのように各点同士の関係性などがつけれれているわけではありませんので当然ながら、法線というものがありません。ただし、ポイントクラウドを表示する場合、疑似的にその点にもなんらかの色をつけ、さらに光源の影響も考慮してレンダリングしてあげたほうがより、物体の形状を把握しやすくなるため、点ごとに法線を設定する場合があり、Open3D には、その法線を簡単に設定できる便利な関数が用意されています。
pcd0.estimate_normals(
search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=10))
法線を設定していないときと違い、細かな凹凸が確認できるようになったのではないでしょうか?
法線を推定するには最低 2 つのベクトルが必要です。つまり、対象のポイントの他に 2 つの点を選び、それらでつくられる2 つのベクトルに垂直なベクトルが法線となります。上のパラメータの場合半径 0.1 以内の範囲の中で、近傍点を最大 10 個について法線を計算しそれを基に法線を決定します。
実践的には、このパラメータにどんな値を設定するかによっても、表示に大きな影響を与えますので、きちんと考える必要がありますが、この計算のために使えそうな関数も Open3D に用意されています。詳しくは、Open3D のマニュアルを調べてみてください。
ポイントだけをざっくりと説明すると、各点について、その最近傍点までの距離の平均をとれば、最低限必要な探索半径を決めることができます。そして、対象としているデータの形状の複雑さ(具体的には分散など)を考慮し、サンプル点として選択しても、誤差があまりでないであろう個数を max_nn に設定するとよいのではないかと思います。なお、さらに細かい話をすると、実際のデータには、ノイズも多く含まれるため、こうしたノイズをきちんと除いた上で、サンプリングすることも重要です。外れ値を検出するための関数も Open3D には、用意されていますので、こちらも調べてみるとよいでしょう。
目的によって、radius と max_nn をどう設定することが適切なのかは異なりますので、結果を確かめながらパラメータを決定することが重要だと思います。
4. 特定の方向に法線を揃える
ところで、先ほどは、いきなり法線の推定しましたが、実は1つ問題があります。2 つのベクトルに垂直なベクトルは、厳密には、向きが正反対のものと合わせて2 つ存在します。つまり、表と裏です。estimate_normalsは、基本的にもとからある法線に近くなるように、表と裏を決定しますが、今回のように元の法線が存在しない場合、Open3D は、表裏をランダムに決定するロジックになっています。
そこで、estimate_normalsを実行する前に、特定の方向にすべての法線を揃える関数 orient_normals_to_align_with_direction または orient_normals_towards_camera_location を実行しておく必要がありますが、この2 つの関数は、何らかの法線をあらかじめ持っているポイントクラウドオブジェクトに対してしか実行できないため、estimate_normals() を引数なしで実行して、法線を仮設定した後に実行するようにします。
pcd0.estimate_normals()
pcd0.orient_normals_towards_camera_location([0., 0., 0.]) # カメラ方向に揃える
または
pcd0.orient_normals_to_align_with_direction([0., 0., 1.]) # (0,0,1) の方向に揃える
以上の法線を揃える関数を実行すると割と十分な結果が得られると思います。なお、厳密には、このあとに改めてestimate_normalsを実行するとより良い結果がでそうですが、先に説明したように、基本的にestimate_normalsは、元の法線を基に裏表を決定しますので、estimate_normals を実行しても、あまり変化が見られない可能性がありますので、目的に応じてこのあとの estimate_normals は省略してもよいかもしれません。
前回につづき、ポイントクラウドの表示に関してかなり説明が長くなってしまいましたが、実際の現場では、こうした細かな点が結果に大きく影響すると思いますので、 Open3D を活用する場合の参考として、次回も、もう少しだけポイントクラウドのデータに関連する説明したいと思います。