ノードの顔検出.さびとwebassemblyのJS


最後の記事で紹介した.この記事ではノードの書き方を紹介します.サービス応用としてのJSベースAI
AIの今日の支配的なプログラミング言語はPythonです.しかし、Web用のプログラミング言語はJavaScriptです.Web上のサービスとしてAI機能を提供するためには、JavaScript、特にノードにAIアルゴリズムをラップする必要があります.js
しかし、PythonもJavaScript自体も計算集約的なAIアプリケーションに適していません.彼らは高レベル、すなわち、低速、重量のランタイムを持つ言語です.彼らの使いやすさは、低いパフォーマンスのコストで来ます.PythonはネイティブのC/C++モジュールでAI計算をラップすることによってこれを回避しました.ノード.JSは同じことをすることができましたが、より良い方法があります.
WebBassembly VMSはノードとの密な統合を提供します.JSと他のJavaScriptのランタイム.彼らは非常にパフォーマンス、メモリセーフ、デフォルトでセキュア、オペレーティングシステム全体に移植可能です.しかし、我々のアプローチは、webassemblyとネイティブコードの最高の機能を組み合わせたものです.

動作方法


ノード.サービスアプリケーションとしてのjsベースのaiは3つの部分から成る.
  • ノード.JSアプリケーションはWebサービスを提供し、AI BASICなどの計算集約タスクを実行するためにWebBassembly関数を呼び出します.
  • データの準備、後処理、および他のシステムとの統合は、webassembly関数によって行われます.最初に、我々はさびを支持します.アプリケーション開発者はこの関数を書かなければなりません.
  • AIモデルの実際の実行は、パフォーマンスを最大化するためにネイティブコードによって行われます.コードのこの部分は非常に短く、セキュリティと安全性のために審査されます.アプリケーション開発者はこのネイティブプログラムをwebassembly関数から呼びます-- Pythonとノードでネイティブ関数がどのように使われるかのように.今日の.

  • 今の例を見てみよう!

    顔検出例


    The face detection web service ユーザーが写真をアップロードすることができます、そして、それは緑の箱でマークされるすべての絵でイメージを表示します.

    The Rust source code for executing the MTCNN face detection model is based on Cetra's excellent tutorial: Face Detection with Tensorflow Rust. We made changes to make the Tensorflow library work in WebAssembly.



    The Node.js application ファイルのアップロードと応答を処理します.
    app.post('/infer', function (req, res) {
      let image_file = req.files.image_file;
      var result_filename = uuidv4() + ".png";
    
      // Call the infer() function from WebAssembly (SSVM)
      var res = infer(req.body.detection_threshold, image_file.data);
    
      fs.writeFileSync("public/" + result_filename, res);
      res.send('<img src="' +  result_filename + '"/>');
    });
    
    ご覧のように、JavaScriptアプリケーションは単にイメージデータとパラメータdetection_threshold , に指定される最小の顔を指定するinfer() 関数を返します.The infer() function は錆で書かれ、webassemblyにコンパイルされ、JavaScriptから呼び出すことができます.
    The infer() 関数は入力画像データを配列に平坦化する.テンソルフローモデルを設定し、モデルへの入力として平坦化画像データを使用する.TensorFlowモデルの実行は、各フェイスボックスの4つの角の座標を示す数値のセットを返します.The infer() 関数は、各顔の周りに緑色の箱を描画し、それを変更した画像をWebサーバー上のPNGファイルに保存します.
    #[wasm_bindgen]
    pub fn infer(detection_threshold: &str, image_data: &[u8]) -> Vec<u8> {
        let mut dt = detection_threshold;
        ... ...
        let mut img = image::load_from_memory(image_data).unwrap();
    
        // Run the tensorflow model using the face_detection_mtcnn native wrapper
        let mut cmd = Command::new("face_detection_mtcnn");
        // Pass in some arguments
        cmd.arg(img.width().to_string())
            .arg(img.height().to_string())
            .arg(dt);
        // The image bytes data is passed in via STDIN
        for (_x, _y, rgb) in img.pixels() {
            cmd.stdin_u8(rgb[2] as u8)
                .stdin_u8(rgb[1] as u8)
                .stdin_u8(rgb[0] as u8);
        }
        let out = cmd.output();
    
        // Draw boxes from the result JSON array
        let line = Pixel::from_slice(&[0, 255, 0, 0]);
        let stdout_json: Value = from_str(str::from_utf8(&out.stdout).expect("[]")).unwrap();
        let stdout_vec = stdout_json.as_array().unwrap();
        for i in 0..stdout_vec.len() {
            let xy = stdout_vec[i].as_array().unwrap();
            let x1: i32 = xy[0].as_f64().unwrap() as i32;
            let y1: i32 = xy[1].as_f64().unwrap() as i32;
            let x2: i32 = xy[2].as_f64().unwrap() as i32;
            let y2: i32 = xy[3].as_f64().unwrap() as i32;
            let rect = Rect::at(x1, y1).of_size((x2 - x1) as u32, (y2 - y1) as u32);
            draw_hollow_rect_mut(&mut img, rect, *line);
        }   
        let mut buf = Vec::new();
        // Write the result image into STDOUT
        img.write_to(&mut buf, image::ImageOutputFormat::Png).expect("Unable to write");
        return buf;
    }
    
    The face_detection_mtcnn command ネイティブコードでMTCNN tensorflowモデルを実行します.これは、画像幅、画像の高さ、検出しきい値の3つの引数をとります.平坦化されたRGB値として準備された実際の画像データは、webassemblyinfer() 経由STDIN . モデルからの結果はJSONで符号化され、STDOUT .
    モデルパラメータをどのように渡したかに注目してくださいdetection_threshold モデルテンソルmin_size , そしてinput テンソルは入力画像データを通過する.The box テンソルを使用してモデルから結果を取得します.
    fn main() -> Result<(), Box<dyn Error>> {
        // Get the arguments passed in from WebAssembly
        let args: Vec<String> = env::args().collect();
        let img_width: u64 = args[1].parse::<u64>().unwrap();
        let img_height: u64 = args[2].parse::<u64>().unwrap();
        let detection_threshold: f32 = args[3].parse::<f32>().unwrap();
        let mut buffer: Vec<u8> = Vec::new();
        let mut flattened: Vec<f32> = Vec::new();
    
        // The image bytes are read from STDIN
        io::stdin().read_to_end(&mut buffer)?;
        for num in buffer {
            flattened.push(num.into());
        }
    
        // Load up the graph as a byte array and create a tensorflow graph.
        let model = include_bytes!("mtcnn.pb");
        let mut graph = Graph::new();
        graph.import_graph_def(&*model, &ImportGraphDefOptions::new())?;
    
        let mut args = SessionRunArgs::new();
        // The `input` tensor expects BGR pixel data from the input image
        let input = Tensor::new(&[img_height, img_width, 3]).with_values(&flattened)?;
        args.add_feed(&graph.operation_by_name_required("input")?, 0, &input);
    
        // The `min_size` tensor takes the detection_threshold argument
        let min_size = Tensor::new(&[]).with_values(&[detection_threshold])?;
        args.add_feed(&graph.operation_by_name_required("min_size")?, 0, &min_size);
    
        // Default input params for the model
        let thresholds = Tensor::new(&[3]).with_values(&[0.6f32, 0.7f32, 0.7f32])?;
        args.add_feed(&graph.operation_by_name_required("thresholds")?, 0, &thresholds);
        let factor = Tensor::new(&[]).with_values(&[0.709f32])?;
        args.add_feed(&graph.operation_by_name_required("factor")?, 0, &factor);
    
        // Request the following outputs after the session runs.
        let bbox = args.request_fetch(&graph.operation_by_name_required("box")?, 0);
    
        let session = Session::new(&SessionOptions::new(), &graph)?;
        session.run(&mut args)?;
    
        // Get the bounding boxes
        let bbox_res: Tensor<f32> = args.fetch(bbox)?;
        let mut iter = 0;
        let mut json_vec: Vec<[f32; 4]> = Vec::new();
        while (iter * 4) < bbox_res.len() {
            json_vec.push([
                bbox_res[4 * iter + 1], // x1
                bbox_res[4 * iter],     // y1
                bbox_res[4 * iter + 3], // x2
                bbox_res[4 * iter + 2], // y2
            ]);
            iter += 1;
        }
        let json_obj = json!(json_vec);
        // Return result JSON in STDOUT
        println!("{}", json_obj.to_string()); 
        Ok(())
    }
    
    私たちの目標は、開発者がライブラリとして使用できるように、一般的なAIモデルのネイティブ実行ラッパーを作成することです.

    顔検出例の配備


    必須条件としては、Rust , Nodeをインストールする必要があります.ジェイピーSecond State WebAssembly VM , とssvmup ツール.Check out the instruction 手順、または単に私たちのDocker画像を使用します.また、あなたのマシンにTensorFlowライブラリを必要とします.
    $ wget https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-gpu-linux-x86_64-1.15.0.tar.gz
    $ sudo tar -C /usr/ -xzf libtensorflow-gpu-linux-x86_64-1.15.0.tar.gz
    
    フェイス検出の例を配置するには、ネイティブのTensorFlowモデルドライバから開始します.のソースコードからコンパイルすることができますthis project .
    # in the native_model_zoo/face_detection_mtcnn directory
    $ cargo install --path .
    
    次に、web application project . svmupコマンドを実行して、rustからwebassembly関数を構築します.このWebBassembly機能がWebアプリケーションのデータ準備ロジックを実行することを思い出します.
    # in the nodejs/face_detection_service directory
    $ ssvmup build
    
    WebAssembly関数を使用して、ノードを開始することができます.JSアプリケーション.
    $ npm i express express-fileupload uuid
    
    $ cd node
    $ node server.js
    
    Webサービスは現在、あなたのコンピュータのポート8080で利用可能です.独自のselifyや家族やグループの写真を試してみてください!

    TensorFlowモデル動物園


    天然さび木枠 face_detection_mtcnn テンソルフローライブラリの脆弱なラッパーです.これは、冷凍保存モデルとして知られて訓練されたTensorflowモデルをロードし、モデルの入力を設定し、モデルを実行し、モデルから出力値を取得します.
    実際には、我々のラッパーは、検出された顔の周りのボックスの座標を取得します.モデルは実際に各顔に目、口、鼻の各検出顔と位置の信頼レベルを提供します.モデルの取得テンソル名を変更することで、ラッパーはこの情報を取得し、WASM関数に戻ることができます.
    別のモデルを使用したい場合は、例に従って簡単に自分のモデルのラッパーを作成する必要があります.あなただけの入力とテンソル名とそのデータ型の出力を知る必要があります.
    この目標を達成するために、我々はプロジェクトと呼ばれるnative model zoo , できるだけ多くのtensorflowモデルのために錆ラッパーを使用する準備を整えること.

    次は何


    本論文では,ノードにおけるサービスユース事例として実世界aiを実装する方法を示した.rustとwebassemblyを使用したjs.我々のアプローチは、より多くのアプリケーション開発者のためのAIライブラリとして機能することができる“モデル動物園”に貢献するために、コミュニティ全体にフレームワークを提供します.
    次のチュートリアルでは、画像分類のための別のTensorflowモデルをレビューし、ラッパーを拡張して、類似モデルのクラスをサポートする方法を示します.