##作ったもの
SNS顔出しNGの方のために、
顔に目隠しを付けてくれるアプリを作りました。
ウェブアプリなので、PC、スマホの機種は問いません。
こちらのリンクからどうぞ。
https://hardcore-ritchie-cca31e.netlify.app/
###使用例
写真をアップロードすると、いい感じに目隠しを付けてくれます。
複数人でも大丈夫。
(普通の飲み会が、なにやら悪巧みしてそうな会合に変貌)
※コロナ禍より前の飲み会です。
秘密のデートも目隠しを入れれば、誰にもバレません。
これで、プライバシーもバッチリ!
(写真はフリー写真サイト「ぱくたそ」 さんより)
これで、みなさんも安心してSNSに投稿できますね。
###番外編
なんと、私の3Dモデル(リアルアバター)にも反応しました。
つまり、私のアバターは人間と見分けがつかない?
ちなみに、このアバターは浅草にあるリアルアバターさんで作ってもらいました。
猫はダメでした。
「人間じゃないよ」ってアラートを出します。
##使用技術
###言語:
JavaScript
###CSSフレームワーク
Bootstrap
レスポンシブデザインを気持ち程度に使用
###ライブラリ
-
face-api.js
顔認証、目の位置をこれで取得 -
humane.js
アラートを表示
face-api.js について
TensorFlow.jsで学習済みの顔認識の機械学習モデルが使えるAPIです。
顔の検出、表情分析、年齢・性別分析などができます。
サービスアカウント不要、アクセストークン・シークレットキー不要、無料で手軽に利用できます。
###導入手順
face-api.jsのREADMEやネット記事を読んで私が実行した手順は以下
- 以下のリンクからZIPファイルをダウンロード (Cloneでも可)
https://github.com/justadudewhohacks/face-api.js/ - distフォルダにある face-api.js を自分のアプリと同じディレクトリに移動する
- weightsフォルダの名前を modelsに変更し、自分のアプリと同じディレクトリに移動する
index.html
face-api.js
models
他のフォルダ、ファイルは不要です。
###本件で使用したモデル
tinyFaceDetector というモデルで顔を検出し、
faceLandmark68Net というモデルで顔のランドマークを検出します。
この写真のように68個の顔のポイント1つ1つの画像上での座標が取得できます。
このような感じで座標を取得できます。(これが全部で68個あります)
ポイント36からポイント45へ線を引けばいいのですが、それだと線が短すぎるので、
線の始点: X軸はポイント0、Y軸はポイント36
線の終点: X軸はポイント45、Y軸はポイント16
を採用してバランスをとりました。
本当は、ポイント36からポイント45の線の傾きを算出して、線を伸ばすようなコードを書けばよいのですが、あまり影響がないので、そのままにしました。
##参考サイト
以下のサイトに大変お世話になりました。
ありがとうございました。
-
face-api.js - ブラウザでの顔認識を行うJavaScript API
顔のランドマークを抽出するコードは、ほとんどこちらのサイト記載のものを使用しました。 -
File APIとCanvasでローカルの画像をアップロード→加工→ダウンロードする
ファイルをアップロードしてCanvas上に表示するコードは、ほとんどこちらのサイト記載のものを使用しました。
##コード
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Bootstrap -->
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous" />
<!-- JS, Popper.js, and jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous" ></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous" ></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous" ></script>
<style>
.container {
padding-top: 50px;
}
</style>
<!-- face-api.jsの読み込み -->
<script src="face-api.js"></script>
<!-- humaneライブラリの読み込み -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/humane-js/3.2.2/themes/boldlight.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/humane-js/3.2.2/humane.min.js"></script>
</head>
<body>
<div class="container">
写真をアップロードしてください
<div class="upload"><input type="file" name="file" id="file"></div>
<div id="result"></div>
<canvas id="canvas"></canvas>
</div>
</body>
<script>
const MODEL_URL = 'models/'
let file = document.getElementById('file')
let canvas = document.getElementById('canvas')
let canvasWidth = 350
let canvasHeight = 350
let uploadImgSrc
//ファイルをアップロードしてCanvas上に表示するのは、ほぼこのサイトどおり
//https://www.tam-tam.co.jp/tipsnote/javascript/post13538.html
// Canvasの準備
canvas.width = canvasWidth
canvas.height = canvasHeight
var ctx = canvas.getContext('2d')
function loadLocalImage(e) {
// ファイル情報を取得
let fileData = e.target.files[0]
// 画像ファイル以外は処理を止める
if(!fileData.type.match('image.*')) {
humane.log("画像ファイルでお願いします")
}
// FileReaderオブジェクトを使ってファイル読み込み
let reader = new FileReader()
// ファイル読み込みに成功したときの処理
reader.onload = function() {
// Canvas上に表示する
uploadImgSrc = reader.result;
canvasDraw(fileData)
}
// ファイル読み込みを実行
reader.readAsDataURL(fileData)
}
// ファイルが指定された時にloadLocalImage()を実行
file.addEventListener('change', loadLocalImage, false)
// Canvas上に画像を表示する
function canvasDraw() {
// canvas内の要素をクリアする
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
// Canvas上に画像を表示
let img = new Image()
img.src = uploadImgSrc
img.onload = function() {
ctx.drawImage(img, 0, 0, canvasWidth, this.height * (canvasWidth / this.width))
let canvasRate = canvasWidth / this.width
//顔の情報を取得
getFaceData(img,canvasRate)
}
}
async function getFaceData(img,canvasRate) {
await faceapi.nets.tinyFaceDetector.load('models/') //モデル読み込み
await faceapi.nets.faceLandmark68Net.load("models/") //モデル読み込み
// 顔検出の実行
const detectionsWithLandmarks = await faceapi.detectAllFaces(img,
new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks()
if (detectionsWithLandmarks.length == 0){
humane.log('人間じゃないよ')
}else{
for (let n = 0; n < detectionsWithLandmarks.length ; n++ ) {
let x0 = detectionsWithLandmarks[n].landmarks._positions[0].x * canvasRate
let y0 = detectionsWithLandmarks[n].landmarks._positions[36].y * canvasRate
let x1 = detectionsWithLandmarks[n].landmarks._positions[16].x * canvasRate
let y1 = detectionsWithLandmarks[n].landmarks._positions[45].y * canvasRate
let line_thickness = (x1-x0) * .2 //線の太さを長さの20%とした
drawLine(x0,y0,x1,y1,line_thickness) //目隠し線を引く関数
}
}
}
//目隠し線を引く
function drawLine(x0,y0,x1,y1,line_thickness){
ctx.strokeStyle = '#000000'
ctx.lineWidth = line_thickness
ctx.beginPath()
ctx.moveTo(x0, y0)
ctx.lineTo(x1,y1)
ctx.closePath()
ctx.stroke()
}
</script>
</html>