映画館ウェブサイトのようなインタラクティブ座席マップ
仕事でつくろうとしている、「会場の画像・座席データ登録して、映画館サイトのようにユーザに選んでもらう機能」の紹介
(個人に新しいし、技術としてシェアする価値があると思ってこのページ作りました)
※製品自体は2022-4頃から開発進んで、2022-6中にリリースされています
作ったものの構成
PartA: 座席登録
会場画像(bitmap)から、社内の人間が簡単に座席領域を選択して、領域に座席情報を登録する専用ツール。会場画像の「可能な座席図形」を認識してデータに書き出す部分 (以下A-1) 、人間が本当の座席図形を選んで、会場登録情報を確定させる部分(以下A-2)で構成する。
A-1: Python + OpenCV 、簡単にCLIツール
会場は頻繁に変動するものではない、また会場増えるたびに座席認識の方法いじったりするので、A-2との結合まだ考えていない。
座席っぽい図形を2Dポリゴンとして認識してJSONに書き出す。
↑のJSONをざっくりTSで表現すると {points: {x: number, y: number}[] } みたいなものです。
↑中核になったのは OpenCVの findContours というメソッド。クローズド図形を基準に認識するので、だいたいの座席の長方形を探してくれる。
A-2: Web app 、Next.js (React)
bitmap + A-1 で書き出したポリゴンJSONをブラウザで表示するWebアプリ。人間が本当の座席図形を選んで、座席番号・区分など入力して、(ポリゴン+座席番号)を会場データの確定版として書き出すツール。
動的サーバを使用しない。(A-1の出力JSONを静的ファイルとして使っている)
PartB: 座席選択
一般人ユーザ、PC・モバイルブラウザ向けのweb app
登録してある会場(画像+座席領域を表すデータ)からUI作って、ユーザがtouchかclickで座席を選ぶ
作りとしては元の画像を背景(html <img>)を後ろに、Part Aで作った座席ポリゴンを含めたsvgを手前に表示することで、インタラクティブなUIになる
svg部分は空白svgをDOM <object> にロードさせて、JSでSVG DOM要素を作る。
1つの座席ポリゴンが1つの <polygon> になる
<polygon>にfillColorなど指定することで、座席のハイライト表現などもできる
この方法では <svg> 内部のDOM要素のevent handlerを自分のJSに繋ぐこともできる。これでクリックされた座標から図形を特定するようなことはしなくて済んだ。
(作る以前に)考えていた技術
SVG
座席領域をSVGで保存する (1座席 = 1 <polygon> みたいな)
Webで使うため、レンダリングが普及している
ブラウザのイベント利用できれば、自力でクリック座標〜座席の検索しなくて済む
UIイベントに反応する図形にもなるし、UI表現(ハイライトetc)にも利用できる
画像認識・画像分割(partA の座席登録のアシスト目的に、画像にあった「座席っぽい」領域を抽出して、partAのユーザに提供したい)
CV用語でいうと Image segmentation ? やりたいことがobject detectionの一般例よりシンプルと思われる
path trace (potraceみたいな) 技術もあるが、方向多少違うかも
今回のsvg利用はマスク / metadata のような目的のため、元のbitmap画像と似てなくてもいい
potraceが生成したSVGはpolygon以外のものもあって、利用が不便そう(image segmentationの出力から、より簡単にpolygon / AABB作れるはず)
※ 前述のように、結局OpenCVと簡単な方法でしばらくもの足りるレベルまでいけた。
開発途中の気づき
<svg> 関連
<object data={svgURL}> 内部のDOMをいじるには、ドキュメントオリジンとURLオリジンが一致しないといけないらしい。今回は空白svg文字列からBlob作って、URL.createObjectURL でsvgURLを作った。
Firefoxではなぜか <object> のloadイベント見てすぐ theObj.getSVGDocument() 呼ぶと例外になる。catchして1秒待って再度やるとうまくいく。
<object>内部のSVGドキュメントと要素を「外のHTMLドキュメントのJavaScript」で使うと、普段の「同じドキュメントのJavaScript」と微妙に違う。
例: svgElement instanceof SVGElement は常にfalseになるため使えない。svgElement.constructor.name === "SVGPolygonElement" なら意図通りに動く。
画像認識関連
OpenCV には一応(Emscriptで生成した?)JS版あるが、完成度がまだ早い感じでした.
Python binding / C++ binding / Java binding はより使える。
ポリゴンの認識は思ったより遥かに簡単な仕組みでできた。simple is the best。
OpenCVが探してくれたポリゴンが全て座席にあたるわけではない。不要なポリゴンを外すには、前述A-2のWebアプリでいくつかの簡単なfeatureを定義して、feature scoreを基準にした。
featureの例:
ポリゴンの面積と会場画像の比例 (小さすぎる、大きすぎるのを除外する)
ポリゴンの面積と「ポリゴンを内包する最小の長方形面積」の比例(形状が長方形から大きく離れたものを除外する)