顔認識AIで年齢・性別・感情を同時に分析する midori222
顔を検出するだけでは物足りない。年齢、性別、そして感情まで、リアルタイムで分析したい。midori222は、face-api.jsとMediaPipe Face Detectionを組み合わせ、カメラ映像から顔の情報を多角的に読み取るシステムです。感情に応じて色が変わる顔枠、リアルタイム統計、カメラ切り替えまで、実用的な機能を詰め込みました。
⚠️
WARNING
カメラ使用について: この記事内のデモでは、お使いのデバイスのWEBカメラを使用します。デモを試す際は、ブラウザからカメラへのアクセス許可が必要です。カメラの映像はすべてブラウザ内で処理され、外部のサーバーには一切送信されません。すべての処理はお使いのデバイス上で完結するため、プライバシーは完全に保護されています。
対象読者
- ブラウザ上で顔認識を実装しているフロントエンドエンジニア
- 年齢・性別・感情分析を統合したい開発者
- face-api.js(顔検出ライブラリ)とMediaPipe Face Detectionの併用を検討している人
- リアルタイム処理のパフォーマンス最適化に興味がある技術者
記事に書いてあること
- face-api.jsによる年齢・性別・感情分析の統合実装
- 感情に応じた色で顔枠を表示する視覚的フィードバック
- 検出結果の点滅を抑制する300ms保持メカニズム
- カメラ切り替え、解像度調整、FPS調整の実装詳細
- TensorFlow.js警告の抑制とパフォーマンス最適化
作ったもの
midori222は、Webカメラの映像から顔を検出し、年齢・性別・感情を同時に分析するブラウザアプリです。face-api.jsのageGenderNetとfaceExpressionNetを使用し、7種類の感情(happy, sad, angry, surprised, fearful, disgusted, neutral)をリアルタイムで検出します。
感情に応じて顔枠の色が変わる。嬉しい顔は黄色、悲しい顔は青、怒った顔は赤。感情が視覚的に伝わる設計にしました。
カメラは複数台に対応し、解像度は640×480、1280×720、1920×1080の3段階から選択できます。FPSも3〜45の範囲で調整可能。用途に応じてパフォーマンスと精度のバランスを取れます。
リアルタイム統計パネルでは、FPS、処理時間、平均信頼度を1秒ごとに更新。検出人数も左上に表示され、顔が検出されると緑色に光ります。
なぜ作ったか
midori221では、MediaPipe Face Detectionで顔を検出するだけでした。目・鼻・口・耳の位置を表示する、シンプルな顔認識システムです。
でも、もっと情報が欲しかった。年齢は何歳か、性別は、そして今どんな感情なのか。写真展示で使うなら、来場者の反応を読み取れると面白い。感情に応じて展示の雰囲気を変えたり、年齢層に合わせたコンテンツを表示したりできる。
face-api.jsなら、年齢・性別・感情を一括で処理できる。統合ライブラリなので、実装も簡単。精度も実用範囲内(年齢±5〜10歳、性別85〜95%)で、リアルタイム処理に最適化されている。
収入がない身としては、サーバー不要でブラウザ単体で動くのが魅力的。API利用料もかからない。無料で使える範囲で、実用的なシステムを作りたかった。
年齢・性別・感情分析の統合実装
face-api.jsのageGenderNetとfaceExpressionNetを使用した統合分析機能です。1回の検出処理で、顔の位置、年齢、性別、感情を同時に取得できます。
// 年齢・性別・感情分析を含む顔検出
const faceApiDetections = await faceapi
.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
.withFaceLandmarks() // 68点の顔ランドマーク
.withAgeAndGender() // 年齢と性別の推定
.withFaceExpressions(); // 感情分析
faceApiDetections.forEach(detection => {
// 年齢・性別情報
const age = Math.round(detection.age); // 推定年齢(整数)
const gender = detection.gender; // 'male' または 'female'
const genderProbability = detection.genderProbability; // 確率
// 感情分析(最も確率の高い感情を取得)
const expressions = detection.expressions;
const maxExpression = Object.keys(expressions).reduce((a, b) =>
expressions[a] > expressions[b] ? a : b
);
const expressionValue = (expressions[maxExpression] * 100).toFixed(1);
console.log(`年齢: ${age}歳, 性別: ${gender}, 感情: ${maxExpression} (${expressionValue}%)`);
});
ℹ️
INFO
推定精度の目安: 年齢は±5〜10歳程度の誤差、性別は85〜95%の精度。照明条件や角度により変動します。
推定の精度:
- 年齢: ±5〜10歳程度の誤差(照明条件や角度により変動)
- 性別: 約85〜95%の精度(正面を向いている場合に高精度)
- 感情: 7種類の感情(happy, sad, angry, surprised, fearful, disgusted, neutral)を検出
照明が暗いと精度が下がる。正面を向いていると精度が上がる。カメラから1〜2m程度の距離が最適です。
感情に応じた色で顔枠を表示
感情に応じて顔枠の色を変える機能を実装しました。感情が視覚的に伝わる設計です。
// 感情に応じた色を設定
let emotionColor = '#00ff00'; // デフォルト(緑)
let emotionEmoji = '😐'; // デフォルト
if (maxExpression === 'happy') {
emotionColor = '#ffff00'; // 黄色
emotionEmoji = '😊';
} else if (maxExpression === 'sad') {
emotionColor = '#0000ff'; // 青
emotionEmoji = '😢';
} else if (maxExpression === 'angry') {
emotionColor = '#ff0000'; // 赤
emotionEmoji = '😠';
} else if (maxExpression === 'surprised') {
emotionColor = '#ff00ff'; // マゼンタ
emotionEmoji = '😲';
} else if (maxExpression === 'fearful') {
emotionColor = '#800080'; // 紫
emotionEmoji = '😨';
} else if (maxExpression === 'disgusted') {
emotionColor = '#008000'; // 緑
emotionEmoji = '🤢';
}
// 顔枠を描画(感情に応じた色)
ctx.strokeStyle = emotionColor;
ctx.lineWidth = 3;
ctx.shadowColor = emotionColor;
ctx.shadowBlur = 10;
ctx.strokeRect(box.x, box.y, box.width, box.height);
感情が一目で分かる。嬉しい顔は黄色、悲しい顔は青、怒った顔は赤。色で感情が伝わる設計にしました。
顔枠にはグロー効果(発光効果)を追加。shadowBlur: 10で、感情の色が光るように見えます。視覚的なインパクトが増します。
検出結果の点滅を抑制する300ms保持メカニズム
検出処理が重いと、フレームごとに検出結果が変わり、表示が点滅します。これを抑制するため、検出結果を300ms保持するメカニズムを実装しました。
// 点滅を抑えるための検出結果保持
let lastDisplayedDetections = [];
let lastDetectionTime = 0;
const detectionTimeout = 300; // 検出結果を300ms保持
// 検出結果の更新判定
let detectionsToDisplay = faceApiDetections;
// 検出があった場合、またはタイムアウトした場合は更新
if (faceApiDetections.length > 0) {
lastDisplayedDetections = faceApiDetections;
lastDetectionTime = now;
} else if (now - lastDetectionTime < detectionTimeout && lastDisplayedDetections.length > 0) {
// 検出がないが、タイムアウト前の場合は前回の結果を表示
detectionsToDisplay = lastDisplayedDetections;
} else {
// タイムアウトした場合はクリア
lastDisplayedDetections = [];
}
最初は検出結果をそのまま表示していました。処理が重いと、フレームごとに検出結果が変わり、顔枠が点滅します。見づらい。
300ms保持するようにしたら、点滅が減りました。検出がない場合でも、300ms以内なら前回の結果を表示。タイムアウトしたらクリアします。
重要な発見
この実装で、表示が安定しました。処理が重い環境でも、視認性が向上します。300msという値は、試行錯誤の結果、点滅を抑えつつ最新の結果を表示できる最適なバランスでした。
カメラ設定のカスタマイズ
解像度とFPSを動的に調整できる高度なカメラ制御を実装しました。
// カメラ設定(解像度とFPS指定)
async function setupCamera(constraints, deviceId = null) {
const videoConstraints = {
width: { ideal: 1280 }, // 解像度(幅)
height: { ideal: 720 }, // 解像度(高さ)
frameRate: { ideal: 30 } // FPS
};
if (deviceId) {
videoConstraints.deviceId = { exact: deviceId };
}
const stream = await navigator.mediaDevices.getUserMedia({
video: videoConstraints
});
video.srcObject = stream;
}
解像度オプション:
640×480: 標準画質、最高速(低負荷PC向け)
1280×720 (HD): 高画質、バランス良好(推奨)
1920×1080 (Full HD): 最高画質、高負荷(高性能PC向け)
FPS調整:
- 3〜15 FPS: 低負荷モード(省電力、バッテリー節約)
- 30 FPS: 標準(推奨、滑らかな動作)
- 45 FPS: 高速モード(要高性能PC、リアルタイム処理に最適)
用途に応じて調整できます。写真展示で使うなら、30 FPSで十分。リアルタイム分析なら、45 FPSまで上げられます。
FPSベースのフレームスキップ
設定したFPS値に基づいて処理をスキップし、パフォーマンスを最適化しました。
let lastFrameTime = 0;
let frameInterval = 1000 / 30; // 30FPS = 33.33ms間隔
async function detectFaces(timestamp) {
// FPSに基づいてフレームをスキップ
if (timestamp - lastFrameTime < frameInterval) {
requestAnimationFrame(detectFaces);
return; // このフレームはスキップ
}
lastFrameTime = timestamp;
// 実際の検出処理
const faceApiDetections = await faceapi
.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
.withFaceLandmarks()
.withAgeAndGender()
.withFaceExpressions();
// 描画処理...
requestAnimationFrame(detectFaces);
}
最初は全フレームを処理していました。60 FPSで動くと、1秒間に60回検出処理が走ります。重い。
FPSベースのスキップを実装したら、負荷が減りました。30 FPSに設定すると、1秒間に30回だけ処理。CPUリソースを効率的に使えます。
バッテリー消費も抑制できます。スマホで使う場合、15 FPSに下げると、バッテリー持ちが良くなります。
TensorFlow.js警告の抑制
複数のライブラリが同じTensorFlow.jsを使用する際の重複警告を抑制しました。
// デバッグログを抑制
tf.env().set('DEBUG', false);
// 警告フィルター
const originalWarn = console.warn;
console.warn = function(...args) {
const message = args.join(' ');
if (message.includes('Platform') && message.includes('already been set')) {
return; // プラットフォーム重複警告を抑制
}
if (message.includes('kernel') && message.includes('already registered')) {
return; // カーネル重複警告を抑制
}
if (message.includes('backend') && message.includes('already registered')) {
return; // バックエンド重複警告を抑制
}
originalWarn.apply(console, args);
};
face-api.jsとMediaPipe Face Detectionを同時に使うと、TensorFlow.jsの重複警告が大量に出ます。コンソールが埋まって、デバッグが困難になります。
警告フィルターの効果
警告フィルターを実装したら、コンソールがすっきりしました。重要なエラーだけが表示され、デバッグが楽になります。プラットフォーム重複、カーネル重複、バックエンド重複の3種類の警告を抑制することで、開発効率が大幅に向上しました。
リアルタイム統計情報の表示
FPS、処理時間、平均信頼度を1秒ごとに更新する統計パネルを実装しました。
let frameCount = 0;
let lastStatsUpdate = Date.now();
let totalProcessingTime = 0;
function updateStatistics(detections, processingTime) {
frameCount++;
totalProcessingTime += processingTime;
const now = Date.now();
const elapsed = (now - lastStatsUpdate) / 1000;
// 1秒ごとに統計を更新
if (elapsed >= 1.0) {
const fps = frameCount / elapsed;
const avgProcessingTime = totalProcessingTime / frameCount;
// FPS表示
document.getElementById('fpsDisplay').textContent = fps.toFixed(1);
// 処理時間表示
document.getElementById('processingTimeDisplay').textContent =
avgProcessingTime.toFixed(1) + 'ms';
// 平均信頼度計算
if (detections.length > 0) {
const avgConfidence = detections.reduce((sum, d) => {
return sum + (d.detection.score || 0);
}, 0) / detections.length;
document.getElementById('avgConfidenceDisplay').textContent =
(avgConfidence * 100).toFixed(1) + '%';
}
// リセット
frameCount = 0;
totalProcessingTime = 0;
lastStatsUpdate = now;
}
}
統計情報を見れば、パフォーマンスが分かります。FPSが低いなら、解像度を下げる。処理時間が長いなら、FPSを下げる。数値で判断できます。
平均信頼度も表示します。信頼度が低いなら、照明を明るくする、カメラを正面に向ける。改善のヒントになります。
実際に試した例
例1: 複数人での検出テスト
3人でカメラの前に立って検出テストをしました。3人とも検出され、年齢・性別・感情が表示されました。
嬉しい顔をすると、顔枠が黄色に変わる。悲しい顔をすると、青に変わる。感情が視覚的に伝わります。
年齢は±5歳程度の誤差がありました。正面を向いていると精度が上がります。
例2: 解像度とFPSの調整
640×480 @ 30 FPSで試したところ、処理時間は約50ms。滑らかに動作しました。
1280×720 @ 30 FPSに上げると、処理時間は約80ms。少し重くなりましたが、精度が上がりました。
1920×1080 @ 30 FPSに上げると、処理時間は約120ms。重くなりましたが、高精度です。
用途に応じて調整できます。リアルタイム分析なら、640×480 @ 30 FPSで十分。高精度が必要なら、1280×720 @ 30 FPSがバランス良好です。
例3: 感情分析の精度検証
7種類の感情を検出できるか検証しました。嬉しい顔、悲しい顔、怒った顔、驚いた顔、怖い顔、嫌悪の顔、無表情。
感情が明確な場合は、90%以上の確率で検出されました。微妙な感情は、確率が低くなります。
照明が暗いと精度が下がります。明るい環境で使うと、精度が上がります。
システムの初期化は7つのステップで構成されています。各ステップの詳細を確認できます。
使ってみて
カメラ設定のデモを開く
感情分析のデモを開く
どちらもモバイル対応なので、スマホでもそのまま触れます。解像度とFPSの調整、感情に応じた色の変化を実際に体験できます。
まとめ
- face-api.jsの
ageGenderNetとfaceExpressionNetで年齢・性別・感情を統合分析(1回の処理で全情報を取得)
- 感情に応じて顔枠の色を変更(嬉しい=黄色、悲しい=青、怒った=赤など、7種類の感情に対応)
- 検出結果を300ms保持することで点滅を抑制(処理が重い環境でも視認性が向上)
- カメラ切り替え、解像度調整(3段階)、FPS調整(3〜45 FPS)で用途に応じた最適化が可能
- リアルタイム統計パネルでFPS、処理時間、平均信頼度を1秒ごとに更新(パフォーマンスの可視化)
さらに深く学ぶには
最後まで読んでくださり、ありがとうございました。少しでも顔認識アプリの実装のヒントになれば嬉しいです。