パンデミック期における公共衛生管理の一環として、マスク着用状況の自動検出は重要な課題です。本稿では、YOLOアーキテクチャを活用したリアルタイムマスク検出システムの設計・実装プロセスを体系的に解説します。特に、YOLOv8を基盤とし、PyTorchによるカスタム学習、OpenCVを用いた推論パイプライン、およびFlaskによる軽量Webフロントエンドの統合に焦点を当てます。
前提環境
以下のソフトウェア構成を準備してください:
- Python 3.9 以上
- torch==2.1.0, torchvision==0.16.0
- opencv-python==4.8.1
- flask==2.3.3
- ultralytics==8.1.0(YOLOv8公式ライブラリ)
依存パッケージのインストールコマンド:
pip install torch torchvision opencv-python flask ultralytics
データセット構造とアノテーション形式
学習用データは以下のような階層で整理します:
mask_dataset/
├── train/
│ ├── images/ # JPEG/PNG画像
│ └── labels/ # YOLOフォーマットのTXTファイル(例: img001.txt)
├── val/
│ ├── images/
│ └── labels/
└── test/
├── images/
└── labels/
各ラベルファイルは1行1オブジェクトで、クラスID(0: マスク着用、1: 無着用)、正規化された中心座標(x_center, y_center)および相対サイズ(width, height)を含みます:
0 0.423 0.517 0.285 0.392
モデル学習の実行
YOLOv8のカスタム学習には、dataset.yaml設定ファイルを作成します:
train: ./mask_dataset/train/images
val: ./mask_dataset/val/images
test: ./mask_dataset/test/images
nc: 2
names: ["masked", "unmasked"]
学習コマンド(GPU利用時):
yolo detect train \
data=dataset.yaml \
model=yolov8s.pt \
epochs=100 \
imgsz=640 \
batch=32 \
name=mask_detection_v1 \
device=0
学習完了後、重みファイルはruns/detect/mask_detection_v1/weights/best.ptに保存されます。
Webアプリケーションの実装
Flaskを用いた推論サーバーを構築します。推論処理では、OpenCVで画像を前処理し、YOLOv8モデルでバウンディングボックスと信頼度を取得し、結果をHTMLに埋め込みます。
app.py(再設計版):
from flask import Flask, request, render_template, jsonify
import cv2
import numpy as np
from pathlib import Path
from ultralytics import YOLO
app = Flask(__name__)
MODEL_PATH = "runs/detect/mask_detection_v1/weights/best.pt"
detector = YOLO(MODEL_PATH)
@app.route("/", methods=["GET"])
def index():
return render_template("upload.html")
@app.route("/predict", methods=["POST"])
def predict():
if "image" not in request.files:
return jsonify({"error": "No image uploaded"}), 400
file = request.files["image"]
img_bytes = np.frombuffer(file.read(), np.uint8)
frame = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR)
# 推論実行(confidence threshold: 0.5)
results = detector(frame, conf=0.5, verbose=False)
annotated_frame = results[0].plot() # BBoxとラベル描画
# OpenCV BGR → RGB変換 → base64エンコード
rgb_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB)
_, buffer = cv2.imencode(".png", rgb_frame)
img_b64 = "data:image/png;base64," + np.array(buffer).tobytes().hex()
# 検出数を集計
class_counts = {"masked": 0, "unmasked": 0}
for box in results[0].boxes:
cls_id = int(box.cls.item())
label = "masked" if cls_id == 0 else "unmasked"
class_counts[label] += 1
return render_template(
"result.html",
image_data=img_b64,
stats=class_counts
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
templates/upload.html:
<!DOCTYPE html>
<html>
<head><title>マスク検出ツール</title></head>
<body style="font-family: sans-serif; padding: 2rem;">
<h1>🪪 マスク着用状況分析</h1>
<p>画像をアップロードして、マスクの有無をAIが判定します。</p>
<form method="post" action="/predict" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*" required><br><br>
<button type="submit">分析開始</button>
</form>
</body>
</html>
templates/result.html:
<!DOCTYPE html>
<html>
<head><title>分析結果</title></head>
<body style="font-family: sans-serif; padding: 2rem;">
<h1>✅ 分析結果</h1>
<div style="margin: 1rem 0">
<strong>着用者:</strong> {{ stats.masked }}人 |
<strong>未着用者:</strong> {{ stats.unmasked }}人
</div>
<img src="{{ image_data }}" alt="検出結果" style="max-width: 100%; border: 1px solid #ccc;"><br><br>
<a href="/">← 別の画像を分析する</a>
</body>
</html>
実行と検証
アプリケーションを起動し、ローカルサーバーでアクセスします:
python app.py
ブラウザで http://localhost:5000 を開き、テスト画像をアップロードすると、検出結果と統計情報が即時に表示されます。