タイル画像を作った記録 #2

前回のあらすじ

id:yellow_73:20130809 の続き。

先にgdalwarpでリサンプリングすることでgdal2tiles.pyではニアレストですむようにし、また gdal2tiles.py の魔改造によりマルチプロセス化した。

魔改造スクリプトはガワが全く無い状態なのでガワを被せたいなと思ってますが、マルチプロセスでプログレスをキャラクタベースで出すなんてTERMCAPなんかとうのむかしに忘れたぜヒャッハー、となっているため無理です。

JPEG の生成で透過部で白と黒が出てあせる

gdal2tiles.py はPNGを生成しますが、ここはJPEGも生成しないといかんでしょう、と考えました。

JPEGはファイルサイズが圧倒的に小さいという利点があります。ただし、アルファチャネルをサポートしてない、コントラストが強い箇所の近傍ではモスキートノイズが出る、という欠点があります。

今回は、対象が背景図に使うので透過は必須でない、地図だけどにじみ等も結構あるのでコントラストが強すぎるとは言えない、むしろ3G回線といった高速とは言えない回線でも使うことを考えたらJPEGも用意するメリットはある、と判断しました。

maptiler ではJPEGもあわせて生成してくれますが、gdal2tiles.py ではできないので、ImageMagick を使いましたPNGファイルからJPEGファイルに変換するのは次の通り。

% convert a.png a.jpg

サフィックスを見て判断してくれます。ImageMagickばんざい…。

find コマンドで *.png を見つけたら jpeg に変換するようにしました。

で結果を見ると…。

pngで透過になっている領域が、黒になった箇所と白になった箇所が出ました。

たぶん gdalwarp で失敗してたんだと思います。

検討しきってないのですが、考えられる原因を。元データはアルファチャネルなし圧縮なしのTIFFで 255,255,255(白) をNoDataにしてました。gdalwarpを実行する際に、アルファチャネルを追加、NoDataを255,255,255,0と指定しました。アルファチャネルはいいとして、NoData指定がgdalwarpによる回転(元データはUTM)による現れる元データが存在していないピクセルに255,255,255,0が入るかなと思ったら入ってない。gdalinfoでwarp後のファイルを見たら全チャネルのNoDataが255になってる。

これ、盛大に勘違いしてる可能性がありますね…。

事前にチェックしてたはずがチェックしてなかった

チェックしてたんですよ、アルファチャネルを取ったら白以外が現れないかは。

GIMPで。

gdalwarpで生成した画像の一部をgdalwarpで取り出して、GIMPで開いてアルファチャネルを除いてやったんです、そうすると透過部が背景色になるのを確認してたんですよ…これ意味無いですからねorz 後で背景色を黒にした上でアルファチャネルを除いたら黒になったところを見ると、元のピクセルが持つR,G,B値に関係なくGIMPで指定した背景色が使われます。

pngから透過をサポートして無いjpegに変換するとチェックできると思います。

JPEG の生成で透過部で白だけが出るようにしたがこれはおすすめしない

手戻りせんでええように考えてたのが、gdalwarp以前まで手戻りしなければならない危機。これは避けたいので、256*256の白一色のpngファイル (ffff.png)を用意して、

convert -composit ffff.png (元png) (出力jpg)

で変換するようにしました。

これでとりあえずは透過領域は白で出るようになった。ほっとした。gdalwarpははっきりいって億ピクセルかつランチョスでりんサンプリング、となってくると1日では終わらんのですから。

で、PNGはどうせ透過サポートですから放置。透過領域の隠れた色問題はpngでも響いてくることになります。

Google Earthさんのばかぁ

その後KML生成もうまくいったので、とりあえずGoogle Earthで見てみることに。

…なんか変なのが出てる。

透過されている部分が表示されていました。前述と同じように、黒領域と白領域が表示されます。

…原因不明の怪現象に呆然とするしかないですね。

呆然としたままいても話が始まらないのですが、とりあえず放置しようと思って別作業にかかっていたところ、ふと前に作成したpngと比較してみようと思いました。

gdalinfo で新旧PNGを見ると、新しい方はインデクス化されていて、古い方はインデクス化されていないのが判明。地理情報を持たない画像ファイルでもインデクスされているかどうかが分かったりするので、gdalinfoを利用してます。

そうすると、旧版は32ビットPNG、新版は気合入れてoptipng -o7で圧縮したカラーインデックスPNG

もしかして、32ビットPNGにしたら問題解決? まさかね… など思いながら、32ビットPNGにしてみたら、ああ、うまくいくやないの…

透過領域を背景色に統一しつつインデックスPNGをG32ビットPNGに変換する

convertで32ビットPNGで出力するには、ファイル名の前に "png32:"を付けるとOK。あと、黒領域問題を解決するため、透過部分は背景色に変更するオプション(-alpha background)を付けることにしました。

convert -alpha background (pngファイル) png32:(中間ファイル)
mv (中間ファイル) (pngファイル)

を繰り返し実行すると、インデクスを使わず、透過部が背景色となった PNG に変換できました。

前述のJPEG生成において、ffff.png と重ねて使うより、先にこちらを実行したうえで JPEG を生成するともっとスマートに行ったと思います。

あと余談ですが、KMLは正距円筒でのみ対応しているので、そちらのPNGしか処理しておらず、Webメルカトルの方は現在作成しているところだったりしますので、隅っこ近くのタイルを落として convert なりでJPEGとかに変換すると「出る」と思いますorz

感動のフィナーレ

ここでは出てこなかった Windows x64 用バイナリを作ってみたが地理情報がうまく載らずにあーだこーだしてた時期を除くと、着手から2週間で、億ピクセル画像のWebメルカトル(z<=17)と正距円筒(z<=16)のタイルを作りサーバにアップできたました。まじめにgdal2tiles.pyを駆動してたら正距円筒のPNGができあがらなかったというのを考えると、かなり速度向上が図れたと思います。

問題点は

  • 魔改造gdal2tiles.pyを外に出せる程度にガワ付けたい
  • 透過領域を持たせたいなら Google Earth に渡す PNG は png32 にしとくべき
  • ImageMagickばんざい optipngばんざい