KonifarPod

わいせつな写真をアップロードするユーザーに立ち向かう(画像フィルタ編)

      2014/07/26

Pocket

Porno filter3

写真を投稿するアプリやサービスだと必ず問題になると思うんですが、たくさんユーザーが集まってくると、卑猥な投稿をするユーザーというのが0.7〜1.0%くらいは発生するんですね。つまりポル◯なんですが、ポ◯ノと書くとGoogle先生にお叱りを受ける可能性があるので控えます。

特に、中東地域のユーザーはものすごくドギツイのを上げてきたりします。中学生が見たらトラウマになっちゃうんじゃないかレベルです。自分も電車の中でイキナリ表示されてすごく気まずい思いをしたのもあって、ちょっと対策を打つことにしました。

 

(1)APIを使って検出する(2014/07/25時点)

困っていることは皆同じで、わいせつ写真を検出するAPIはいくつかあります。ざっと調べて出てきたのが2つありました。

SightEngine

こんな感じで、かなり細かくチェックできるみたいです。実際にテストしてみても結構いい精度で検出されました。Facebookがやってた乳首が映ってた判定みたいのも簡単に実装できます。

ただ、料金はこんな感じ。ちょっと高くて従量課金なのがガクブルです。

Porno filter1

 

② Nudity Detection Service

こちらはあんまり精度がよくなかったです。レスポンスには is_nude というkeyにtrue/falseが返ります。Webページから気軽にテストできるのはよかったです。

料金は少し安め。ただ個人で作ってるっぽいので今後どうなるかは不安です。

Porno filter2

 

サービスの規模にもよると思うんですが、1日5,000投稿を超えているような場合だと導入するにはちょっと高いんですよね。。。1%以下の変態の投稿のためにこんなにお金使うの・・・?みたいな。なので、今回は見送ることにしました。

 

(2)フィルタを自作する

じゃあ自作するしかないかということで、わいせつ写真をフィルタリングするロジックを書くことにしました。

① 傾向と対策

まず、20,000枚くらいのユーザー投稿写真を手動チェックして、180枚くらいのわいせつ写真をローカルにダウンロードします。さらっと書きましたが、ここが一番大変でした。。。で、それをサンプルとしてどういうフィルタにすればいいのか傾向と対策を考えます。

その結果、肌色の割合が多いものがわいせつである可能性が高い、という結論にたどり着きました。まぁ当然っちゃ当然なのですが。

 

② ロジックを決める

じゃあ肌色の割合を検出すればいいよね、ということで肌色検出の方法を調べてみました。肌色判定のロジックはたぶんどこかに落ちてるだろうなと思って調べてみたら、やはりありました。

http://stackoverflow.com/questions/713247/what-is-the-best-way-to-programatically-detect-porn-images

コードはPythonですがやってることは単純で、1pxずつRGBの値をチェックしてそれが肌色っぽいか判定して、肌色とみなされたものが全体の何%だったかを算出する、というものでした。これならRubyでもJavaでも書けそうです。

 

③ とりあえず実装

とりあえずRailsでスクリプト作ってテストしてみることにしました。RMagickを使ったので、結構簡単に書けました。

require 'RMagick'
require 'open-uri'

class PornoFilter

  def self.run(attrs={})
    check attrs[:url]
  end

  private
  def self.check(url)
    url_image = open(url)
    img = Magick::ImageList.new.from_blob(url_image.read).first

    t = 0
    f = 0

    # 1pxずつ取り出して、RGBをチェックする
    img.color_histogram.inject({}) do |hash, key_val|
      red = key_val.first.red
      green = key_val.first.green
      blue = key_val.first.blue

    # ここが肌色判定のロジック
      if red > 60 && green < (red*0.85) && blue < (red*0.7) && green > (red*0.4) && blue > (red*0.2)
        t += 1
      else
        f += 1
      end
    end

    # 肌色が全体の50%以上だったらわいせつ判定
    if t > f
      p "porno! #{t}, #{f} : #{url}"
    end  rescue => e
    p e.message
  end
end

 

こんな感じで実行すると、結果が表示されます。(引数はサンプルです。若干わいせつなので注意)

rails runner “PornoFilter.run(url: 'http://blog-imgs-30.fc2.com/a/r/a/arakuma1975/eri.jpg')"

 

④ 精度をあげる

サンプルでは肌色率50%以上でわいせつ判定としていたのですが、これで180枚のわいせつ画像をグルグル回してチェックしてみると全体の20%くらいの画像しか拾えませんでした。ただ、あまり比率を下げるとわいせつ以外の画像もわいせつ判定されてしまうので、少しずつ精度を上げていきました。

その結果、肌色率30%が適正値っぽいという結論に至りました。これでわいせつ画像の50%〜60%くらいはフィルタできます。もちろん完璧ではなく、夕焼けの画像などを間違えてフィルタしてしまうこともあります。まぁ疑わしきはフィルタして、後でフィルタを外してあげられるツールがあればいいのかなと思っています。自分がアップロードしたウニ丼の写真がフィルタされた時はちょっと悲しくなりましたが。。。

また、肌色判定のロジック自体もまだ改善の余地はあって、とても色白な人や黒人の写真は拾えないです。ここの調整は工数とのトレードオフなので、今はまだ手をつけられていません。

 

⑤ Javaで実装

じゃあこのフィルタをどこでかけるか、というところですが、今回はAndroid(クライアント)側でかけることにしました。少しでもサーバーに負荷をかけたくなかったのと、各端末でチェックした方が影響範囲も少ないかなと考えたからです。

AndroidJavaではUtilityクラスにメソッドを作って判定させるようにしました。画像をサーバーにアップロードする前にBitmapを引数で渡してわいせつ判定します。コードはこんな感じ。

public static boolean isPorno(Bitmap bmp) {
    int t = 0;
    int f = 0;

    for (int i = 0; i < bmp.getHeight(); i++) {
        for (int j = 0; j < bmp.getWidth(); j++) {
            int pixel = bmp.getPixel(j, i);
            int red = Color.red(pixel);
            int green = Color.green(pixel);
            int blue = Color.blue(pixel);

            if (red > 60 
                && green < (red * 0.85)
                && blue < (red * 0.7)
                && green > (red * 0.4)
                && blue > (red * 0.2)) {
                t++;
            } else {
                f++;
            }
        }
    }
    return t > (t + f) * 0.3;
}

 

(3)結果検証

Androidにわいせつフィルタを組み込んだものをリリースした結果、想定通り全体の50〜60%のわいせつ画像をフィルタすることができました。半分かぁと思われるかもしれませんが、半分減るだけでもわいせつ画像の遭遇率はかなり減ります。あまり工数かけずに実装したにしては、結構効果の高い改善だったのではないかと思います。

わいせつ画像にお困りの方は、参考にどうぞ。他にも、60%の検出率を70%にあげる施策(テキスト解析編)も実施できたので、近いうちに書きたいと思います。

(2014/07/26)書きました。⇒
わいせつな写真をアップロードするユーザーに立ち向かう(テキスト解析編)

 

Pocket

 - Develop ,