3D画像ファイルの点群とは?
距離情報付き画素データを持つ点群を解きほぐす
はじめに
前々回のでは、glTFファイルなどの3D画像ファイルでは画像データ(明るさや色情報)をどのように扱って記録しているのかを調べました。
3D画像ファイルでは、奥行きを表現するのに必要な距離情報は、3D物体の表面形状(ジオメトリ)を表すデータとして格納されています。
ほとんどの3D画像ファイルでは、ジオメトリデータはポリゴンの形で表現されていて、距離情報はポリゴンを構成する頂点データに格納されているのですが、単純な立方体の場合、頂点は8個であるので距離情報はたった8個だけです。
立方体よりもっと複雑な3D物体の場合には、膨大な数のポリゴンが必要になります。
3D物体を平面のポリゴンに分割して構成するのではなく、3D画像の画素ごとに距離情報を持たせれば精確に3D物体を表現できるのではという自然な考えが生まれます。
そういう物体の各点の座標値を格納した「点群」データと呼ばれるデータが様々な分野で活用されています。
ウィキペディアには、
引用:点群 (データ形式)
と記されています。
また、
引用:【2023年版】点群処理ソフト5選 / メーカー15社一覧
という解説もあります。
点群を扱う3D画像ファイルには数多くの種類があり、使われる技術分野でそれぞれ発展しているようで、例によって点群についての沢山の解説記事が見つかります。
個々のファイルの仕様についてはそれら解説記事を参照頂くことにして、
ここでは、画像データの距離情報をどのように扱って記録しているのか?に焦点を当てて、分かり易く解きほぐしたいと考えています。
今回取り上げる3D画像ファイルは、
・ポリゴンを用いる3D画像ファイルの世界からPLYファイル、
・LIDARスキャナーなどの測距技術の世界からPTSファイル、
・地図情報の世界からシェープファイル(Shapefile)
の3つです。
PLYファイルの点群
PLYファイルは、
引用:PLYファイルの作成方法
とあるように点群データを格納します。
また、
引用:Blenderで点群データを使用するための方法
とも紹介されています。
ウィキペディアには、
引用:PLY (ファイル形式)
と記されています。
PLYファイルのファイルフォーマット仕様については、PLY - Polygon File Formatに書かれています。
PLYファイルは、ヘッダ部とデータ部に分かれ、ヘッダ部にはASCIIテキストでデータの型などが記述され、データ部にはそのヘッダの記述に基づいたデータが格納されています。
具体例1
今回も著名なオープンソースの3DCGソフトであるBlenderから出力された、単純な立方体モデルのplyファイルを拝借します。
Blenderのデフォルト状態(筆者の環境)で、BaseColorを(1,1,1)にして頂点ペイントで立方体の8個の頂点を赤、緑、青、黄、マゼンタ、シアン、白色で塗った状態を、plyで頂点カラーモードだけにしてエクスポートしました。
法線ベクトルなしのモードですので、plyファイルのデータには、8個の頂点座標値と8個の頂点カラー値と6個の頂点インデックスデータだけが格納されます。
PLYファイルには法線ベクトルデータを格納することもできます。
具体例1のヘッダ部は、
ply
format binary_little_endian 1.0
comment Created by Blender 3.4.1 - www.blender.org
element vertex 8
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
property uchar alpha
element face 6
property list uchar uint vertex_indices
end_header
となっていて、
データ部は
POSITION COLOR
P0( 1, 1, 1)03 FF 03 FF 緑
P1(-1, 1, 1)57 57 FF FF 青
P2(-1,-1, 1)FF FF FF FF 白
P3( 1,-1, 1)FF 01 01 FF 赤
P4( 1,-1,-1)FF FF 0E FF 黄
P5(-1,-1,-1)FF FF FF FF 白
P6(-1, 1,-1)1A FF FF FF シアン
P7( 1, 1,-1)FF 02 FF FF マゼンタ
になっています。
頂点座標値は、各頂点に対し、
バイト数 データ型
12Byte float × 3
計8個×12=96Byteのデータが格納されています。
この8個の座標値(POSITION値)は立方体の8個の頂点に対応しており【図1】に右手座標系として割り当てて表示しています。
頂点0、1、2、・・・の番号はそれら頂点座標値データの格納順です。
記号Pは筆者が適当に付与した記号です。
Blenderでの頂点ペイント設定時にはRGB値に1と0だけを記載したのですが、PLYファイルの頂点カラー値は0になりませんでした。
PLYファイルの解説
同じplyファイルをBlenderの「インポート」で読み込んで表示させた例を【図3】に示します。
【図2】では点群だけが表示されていることが分かります。
前々報ので調べたglTFファイルも同じ理屈で点群データとして扱うことができると言えそうです。
glTFファイルは8バイトで頂点カラーを表していましたが、PLYファイルは4バイトで頂点カラーを表しているのでより単純なデータになっています。
PTSファイルの点群
PTSファイルは、
引用:【Point Cloud】色付きの点群は立体になる
とあるようにスキャナーから得られた点群データを格納します。
PTSファイルのフォーマット仕様は、pts - Laser scan plain data formatにも書かれています。
PTSファイルは、全部ASCIIテキストで書かれ、最初の行は点の個数、次の行から各点に対する行が続き、各行はX座標値、Y座標値、Z座標値、intensity値、R値、G値、B値の7個のデータで構成されています。
上記引用引事では、RGB値は0〜255、intensity値は表面によって反射された入射放射線の割合で0〜255とされていますが、intensity値については-2048〜2047とされる場合もあるようです(引用:PTS file format specifications)。
例えば 渋谷駅地下3Dデータなどで紹介されているShibuyaUnderground.ptsのデータは、
-11986.492000 -37798.940000 10.834641 -1927 233 249 246
などとintensity値が-1927になっています。
PTSファイルはLeicaによって策定されたのですが、同じくLeicaによって策定されたPTXファイルもあります。
PTXファイルのフォーマット仕様は、PTX file formatに書かれています。
PTXファイルは、データの行数と列数や作成したスキャナーの位置や視角などの情報が入ったヘッダ部がPTSデータに付加された構成になっています。
具体例2
上記【図2】に示したPLYファイルの点群と同じ形になるように、バイナリエディタを用いてPTSファイルを作成してみました。
立方体の8個の頂点に相当する点に赤、緑、青、黄、マゼンタ、シアン、白色を割り振り、intensity値は255にして全部で179バイトの小さな容量になっています。
データはASCIIテキスト形式で、
8
1 1 1 255 255 0 0
-1 1 1 255 255 255 255
-1 -1 1 255 255 255 255
1 -1 1 255 255 255 0
1 -1 -1 255 0 255 255
-1 -1 -1 255 255 0 255
-1 1 -1 255 0 0 255
1 1 -1 255 0 255 0
としています。
PTSファイルの解説
【図4】は点群のみを表示、【図5】は頂点を結ぶ線と共に点群を表示しています。
ASCIIテキストによるデータですので、データ容量は大きくなりますが、どんなに大きな値であっても記載できることが長所です。
その測距データを格納するファイルの一つとしてPTSファイルがあり、用途の広がりに合わせてこういう測距データ格納ファイルも進化していくのだろうと思われます。
LIDARスキャナーで使われるLASファイルを調べるなら
シェープファイルの点群
シェープファイルは、
例えば、井戸、川、湖などの空間要素がベクター形式であるポイント、ライン、ポリゴンで示され、各要素に固有名称や温度などの任意の属性を付与できる。」
引用:シェープファイル
引用:シェープファイルについて
とあるように地理情報システム(GIS)のデータを格納します。
この引用記事に「Shapeファイルと記載するのは誤った記載」とされているので、シェープファイル、または、Shapefileと記載することにします。
シェープファイルのフォーマット仕様は、シェープファイルの技術情報に書かれています。
また、上記引用記事には、
「シェープファイルという用語はかなりよく知られているが、この用語は誤解を招きやすい一面もある。なぜならシェープファイル形式は、共通のファイル名(プレフィックス)を持つ複数のファイルを同一ディレクトリに格納しておかねばならないからである。shp、.shx、.dbf(dBASE)の拡張子を持つ3種類のファイルは必須である。
必須ファイル:
オプションのファイル:
(以下続く)」
の記載があり、シェープファイルは複数のファイルで構成されています。
また、上記仕様書には、
「ひとつのシェープファイル内では、Null Shapeを除いてすべて同じシェープタイプでなければなりません。シェープタイプは、それぞれ次の値であらわします。
0 Null Shape
1 Point
3 PolyLine
5 Polygon
8 MultiPoint
11 PointZ
13 PolyLineZ
15 PolygonZ
18 MultiPointZ
21 PointM
23 PolyLineM
25 PolygonM
28 MultiPointM
31 MultiPatch 」
とあり、シェープタイプ毎にデータ構造が異なります。
このシェープタイプを確認するには32Byte目の値を調べるしかなさそうです。
今回着目するのは、奥行き(Z方向)の距離データを格納するシェープタイプPointZを持つシェープファイルです。
地理情報システム(GIS)における2Dの点群
地理情報システム(GIS)は、
「GISとは、位置に関する様々な情報を持ったデータを電子的な地図上で扱う情報システム技術の総称です。」
引用:GIS(地理情報システム)
とあるように地図を扱うので、2次元(2D)の地図が中心的な対象物になります。
点群も「2Dの点群」というべき形態があり、これを表現するシェープファイルはシェープタイプが1の「Point」が用いられます。
【図6】は国土数値情報 ダムデータからダウンロードしたシェープファイルW01-14-g_Dam.shpをシェープファイルのビューアソフトであるShape Viewerを使って表示させた例です。
全国のダムの位置が点で示されています。
このシェープファイルW01-14-g_Dam.shpは、シェープタイプが1の「Point」になっています。
地理情報システム(GIS)における奥行き(z方向)の距離情報
地理情報システム(GIS)で奥行きの距離情報に相当する値に標高値があります。
地図に標高値を加えれば3次元(3D)の地図ができることになります。
「DEMとは数値標高モデル(Digital Elevation Model)の略で、各種測量法で計測された平面位置(2次元)および標高値を用いた3次元座標をデジタルで表現したものである。」
引用:数値標高モデル(DEM)と地図の歴史
とあるように、標高値データは数値標高モデル(DEM)として使われています。
国土地理院の数値標高データはシェープファイルの形では見つかりませんでしたが、XMLファイルの形で提供されています。
国土地理院の数値標高データを使って3D表示をする方法を紹介している、1区画の3次元メッシュ(xml)という記事を見つけました。
この記事には、
「今度はとりあえず、3次元メッシュの地図をBlenderのPythonで作ってみます。国土地理院の数値標高データーを利用します。上記の画像は東京都の鳥島で10mメッシュです。他サイトやブログでは、GDALが出てきてxmlからtif形式に変換して、何かのソフトを通して、また変換してと、随分と回りくどいことをやっています。BlenderではPythonでそのままxmlのデーターをメッシュにすればよいだけです。」
とあり、既存のやり方にとらわれないでシンプルな解決策を探す姿勢は筆者も大好きなので、この方の方法(以下Toikanbetsuさんの方法と呼びます)をトレースして、元となる数値標高データのXMLファイルを調べることにしました。
Toikanbetsuさんの方法に倣って、
国土地理院の基盤地図情報のダウンロードから鳥島の地図にあたるFG-GML-4540-52-DEM10B.zipをダウンロードし、XMLファイルFG-GML-4540-52-dem10b-20161001.xmlを取り出します。
【図7】は基盤地図情報のビューアである基盤地図情報ビューア(基盤地図情報ビューアから取得出来ます)を使ってそのFG-GML-4540-52-dem10b-20161001.xmlを表示させた例です。
この基盤地図情報ビューアは3D表示ソフトではないので上から見るだけの地図になります。
【図8】はToikanbetsuさんの方法で添付されているPythonサンプルコード(自分の環境に合わせて一部変更しています)をBlenderで実行させて回転せずに表示させた例です。
【図9】は【図8】をBlenderで回転させて3D表示させた例です。
XMLファイルのデータをBlenderのメッシュデータに変換するToikanbetsuさんのサンプルコードはうまく動きました。
Toikanbetsuさん、ありがとうございました。
具体例3(地理情報システム(GIS)のシェープファイル)
【図7】で取得したFG-GML-4540-52-dem10b-20161001.xmlを表示させていますが、この基盤地図情報ビューアのエクスポート機能を使うとシェープファイルに変換できます。
【図10】に示すように、「標高メッシュをシェープファイルへ出力」を選び、「直角座標系に変換して出力」にチェックを入れ、このファイルの鳥島は東京にあって平面直角座標系9系に属するので「9系」を選択して、OKをクリックするとシェープファイルが出力されます。
「全データを出力」と「おおむね現在表されている要素のみを出力」の選択は、この鳥島の場合はどちらも同じファイルが出力されました。
この出力されたシェープファイルはシェープタイプ11の「PointZ」になっており、これをを具体例3として調べます。
具体例3のメインファイルヘッダ部は、100Byteのサイズで、
サイズ 値
ファイルコード 4Byte 9994
未使用 20Byte
ファイル長 4Byte 801598
バージョン 4Byte 1000
シェープタイプ 4Byte 11
Xmin 8Byte 43464.5
Ymin 8Byte -612896.8
Xmax 8Byte 43464.5
Ymax 8Byte -610295.4
Zmin 8Byte 0
Zmax 8Byte 392.8
Mmin 8Byte 0
Mmax 8Byte 0
となっていて、
データ部は、
レコード番号 4Byte 1~36,434
シェープタイプ 4Byte 11
レコードコンテンツ
X 8Byte
Y 8Byte
Z 8Byte
M 8Byte
の構成で36434個のレコードコンテンツが格納されています。
レコード番号36433のレコードコンテンツを抜き出してみると、
X 8Byte 45154.4
Y 8Byte -610295.4
Z 8Byte 2.9
M 8Byte 不明の大きな値
X、Y、Z、Mの値は全部倍精度浮動小数点数である8Byteのdouble型で表されています。
M(メジャー値)は使われていないので不明の値になっています。
シェープファイルの解説
その元のXMLファイルXMLFG-GML-4540-52-dem10b-20161001.xmlは、
GMLタグに、
<gml:lowerCorner>
30.416666667 140.25
</gml:lowerCorner>
<gml:upperCorner>
30.5 140.375
</gml:upperCorner>
<gml:GridEnvelope>
<gml:low>0 0</gml:low>
<gml:high>
1124 749
</gml:high>
</gml:GridEnvelope>
<gml:sequenceRule order="+x-y">
Linear
</gml:sequenceRule>
<gml:startPoint>
0 0
</gml:startPoint>
が書かれているので、
データ範囲は、
gml:lowerCornerにより、
緯度30.416666667経度140.25の南西端と、
gml:upperCornerにより、
緯度30.5経度140.375の北東端に囲まれた四角形領域を、
gml:low 0 0
gml:high 1124 749
により、横1125縦750で区切られたグリッドセルになっています(【図11】参照)。
このグリッドセルを、
gml:sequenceRule order="+x-y"
gml:startPoint 0 0
により、x0、y0から開始して、x方向(西から東)、y方向(北から南)の走査順でそれぞれの標高データが格納されます。
標高データは、
<gml:tupleList>
のタグにUTF8形式で
その他,2.90
のように羅列格納され、標高値が未定義の場合は
その他,-9999.00
となっています。
「その他」の部分は、列挙型で「地表面」とか書かれるという記事もありましたが、ASCII形式(UTF8)で「その他」になっていました。
このXMLファイルの標高データが、シェープファイルに変換されることになります。
シェープファイルは、上記の通り36434個のレコードコンテンツになっています。
すなわち、1125×750=943750個のグリッドセルの内、海などの標高値が未定義であるデータを除いて、島の陸地の領域である36434個の標高データが抽出されているのです。
具体例3のシェープファイルのレコードコンテンツは、
36434個の点群に対してそれぞれX、Y、Z、M値のデータで構成され、Z値は標高データになります。
XY値はその点のXY座標値であるのですが、XMLファイルにはXY座標値に対応するデータはグリッドセルの緯度経度値だけでそれぞれの点に対する座標データは格納されていません。
シェープファイルを作成する際に、グリッドセルの緯度経度値からXY座標値を計算してレコードコンテンツにXY座標値を格納していると思われます。
しかしこのXY座標値は正しいのでしょうか?
国土地理院は、
「平面直角座標系は、現在全国を19の座標系に区分しています。平面直角座標系は地点の座標値が次の条件に従ってガウス・クリューゲルの等角投影法によって表示されるように設けられています。
X=0.000メートル Y=0.000メートル」
引用:GIS(地理情報システム)
と定義しています。
今回使用している鳥島の座標系区分は9であって、その原点座標値は
東経139度50分0秒
北緯36度0分0秒
になっています。
緯度経度から平面直角座標系のXY値に変換する計算式は、地球が球体であるため簡単ではありません。
緯度経度と平面直角座標の相互変換を実装するための数式や、
JavaScriptによる緯度経度と地図のXY(平面直角座標)との変換、および地理学入門
などの記事が参考になると思います。
上記具体例3のシェープファイルのレコード番号36433のレコードコンテンツにある標高データ2.9mとなる点を基盤地図情報ビューアを使って探してその点の緯度経度とXY座標値を取得しました。
経度はE140:18:13.40 = 140.303722 、
緯度はN30:29:45.40 = 30.495944、
X座標はX=-610295.4、
Y座標はY=45154.4
になっていました。【図12】参照。
この緯度経度値を、緯度経度から平面直角座標への換算ツールである平面直角座標への換算を使ってXY座標に換算すると、【図13】に示すように、
X座標はX=-610295.4793、
Y座標はY=45154.4129
になって、基盤地図情報ビューアで表示される上記XY座標値に一致しています。
「座標系のX軸は、座標系原点において子午線に一致する軸とし、真北に向かう値を正とし・・」と定義されていて、X軸は経度方向であって原点は鳥島よりずっと北にあるのだから、X座標値は負の値であるはずです。
一方、シェープファイルの方は、
レコード番号36433のレコードコンテンツが上記のように、
X 8Byte 45154.43403631272
Y 8Byte -610295.429924438
Z 8Byte 2.9
となっていて、XとYの値が逆になっています。
地図の縦方向がX軸だとされるのも違和感がありますが、XY座標値が定義通りに処理されないと混乱を招くのではと考えます。
農林水産省が提供している農地筆ポリゴン(世界測地系)_13東京都 からダウンロードしたシェープファイル13401八丈町2019.shpを調べると、
X 139.75
Y 33.12
などと緯度経度がそのままXY座標値として使われていました。
シェープファイルのXY座標値は自由に使って良いことになっているのでしょうか?
おわりに
できるだけ全体を俯瞰できるように、
を代表として選んで調べることにしました。
具体例1のPLYファイルはfloat(単精度浮動小数点数)でz値が表現されています。
PTSファイルはASCIIテキストでz値が表現されるので、大きな数値でも扱えますがデータ容量は大きくなってしまいます。
今回はシェープタイプPointZに絞って調べましたが、他にもPolygonやPolyLineなどいろいろな種類のシェープタイプがあり、用途に応じて使い分けられています。
シェープタイプPointZのシェープファイルはdouble(倍精度浮動小数点数)で標高値であるz値が表現されています。
基盤地図情報ビューアのシェープファイルエクスポートプログラムが、何らかの意図があってそのような処理をしているのだろうと思われますが、理由は不明です。