9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Teachable Machine】この人誰だっけ…をなくす顔認証アプリを作ってみた!LINE通知も🔔

9
Last updated at Posted at 2025-07-09

みなさんこんにちは。4回目の投稿です!
小売業の本社で働いている、デジタル勉強中の会社員です!💻

前回はChatGPTを活用しながらExcelのマクロ(VBA)に初挑戦し、自動化の仕組みを作りました!
記事はこちら👇

今回は自動化とは少し離れて、職場や日常生活で使えそうな顔認証アプリを作っていきます!

🎬 作ったもの

🧠 きっかけ:顔と名前を覚えるのが苦手な私の課題

私はとにかく顔と名前を覚えるのが苦手で、

  • 社内の違う部署の人に声をかけられて「この人誰だったっけ…」と焦る
  • テレビで芸能人を見て「あ~見たことあるけど名前が出てこない!」とモヤモヤ

これがけっこうストレスで、「顔を読み取ったら名前を教えてくれる仕組みがあればいいのに」とずっと思っていました。
そんなときに思いついたのが Teachable Machineの画像分類です。
Teachable Machineは「誰でも短時間で簡単に機械学習モデルを作成できるウェブベースのツール」で、サンプル画像や音声を収集、トレーニングさせて分類分けすることができます。

今回はその仕組みを使って顔データを学習させ、「顔を映すと名前が表示されるアプリ」を作ることにしました。

🎯今回の目標

  • 顔画像を元に顔認証すると名前が表示される
  • スマホや PC のブラウザ上で簡単に実行できる形にする
  • 名前とあわせて「部署」「担当業務」などの情報も表示
  • LINE 通知やスプレッドシート記録でログを残すことも可能に

Teachable Machineで完結しても目的は果たせますが、せっかくなら今まで勉強したツールを使いたいと思い、Googleスプレッドシート転記 → LINE連携 に挑戦してみようと思います✨

🛠️ 使用したツール

  • Teachable Machine(顔認証モデルの作成)
  • CodePen (簡易 Web アプリ化)
  • Make(旧 Integromat)(Webhook 受信・処理フロー)
  • Google スプレッドシート(記録用)
  • LINE(通知用)

🧠 1. Teachable Machine で顔認証モデルを作成する

  1. Teachable Machine にアクセス

  2. 「画像プロジェクト」→「標準の画像分類」 を選択
    image.png
    image.png

  3. 各クラス(人)ごとに「WEBカメラ」や「画像アップロード」で顔データを登録
    image.png

今回はChatGPTに架空の3人の社員を作ってもらいました!
みんな仕事できそうでとってもいい感じ…☺

image.png

4.先ほど作った社員の画像データを順番にアップロードしていきます。
image.png

5.「モデルをトレーニングする」で学習(数十秒で完了)
image.png

6.このようにカメラに映し出すと名前の%が表示されます(今回はたまたますべて100%になってますが、映し方で変動します)
image.png

7.トレーニングが完了したらプレビュー横の「モデルをエクスポートする」 →「クラウドモデルを更新する」→ 「共有可能なリンク」が出てくるのでコピーしておく

image.png
💡ここで得られたURL(例:https://teachablemachine.withgoogle.com/models/xxxxx/
が、後ほどCodePenで使うモデルのリンクになります。

🧪 2. CodePen で顔認証アプリを Web 上に構築

Teachable Machineで作成したモデルURLを活用して、ブラウザで動作する顔認証アプリを作っていきます。
今回は、コードをブラウザ上で手軽に試せる「CodePen」を使いました。

🤖Chat GPTへの指示内容まとめ

  • Teachable Machine で作成した顔認識モデルを、ブラウザで動かすWebアプリとして使いたい
  • 「名前・部署・担当」を表示するUIを作ってほしい
  • 内カメラ / 外カメラの切り替え機能を実装したい
  • カメラのON/OFFをボタンで制御できるようにしたい
  • うまくいったらMakeと連携できるようにしたい

🪜CodePen でプロトタイプ作成

1. CodePenにアクセス

  • CodePen にアクセスしてログイン(無料)

  • 右上の「Create → New Pen」をクリック

2. 外部ライブラリの追加

顔認証アプリは、機械学習(AI)モデルを動かすための外部ライブラリが必要です。
そのため、CodePenで以下のライブラリ(CDN)を読み込む設定を行います。

1.CodePen 編集画面の上部バーにある「⚙ Settings」ボタンをクリック

2.「JS(JavaScript)」タブを選択

3.下の Add External Scripts/Pens の欄に以下の 2 つの URL を1つずつコピペして追加👇
→「Save&Close

https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.9.0/dist/tf.min.js
https://cdn.jsdelivr.net/npm/@teachablemachine/image@0.8.4/dist/teachablemachine-image.min.js

上記を追加すると、AIモデルを読み込むための TensorFlowTeachable Machine のライブラリが使えるようになります。

これを忘れると
tmImage is not defined(tmImageが見つかりません)
というエラーが出てしまいます。
JavaScript で AI を使うときは、こうした 外部ライブラリを読み込むことが基本になります。

3. HTML の入力(UIパーツの準備)

コードを開く(折りたたみ)
<h2>カメラで顔を映してください</h2>

<!-- カメラ選択 -->
<select id="cameraSelect">
  <option value="user">内カメラ(フロント)</option>
  <option value="environment">外カメラ(背面)</option>
</select>

<!-- ボタン -->
<button id="startBtn">▶ 顔認証を開始</button>
<button id="stopBtn" disabled>■ 顔認証を停止</button>

<!-- カメラ映像 -->
<div id="webcam-container">カメラ停止中</div>

<!-- 結果表示 -->
<div id="result">
  <h3 id="name">名前:―</h3>
  <p id="dept">部署:</p>
  <p id="task">担当:</p>
</div>

4. CSSの入力(見た目の調整)

コードを開く(折りたたみ)
body {
  font-family: sans-serif;
  padding: 20px;
  text-align: center;
}
button, select {
  padding: 10px 16px;
  font-size: 16px;
  margin: 6px;
}
#webcam-container canvas {
  border: 2px solid #ccc;
  border-radius: 8px;
  margin-top: 16px;
}
#result {
  margin-top: 16px;
  padding: 10px;
  background: #f7f7f7;
  border-radius: 6px;
  min-height: 70px;
}

5. JavaScriptの入力(AI認識の本体)

Teachable Machineのモデルアップロード時にコピーしたURLhttps://teachablemachine.withgoogle.com/models/xxxxx/を、
下記コードの一番上const MODEL_URL=のあとのURLのところに書き換えます。

コードを開く(折りたたみ)
// Teachable Machine モデル URL(末尾に / を忘れず)
const MODEL_URL = "https://teachablemachine.withgoogle.com/models/xxxxxxxx/";

let model, webcam, rafId = null, isRunning = false;

// ボタン・セレクト DOM
const startBtn = document.getElementById("startBtn");
const stopBtn  = document.getElementById("stopBtn");
const camSel   = document.getElementById("cameraSelect");

// ボタンイベント
startBtn.addEventListener("click", startCamera);
stopBtn .addEventListener("click", stopCamera);

// 顔認証開始処理
async function startCamera(){
  if (isRunning) return;
  isRunning = true;
  toggleButtons();

  if (!model){
    model = await tmImage.load(
      MODEL_URL + "model.json",
      MODEL_URL + "metadata.json"
    );
  }

  // カメラセットアップ
  const facing = camSel.value;           // "user" or "environment"
  const flip   = facing === "user";
  webcam = new tmImage.Webcam(224, 224, flip);
  await webcam.setup({ facingMode: { exact: facing } });
  await webcam.play();

  document.getElementById("webcam-container").innerHTML = "";
  document.getElementById("webcam-container").appendChild(webcam.canvas);

  loop(); // 推論ループ開始
}

// カメラ停止処理
function stopCamera(){
  if (!isRunning) return;
  isRunning = false;

  cancelAnimationFrame(rafId);
  if (webcam?.stream){
    webcam.stream.getTracks().forEach(t => t.stop());
  }

  document.getElementById("webcam-container").innerHTML = "カメラ停止中";
  resetResult();
  toggleButtons();
}

// 推論ループ
async function loop(){
  if (!isRunning) return;
  webcam.update();
  await predict();
  rafId = requestAnimationFrame(loop);
}

// 推論処理
async function predict(){
  const p = await model.predict(webcam.canvas);
  p.sort((a,b) => b.probability - a.probability);
  const best = p[0];
  const name = best.className;

  document.getElementById("name").textContent = `名前:${name}`;
  document.getElementById("dept").textContent = `部署:${dept(name)}`;
  document.getElementById("task").textContent = `担当:${task(name)}`;

  // --- Make Webhook へ送信 ---
  const WEBHOOK = "https://hook.make.com/xxxxxxxxxxxxxxxxxxxx";
  fetch(WEBHOOK, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name,
      department: dept(name),
      task: task(name),
      confidence: best.probability,
      timestamp: new Date().toISOString()
    })
  });
}

// 名前に応じた情報
function dept(n){
  return {
    "佐藤 彩子":"商品部",
    "高橋 大輔":"販促部",
    "田中 愛美":"業務支援部"
  }[n] || "";
}
function task(n){
  return {
    "佐藤 彩子":"ギフト企画",
    "高橋 大輔":"SNS広告",
    "田中 愛美":"マニュアル整備"
  }[n] || "";
}

// ボタン状態
function toggleButtons(){
  startBtn.disabled = isRunning;
  stopBtn.disabled  = !isRunning;
  camSel.disabled   = isRunning;
}
// 結果リセット
function resetResult(){
  document.getElementById("name").textContent = "名前:―";
  document.getElementById("dept").textContent = "部署:";
  document.getElementById("task").textContent = "担当:";
}

✅ 3. Google スプレッドシートに記録する(Make を活用)

顔認証した情報を画面に表示するだけでなく、記録として残すこともできます。
ここでは、以下のような情報をGoogleスプレッドシートに自動記録する仕組みを作ります

  • 名前(顔認証された人の名前)
  • 部署
  • 担当
  • 確信度(AIがどれくらい自信を持って判定したか)
  • タイムスタンプ(いつ認識されたか)

🔧 使用するツール:Make(旧Integromat)

Makeは、いろいろなサービスを自動連携してくれるツールです。
今回は「Webhook」機能を使って、CodePenからMakeに情報を送り、スプレッドシートに記録します。

🧩 手順①:Googleスプレッドシートの準備

  1. Googleスプレッドシートを作成

  2. A列〜E列に、次のような見出しを書きます:

A列 B列 C列 D列 E列
名前 部署 担当 確信度 タイムスタンプ

🧩 手順②:Make で Webhook を作成

  1. Makeにログイン
  2. 新しいシナリオを作成
  3. 最初のモジュールとして「Webhook」→「Custom webhook」→「Add」を選択
  4. 任意の名前を付けて作成 → 「Webhook URL」が表示されるのでコピーしておく(これをJavaScriptに貼ります)

🧩 手順③:Google Sheets へ転記

  1. 「+」ボタンで新しいモジュールを追加
  2. 「Google Sheets」を選び「Add a Row」
  3. Googleアカウントに接続し、先ほど作成したスプレッドシートを指定
  4. A列〜E列に対応するように、Webhookからの情報をマッピングする
Webhook 変数 シート列
name A列(名前)
department B列(部署)
task C列(担当)
confidence D列(確信度)
timestamp E列(タイムスタンプ)

🧩 手順④:CodePenのJavaScriptを修正

Makeで取得したWebhook URLを、JavaScriptのこの部分に貼り付けます👇

const WEBHOOK = "https://hook.make.com/xxxxxxxxxxxxxxxxxxxx";

すでに含まれていた fetch() 関数の中身がMakeへ情報を送る役割を果たしています。
💡これで、顔を認識すると自動でGoogleスプレッドシートに記録されるようになります!

image.png
※このスプレッドシートの「確信度」と「タイムスタンプ」の表記は今かなり見づらくなっているので改善したい…。

✅ 4. LINE に顔認証結果を通知(Make 連携)

image.png

Googleスプレッドシートに記録するだけでなく、顔を認識したタイミングでLINEに自動通知が来るようにすれば、
たとえば「誰が来たかをリアルタイムで把握したい」ときに便利です!

🔧 使用ツール:Make × LINE公式アカウント(Messaging API)

Makeでは、LINEのMessaging API を使って「特定のLINEユーザーへ自動メッセージを送信」できます。

🧩 手順①:LINE Bot を用意

  1. LINE Developers にアクセス
  2. プロバイダーを作成(初回のみ)
  3. 「Messaging API」を選び、新しいチャネル(Bot)を作成します

🧩 手順②:チャネル設定を確認

LINE公式アカウントの管理画面から、以下の情報を控えておきます:

  • チャネルアクセストークン(ロングターム)
  • LINEユーザーのID(通知を受け取る相手のID)

自分自身のユーザーIDを取得するには、一度Botと友達になり、プロフィール取得APIなどを活用すると取得できます。

🧩 手順③:MakeでLINE通知を送るモジュールを追加

  1. 先ほどのシナリオに、スプレッドシート転記のあとに「+」でモジュールを追加

  2. 「HTTP」モジュール → 「Make a request」を選択

🧩 手順④:Make に HTTP モジュールを追加

  1. 先ほどのシナリオに、スプレッドシート転記のあとに「+」でモジュールを追加

  2. 「HTTP」モジュール → 「Make a request」を選択

  3. 以下のように設定

項目 設定内容
Method POST
URL https://api.line.me/v2/bot/message/push
Headers Content-Type: application/json
Authorization: Bearer {チャネルアクセストークン}
Body Type Raw
Content Type application/json
Body 下記 JSON を入力
{
  "to": "ユーザーID",
  "messages": [
    {
      "type": "text",
      "text": "👤 顔認証されました!\n名前:{{name}}\n部署:{{department}}\n担当:{{task}}"
    }
  ]
}

💡 これで、顔を認識するたびにLINEへ通知が届くようになります🎉

🧠 最終形:LINE通知つき顔認証アプリの完成コード

最終的に下記の条件をJavaScriptに加えて完成に至りました!

  • 推論確信度が 90%以上 (THRESHOLD = 0.9)
  • クラス名が「該当なし」(背景クラス)ではない
  • 直前に送信した人物と同じでない(重複通知防止)

これら 3 つをすべて満たした場合のみ、Make の Webhook に JSON を送信します。
条件をつけないと顔が認識される度にLINE通知が飛び大変なことになります。

JavaScript最終形👇

コードはこちら(折りたたみ)
/* ---------------- 必ず書き換え ---------------- */
const MODEL_URL = "https://teachablemachine.withgoogle.com/models/xxxxx/"; // 末尾「/」必須
const WEBHOOK   = "https://hook.make.com/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";    // Make の URL
/* -------------------------------------------- */

/* ======= 状態変数 ======= */
let model, webcam, rafId = null, isRunning = false;
let lastSent = "";                         // 直前に送信した人物名

/* ======= DOM ======= */
const startBtn = document.getElementById("startBtn");
const stopBtn  = document.getElementById("stopBtn");
const camSel   = document.getElementById("cameraSelect");

/* ======= イベント登録 ======= */
startBtn.addEventListener("click", startCamera);
stopBtn .addEventListener("click", stopCamera);

/* ======= 開始処理 ======= */
async function startCamera () {
  if (isRunning) return;
  isRunning = true;
  toggleButtons();

  try {
    /* モデル読み込み(初回のみ) */
    if (!model) {
      model = await tmImage.load(
        MODEL_URL + "model.json",
        MODEL_URL + "metadata.json"
      );
    }

    /* カメラセットアップ(内/外カメラ切替対応) */
    const facing = camSel.value;       // "user" or "environment"
    const flip   = facing === "user";  // front は鏡像
    webcam = new tmImage.Webcam(224, 224, flip);
    await webcam.setup({ facingMode: { exact: facing } });
    await webcam.play();

    /* 映像を画面へ表示 */
    const box = document.getElementById("webcam-container");
    box.innerHTML = "";
    box.appendChild(webcam.canvas);

    /* 推論ループ開始 */
    loop();
  } catch (err) {
    alert("モデルまたはカメラ初期化失敗:\n" + err.message);
    console.error(err);
    stopCamera();
  }
}

/* ======= 停止処理 ======= */
function stopCamera () {
  if (!isRunning) return;
  isRunning = false;
  toggleButtons();

  cancelAnimationFrame(rafId);
  webcam?.stream?.getTracks().forEach(t => t.stop());
  document.getElementById("webcam-container").innerHTML = "カメラ停止中";
  resetResult();
}

/* ======= ループ ======= */
async function loop () {
  if (!isRunning) return;
  webcam.update();
  await predict();
  rafId = requestAnimationFrame(loop);
}

/* ======= 推論 ======= */
const THRESHOLD = 0.9;        // ★ 90% 以上のみ送信
const BG_CLASS  = "該当なし";  // ★ 背景クラス名

async function predict () {
  const p = await model.predict(webcam.canvas);
  p.sort((a, b) => b.probability - a.probability);
  const best = p[0];                  // { className, probability }
  const name = best.className;

  /* 画面表示 */
  document.getElementById("name").textContent = `名前:${name}`;
  document.getElementById("dept").textContent = `部署:${dept(name)}`;
  document.getElementById("task").textContent = `担当:${task(name)}`;

  /* ----- 送信条件チェック ----- */
  const overThreshold = best.probability >= THRESHOLD; // ① 90% 以上
  const notBg         = name !== BG_CLASS;             // ② 背景クラス以外
  const notDuplicate  = name !== lastSent;             // ③ 直前と同じ人物でない

  if (overThreshold && notBg && notDuplicate) {
    /* Webhook へ送信(Make 側で Sheets & LINE 通知) */
    fetch(WEBHOOK, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name,
        department: dept(name),
        task: task(name),
        confidence: best.probability.toFixed(3),
        timestamp: new Date().toISOString()
      })
    });
    lastSent = name;
  }
}

/* ======= 辞書 ======= */
function dept (n) {
  return {
    "佐藤 彩子": "商品部",
    "高橋 大輔": "販促部",
    "田中 愛美": "業務支援部"
  }[n] || "";
}
function task (n) {
  return {
    "佐藤 彩子": "ギフト企画",
    "高橋 大輔": "SNS広告",
    "田中 愛美": "マニュアル整備"
  }[n] || "";
}

/* ======= UI ======= */
function toggleButtons () {
  startBtn.disabled =  isRunning;
  stopBtn.disabled  = !isRunning;
  camSel.disabled   =  isRunning;
}
function resetResult () {
  document.getElementById("name").textContent = "名前:―";
  document.getElementById("dept").textContent = "部署:";
  document.getElementById("task").textContent = "担当:";
}

💭実際に使ってもらった感想

このアプリを試しに家族に使ってもらいました!
⭕人と会うことが多い場面ですごく使えそう
⭕営業さんが使うのによさそう
❌顔写真を事前に登録しておくのが難しそう
❌実際に相手にカメラを向けれないかも…

たしかに名前がわからないからといってその場でカメラは向けれないですよね。想定はしていましたが、今回の目的では職場での利用は難しそうです・・・
「芸能人がテレビに映ってるけど名前がわからない」というときに使えるよと職場以外の活用方法を提案したところ、
⭕それは使えそう!名前出てこないときけっこうあるよね。高齢者が使うのにもいいかも!
という前向きな意見をもらうことができました。

💼 職場での活用例

🏢 来客管理の効率化(受付無人化)

今回はテキスト(名前・部署・担当)のみですが、
顔を認識したその瞬間の画像をLINEに添付して通知することで、例えば来訪者の顔を認識 → LINEで担当社員へ通知(+画像つき)という運用で、受付業務の負担を軽減できます。

🧑‍🏫 新入社員のためのサポート制度

新人が「誰が誰だか覚えられない…」という悩みを、リアルタイム顔認識でフォロー。
個人情報が漏れない範囲での活用が可能です。

💡 おわりに

今回作った顔認証アプリは、手軽に作成から実用までできて誰でも使いやすいと感じました。
GoogleスプレッドシートやLINEとの連携は今回の目的では必要なかったと思う反面、勤怠管理や受付業務などで使うには必要になってくるので、さまざまな場面で活用できそうです。
また、日常生活での活用例として、例えば芸能人の画像を登録しておき、テレビで出てきたときに今回作成したアプリで読み取るなど、私のような顔と名前を覚えるのが苦手な人にとってはあると本当に便利だと思いました!

今回は顔認識からLINE通知までの一連の流れを試しましたが、
Make や Googleスプレッドシートなど、今まで学んだツールと組み合わせることで、
「画像認識だけで終わらないプロトタイプ」
を作れるようになってきたと実感しています。

今後も仕事やプライベートで少しでも楽できるよう、あらゆるデジタルツールを使いながら課題を解決していきたいと思います☺


📃参考記事

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?