ブラウザだけで動く幾何補正ライブラリです。基準点(GCP)から変換を推定し、画像を変形して地図座標に合わせます。重い処理は Web Worker で並列に行い、メインスレッドを止めません。
ImageStatic にそのまま渡せる設計の背景は docs/geometry-design.md にあります。npm にはまだ公開していません。すべての関数・型の説明は API リファレンス にあります。
import { fitTransform, warp } from 'browser-image-geometry';
// 画像上の位置(画素)と、その場所の地図座標(経度・緯度など)の組
const fit = fitTransform(
[
{ pixel: [120, 80], world: [139.7, 35.69] },
{ pixel: [1830, 95], world: [139.8, 35.69] },
{ pixel: [1810, 1420], world: [139.8, 35.62] },
{ pixel: [140, 1400], world: [139.7, 35.62] },
],
{ model: 'projective' }, // 'affine'(3 点以上)| 'projective'(4 点)| 'polynomial2'(6 点)| 'polynomial3'(10 点)
);
console.log(fit.rms, fit.residuals); // 当てはまりの確認
const out = await warp(imageElementOrBlob, fit.transform, { output: 'canvas' });
out.image; // 北が上になるよう変形した画像
out.extent; // [minX, minY, maxX, maxY]
out.geoTransform; // [左上x, 画素幅, 0, 左上y, 0, -画素高](GDAL と同じ並び)
OpenLayers に重ねる例:
import ImageLayer from 'ol/layer/Image';
import Static from 'ol/source/ImageStatic';
const canvas = out.image as HTMLCanvasElement;
map.addLayer(
new ImageLayer({
source: new Static({ url: canvas.toDataURL(), imageExtent: out.extent, projection: 'EPSG:4326' }),
}),
);
基準点は経度・緯度で、出力は Web メルカトルにしたい、という場合は coordinateTransform に forward と inverse を持つ変換を渡します。proj4 の変換オブジェクトがそのまま使えます。
import proj4 from 'proj4';
const out = await warp(img, fit.transform, {
coordinateTransform: proj4('EPSG:4326', 'EPSG:3857'),
});
// out.extent はメートル(EPSG:3857)
変換関数は Worker に送れないので、メインスレッドで出力の上に粗い格子(既定 32 画素間隔)を置いて計算し、格子の中は補間します。誤差が tolerance(既定 0.125 画素)を超えるときは格子を自動で細かくします。
const fit = fitTransform(
[
{ pixel: [212, 95], world: [0, 0] },
{ pixel: [988, 120], world: [800, 0] },
{ pixel: [1040, 870], world: [800, 600] },
{ pixel: [160, 840], world: [0, 600] },
],
{ model: 'projective', target: 'image' }, // y が下向きの画像座標
);
const { image } = await warp(photo, fit.transform, { extent: [0, 0, 800, 600], width: 800, height: 600 });
ImageData を受け取り、新しい ImageData を返します。メインスレッドで動きます。
import { crop, flip, resize, rotate } from 'browser-image-geometry';
rotate(imageData, 15); // 時計回り。キャンバスを広げて全体を収める
rotate(imageData, 15, { expand: false }); // 元の大きさのまま
rotate(imageData, 90); // 90° 単位は画素がずれない
flip(imageData, 'horizontal'); // 'vertical' | 'both'
crop(imageData, { x: 10, y: 20, width: 300, height: 200 });
resize(imageData, 640, 480, { resample: 'bicubic' });
任意の変換を同期で掛けるときは warpImageData(imageData, transform, options) を使います(戻り値は warp と同じ形で、image は ImageData)。
衛星画像や標高データのように 8bit RGBA ではないデータは warpRaster で変形します。値の型とバンド数はそのまま保たれ(16bit の 4 バンドなら 16bit の 4 バンド)、no-data の画素は補間に使わず、入力の外側は no-data になります(no-data がなければ整数は 0、浮動小数は NaN)。オプションは warp と同じです(background を除く)。
import { warpRaster } from 'browser-image-geometry';
const { raster, geoTransform, extent } = warpRaster(
{ width, height, bands: 4, data: uint16Array, noData: 0 }, // 画素ごとにバンドが並ぶ(interleave)
fit.transform,
{ resample: 'bilinear', coordinateTransform: proj4('EPSG:4326', 'EPSG:3857') },
);
raster.data; // Uint16Array
coordinateTransform には任意の関数を渡せるので、RPC と DEM による正射投影もこれで書けます(画像ビューアのユースケースの usecase/image-viewer/src/ortho.ts)。
変換はただのオブジェクトなので、JSON で保存したり Worker に送ったりできます。
import { affine, applyTransform, composeTransforms, invertTransform, projective, rotation, scaling, translation } from 'browser-image-geometry';
const t = affine([a, b, c, d, e, f]); // X = a·x + b·y + c, Y = d·x + e·y + f
applyTransform(t, [10, 20]); // 入力画素 → 出力座標
invertTransform(t);
composeTransforms(scaling(2), translation(5, 0)); // 拡大してから平行移動
画像の座標は連続値で、画素 (i, j) の中心は (i + 0.5, j + 0.5) です。
warp / warpImageData)| オプション | 既定値 | 内容 |
|---|---|---|
resample |
'bilinear' |
'nearest' / 'bilinear' / 'bicubic' |
background |
[0, 0, 0, 0] |
入力の外側の色(RGBA、0〜255) |
extent |
入力全体 | 出力する範囲 [minX, minY, maxX, maxY](出力座標) |
pixelSize |
入力と同程度 | 出力の画素サイズ(出力座標の単位)。数値か [x, y] |
width / height |
— | 出力の画素数。指定すると pixelSize より優先 |
coordinateTransform |
— | 変換先の座標を出力座標に変える { forward, inverse }(proj4 など) |
gridStep / tolerance |
32 / 0.125 |
coordinateTransform の近似格子の間隔と許容誤差(入力の画素) |
yUp |
変換に従う | true で y の大きい側を上にする |
output |
'imageData' |
warp のみ。'canvas' / 'blob' も可 |
type / quality |
'image/png' |
output: 'blob' のときの形式と品質 |
worker |
true |
false でメインスレッドで処理 |
signal |
— | AbortSignal で中断 |
出力を横帯に分けて複数の Worker で処理し、各 Worker には帯が参照する入力の範囲だけを渡します。Worker が使えない環境(CSP で禁止されている、など)では自動でメインスレッドで処理します。結果はどちらでも同じです。
import { configureWorkers, terminateWorkers } from 'browser-image-geometry';
configureWorkers({ maxWorkers: 2 });
terminateWorkers();
ビルド済みのファイルは dist/cdn/ にあります(Worker を同梱した ES モジュールと、グローバル変数 BrowserImageGeometry を作る <script> 用)。GitHub のタグから jsDelivr で配信できます。
<script type="module">
import { fitTransform, warp } from 'https://cdn.jsdelivr.net/gh/Tosei-sudo/browser-image-enhancement@<タグ>/packages/browser-image-geometry/dist/cdn/browser-image-geometry.min.js';
</script>
npm install # リポジトリのルートで
cd packages/browser-image-geometry
npm run typecheck
npm test # Vitest(ユニットテスト)
npm run test:browser # Playwright(実ブラウザで Worker、CDN 読み込み、CSP を確認)
dist/ はコミットしています。ソースを変えたら npm run build して一緒にコミットしてください。
MIT