Edited at
IoTLTDay 25

CROSS MEで黒髪のメガネ女子とマッチングするデバイスをAIとIoTを駆使して作った話 #iotlt

メリークリスマス!ですね。

今年も相変わらず彼女いないですので、クロスミーネタやっていきます。

IoTLTアドベントカレンダーの〆に相応しいネタだと思ってます()

今回はクリスマスなので AIとIoTを駆使して黒髪のメガネ女子とマッチしたいと思います。

過去の記事はこんな感じです。

毎年恒例になってきた気がします。

今回やることはざっくり言うと 女性のタイプを判断して"いいね"を押すかどうかの判断をします。


つまりこんなのを作った

つまりこんなのです。

後ろの方にも書いてますが旗上げもあります。


CROSS ME(クロスミー)とは

これ毎年書いてますがマッチングサービスです。

https://crossme.jp/

位置情報を使ったいわゆる出会い系アプリです。

もう利用3年目になるのかと思うと感慨深いですね(謎)


去年やろうとしたこと = ひたすら足跡を付ける


CROSS ME(クロスミー)で女の子とマッチングを上げるための方法


去年の記事で書いてますが、足跡をひたすら残せばマッチング率は上がります。(検証済)

ただ、この記事書いたときによく言われたのが、 好みのタイプの判定をしてごにょごにょしたいよねって話です。


クロスミーの「いいね」と「次へ」

この手のアプリをやったことがある人だとわかりますが、 相手のことが良いなと思ったら「いいね」、そうでもないなと思ったら「次へ」を押してお互いに「いいね」だったらマッチングするという仕様です。

ただし、「いいね」には数限りがあって無駄撃ちは出来ません。

足りなくなったら課金です。


好みのタイプを判定していいねを押すかどうかの判断をしたて、スマホのボタンを押す

既存の仕組みだといいねの判断ができなかったのでAI技術を使ってみます。

僕は学習したい側ではなくて作られたモデルを使えればいいのでAPIを探して活用してみました。

そしてデバイス側も実装します。

スマホのボタンを押す仕組みまでつなげてみましょう。


Azure Face API

Microsoftが提供しているAPIで写真を食わせると年齢、性別、髪の色、メガネの有無などを返してくれます。便利。

試してみた内容はNode.jsでAzure Face APIを使ってみるに書いてあります。


Azure Face APIを出会い系サービスで試す

こんな感じの画面キャプチャを撮ってAPIに投げると

JSONデータで渡ってくるので以下のような雰囲気に加工して取り出します。

{ gender: 'female',

age: 22,
glasses: 'NoGlasses',
emotion: 'happiness',
hair: 'brown' }

パッと見た感じだと女性のタイプの判定に使えそうな情報はこの辺かなぁという印象です。


クロスミーアプリの画面をAPIに投げるための工夫

Face APIは画像のホスティング先のURLを指定してAPIにリクエストします。

ダウンロードした画像をどこかにアップロードして、APIをリクエストするってのを手動でやっていたら日が暮れそうなのでここは自動化します。


iPhoneの画面をMacに映し出す

これは結構簡単で、USBケーブルでMacとiPhoneを接続してQuickTimePlayerからiPhoneの画面を呼び出せます。

(OSのバージョンによって出来ないとかもあるかもですが)

こんな感じですね。

最初、カメラを使ってスマホ画面を撮影して、それをWebRTCのストリームに流して判定してたんですけど、カメラを通すよりも圧倒的に画質が良くてAPI的に判定確率が高かったのでこちらのやり方にしました。


Macの画面の特定箇所のスクリーンショットをNode.jsで取得する

screenshot-nodeというモジュールを利用します。

このモジュールが優秀なのが、範囲指定が出来ることです。トリミング(crop)とスクリーンショット撮影を同時に行えます。

こんな感じで2秒ごとにimage.pngという名前で保存していきます。


screenshot.js

const screenshot = require('screenshot-node');

function save(){
const x = 50;
const y = 130;
const w = 250;
const h = 300;
screenshot.saveScreenshot(x, y, w, h, "image.png", (err) => {
if(err) console.log(err);
console.log('done');
});
}

setInterval(save,2000);


はじめはscreenshot-desctopというモジュールを使ってたんですけど、これだと画面全体のスクリーンショットになってしまって画像サイズが大きくてAPIのリクエストが遅くなるのと、画面全体だと女の子が写ってる部分以外も写ってしまう画像になるのでノイズが大きく、APIの顔認識判定がかなりシビアになってしまうのでトリミングすることにしました。

画面全体のスクショ撮影 -> 画像加工を...って感じで調べてましたが、色々うまくいかなかったところで、今回使ったscreenshot-nodeに出会えてなんとか助かりました。


女の子の顔の画像をGyazoにアップロードする

(この章タイトルの字面やばいですね苦笑)

さっきも書いたように、Face APIがホスティングされているURLじゃないとうまくリクエスト出来ません。

(ngrokも試したけどうまく行かなかった...なぜだろう)

ということでGyazoのAPIに画像を投げて、そのあとでFace APIにリクエストしてあげます。

こちらも過去に試した記事があるのでこちらをもとに


参考: GyazoAPIをNode.jsで使ってみる


こんな感じで先ほどの2秒間隔で保存しているスクリーンショット(image.png)をGyazoにあげてからFace APIになげます。


crossme.js

省略

const gyazores = await client.upload('./image.png', {
title: "my picture",
desc: "upload from nodejs"
});

const config = {
baseURL: uriBase,
method: 'post',
headers: {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': subscriptionKey
},
data: '{"url": ' + '"' + gyazores.data.url + '"}',
params: params,
}

let crossme = {};
const faceres = await axios.request(config);
省略


APIの仕様見てないけどあんまりいっぱい上げると制限かけられそうな気がしたので、スクリーンショット撮影ごとにGyazoにアップロードではなくて、Face APIに投げる直前のみに使うようにしました。


デバイス側

自動的にスマホを

今回はArduino Uno+Grove BASEシールドでGroveサーボを利用しています。

写真はNefryBTで試してますが電圧降下でESP32が耐えられないっぽくてある程度動かすと再起動してしまうのでArduino Unoにしました。Arduino Leonardoでも試しましたが同様です。Unoはやはり強い。

これも試してみた記事があるのでご参照ください。


参考: Nefry BTでGrove Servoを動かしてみる


ArduinoにはFirmataプログラムを書き込んでJohnny-Fiveで動かします。

ちなみにこんな感じで試しました。


servo.js

const five = require("johnny-five");

const board = new five.Board();
let servo1;
let servo2;

function like(){
servo1.to(30);
setTimeout(()=>servo1.to(0),800);
}

function skip(){
servo2.to(0);
setTimeout(()=>servo2.to(30),800);
}

board.on("ready", function() {
servo1 = new five.Servo(2);
servo2 = new five.Servo(3);
setInterval(like,3000); //3秒ごとにlike(いいね)
setInterval(skip,3000); //3秒ごとにskip (次へ)
});


このコードに先ほどのFace APIでcrossmeの画像判定とつなげてあげます。


result.js

const five = require("johnny-five");

const crossme = require("./crossme");

async function main(){
const res = await crossme();
console.log(res);

//25歳以下を判定
if(res.age <= 20){
like();
}else{
skip();
}
}

省略


こんな雰囲気で 年齢が20以下だったら判定をしてみます。

念の為言っておくと、僕が20歳以下しか無理とかそういう話ではなく


  • たぶんFace API側の仕様で日本人は割と童顔判定が多い

  • クロスミーにいる子のプロフ画像はだいたいSNOWとかで盛ってる

などの要因で実年齢より大分若い判定にしかならないので、試す時の閾値が低くなってしまうといった感じです。


スマホを押す機構

ここは僕一人だと厳しかったのでうこ(@ukk0)くんに手伝ってもらいました。うこさんすごい。

まずはスマホのサイズを測って「いいね」と「次へ」のボタンの位置を確認します。

こんな感じでAmazonの段ボールでヤグラっぽいのを作ってボタンの位置に穴を開けます。

動かすとこんな感じで、上からストンと落としてスマホタップする仕組みです。

あと、タップするところは静電式なのでタッチペンを100均で買ってきて、分解します。

先っぽの部分を割り箸につけつつ、電気が通らないといけないのでArduinoの3.3vと5vのピンから電気を通します。


ついに黒髪のメガネ女子を判定する

ついにこの時が来ました。

黒髪のメガネ女子判定です。さっきのサーボ制御とFace APIの箇所をがっちゃんこします。

{ gender: 'female',

age: 22,
glasses: 'NoGlasses',
emotion: 'happiness',
hair: 'brown' }

こんなJSONが返却されるように加工してあるので


result.js

const five = require("johnny-five");

const crossme = require("./crossme");

async function main(){
const res = await crossme();
console.log(res);

//申し訳程度に35歳以上も対象外に
if(res.age <= 35 && res.hair === "black" && res.glasses !== "NoGlasses"){
like();
}else{
skip();
}
}

省略


うん、シンプル。


そして試してみた結果...

冒頭に載せたこれです。

いい感じなんですけど。黒髪のメガネ女子じゃくないか.....?と鋭い人は気付いたと思います。

そうなんです。

クロスミーに黒髪のメガネ女子が全くいません。

これは予想外だったんですけど、だいたいが 茶髪のメガネ無し女子です。

ということで流石にマッチしなさすぎるので年齢判定したくらいのときのデモを載せました苦笑


まとめ

こんな感じでしたが、

画像取得 -> AI(API) -> 判定 -> サーボ制御 -> スマホを押す機構

とてんこ盛り実装をやってみました。

黒髪のメガネ女子がいなかったのでタイトルはタイトル詐欺になったように見えますが、 黒髪のメガネ女子が入れば判定できるはずなのでそこまで詐欺ではないかなと思っています笑

こんなAI周りの技術もAPIとかが充実してきて使うだけならライトウェイトな雰囲気出てきましたね!

ぜひ皆さんもこんな仕組み真似て作ってみてください()

ではでは、IoTLT

今年も皆さん、ありがとうございました!

良いお年を!!

IoTLT新年会でお会いしましょう!きてね!


蛇足

ちなみに僕が黒髪メガネ女子が好きって話ではないのです。