Twitterの飯テロ画像をブロックするchrome拡張機能を作った

## 駆逐してやる...！飯テロ画像を...1枚残らず...!

Twitterに蔓延る飯テロ画像、許すまじ。
という事で、Twitter上の飯テロ画像を機械学習で検知し、ブロックするクローム拡張機能を作りました。

画像のような食欲をそそるラーメンも...
![meshi-tero](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257943/ec89a9fc-0bee-f509-ec3f-736394c6a7e0.png)

このとおり！きれいに隠すことができます！
![before after](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257943/df69a0eb-ee24-051e-7800-3defc9ce75a6.gif)

拡張機能としてリリースしているので、よろしければこちらのリンクからDLしてください！
[Chrome拡張機能 - 飯テロブロッカー](https://chrome.google.com/webstore/detail/food-porn-blocker/olpfpfbklfeikemgdohicmibkmfnboeh?hl=ja)

ソースコードも公開中です。
[https://github.com/Nemurino-kai/FoodPornBlocker](https://github.com/Nemurino-kai/FoodPornBlocker)


以下、拡張機能の仕組みなどについて、技術的な話をしていきます。

## 導入したライブラリ
次の外部のライブラリを導入しました。

* [ml5.js](https://ml5js.org/)
    * Tensorflowをjavascript上で動かすことができます。
- [jQuery](https://jquery.com/)
 - HTML操作のため。

## 大まかな仕組み
1.ドメインがtwitter.comならjsを起動、ページを監視
2.ページに変化があったら、ページの画像部分を抽出してくる
3.画像をImageNetで学習したMobileNetに入力し、クラス分類する。
4.分類されたクラスが食品なら、画像を置き換える。食品でなければ、そのまま表示

#### 1.ページを監視する
```javascript
var observer = new MutationObserver(replace_image);
observer.observe(document, {
    childList: true,
    subtree: true,
});
```
MutationObserverは、「Mutation（変化）」を「Observe（監視）」するオブジェクトです。
変化があったとき、ここではreplace_image関数が実行されます。

#### 2.ページに変化があったら、ページの画像部分を抽出してくる
```javascript
function replace_image() {
    $('div').each(function(){
        if (this.parentNode && (this.parentNode.getAttribute("aria-label")=="画像"||this.parentNode.getAttribute("aria-label")=="Image") && !this.classList.contains('processed')){
            this.classList.add('processed');
            var image_url = this.style.backgroundImage.match(/\"(.+)\"/)[1]
            console.log("loaded:" + image_url);
            // いったん隠す
            this.style.backgroundImage = 'url()';
```
ページ内で、親ノードのaria-labelが"画像"or"Image"である要素を探します。
この条件に当てはまるノードが、画像へのリンクを持っています。
無駄な処理を避けるため、処理済のelementには'processed'クラスを付与し、繰り返し処理を行わないようにしています。

その後、elementから画像のurlを取得し、一旦画像を隠します。

#### 3.画像をImageNetで学習したMobileNetに入力し、クラス分類する。
```javascript
            app(image_url).then(
              (answer) =>{
                if(isFood(answer)){
                    this.style.backgroundImage='url(' + chrome.runtime.getURL("replace.jpg") + ')';
                }else{
                    this.style.backgroundImage='url(' + image_url + ')';
                }
              }
            )
```
次は取得した画像urlをapp関数に入れます。app関数は画像のurlを入力として、予測クラスを返り値とする関数です。

ではここで、app関数を確認してみましょう。

```javascript
async function app(set_src) {
    
    const model_url = chrome.runtime.getURL("mobilenet_v1_025_224/model.json")

    const classifier = ml5.imageClassifier(model_url, modelLoaded);

    console.log("seted:" + set_src);
    
    // 画像分類器に入れるelementの生成
    let imgEl = document.createElement('img');
    //CORSに引っかからないように
    imgEl.crossOrigin = "anonymous"
    imgEl.src = set_src

    var answer

    await classifier.classify(imgEl)
    .then(results => {
        console.log("classified:"+set_src);
        console.log(results[0].label);
        answer = results[0].label;
    });

    return answer
    
}
```
まず初めに、ローカルに保存しておいたMobileNetのモデルをロードします。（[モデルはこちらからDLできます](https://github.com/ml5js/ml5-data-and-models-server)）
次に分類器に入力するためのElementを作成します。ml5.jsでは、imgタグをそのまま入力してあげれば、結果を返してくれます。（便利！）
このElementを分類器に入れればおｋです。

#### 4.分類されたクラスが食品なら、画像を置き換える。食品でなければ、そのまま表示
```javascript
            app(image_url).then(
              (answer) =>{
                if(isFood(answer)){
                    this.style.backgroundImage='url(' + chrome.runtime.getURL("replace.jpg") + ')';
                }else{
                    this.style.backgroundImage='url(' + image_url + ')';
                }
              }
            )
```
app関数の実行後、予測クラスをisFood関数に入力しています。isFood関数は、予測クラスを入力し、それが食品であるかの真偽値を返す関数です。
isFoodが真であれば、飯テロ画像として、画像のurlを置き換えます。
isFoodが偽であれば、飯テロ画像ではないので、画像のurlを元に戻します。よって、画像は普通に表示されます。

## おわりに
機械学習を手軽にWeb開発に組み込める、ml5.js、他にも色々な使い道があって面白そうです。夢が膨らみますね✨