3Dモデルをいろいろ作ってみたい

3Dモデルをいろいろ作ろうとがんばっています。苦労した点、役に立ちそうな情報を発信していきます。

iPhone Xで3Dスキャンを試す

最近、スマホiPhone XSに乗り換えました。今まで使っていたiPhone5Sで特に不満を感じていなかったのですが、Xの顔認証にはDepth Sensorが使われていてこれを使って3Dスキャンができるという話を(遅ればせながらですが)知って試してみたくなったのでです。

Capture: 3D Scan Anything

Capture: 3D Scan Anything

  • Standard Cyborg
  • 写真/ビデオ
  • 無料

 Captureというアプリの使い方は簡単で

  1. 前面カメラを対象に向けて静止した状態でレコードボタンをクリック
  2. 3,2,1と数字がカウントダウンされるのを待つ
  3. ゆっくりとiPhoneを動かして対象物をスキャン
  4. レコードボタンをクリックしてスキャンを終了

即座に得られた点群データ(Point Cloud)が表示されます。

f:id:ichidaya:20190307182441j:plain

キャプチャーされた点群データ

このデータをローカルにセーブしたり、(無料のアカウントを作成しておけば)クラウドにアップロードすることができます。

クラウドからはOBJやPLYの形式に変換してデータをダウンロードすることはできますが残念ながら点のデータを保存しているだけなのでそのままでは3Dモデリングの材料に使えません。OBJファイルの中身を見ると”v”で始まる点データを表す行と”vn”で始まる法線ベクトルを表す行があるだけで、面を表す"f"で始まる行がありません。ZBrushでこのOBJファイルを読み込もうとしてもエラーになります。

でも、大丈夫。ネットで調べたらポイントクラウドから3Dのメッシュを生成してくれる(それ以外にもいろいろできますが)オープンソースのソフトがあることがわかりました。

CloudCompare - Open Source project

 PLY形式でダウンロードした点群データをこのCloudCompareに読み込みます。

f:id:ichidaya:20190307211124p:plain

点群データの読込み

左上のDB Treeというパネル上で対象の点群データを選ぶと画面上で点群に対して黄色の外接矩形が表示されいろんな処理が行える状態になります。例えばEdit->Segmentで切出しのためのアイコン群が画面右上に表示され、注目部分だけを切り取ることができます。

f:id:ichidaya:20190307211718p:plain

点群データの切出し

さらにPlugins->Poisson Reconで点群データからメッシュ(つまり面)のデータを再構築することができます。

f:id:ichidaya:20190307212425p:plain

メッシュ再構成結果

パラメータが何個かあって実はまだ意味がわかっていないのですが、とりあえずデフォルトのままで上のような結果が得られました。再構築にあたっては法線ベクトルの情報も参照されるようです。何個かの画像を試してみたとき中にはメッシュ再構築の前に法線ベクトルの再計算(Edit->Normals->Compute)や反転(Edit->Normals->Invert)を行っておいたほうが良い結果を得られることがありました。

メッシュ化したデータをFile->Saveでファイル修飾子objやplyを指定して格納すれば面の情報を持った3Dデータが格納されます。ZBrushで読み込むにはOBJ形式のデータが欲しいのですが、CloudCompareで出力したOBJファイルには色の情報が含まれていません(OBJ形式で点の座標といっしょにRGBを記載するのは正式な仕様ではないらしい)。いったんPLY形式で出力してこれをMeshLabで読み込んでOBJ形式で出力するとカラーの情報を含んだOBJファイルを作ることができます。ZBrushでこのファイルを読み込めば色付きのメッシュの3Dモデルが手にはいります。

f:id:ichidaya:20190307215020j:plain

ZBrushに取り込んだ3Dモデル

 あとはうちのわんこがスキャンしている間じっとしていてくれればよいのですが、こちらはまだ解決のめどがたっていません。人の顔を見るとおやつのおねだりでどんどん前にでてきてしまうので。

 

 

 

立体地図を作る -ZBrushで行う作業あれこれ-

近畿地方の7府県について水平方向222万分の1、垂直方向70万分の1の縮尺で立体地図を造形しなおしました。縦方向の高さが横方向よりも3倍ぐらいに強調されていることになります。せっかく立体地図を作るのだから山や谷の凹凸をはっきりさせたい、一方で全国をこの縮尺で作ったとき富士山のコニーデ型のシルエットは残したいという2つの欲求のせめぎあいでこの線に落ち着きました。

f:id:ichidaya:20190302234654j:plain

近畿7府県の立体地図

今回はOpenSCADを使わずに自作のスクリプトでOBJファイルを生成しています。おかげで面積の広い兵庫県などもデータを分割せず一発で生成することができました。また、今回は各ピースの裏側(底面)を少し押し込んだ上(材料を少なくすると若干安く造形できる)で府県名を押し出しで表現しました。

f:id:ichidaya:20190302235725j:plain

近畿7府県(裏側)

後は今まで記録していなかったZBrush作業上の注意点などを..

JAXA国土地理院から取得した地形データをOpenSCADや自作のGenObjスクリプトを使ってそのまま3Dモデルに変換すると、海岸沿いの小さな島が陸地とは別れてしまったり、内陸でも標高が海抜以下の場所に穴が開いてしまったりします。これでは地図パズルのピースとしては都合が悪いので、現状ではOBJファイルをZBrushに取り込んだ後に手作業で修正しています。

下の図で青色で囲った部分は微小部分がピースから別れてしまうので事前に削除しておきます。Subtool->Split->Split To partsで別々のサブツールに分離した後でDel Otherで本体以外のサブツールを削除します。黄緑で囲った部分はメッシュ1個分(計算上0.25mm×0.25mm)の幅でつながっていたり突出している部分です。造形した後での強度が心配なのと他のピースとのはめ合わせに余裕をもたせたいという理由からこれらも削除しています。該当部分をマスクしてから、Subtool->Split->Split Masked Pointsで分離、本体以外の部分を削除します。削除した部分には穴が開いているので、Geometry->Modify Topology->Close Holesで穴をふさぎます。

f:id:ichidaya:20190303133243p:plain

手作業での修正対象の例

(赤枠で囲った)穴が開いている部分については面としては閉じているのでまず穴の4個の側面を削除(ZModeler Polygon ActionsのDeleteを使います)してから上下の穴を塞ぎます。

 底面を少し押し下げた後でText 3D & Vector Shapesのプラグインを使って府県名の文字列を作成、地図本体とMergeします。

f:id:ichidaya:20190303142238p:plain

底面の押込みと名前の付加

 山の部分が四角形のメッシュの単位でカクカクなるのがいやなので最後にスムージングかけています。いろんなやりかたを試していますが、いまのところはResolution=400ぐらいでDynamesh化を行っています。

 

ラテアート用ステンシルの作成

ステンシル本体はサイズをきっちり決めて造形したかったのでFusion 360で円部分の直径やもち手の長さを指定してモデリングを行いました。

f:id:ichidaya:20190228134507p:plain

ステンシル本体のモデリング

直径85mmの円と10mm×45mmの長方形を組み合わせたスケッチを厚さ2.5mmで押出ています。強度的には厚さ1mmちょっとでもいいはずですが、今回はナイロン(磨きあり)で造形するつもり(以前に行った地図の造形は磨き無し)だったので磨き処理で薄くなる部分を考慮して気持ち厚めにしました。このモデルをOBJ形式でエクスポートしてからZBrushに取り込んで犬の顔のかたちに穴を開けていきます。

 元になる犬の顔の絵は写真をもとに奥さんに描いてもらいました。

f:id:ichidaya:20190228170523p:plain

写真からステンシル用の線画を作成

ZBrushで下絵を表示する方法はいくつもあって選択に迷うのですが、今回はDrawメニューの中のUp-Down->Map1に画像ファイルを指定する方法をとりました。Divideを何回か繰り返した面(写真ではステンシル本体)とこの絵を重ねて絵の線の部分をマスクしていきます。Divideの回数は線を滑らかにマスクできるように試行しながら決めています。マスクで線を描くときステンシル本体の内側の島を線で完全に囲ってしまうとその部分を支えることができなくなります。絵として不自然にならない範囲で線を分断しておきます。マスクした部分をSubtool->Extractで線画部分を押し出した(両面指定、Thickの値は最大にしておく)立体を作ります。

f:id:ichidaya:20190228174508p:plain

絵柄を彫り込む手順(1)

絵柄の部分はラテアート以外にも使える可能性があるので独立したパーツとして保存しておきます。

f:id:ichidaya:20190228193313p:plain

絵柄の彫り込み用パーツ

ステンシル本体を改めてZBrushに読み込み、上述の彫り込み用パーツの位置や大きさを調整したうえでBool演算を実行すれば完成です。

f:id:ichidaya:20190228195744p:plain

ステンシル本体と彫り込み用パーツの間でBool演算を実施

 

なかなか好評だったラテアートステンシル

ラテアート用のステンシルをうちのわんこをモデルに作ってみました。ニコちゃんマークや★などを意匠にしたものはamazonなどでも見かけますね。

f:id:ichidaya:20190225000012p:plain

モデルとともに

ラテを作ってからカップの上にステンシルを重ねてシナモンパウダーをふりかけます。開いている穴の部分から落ちた粉で絵を描けるというしくみです。

f:id:ichidaya:20190224234938p:plain

ステンシルの使い方

 ネット販売で出回っているステンシルは簡単な図形が多いです。それらに比べるとこれくらい込み入った図形をパウダーで表現できるのか心配だったのですが試してみたら意外といい感じです。うちの奥さんにも好評でした。

立体地図用テクスチャ画像作成に関する備忘録

カラー造形のためのテクスチャ画像を作成する考え方は前回述べたとおりなのですがもう少し実装よりというか、泥臭い作業上の注意点なども記録しておきたいと思います。

テクスチャ画像の生成スクリプトソースコードを下記に置きました。

MapTexture.py

このPythonスクリプトは画像編集ソフトGimpプラグインとして動作します。単独では動きませんのでご注意ください。GimpはフリーのソフトウェアでWindowsMac両方で利用可能ですが、私のPythonスクリプトWindows環境でのみテストしています。

Gimpダウンロード

 PythonスクリプトのファイルをWindowsのユーザフォルダ下の".gimp-2.8\plug-ins"フォルダ(私はGimp2.8を使っているのでこの名前になります)にコピーしておいてGimpを起動するとテクスチャ画像生成をGimpのコマンドとして利用できるようになります。

フィルター(R)->Python-fu->Map->Gen Texture
で以下のようなダイアログが表示されます。

f:id:ichidaya:20190207230219p:plain

テクスチャ画像生成ダイアログ

File NameにはUVマッピングの結果を含んだOBJファイルを指定します(重要!! ファイルパスの文字列にダブルバイト文字が含まれていると異常終了します。OBJファイルを置いておくフォルダ名には半角文字を使ってください)。Image Sizeにはテクスチャ画像の縦と横(正方形なので同じ値になります)の画素数を指定します。一般に大きな値を指定したほうが精密な絵が描けますが、頂点間の間隔があいているところを塗りきりなかったり(後述)します。形状のポリゴンの数や大きさも影響しますので、私は512から2048ぐらいの値で悩みながら決めています。BaseとVer.Scaleはプリント用の3Dモデルの高さから元の標高を逆算するのに必要なパラメータです。Excelで直方体のデータを出力するとき、標高の値を縦方向に70万分の1に縮尺してから台の部分を5mm分足しているのでこの逆を行うわけです。得られた標高値に応じて色を決めているのですがその部分は現時点ではハードコーディングされていて以下のような感じです。

    height = ((z - base)*ver_scale)/1000.0
    thr    =  - ((base/3.0)*ver_scale)/1000.0

    if   height < thr :
        color = (230,230,230,1.0)
    elif height < 0.1 :
        color = (43,131,186,1.0)
    elif height < 10.0 :
        color = (100,171,176,1.0)
    elif height < 200.0 :
        color = (157,211,167,1.0)
    elif height < 300.0 :
        color = (199,233,173,1.0)
    elif height < 500.0 :
        color = (237,248,185,1.0)
    elif height < 1000.0 :
        color = (255,237,170,1.0)
    elif height < 1500.0 :
        color = (254,201,128,1.0)
    elif height < 2000.0 :
        color = (249,158,89,1.0)
    elif height < 2500.0 :
        color = (232,91,58,1.0)
    else :
        color = (215,25,28,1.0)

実は、あともう1つこのダイアログには含まれないのですがテクスチャ画像の出来栄えに影響を与えるパラメータがあります。下の図で青枠で囲った数字、指定された色で点を打つときのブラシサイズです。現状のスクリプトでは実行時に設定されていたサイズで点を打っていきます。

f:id:ichidaya:20190208172808p:plain

テクスチャ画像作成時の点の大きさの指定

 現状のスクリプトでは点のサイズを小さくすると精密な着色ができるのですが大きなポリゴンを塗り切れないことがあります(下の図の赤枠部分)。点を打つのがポリゴンの頂点の位置で中を塗りつぶすようなロジックになっていないからです。

 

f:id:ichidaya:20190208181712p:plain

テクスチャを塗り切れない例

ZBrushのDynaMeshの機能でポリゴンの大きさを均質かつ十分小さくしておけばこういうことはおこらないのですがテクスチャ生成に非常に時間がかかってしまうことがあります。ブラシのサイズを大きめに設定してテクスチャ画像を生成すると 粗くはなるが塗り残しをなくすことができます。

f:id:ichidaya:20190208182944p:plain

ブラシサイズを大きくしたテクスチャ画像

 2つの画像を重ね、塗り残し部分は粗い画像の情報で補うという手もあります。

f:id:ichidaya:20190208183521p:plain

粗い画像と精微な画像の重ね合わせ

ここらへんはどんなやりかたが良いのかいまだ試行錯誤を続けている状況です。

 

 画像が生成できたらZBrushにテクスチャ画像に取り込んでもう一度OBJ形式でエクスポートを行って以下の3つのファイルを作成します。

XXX.OBJ

XXX.mtl

XXX.BMP

OBJファイルににmtlファイルへのパスを記述し、mtlファイルの中にBMPファイルへのパスを記述するというのがカラー造形を依頼するときのお作法になっているためです。

 

 





立体地図を作る -補足2 標高で色分けする-

先日投稿した標高で色分けした立体地図の作り方の説明です。
業者に色付きで造形してもらうために渡すデータの形式はいくつかありますが、私はOBJ形式ファイル+テクスチャ画像を渡して依頼しました。ここでいうテクスチャ画像とは立体の表面にシーム(切れ目)を入れて平面に展開した画像です。立体のどこにどうシームをいれて展開するかということを規定する情報はUVマッピングと呼ばれていてその記述方法はOBJ形式の仕様で決められています。前の投稿で説明した”v”と”f”によるOBJ形式の記述はUVマッピングを持たない形状だけの情報でしたが、これにUVマッピングの情報が追加されます。したがって、色付きの造形データを準備するためには

  1.  UBマッピング付きのOBJファイルを作成する
  2. UVマッピングに沿った色付きのテクスチャ画像を作成する

という2ステップの作業を行うことになります。

1. UBマッピング付きのOBJファイルを作成する

よほど単純な形状でない限り手作業でUVマッピングを行うのは無理なので専用のソフトウェアの手助けが必要になります。私の場合、ZBrushのUVマスターというプラグインを使いました。UVマスターでは直感的にわかりやすい展開画像が得られるようシームを入れてほしくない場所(赤)、逆にここに切り分けて展開してほしいという場所(青)を人手で指定したうえで展開処理(Unwrap)を起動します。

f:id:ichidaya:20190207100025p:plain

UVマスターでシーム位置の指定

 展開結果は下の図のようになります。

f:id:ichidaya:20190207115755p:plain

UVマッピングの結果得られた展開画像

 

 2.UVマッピングに沿った色付きのテクスチャ画像を作成する

展開画像に対してPhotshopなどの画像編集ソフトウェアを使って手作業で色を塗ることもできますが、今回は標高の数値に応じてきちんと塗り分けたいのでそのためのスクリプトを作成しました。

ZBrushでUVマッピングを行った後でOBJ形式でモデルをエクスポートすると

v 133.550 47.474 9.898
v 133.710 47.439 9.912
v 133.529 47.291 9.871
v 133.723 47.288 9.936
:
vt 0.66436 0.38941
vt 0.66386 0.38996
vt 0.66326 0.38942
vt 0.66425 0.38883
:
f 2/1 1/2 3/3 4/4
f 5/5 2/1 4/4
f 3/3 24/6 8/7 4/4
f 5/5 4/4 8/7 9/8

といったデータが出力されます。形状の情報のみのを保管した場合と比べると"vt"とい文字列で始まる行が追加され、面を表現する”f”で始まる行の記法が変わっています。"vt"で始まる行の2つの数字は展開後の画像のX座標、Y座標です(3次元形状のX,Y,Z座標と区別するためにUV座標と呼ばれます)。この座標の値は0から1の間の値に正規化されているので、テクスチャ画像を作成する側で決めた画像の大きさに応じて実際の点の位置が決まります。

”f”で始まる行で面を構成する頂点を記述していくときには
3次元の頂点のインデックス/UV座標の点のインデックス

という組で記述されます。上の例で2/1という記述はインデックス2の頂点(133.710 47.439 9.912 )はインデックス1のUV座標( 0.66436 0.38941 )と対応していることを示しています。

以上から、標高で塗り分けられたテクスチャ画像を作成するには

面を構成する(”f”で始まる行にでてくる)すべてのインデックスの組に対して
 1 3次元頂点のインデックス番号をもとに頂点のZ座標(高さ)をもとめる
 2 Z座標から何色(RGBの値)で塗るかを決定する(例えば標高5m以下なら水色)
 3 UVのインデックス番号からUV座標を取得、画像上の座標に換算する
 4 3でもとめた画像上の座標に2でもとめた色の点をうつ
という処理を 繰り返せばよいことがわかります。

 

上記のアルゴリズムPythonスクリプトで実装し実行した結果です。

f:id:ichidaya:20190207150819p:plain

スクリプトで作成したテクスチャ画像

作成したテクスチャをZBrushに取り込んでみました。この状態でもう一度OBJファイルをエクスポートすると、OBJ、mtl、BMPの3つのファイルが作成されるのでこれをZipに固めて造形を依頼します。

f:id:ichidaya:20190207152004p:plain

作成したテクスチャの貼り付け

 

立体地図を作る -標高メッシュから直接OBJ形式データを生成する-

なんとか立体地図が作成できるようにはなったものの、いくつかのの県についてパズルのピースを作ってみると手順にいろいろ不具合があることもわかってきました。今回は今まで汎用のソフトウェアを使って行っていたOBJ形式データの生成処理を専用のプログラムで置き換えることで作業の効率化とモデル品質の向上を図ります。

これまでQGISで作成した標高メッシュの情報から3Dモデルのデータ(OBJファイル)を生成するためにOpenSCADというプログラムを利用していましたが、これには2つ大きな問題がありました。

問題1 処理できるデータ量に制限がある

立体地図作成のためにはメッシュごとに単純な直方体の3Dデータを生成していけばよいのですがOpenSCADでは生成する直方体の数(あるいはプログラムの行数)が19,000を超えたあたりでメモリー不足で異常終了してしまいます。PCのメモリ使用状況としては余裕があるので、OpenSCADの処理系内部のどこかで固定でワーク領域を確保しているのではないかと疑っています。異常終了を避けるためにはメッシュデータを分割して3Dデータを生成してから、ZBrushなどの3Dモデリングソフトを使って統合する必要があります。手間がかかることに加えて、(問題2とも関係しているのですが)なかなかきれいに統合できないでいました。

問題2  STL形式のデータしか出力できない

OpenSCADが出力できるのはSTLという3Dプリント出力用のデータのみです。STL形式では三角形の集合で面を表現しています。ZBrushSTL形式を読み込めないので、いったんMeshLabで読み込んでOBJ形式(三角形を含む多角形の集合で面を表現している)に変換してからZBrushに読み込んで前出の統合処理やスムージングなどを行なっていました。3Dプリントのためには最終的にはSTL形式を出力するので、STLをOBJに変換、OBJ形式を読み込んで編集、再びSTL形式で出力という段階をふむ必要があります。くわえて、もともとの地図データは直方体なので四角形の集合で面をきれいに表現できていたのが一度三角形の集合になったものを読み込むと元の四角形の集まりとは微妙に異なってしまいます。問題1の対応のために静岡県のモデルを分割生成してとりこんだときは接合面の頂点ががぴったり合わず形状にスジが残ってしまいました。

 こういった問題を解決するため標高メッシュのデータからOBJ形式を生成するプログラムをつくることにしました。OpenSCADは3Dモデル作成のためにさまざまな基本形状や形状に対する操作をサポートしていますが、立体地図作成に必要なのは単純な直方体形状をグリッド上に配置していく操作のみです。この部分だけなら比較的簡単に実装することができます。

 出力するOBJファイルのフォーマットについてもインターネット上で情報を見つけることができます。改行で区切られたテキスト形式のファイルで"v"で始まる行は頂点、"f"で始まる行が1つの面(多角形)を表すというのが基本です。 頂点を表す行は"v"の後にXYZの座標を示す3つの数字が空白で区切られて続きます。面は多角形の頂点を指すインデックス番号を空白で区切って記述します。下の例で”f 1 4 3 2”の1は1番目の頂点(48.750,100.250,0.000)を示しています。直方体の面はいずれも四角形ですから"f"の後には整数値を4つ記述することになります。面には表と裏があって正式には"vn"で始まる行で法線ベクトルを記述するようですが、1->4->3->2といった頂点を記述する順番(表面からみて反時計回りになるような順番)で裏表を指定できるという仕様(慣習?)がありZBrushも”vt”無しのデータを読み込んでくれました。

 

OBJ形式ファイルの例

v 48.750 100.250 0.000
v 49.000 100.250 0.000
v 49.000 100.500 0.000
v 48.750 100.500 0.000
v 48.750 100.250 5.000
v 49.000 100.250 5.000
v 49.000 100.500 5.000
v 48.750 100.500 5.000

f 1 4 3 2
f 8 5 6 7
f 1 5 8 4
f 3 4 8 7

下のような1個の直方体の情報を表すのには8個の頂点と6個の面の情報を出力すればよいわけです。

f:id:ichidaya:20190204230227p:plain

単純な直方体

実際には隣合う直方体の間では頂点を共有したり、面が別の面で覆われたりすることを考慮しないといけません。下の図で青丸で示すような2つ以上の面で共有されている頂点は各面で同一のインデックス番号で指定されないといけません。同じ座標を持った頂点を複数個出力するのはNGです。面についても互いに接している部分は除いて重なっていない部分(下図の青枠部分)だけの面の情報を出力します。

f:id:ichidaya:20190204234943p:plain

隣接する直方体

上記のような点を考慮してOBJファイルを生成するスクリプトPythonで記述しました(末尾に添付)。

入力は基本、OpenSCADに与えていたのと同じ情報ですが数値の部分だけをカンマで区切ったCSV形式で用意します。

 48.75,100.25,0,0.25,5
49,100.25,0,0.25,5
49.25,100.25,0,0.25,5
49.5,100.25,0,0.25,5

各行、5つの数字で直方体を表しています。最初の3つは左下角の頂点を示すX,Y,Z座標値、4つ目は底面の正方形の1辺の長さ、最後の数字は直方体の高さになります(単位はmmです)。スクリプトにはこのCSVファイルと出力するOBJファイルのパスを引数として指定します。

Python GenObj.py I:\local\3D-Work\Shizuoka.csv I:\local\3D-Work\Shizuoka.obj

厳密なOBJ形式の仕様に則しているかは若干怪しいですがZBrushは出力されたファイルを問題なく読み込んでくれています。

f:id:ichidaya:20190205121136p:plain

静岡県のOBJファイルを読み込んだ画面

この静岡県は30,000弱 の直方体で構成されておりOpenSCADではいちどに3Dモデルを生成できなかったのですが作成したスクリプトでは問題なく処理できました。また、処理時間も数秒でOpenSCADでの処理に比べて大幅に向上しました。

以下にスクリプトのソースを開示します。自分で使っている範囲内でしかテストしていないので、利用する場合は自己責任でお願いします。

GenObj.py