- 投稿日:2020-06-23T20:18:46+09:00
SemanticSegmentationを行うnpmパッケージを作ったので使い方を説明してみる
はじめに
先日、大きな画像を分割してsemantic segmentationを行うnpm パッケージを作成したので、使い方の説明を兼ねてデモの作り方を説明してみます。
このパッケージは、スマホなどの性能の限られたデバイス上で精度と応答速度のトレードオフを調整しながらSemanticSegmentationするためのものです。
今回のデモとしては、このようなものを作ります。バーコードを検出してセグメンテーションを行います。前回のnpmパッケージを作成した際の投稿はこちらです。
https://qiita.com/wok/items/a6445d17724ed700e448前準備
今回はReactでデモを作成します。
まずは環境構築をしましょう$ create-react-app demo --typescriptパッケージをインストール
それではvirtual背景のパッケージをインストールします。
$ npm install scalable-semantic-segmentation-js $ node node_modules/scalable-semantic-segmentation-js/bin/install_worker.js public file is copied使用するモデルの準備
次に使用するSemantic Segmentationのモデルを用意します。
このモデルは、[batch, height, width, channels]のshapeのテンソルを入力として想定しています。$ ls public/WEB_MODEL/300x300_0.10/ group1-shard1of1.bin model.jsonデモのコード
準備ができたら、ソースコードを作成します。
ここでは、重要と思われる部分のみ解説します。ソース全体は下記のリポジトリに置いてあります。本デモでは、Reactコンポーネントのメンバー変数としてモジュールのクラスのインスタンスを作っておきます。
scalableSS:ScalableSemanticSegmentation = new ScalableSemanticSegmentation()では、
componentDidMout
からみて行きます。componentDidMount() { console.log('Initializing') const initWorkerPromise = this.initWorker() // <-- (1) if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // <-- (2) const webCamPromise = navigator.mediaDevices .getUserMedia({ audio: false, video: DisplayConstraintOptions[this.state.videoResolution] }) .then(stream => { console.log(this.videoRef) this.videoRef.current!.srcObject = stream; // <-- (3) return new Promise((resolve, reject) => { this.videoRef.current!.onloadedmetadata = () => { resolve(); }; }); }); Promise.all([initWorkerPromise, webCamPromise]) .then((res) => { console.log('Camera and model ready!') }) .catch(error => { console.error(error); }); } }(1)で今回SemanticSegmentationを行うインスタンスをイニシャライズします。このメソッドの内部は後述します。
(2)でビデオデバイスを取得し、(3)でHTMLVideoElementのソースに設定しています。次に、
initWorker
の中身を見ていきます。async initWorker() { // SemanticSegmentation this.scalableSS.addInitializedListener(()=>{ // <-- (1-1) const props = this.props as any this.setState({initialized:true}) this.requestScanBarcode() // <-- (1-2) }) this.scalableSS.addMaskPredictedListeners((maskBitmap:ImageBitmap)=>{// <-- (2-1) // 再キャプチャ this.requestScanBarcode() // <-- (2-2) }) this.scalableSS.init( AIConfig.SS_MODEL_PATH, AIConfig.SPLIT_WIDTH, AIConfig.SPLIT_HEIGHT, AIConfig.SPLIT_MARGIN) // <-- (3) return }本メソッドでは、SemanticSegmentationを行うインスタンスをイニシャライズしています。
まず(1-1)で、インスタンス内部で使用するモデルのロードなどの初期化が完了したときのコールバック関数を設定します。
コールバック関数の中では(1-2)バーコードスキャンを行う関数を呼び出しています。
(2-1)は、セグメンテーションが完了したときに呼び出されるコールバック関数を設定しています。
このとき受け取るパラメータがセグメンテーションの結果のビットマップイメージになります。
コールバック関数の中でも(2-2)バーコードスキャンを行う関数を呼び出しています。これにより処理をループさせています。
(3)でインスタンスに使用するモデルの情報と、画像を分割する際に使用するマージン(分割後の隣り合う画像間でオーバラップする領域の割合)を指定します。第1引数がモデルのパス、第2、3引数がモデルで使用する幅と高さ、第4引数がマージンです。最後に、
requestScanBarcode
です。requestScanBarcode = async () => { console.log('requestScanBarcode') const video = this.videoRef.current! const controller = this.controllerCanvasRef.current! controller.width = this.overlayWidth controller.height = this.overlayHeight const captureCanvas = captureVideoImageToCanvas(video) <--(1) if(captureCanvas.width === 0){ captureCanvas.remove() window.requestAnimationFrame(this.requestScanBarcode); return } this.scalableSS.predict(captureCanvas, this.state.colnum, this.state.rownum) <--(2) captureCanvas.remove() }ここでは、SemanticSegmentationを行う対象となる画像を取得し、SemanticSegmentionを実行しています。
(1)で、SemanticSegmentationの対象となる画像をHTMLVideoElementから取得しています。この画像を(2)の引数としてモジュールのインスタンスに与え、SemanticSegmentationを実行させています。第2,3引数は分割する行数と列数です。以上で、セグメンテーションを行う処理は完了です。
デバッグ用に、セグメンテーションの様子や、グリッドの情報を表示することもできます。
<Label basic size="tiny" color={this.state.showSS?"red":"grey"} onClick={()=>{ const newValue = !this.state.showSS this.scalableSS.previewCanvas = newValue ? this.workerSSMaskMonitorCanvasRef.current! : null this.setState({showSS:newValue}) }}>ss</Label> <Label basic size="tiny" color={this.state.showGrid?"red":"grey"} onClick={()=>{ const newValue = !this.state.showGrid this.scalableSS.girdDrawCanvas = newValue ? this.controllerCanvasRef.current! : null this.setState({showGrid:!this.state.showGrid}) }}>grid</Label>this.scalableSS.previewCanvasとthis.scalableSS.girdDrawCanvasにそれぞれ描画するHTMLCanvasElementを設定してください。
デモ
https://scalable-ss-demo.herokuapp.com/index.html
ソースコードとnpmパッケージ
本ソースコードは下記のリポジトリに格納してあります。
https://github.com/FLECT-DEV-TEAM/ScalableSemanticSegmentationjs_demonpmパッケージのページは次のURLになります。
https://www.npmjs.com/package/scalable-semantic-segmentation-js
- 投稿日:2020-06-23T18:43:54+09:00
HTMLのタイトルを取得する [Node.js, シェルスクリプト]
httpリクエストでHTMLのページを取得して正規表現でタイトルを引っ張り出します。Node.jsを使う方法とシェルスクリプト(Bash)を使う方法で出来たので書き残しておきます。
ちなみに、Node.jsもシェルスクリプトも初心者です。
環境
Mac OS 10.14.6Node.js
Node.jsからダウンロードします。
$ node -v v12.18.1requestモジュール
Node.js標準でもHTTPリクエストできるそうですがrequestモジュールを使うと楽、らしい。
$ npm install requestこれはできるだけ上位のディレクトリでやったほうがいい。インストールしたところ以下で
request
が使えるがそれより上のディレクトリでは使えない。(個人的な実験の結果)(npmについてのまとめ - Qiitaに詳しいことが書いてある(グローバルインストールとやらに失敗したので上の階層に入れた))実装
get_title.jsvar request = require('request'); var URL = process.argv[2]; var re_title = RegExp("(?<=<title.*>).*(?=</title>)") function callback(error, response, body) { if (!error && response.statusCode == 200) { var title = re_title.exec(body); if(!title){ console.log("No title") }else{ console.log(title[0]); } }else{ console.log("Error!"); } } request(URL, callback);
- 1行目 requestモジュールを使えるようにする
- 3行目 コマンドライン引数からURLを取得する
- 5行目 title要素を取得する正規表現(後述)
- 7行目~ requestの結果に対するコールバック関数(後述)
- 最終行 request実行
実行
$ node get_title.js https://... タイトル正規表現
ここで使う正規表現は
"(?<=<title.*>).*(?=</title>)"です。(参考 正規表現 - JavaScript | MDN)
(?<=<title.*>)
: これは「後読み」と呼ばれ、<title.*>
に続くものをマッチさせますが<title.*>
自体はマッチしません.*
: これは「任意の文字の(.)」「0回以上の繰り返し(*)」にマッチします(?=</title>)
: これは「先読み」と呼ばれ、</title>
が後に続くものをマッチさせますが</title>
自体はマッチしませんこの正規表現でHTMLを探索することでtitle要素の中身にマッチさせることができます。ちなみに、title開始タグを見つける「後読み」部分が
<title>
ではなく<title.*>
を使っているのは、title開始タグにオマケがついているページがあったからです。ここ→request - npmなんですけど、ソースを見ると<title data-react-helmet="true">request - npm</title>って書いてあります。何のこっちゃわからないのですが。
requestのコールバック関数
そんなに深入りしないです。
リクエストがエラー吐いたりステータスコードが200じゃなかったら
"Error!"
と出力して終わる。
OKだったら正規表現でbody
から.exec()メソッドで最初にマッチするところを探して結果をtitle
に入れる。
マッチすればtitle[0]
がタイトルだし、マッチしなければnull
が返ってくるので"No title"
と出力。まあ基本正常に返ってくることを期待しているので。
Bash
こっちのほうが、複雑だが面白かった。
実装
get_title.sh#!/bin/bash read URL #RegExp='s/.*<title.*>\(.*\)<\/title>.*/\1/p' RegExp='/.*<title.*>\(.*\)<\/title>.*/{s/.*<title.*>\(.*\)<\/title>.*/\1/p;q;}' title=`curl -s $URL | sed -n $RegExp` echo -e "\n[$title]($URL)\n" #markdown式
- 1行目 おまじない。よくわからんけど書いとけ
- 3行目 標準入力からURLを受け取る
- 5,6行目 sedに送るコマンド
- 8行目 curlとsedでページタイトルの取得
- 10行目 出力(markdown式にカスタマイズ)
実行
$ bash get_title.sh https://qiita.com/ [Qiita](https://qiita.com/)curl
$ curl -s {URL}で
{URL}
の中身であるHTML文書を取得。さらにパイプで次のsedコマンドに流している。-s
は進捗の表示をさせないオプションsed
パイプでやるという条件を揃えて書くと
#!/bin/bash $ echo -e {複数行文字列} | sed -n s/{置換前}/{置換後}/pで、
{複数行文字列}
から{置換前}
を含む行があれば{置換後}
に置き換えて出力。-n
は、デフォルトでは置換の有無にかかわらず毎行出力していたのを無効にするオプション。p
は明示的に出力するフラグ#!/bin/bash $ echo -e "hogehoge\nfugafuga" | sed -n "s/g.*g/QQ/p" hoQQe fuQQa5行目のコマンド
s/.*<title.*>\(.*\)<\/title>.*/\1/p 大枠 = s/{置換前}/{置換後}/p {置換前} = .*<title.*>\(.*\)<\/title>.* {置換後} = \1
- {置換前}: 「任意文字列(
.*
)」「<title.*>
」「.*
」「<\/title>
」「.*
」の文字列を見つける。真ん中は「\(.*\)
」としてエスケープつきの括弧で囲むことでグループ化して一時保存しています- {置換後}: 先ほど一時保存したところを
\1
で呼び出しています#!/bin/bash $ echo -e "hogehoge\nfugafuga" | sed -n "s/.*g\(.*\)g.*/\1/p" eho afu6行目のコマンド
/.*<title.*>\(.*\)<\/title>.*/{s/.*<title.*>\(.*\)<\/title>.*/\1/p;q;} 大枠 = /{文字列}/コマンド 文字列 = .*<title.*>\(.*\)<\/title>.*(さっきの探索文字列) コマンド = {s/.*<title.*>\(.*\)<\/title>.*/\1/p;q;}
- 大枠: {文字列}がある行に対してコマンドを実行
- コマンド:
{}
で囲んで複文にする。前半s/.*<title.*>\(.*\)<\/title>.*/\1/p;
は先述の通りの置換&表示コマンド。後半q;
はsedコマンドの終了。これがなぜ欲しいかというと、request - npmの中になんだか知らんがtitle要素が出現するところが2行あるので。1回マッチしたら{変換&終了}する。
その他
sedの後一回変数に入れたいなぁと思って色々調べたら、式自体をバッククォートで囲んで代入すれば良いとのこと。
bashで改行付きでechoするためには
-e
オプションが必要→echo -e "\n[$title]($URL)\n"
おしまい
bashの方が色々設定とかせずにすみそうで良いな
参考
- 投稿日:2020-06-23T11:38:29+09:00
AWS Lambda Nodejsのバージョンが8から12に強制アップデートされたら Error: Cannot find module 'jmespath' と出る問題
要約
AWS Lambdaで利用するnodejsのバージョンが nodejs8 -> nodejs10以降のアップデートすると aws-sdkが組みこまれるようになるため
node_modulesにaws-sdkを含んでいるとエラーになるので、取り除く必要がある本文
nodejs8で書かれたAWS Lambdaスクリプトは非推奨となりました。
AWS Lambdaのnodejs10やnodejs12環境で同じスクリプトを実行すると、以下のようなエラーメッセージが表示される。{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'jmespath'","stack":["Runtime.ImportModuleError: Error: Cannot find module 'jmespath'"," at _loadUserApp (/var/runtime/UserFunction.js:100:13)"," at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)"," at Object.<anonymous> (/var/runtime/index.js:36:30)"," at Module._compile (internal/modules/cjs/loader.js:701:30)"," at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)"," at Module.load (internal/modules/cjs/loader.js:600:32)"," at tryModuleLoad (internal/modules/cjs/loader.js:539:12)"," at Function.Module._load (internal/modules/cjs/loader.js:531:3)"," at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)"," at startup (internal/bootstrap/node.js:283:19)"]}モジュール'jmespath'自体はpackage.jsonをみても見つからない、普通のモジュールの場合は、 requireの書き方を修正しすればよいが、
aws-sdkの場合は対応が異なる# この様になっているものを var aaa = require('foobar') # このように相対パスを指定してあげれば良い var aaa = require('./foobar')node_moduleを使って
aws-sdk
下記のように呼び出している場合は aws-sdk を node_modulesから削除する必要があるvar aws = require('aws-sdk');aws-sdkを取り除くyarnの場合
yarn remove aws-sdkaws-sdkを取り除くnpmの場合
npm uninstall aws-sdknodejs10 から lambda に aws-sdk が追加されました。 see: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html