📋 このステップで学ぶこと
- 動画の基礎知識(フレーム、FPS、コーデック)
- OpenCVでの動画処理(読み込み、書き込み、フレーム抽出)
- 光学フロー(Optical Flow)の計算と可視化
- 行動認識(Action Recognition)の基礎
- Two-Stream Networks(RGB + 光学フロー)
- 3D CNN(C3D、I3D)の仕組み
- TSN(Temporal Segment Networks)
- 動画物体検出(Video Object Detection)
- 実装:動画分類システムの構築
🎥 1. 動画の基礎知識
動画処理を学ぶ前に、まず動画とは何かを理解しましょう。動画は「連続した静止画像の集まり」であり、これを高速に切り替えることで動きを表現しています。
1-1. 動画の構成要素
【動画の基本構造】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
動画 = 連続した静止画像(フレーム)+ 音声
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
例: 30秒の動画(30 FPS)
= 30秒 × 30フレーム/秒
= 900枚の静止画像
【4つの基本要素】
1. フレーム(Frame)
━━━━━━━━━━━━━━━━━━━━━━━━━━
・動画を構成する1枚1枚の静止画像
・パラパラ漫画の1ページに相当
例: 1920×1080ピクセルの画像が900枚
2. FPS(Frames Per Second)
━━━━━━━━━━━━━━━━━━━━━━━━━━
・1秒あたりに表示されるフレーム数
・数値が高いほど滑らかな動き
一般的なFPS:
┌────────┬─────────────────────────────┐
│ FPS │ 用途 │
├────────┼─────────────────────────────┤
│ 24 │ 映画(フィルム感) │
│ 30 │ テレビ、YouTube、一般動画 │
│ 60 │ スポーツ、ゲーム、高品質 │
│ 120+ │ スローモーション撮影 │
└────────┴─────────────────────────────┘
3. 解像度(Resolution)
━━━━━━━━━━━━━━━━━━━━━━━━━━
・各フレームのサイズ(幅×高さ)
一般的な解像度:
┌─────────────┬───────────────┐
│ 名称 │ サイズ │
├─────────────┼───────────────┤
│ 480p (SD) │ 640×480 │
│ 720p (HD) │ 1280×720 │
│ 1080p (FHD) │ 1920×1080 │
│ 4K (UHD) │ 3840×2160 │
└─────────────┴───────────────┘
4. コーデック(Codec)
━━━━━━━━━━━━━━━━━━━━━━━━━━
・動画の圧縮・展開方式
・COder/DECoderの略
一般的なコーデック:
・H.264 (AVC): 最も普及、互換性が高い
・H.265 (HEVC): H.264の2倍の圧縮率
・VP9: YouTube で使用
・AV1: 次世代、オープンソース
1-2. 動画のサイズ計算
【非圧縮動画のサイズ】
計算式:
幅 × 高さ × チャンネル数 × フレーム数 = バイト数
例: 1080p、30秒、30 FPS の動画
1920 × 1080 × 3 (RGB) × 900 (フレーム)
= 5,598,720,000 バイト
= 約 5.6 GB(非圧縮)
↓ コーデックで圧縮(H.264の場合)
実際のサイズ: 約 50MB〜100MB
圧縮率: 約 1/50〜1/100
【なぜ圧縮が必要か】
1分の4K動画(非圧縮):
3840 × 2160 × 3 × 60 × 60
= 約 89 GB
圧縮後:
約 500MB〜1GB
【画像処理 vs 動画処理の違い】
┌────────────┬─────────────────┬─────────────────┐
│ │ 画像処理 │ 動画処理 │
├────────────┼─────────────────┼─────────────────┤
│ 入力 │ 1枚の画像 │ 複数フレーム │
│ 情報 │ 空間情報のみ │ 空間 + 時間 │
│ 計算量 │ 少ない │ 大幅に増加 │
│ 考慮事項 │ 画像内容 │ + 時系列関係 │
└────────────┴─────────────────┴─────────────────┘
1-3. OpenCVでの動画処理
OpenCVを使って動画を読み込み、フレームを処理する方法を学びます。
💡 動画処理の基本パターン
1. 動画を開く(VideoCapture)
2. フレームを1枚ずつ読み込む(read)
3. 各フレームに処理を適用
4. 結果を表示または保存
5. 動画を閉じる(release)
※ コードが横に長い場合は横スクロールできます
# ===================================================
# OpenCVで動画を扱う基本
# ===================================================
import cv2
import numpy as np
import matplotlib.pyplot as plt
# ===================================================
# 1. 動画の読み込みとプロパティ取得
# ===================================================
# cv2.VideoCapture(): 動画ファイルを開く
# 引数: 動画ファイルのパス
# Webカメラの場合は 0(デフォルトカメラ)を指定
cap = cv2.VideoCapture(‘video.mp4’)
# 動画が正常に開けたか確認
if not cap.isOpened():
print(“動画ファイルを開けませんでした”)
else:
print(“動画ファイルを開きました”)
# cap.get(): 動画のプロパティを取得
# CAP_PROP_FPS: フレームレート
fps = cap.get(cv2.CAP_PROP_FPS)
# CAP_PROP_FRAME_COUNT: 総フレーム数
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# CAP_PROP_FRAME_WIDTH/HEIGHT: フレームの幅と高さ
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 動画の長さを計算
duration = frame_count / fps
print(f”FPS: {fps}”)
print(f”総フレーム数: {frame_count}”)
print(f”解像度: {width}×{height}”)
print(f”動画の長さ: {duration:.2f}秒”)
# 使い終わったら閉じる
cap.release()
実行結果:
動画ファイルを開きました
FPS: 30.0
総フレーム数: 900
解像度: 1920×1080
動画の長さ: 30.00秒
# ===================================================
# 2. フレームの読み込みと表示
# ===================================================
cap = cv2.VideoCapture(‘video.mp4’)
# cap.read(): 1フレームを読み込む
# 戻り値: (成功したか, フレーム画像)
ret, frame = cap.read()
# ret: True なら読み込み成功、False なら失敗(動画の終わり)
# frame: BGR形式の画像(NumPy配列)
if ret:
print(f”フレームの形状: {frame.shape}”)
# 例: (1080, 1920, 3) = 高さ, 幅, チャンネル(BGR)
# OpenCVはBGR、matplotlibはRGBなので変換
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 画像を表示
plt.figure(figsize=(10, 6))
plt.imshow(frame_rgb)
plt.title(‘最初のフレーム’)
plt.axis(‘off’)
plt.savefig(‘first_frame.png’, dpi=150, bbox_inches=’tight’)
plt.show()
cap.release()
# ===================================================
# 3. すべてのフレームを順番に処理
# ===================================================
cap = cv2.VideoCapture(‘video.mp4’)
frame_number = 0
# 無限ループで全フレームを処理
while True:
# フレームを読み込む
ret, frame = cap.read()
# ret が False なら動画の終わり
if not ret:
print(“動画の終わりに達しました”)
break
# ===== ここでフレームに処理を加える =====
# 例1: グレースケール変換
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 例2: エッジ検出
edges = cv2.Canny(gray, 100, 200)
# 例3: ぼかし
blurred = cv2.GaussianBlur(frame, (15, 15), 0)
# ========================================
# ウィンドウに表示(Jupyter以外の環境用)
# cv2.imshow(‘Frame’, gray)
frame_number += 1
# 10フレームごとに進捗表示
if frame_number % 100 == 0:
print(f”処理済み: {frame_number} フレーム”)
# ‘q’キーで終了(cv2.imshow使用時)
# if cv2.waitKey(1) & 0xFF == ord(‘q’):
# break
print(f”合計 {frame_number} フレームを処理しました”)
cap.release()
cv2.destroyAllWindows()
実行結果:
処理済み: 100 フレーム
処理済み: 200 フレーム
…
動画の終わりに達しました
合計 900 フレームを処理しました
# ===================================================
# 4. 特定のフレームを抽出する関数
# ===================================================
def extract_frame(video_path, frame_number):
“””
動画から特定のフレームを抽出する
Args:
video_path: 動画ファイルのパス
frame_number: 抽出するフレーム番号(0から始まる)
Returns:
RGB形式のフレーム画像(NumPy配列)、失敗時はNone
“””
cap = cv2.VideoCapture(video_path)
# cap.set(): プロパティを設定
# CAP_PROP_POS_FRAMES: フレーム位置を指定
# → 指定したフレーム番号に移動
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
# そのフレームを読み込む
ret, frame = cap.read()
cap.release()
if ret:
# BGRからRGBに変換して返す
return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
else:
return None
# 使用例: 100フレーム目を抽出
frame_100 = extract_frame(‘video.mp4’, 100)
if frame_100 is not None:
plt.figure(figsize=(10, 6))
plt.imshow(frame_100)
plt.title(‘100フレーム目’)
plt.axis(‘off’)
plt.show()
print(f”フレーム100の形状: {frame_100.shape}”)
else:
print(“フレームの抽出に失敗しました”)
# ===================================================
# 5. 動画の書き込み(処理結果を保存)
# ===================================================
# 入力動画を開く
cap = cv2.VideoCapture(‘input.mp4’)
# 動画のプロパティを取得
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# cv2.VideoWriter_fourcc(): コーデックを指定
# ‘mp4v’: MP4形式
# ‘XVID’: AVI形式
# ‘MJPG’: Motion JPEG
fourcc = cv2.VideoWriter_fourcc(*’mp4v’)
# cv2.VideoWriter(): 動画書き込みオブジェクトを作成
# 引数: (出力ファイル名, コーデック, FPS, (幅, 高さ))
out = cv2.VideoWriter(‘output.mp4’, fourcc, fps, (width, height))
frame_count = 0
while True:
ret, frame = cap.read()
if not ret:
break
# フレームに処理を加える
# 例: エッジ検出結果をカラーに変換
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 200)
# グレースケールをBGRに変換(VideoWriterはBGR形式が必要)
edges_bgr = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
# out.write(): フレームを書き込む
out.write(edges_bgr)
frame_count += 1
# リソースを解放
cap.release()
out.release()
print(f”動画処理完了! {frame_count}フレームを処理しました”)
1-4. フレームサンプリング(行動認識の準備)
行動認識では、動画全体ではなく一定間隔でサンプリングしたフレームを使うことが一般的です。
# ===================================================
# 6. 均等にフレームをサンプリング
# ===================================================
def sample_frames(video_path, num_samples=16, target_size=(224, 224)):
“””
動画から均等にフレームをサンプリング
行動認識モデルの入力として使用
Args:
video_path: 動画ファイルのパス
num_samples: サンプリングするフレーム数(通常8, 16, 32)
target_size: リサイズ後のサイズ
Returns:
サンプリングされたフレームのNumPy配列
形状: (num_samples, height, width, 3)
“””
cap = cv2.VideoCapture(video_path)
# 総フレーム数を取得
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# np.linspace(): 等間隔の数列を生成
# 0からframe_count-1までをnum_samples個に分割
# 例: 900フレームから16個 → [0, 60, 120, 180, …]
indices = np.linspace(0, frame_count – 1, num_samples, dtype=int)
print(f”総フレーム数: {frame_count}”)
print(f”サンプリングするインデックス: {indices[:5]}…”)
frames = []
for idx in indices:
# 指定フレームに移動
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
ret, frame = cap.read()
if ret:
# BGRからRGBに変換
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# リサイズ(行動認識モデルは通常224×224)
frame_resized = cv2.resize(frame_rgb, target_size)
frames.append(frame_resized)
cap.release()
return np.array(frames)
# 使用例
sampled_frames = sample_frames(‘video.mp4’, num_samples=16)
print(f”サンプリング結果の形状: {sampled_frames.shape}”)
# 期待: (16, 224, 224, 3)
# サンプリングしたフレームを可視化
fig, axes = plt.subplots(2, 8, figsize=(16, 4))
for i, ax in enumerate(axes.flat):
if i < len(sampled_frames):
ax.imshow(sampled_frames[i])
ax.set_title(f'Frame {i}', fontsize=10)
ax.axis('off')
plt.tight_layout()
plt.savefig('sampled_frames.png', dpi=150, bbox_inches='tight')
plt.show()
実行結果:
総フレーム数: 900
サンプリングするインデックス: [ 0 60 120 180 240]…
サンプリング結果の形状: (16, 224, 224, 3)
🌊 2. 光学フロー(Optical Flow)
光学フローは、連続するフレーム間でのピクセルの動きを表すベクトル場です。行動認識で「動き」の情報を抽出するために重要な技術です。
2-1. 光学フローとは
【光学フローの基本概念】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
定義
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
連続する2つのフレーム間で、
各ピクセルがどの方向にどれだけ動いたかを表すベクトル場
イメージ図:
フレーム t フレーム t+1
┌─────────┐ ┌─────────┐
│ ● │ → │ ● │
│ │ │ │
└─────────┘ └─────────┘
● が右に移動した
→ 光学フロー: (dx=3, dy=0)
出力:
各ピクセルの移動ベクトル (dx, dy)
dx: x方向(横)の移動量
dy: y方向(縦)の移動量
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
用途
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 動き検出
・動いている物体を検出
・背景と前景の分離
2. 物体追跡
・特定の物体を追跡
3. 行動認識
・人の動きのパターンを認識
・Two-Stream Networksの入力
4. ビデオ圧縮
・フレーム間の差分を利用
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2つの主な手法
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Dense Optical Flow(密な光学フロー)
・すべてのピクセルの動きを計算
・手法: Farneback法
・計算コスト: 高い
・用途: 行動認識(全体の動きが必要)
2. Sparse Optical Flow(疎な光学フロー)
・特徴点のみの動きを計算
・手法: Lucas-Kanade法
・計算コスト: 低い
・用途: 物体追跡(特定の点のみ追跡)
2-2. Dense Optical Flow(Farneback法)
すべてのピクセルの動きを計算する方法です。行動認識では、この方法で抽出した光学フローを使います。
# ===================================================
# Dense Optical Flow(Farneback法)
# ===================================================
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 動画を開く
cap = cv2.VideoCapture(‘video.mp4’)
# 最初のフレームを読み込む
ret, frame1 = cap.read()
# グレースケールに変換(光学フローはグレースケールで計算)
prev_gray = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
# HSV画像を作成(可視化用)
# 光学フローの方向を色相(H)、大きさを明度(V)で表現
hsv = np.zeros_like(frame1)
hsv[…, 1] = 255 # 彩度を最大に固定
frame_count = 0
while True:
ret, frame2 = cap.read()
if not ret:
break
# 現在のフレームをグレースケールに変換
gray = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
# cv2.calcOpticalFlowFarneback(): Dense Optical Flowを計算
# 引数:
# prev_gray: 前のフレーム(グレースケール)
# gray: 現在のフレーム(グレースケール)
# None: 出力先(Noneで自動作成)
# pyr_scale: ピラミッドスケール(0.5 = 各レベルで半分)
# levels: ピラミッドレベル数
# winsize: 平均化ウィンドウサイズ
# iterations: 各レベルでの繰り返し回数
# poly_n: ピクセル近傍のサイズ
# poly_sigma: 平滑化パラメータ
# flags: フラグ
flow = cv2.calcOpticalFlowFarneback(
prev_gray, gray,
None,
pyr_scale=0.5,
levels=3,
winsize=15,
iterations=3,
poly_n=5,
poly_sigma=1.2,
flags=0
)
# flowの形状: (高さ, 幅, 2)
# flow[…, 0]: x方向の移動量
# flow[…, 1]: y方向の移動量
# 光学フローを可視化(HSV色空間で表現)
# cv2.cartToPolar(): 直交座標を極座標に変換
# (x, y) → (大きさ, 角度)
mag, ang = cv2.cartToPolar(flow[…, 0], flow[…, 1])
# 角度(0〜2π)を色相(0〜180)に変換
# 方向によって色が変わる(右=赤、上=緑、左=青など)
hsv[…, 0] = ang * 180 / np.pi / 2
# 大きさを明度に変換(正規化して0〜255に)
hsv[…, 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
# HSVをBGRに変換(表示用)
flow_bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
# 表示(Jupyter以外の環境用)
# cv2.imshow(‘Original’, frame2)
# cv2.imshow(‘Optical Flow’, flow_bgr)
# 次のイテレーション用に現在のフレームを保存
prev_gray = gray
frame_count += 1
# 最初の数フレームだけ処理して結果を保存
if frame_count == 30:
# 結果を保存
flow_rgb = cv2.cvtColor(flow_bgr, cv2.COLOR_BGR2RGB)
frame_rgb = cv2.cvtColor(frame2, cv2.COLOR_BGR2RGB)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].imshow(frame_rgb)
axes[0].set_title(‘元のフレーム’)
axes[0].axis(‘off’)
axes[1].imshow(flow_rgb)
axes[1].set_title(‘光学フロー(色=方向、明るさ=速度)’)
axes[1].axis(‘off’)
plt.savefig(‘optical_flow_visualization.png’, dpi=150, bbox_inches=’tight’)
plt.show()
break
cap.release()
print(f”光学フローの形状: {flow.shape}”)
実行結果:
光学フローの形状: (1080, 1920, 2)
🎯 光学フローの可視化の読み方
色(色相):動きの方向を表す
・赤系: 右方向への動き
・緑系: 上方向への動き
・青系: 左方向への動き
明るさ(明度):動きの速さを表す
・明るい: 速い動き
・暗い: 遅い動き(または静止)
2-3. Sparse Optical Flow(Lucas-Kanade法)
特徴点のみの動きを追跡する方法です。計算が高速で、物体追跡に適しています。
# ===================================================
# Sparse Optical Flow(Lucas-Kanade法)
# ===================================================
import cv2
import numpy as np
cap = cv2.VideoCapture(‘video.mp4’)
# 最初のフレームを読み込む
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# cv2.goodFeaturesToTrack(): 追跡に適した特徴点を検出
# Shi-Tomasi法でコーナーを検出
feature_params = dict(
maxCorners=100, # 検出する最大特徴点数
qualityLevel=0.3, # 品質レベル(0〜1、高いほど厳選)
minDistance=7, # 特徴点間の最小距離
blockSize=7 # 検出時のブロックサイズ
)
# 特徴点を検出
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
print(f”検出された特徴点数: {len(p0)}”)
# Lucas-Kanade法のパラメータ
lk_params = dict(
winSize=(15, 15), # 検索ウィンドウサイズ
maxLevel=2, # ピラミッドレベル
criteria=( # 終了条件
cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,
10, # 最大繰り返し回数
0.03 # 精度
)
)
# 各特徴点に色を割り当て(可視化用)
colors = np.random.randint(0, 255, (100, 3))
# 軌跡を描画するためのマスク画像
mask = np.zeros_like(old_frame)
frame_count = 0
while True:
ret, frame = cap.read()
if not ret:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# cv2.calcOpticalFlowPyrLK(): Sparse Optical Flowを計算
# 引数:
# old_gray: 前のフレーム
# frame_gray: 現在のフレーム
# p0: 追跡する特徴点
# None: 出力先
# 戻り値:
# p1: 新しい特徴点の位置
# st: ステータス(1=成功、0=失敗)
# err: エラー値
p1, st, err = cv2.calcOpticalFlowPyrLK(
old_gray, frame_gray, p0, None, **lk_params
)
# 追跡に成功した特徴点のみを選択
if p1 is not None:
# st == 1 の点のみ選択
good_new = p1[st == 1]
good_old = p0[st == 1]
# 軌跡を描画
for i, (new, old) in enumerate(zip(good_new, good_old)):
# 座標を取得
a, b = new.ravel() # 新しい位置
c, d = old.ravel() # 古い位置
# 軌跡の線を描画
mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)),
colors[i].tolist(), 2)
# 現在位置に円を描画
frame = cv2.circle(frame, (int(a), int(b)), 5,
colors[i].tolist(), -1)
# 元のフレームと軌跡を合成
output = cv2.add(frame, mask)
# 次のイテレーション用に更新
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
frame_count += 1
# 60フレーム後に結果を保存
if frame_count == 60:
output_rgb = cv2.cvtColor(output, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(12, 8))
plt.imshow(output_rgb)
plt.title(‘Sparse Optical Flow(特徴点追跡)’)
plt.axis(‘off’)
plt.savefig(‘sparse_optical_flow.png’, dpi=150, bbox_inches=’tight’)
plt.show()
break
cap.release()
print(f”追跡中の特徴点数: {len(good_new)}”)
2-4. 行動認識用の光学フロー抽出
Two-Stream Networksで使用するために、動画から光学フローを抽出する関数を作成します。
# ===================================================
# 行動認識用の光学フロー抽出
# ===================================================
def extract_optical_flow(video_path, num_frames=16, target_size=(224, 224)):
“””
動画から光学フローを抽出(行動認識用)
Args:
video_path: 動画ファイルのパス
num_frames: 抽出するフレーム数
target_size: 出力サイズ
Returns:
光学フローのNumPy配列
形状: (num_frames, height, width, 2)
2 = (x方向の移動, y方向の移動)
“””
cap = cv2.VideoCapture(video_path)
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# 均等にサンプリングするインデックス
# 最後のフレームは使わない(次のフレームが必要なため)
indices = np.linspace(0, frame_count – 2, num_frames, dtype=int)
flows = []
for idx in indices:
# 2つの連続フレームを取得
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
ret1, frame1 = cap.read()
ret2, frame2 = cap.read()
if not (ret1 and ret2):
break
# グレースケール変換
gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
# 光学フローを計算
flow = cv2.calcOpticalFlowFarneback(
gray1, gray2, None,
0.5, 3, 15, 3, 5, 1.2, 0
)
# リサイズ
flow_resized = cv2.resize(flow, target_size)
flows.append(flow_resized)
cap.release()
return np.array(flows)
# 使用例
optical_flows = extract_optical_flow(‘video.mp4’, num_frames=16)
print(f”光学フローの形状: {optical_flows.shape}”)
# 期待: (16, 224, 224, 2)
# 光学フローの統計情報
print(f”x方向の移動量: min={optical_flows[…, 0].min():.2f}, max={optical_flows[…, 0].max():.2f}”)
print(f”y方向の移動量: min={optical_flows[…, 1].min():.2f}, max={optical_flows[…, 1].max():.2f}”)
実行結果:
光学フローの形状: (16, 224, 224, 2)
x方向の移動量: min=-12.34, max=15.67
y方向の移動量: min=-8.92, max=11.23
🏃 3. 行動認識(Action Recognition)
行動認識とは、動画から人間の行動を自動的に認識・分類するタスクです。画像分類の動画版と考えることができます。
3-1. 行動認識の基礎
【行動認識とは】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
定義
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
入力: 動画(複数のフレーム)
出力: 行動のクラスラベル
例:
入力: 人が走っている動画
出力: “running”
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
認識できる行動の例
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
基本動作:
歩く、走る、ジャンプ、座る、立つ
スポーツ:
バスケットボールのダンク、サッカーのシュート、
野球のバッティング、テニスのサーブ
日常動作:
手を振る、拍手する、電話する、
料理する、掃除する
複雑な動作:
ギターを弾く、ボウリングをする、
赤ちゃんをあやす
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
画像分類との違い
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌────────────┬─────────────────┬─────────────────┐
│ │ 画像分類 │ 行動認識 │
├────────────┼─────────────────┼─────────────────┤
│ 入力 │ 1枚の画像 │ 動画(複数枚) │
│ 情報 │ 空間情報のみ │ 空間 + 時間 │
│ 例 │ 「犬」「猫」 │「走る」「歩く」 │
│ 難しさ │ 見た目の違い │ 動きの違い │
└────────────┴─────────────────┴─────────────────┘
なぜ難しい?:
「立っている人」と「歩いている人」は
1枚の画像では区別が難しい
→ 時間的な変化(動き)が必要
3-2. 代表的なデータセット
| データセット |
クラス数 |
動画数 |
特徴 |
| UCF101 |
101 |
13,320 |
スポーツ、日常動作。行動認識の標準ベンチマーク |
| HMDB51 |
51 |
6,849 |
映画からの抽出。UCF101より難しい |
| Kinetics-400 |
400 |
約30万 |
YouTube動画。大規模、多様 |
| Kinetics-700 |
700 |
約65万 |
Kineticsの拡張版 |
| Something-Something |
174 |
約22万 |
物体操作。外観より動きが重要 |
3-3. Two-Stream Networks
Two-Stream Networks(2014年)は、空間情報と時間情報を別々のネットワークで処理し、最後に統合する手法です。
【Two-Stream Networksの仕組み】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
基本アイデア
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
人間が動画を見るとき:
1. 何が映っているか?(空間情報)
2. どう動いているか?(時間情報)
この2つを別々に処理して統合する
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2つのストリーム
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Spatial Stream(空間ストリーム)
・入力: 1枚のRGB画像
・処理: CNNで特徴抽出
・認識: 物体、シーン、人物の姿勢
・例: 「人がボールを持っている」
2. Temporal Stream(時間ストリーム)
・入力: 光学フローの積み重ね(10〜20フレーム分)
・処理: CNNで特徴抽出
・認識: 動きのパターン
・例: 「何かを投げる動き」
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
アーキテクチャ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
入力動画
│
├──────────────────┬──────────────────┐
↓ ↓ │
RGB画像 光学フロー抽出 │
│ │ │
↓ ↓ │
┌─────────────┐ ┌─────────────┐ │
│ Spatial │ │ Temporal │ │
│ Stream │ │ Stream │ │
│ (CNN) │ │ (CNN) │ │
│ │ │ │ │
│ ResNet等 │ │ ResNet等 │ │
└─────────────┘ └─────────────┘ │
│ │ │
↓ ↓ │
空間特徴 時間特徴 │
│ │ │
└────────┬─────────┘ │
│ │
↓ │
┌─────────────────┐ │
│ Late Fusion │ │
│ (スコアの平均) │ │
└─────────────────┘ │
│ │
↓ │
行動クラス予測 │
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
光学フローの入力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
10フレームの光学フローを使う場合:
各フレームの光学フロー: (H, W, 2)
→ dx方向とdy方向の2チャンネル
10フレーム積み重ね: (H, W, 20)
→ 10 × 2 = 20チャンネル
CNNへの入力: (224, 224, 20)
最初の層: 20チャンネル → 64チャンネル
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
利点と欠点
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
利点:
✓ 空間と時間を明示的に分離
✓ 光学フローで動きを明確にキャプチャ
✓ ImageNetの事前学習を活用可能
✓ 高精度
欠点:
✗ 光学フローの計算コストが高い
✗ 2つのネットワークが必要(メモリ消費大)
✗ 光学フローのストレージが必要
✗ End-to-endではない
3-4. 3D CNN(C3D、I3D)
3D CNNは、2Dの畳み込みを3次元に拡張し、時間と空間を同時に処理する手法です。
【3D CNNの仕組み】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2D CNN vs 3D CNN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2D CNN:
入力: (H, W, C) = 高さ × 幅 × チャンネル
カーネル: (k, k) = k × k
空間方向のみの畳み込み
3D CNN:
入力: (T, H, W, C) = 時間 × 高さ × 幅 × チャンネル
カーネル: (t, k, k) = 時間 × k × k
時間 + 空間方向の畳み込み
図解:
2D畳み込み(画像) 3D畳み込み(動画)
┌───┬───┬───┐ ┌───────────────┐
│ │ │ │ │ フレーム3 │
├───┼───┼───┤ ├───────────────┤
│ │ ● │ │ 3×3 │ フレーム2 │ 3×3×3
├───┼───┼───┤ ├───────────────┤
│ │ │ │ │ フレーム1 │
└───┴───┴───┘ └───────────────┘
空間方向のみ 時間 + 空間方向
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
C3D(Convolutional 3D, 2014年)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
特徴:
・すべてのカーネルが 3×3×3
・入力: 16フレーム × 112×112
・シンプルなアーキテクチャ
アーキテクチャ:
conv1: 64 filters
pool1: 1×2×2(時間次元は保持)
conv2: 128 filters
pool2: 2×2×2
conv3a, conv3b: 256 filters
pool3: 2×2×2
conv4a, conv4b: 512 filters
pool4: 2×2×2
conv5a, conv5b: 512 filters
pool5: 2×2×2
fc6, fc7: 4096
fc8: クラス数
問題点:
・ImageNetの事前学習が使えない
・パラメータ数が多い
・大量の訓練データが必要
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
I3D(Inflated 3D, 2017年)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
アイデア:
ImageNetで事前学習した2D CNNを
3D CNNに「膨張」(Inflate)させる
方法:
2Dカーネル (k, k) → 3Dカーネル (t, k, k)
重みの複製:
2D: W[k, k]
↓
3D: W[k, k] / t を t回繰り返し
例: 3×3 → 3×3×3
重みを3で割って3回積み重ね
利点:
✓ ImageNetの事前学習を活用
✓ C3Dより高精度
✓ Two-Stream版もあり(RGB + Flow)
結果:
Kinetics-400で当時最高精度
行動認識の標準手法の一つに
3-5. TSN(Temporal Segment Networks)
【TSN(Temporal Segment Networks, 2016年)】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
問題
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
長い動画(数分)をどう扱うか?
Two-Stream:
中央の数フレームのみを使用
→ 動画全体の情報を使っていない
3D CNN:
16〜64フレーム程度が限界
→ 長い動画は扱えない
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TSNのアイデア
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
動画を複数のセグメントに分割し、
各セグメントから代表フレームをサンプリング
処理の流れ:
1. セグメント分割
━━━━━━━━━━━━━━━━━━━━━━━━
動画をK個のセグメントに等分割(例: K=3)
動画(300フレーム):
[セグメント1: 1-100] [セグメント2: 101-200] [セグメント3: 201-300]
2. フレームサンプリング
━━━━━━━━━━━━━━━━━━━━━━━━
各セグメントから1フレームをランダムに選択
訓練時: ランダムサンプリング(データ拡張効果)
テスト時: 中央フレームを選択
3. CNN特徴抽出
━━━━━━━━━━━━━━━━━━━━━━━━
各フレームを独立にCNNに入力
3枚のフレーム → 3つの特徴ベクトル
4. Consensus(合意関数)
━━━━━━━━━━━━━━━━━━━━━━━━
K個の予測を統合して最終予測
方法:
・Average: 平均(最も一般的)
・Maximum: 最大値
・Weighted Average: 重み付き平均
図解:
動画
┌─────────────────────────────┐
│ セグメント1 │ セグメント2 │ セグメント3 │
└─────────────────────────────┘
│ │ │
↓ ↓ ↓
フレーム1 フレーム2 フレーム3
│ │ │
↓ ↓ ↓
┌─────┐ ┌─────┐ ┌─────┐
│ CNN │ │ CNN │ │ CNN │ (重み共有)
└─────┘ └─────┘ └─────┘
│ │ │
↓ ↓ ↓
スコア1 スコア2 スコア3
│ │ │
└─────────┬─────────┘
↓
Consensus(平均)
↓
最終予測
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
利点と欠点
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
利点:
✓ 長い動画に対応
✓ 効率的(全フレーム処理不要)
✓ データ拡張効果が大きい
✓ Two-Streamと組み合わせ可能
欠点:
✗ フレーム間の関係を明示的にモデル化しない
✗ 短時間の動作を見逃す可能性
3-6. 手法の比較
| 手法 |
入力 |
特徴 |
適した用途 |
| Two-Stream |
RGB + 光学フロー |
空間と時間を分離処理。高精度だが光学フロー計算が必要 |
精度重視の認識。オフライン処理 |
| C3D |
RGB(16フレーム) |
3D畳み込み。End-to-end学習可能 |
特徴抽出、転移学習 |
| I3D |
RGB(64フレーム) |
ImageNet事前学習を活用。高精度 |
高精度が必要な認識 |
| TSN |
RGB(K枚) |
長い動画に対応。効率的 |
長時間動画の認識 |
| SlowFast |
RGB(2経路) |
低速経路と高速経路。最新手法 |
リアルタイム認識 |
📦 4. 動画物体検出
動画物体検出は、動画の各フレームで物体を検出し、時間的な一貫性を保ちながら追跡するタスクです。
4-1. 動画物体検出の課題
【動画物体検出の課題】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
画像物体検出 vs 動画物体検出
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
画像物体検出:
1枚の画像から物体を検出
動画物体検出:
各フレームで物体を検出
+ フレーム間の一貫性を保つ
+ 同じ物体には同じIDを割り当て
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
主な課題
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 計算コスト
問題: 全フレームで検出 → 非常に遅い
例: 30 FPSの動画、1フレーム100msの検出
→ 10 FPSしか処理できない
対策:
・キーフレームのみ検出
・特徴の伝播(Flow-Guided)
・軽量モデルの使用
2. 時間的な一貫性
問題: 隣接フレームで検出が大きく変わる
(flickering)
例: フレームtで検出、フレームt+1で未検出
対策:
・Temporal Smoothing(時間方向の平滑化)
・トラッキングとの統合
・複数フレームの特徴を集約
3. モーションブラー
問題: 動きによるぼやけで検出精度低下
対策:
・複数フレームの情報を統合
・光学フローの活用
4. 遮蔽(オクルージョン)
問題: 物体が隠れて検出できない
対策:
・トラッキングで位置を予測
・再出現時にIDを維持
4-2. YOLOv8による動画物体検出
YOLOv8を使って動画から物体を検出し、トラッキングも行います。
# ===================================================
# YOLOv8で動画物体検出
# ===================================================
# インストール(必要な場合)
# !pip install ultralytics
from ultralytics import YOLO
import cv2
import matplotlib.pyplot as plt
# ===================================================
# 1. モデルのロード
# ===================================================
# YOLOv8モデルをロード
# ‘yolov8n.pt’: nanoモデル(最速、精度は低め)
# ‘yolov8s.pt’: smallモデル
# ‘yolov8m.pt’: mediumモデル
# ‘yolov8l.pt’: largeモデル
# ‘yolov8x.pt’: extralargeモデル(最も高精度)
model = YOLO(‘yolov8n.pt’)
print(“モデルをロードしました”)
print(f”クラス数: {len(model.names)}”)
print(f”クラス例: {list(model.names.values())[:10]}…”)
実行結果:
モデルをロードしました
クラス数: 80
クラス例: [‘person’, ‘bicycle’, ‘car’, ‘motorcycle’, ‘airplane’, ‘bus’, ‘train’, ‘truck’, ‘boat’, ‘traffic light’]…
# ===================================================
# 2. 動画の読み込みと検出
# ===================================================
cap = cv2.VideoCapture(‘video.mp4′)
# 動画のプロパティ
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f”FPS: {fps}”)
print(f”解像度: {width}×{height}”)
print(f”総フレーム数: {total_frames}”)
# 出力動画の設定
fourcc = cv2.VideoWriter_fourcc(*’mp4v’)
out = cv2.VideoWriter(‘output_detection.mp4’, fourcc, fps, (width, height))
frame_number = 0
while True:
ret, frame = cap.read()
if not ret:
break
# YOLOv8で物体検出
# verbose=False: 進捗を非表示
results = model(frame, verbose=False)
# results[0].plot(): 検出結果を画像に描画
# Bounding Box、クラス名、信頼度が描画される
annotated_frame = results[0].plot()
# 出力動画に書き込み
out.write(annotated_frame)
frame_number += 1
# 進捗表示
if frame_number % 30 == 0:
print(f”処理済み: {frame_number}/{total_frames} フレーム”)
cap.release()
out.release()
print(f”検出完了! {frame_number}フレームを処理しました”)
# ===================================================
# 3. トラッキング(物体追跡)の追加
# ===================================================
# YOLOv8には組み込みのトラッキング機能がある
# ByteTrack、BoT-SORTなどのアルゴリズムを使用
cap = cv2.VideoCapture(‘video.mp4′)
fourcc = cv2.VideoWriter_fourcc(*’mp4v’)
out = cv2.VideoWriter(‘output_tracking.mp4′, fourcc, fps, (width, height))
frame_number = 0
while True:
ret, frame = cap.read()
if not ret:
break
# model.track(): 検出 + トラッキング
# persist=True: 前のフレームの追跡情報を保持
# tracker=’bytetrack.yaml’: ByteTrackアルゴリズムを使用
results = model.track(frame, persist=True, verbose=False)
# 検出結果にIDが割り当てられる
annotated_frame = results[0].plot()
out.write(annotated_frame)
frame_number += 1
# 30フレーム目の検出結果を詳しく表示
if frame_number == 30:
print(“\n=== 30フレーム目の検出結果 ===”)
if results[0].boxes is not None:
boxes = results[0].boxes
for i, box in enumerate(boxes):
# box.xyxy: Bounding Boxの座標
x1, y1, x2, y2 = box.xyxy[0].tolist()
# box.cls: クラスID
cls_id = int(box.cls[0])
cls_name = model.names[cls_id]
# box.conf: 信頼度
conf = float(box.conf[0])
# box.id: トラッキングID(追跡時のみ)
track_id = int(box.id[0]) if box.id is not None else -1
print(f”物体{i+1}: {cls_name}, ID={track_id}, 信頼度={conf:.2f}”)
cap.release()
out.release()
print(f”\nトラッキング完了!”)
実行結果:
=== 30フレーム目の検出結果 ===
物体1: person, ID=1, 信頼度=0.92
物体2: person, ID=2, 信頼度=0.88
物体3: car, ID=3, 信頼度=0.85
物体4: car, ID=4, 信頼度=0.79
トラッキング完了!
🎯 トラッキングの効果
検出のみ(track=False):
・各フレームで独立に検出
・同じ物体でもフレームごとに異なる扱い
トラッキングあり(track=True):
・同じ物体には同じIDが割り当てられる
・一時的に見えなくなっても追跡を維持
・物体の軌跡を分析可能
📝 練習問題
問題1:動画の基礎知識(基礎)
FPS(Frames Per Second)とは何か、また一般的なFPS(24、30、60)がそれぞれどのような用途で使われるか説明してください。
解答:
【FPSとは】
FPS(Frames Per Second)= 1秒あたりのフレーム数
動画の「滑らかさ」を表す指標
数値が高いほど滑らかな動きになる
【一般的なFPSと用途】
┌────────┬─────────────────────────────────────┐
│ FPS │ 用途と特徴 │
├────────┼─────────────────────────────────────┤
│ 24 │ 映画 │
│ │ ・フィルム時代からの標準 │
│ │ ・「映画らしい」雰囲気 │
│ │ ・動きに若干のぼやけ(モーションブラー)│
├────────┼─────────────────────────────────────┤
│ 30 │ テレビ、YouTube、一般的な動画 │
│ │ ・NTSC方式の標準(正確には29.97) │
│ │ ・標準的な滑らかさ │
│ │ ・ファイルサイズと品質のバランス │
├────────┼─────────────────────────────────────┤
│ 60 │ スポーツ、ゲーム、高品質動画 │
│ │ ・非常に滑らかな動き │
│ │ ・高速な動きを捉えやすい │
│ │ ・ファイルサイズは30 FPSの約2倍 │
├────────┼─────────────────────────────────────┤
│ 120+ │ スローモーション撮影 │
│ │ ・120 FPSで撮影→30 FPSで再生 │
│ │ ・4倍のスローモーション │
└────────┴─────────────────────────────────────┘
【なぜ違いがあるか】
・人間の目: 約24 FPS以上で「連続した動き」と認識
・24 FPS: 最低限の滑らかさ、映画の芸術的表現
・30 FPS: テレビ放送規格、コスト効率が良い
・60 FPS: ゲームやスポーツで重要、反応速度に影響
問題2:光学フローの役割(中級)
光学フロー(Optical Flow)とは何か、また行動認識においてどのように活用されるか説明してください。
解答:
【光学フローとは】
定義:
連続する2つのフレーム間での
ピクセルの動きを表すベクトル場
出力:
各ピクセルの移動ベクトル (dx, dy)
・dx: x方向(横)の移動量
・dy: y方向(縦)の移動量
【2種類の光学フロー】
1. Dense Optical Flow(密な光学フロー)
・すべてのピクセルの動きを計算
・手法: Farneback法
・計算コスト: 高い
・行動認識で使用
2. Sparse Optical Flow(疎な光学フロー)
・特徴点のみの動きを計算
・手法: Lucas-Kanade法
・計算コスト: 低い
・物体追跡で使用
【行動認識での活用】
1. Two-Stream Networks
━━━━━━━━━━━━━━━━━━━━━━━━
・Spatial Stream: RGB画像(何が映っているか)
・Temporal Stream: 光学フロー(どう動いているか)
・2つを統合して行動を認識
2. 入力形式
━━━━━━━━━━━━━━━━━━━━━━━━
・10〜20フレームの光学フローを抽出
・チャンネル方向に積み重ね
・例: 10フレーム × 2チャンネル = 20チャンネル
・CNNに入力して動きパターンを学習
3. 利点
━━━━━━━━━━━━━━━━━━━━━━━━
・動きを明示的に表現
・「歩く」vs「走る」の違いを捉えやすい
・外観の変化(服の色など)に頑健
4. 欠点
━━━━━━━━━━━━━━━━━━━━━━━━
・計算コストが高い
・ストレージを消費
・リアルタイム処理が困難
問題3:Two-Stream Networksと3D CNNの比較(中級)
行動認識におけるTwo-Stream Networksと3D CNN(C3D、I3D)のアプローチの違いと、それぞれの利点・欠点を説明してください。
解答:
【Two-Stream Networks】
アプローチ:
空間情報と時間情報を「別々に」処理
Spatial Stream: RGB画像 → CNN → 空間特徴
Temporal Stream: 光学フロー → CNN → 時間特徴
最後に Late Fusion で統合
利点:
✓ 空間と時間を明示的に分離
✓ 光学フローで動きを明確にキャプチャ
✓ ImageNetの事前学習を活用しやすい
✓ 各ストリームを独立に最適化可能
欠点:
✗ 光学フローの計算コストが高い
✗ 2つのネットワークが必要(メモリ消費大)
✗ 光学フローのストレージが必要
✗ End-to-end学習ではない
【3D CNN(C3D、I3D)】
アプローチ:
空間と時間を「同時に」処理
3D畳み込み: (時間, 高さ, 幅) を同時に処理
カーネル: 3×3×3(時間×高さ×幅)
C3D:
・すべて3×3×3カーネル
・16フレームを入力
・ImageNetの事前学習が使えない
I3D:
・2D CNNを3Dに「膨張」
・ImageNetの事前学習を活用
・Two-Stream版もあり
利点:
✓ End-to-end学習が可能
✓ 光学フローの計算不要
✓ 時空間の特徴を同時に学習
✓ 単一のネットワーク
欠点:
✗ パラメータ数が非常に多い
✗ 計算コストが高い
✗ 大量の訓練データが必要
✗ メモリ消費が大きい
【使い分け】
・計算資源が限られている → Two-Stream
・End-to-endが重要 → 3D CNN
・最高精度が必要 → I3D(Two-Stream版)
・リアルタイム処理 → 軽量3D CNN(X3D等)
問題4:動画処理システムの設計(上級)
監視カメラの映像から「転倒」を自動検出するシステムを設計してください。必要な技術、処理の流れ、課題と対策を含めてください。
解答:
【転倒検出システムの設計】
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 必要な技術
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
・物体検出: YOLOv8(人物検出)
・姿勢推定: MediaPipe Pose(キーポイント検出)
・トラッキング: ByteTrack/SORT(人物追跡)
・行動認識: LSTM または ルールベース判定
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2. 処理の流れ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ステップ1: 人物検出
各フレームでYOLOv8により人物を検出
Bounding Boxを取得
ステップ2: トラッキング
検出された人物にIDを割り当て
フレーム間で同一人物を追跡
ステップ3: 姿勢推定
人物領域から姿勢(キーポイント)を推定
頭、肩、腰、膝、足首の座標を取得
ステップ4: 転倒判定
【方法A: ルールベース】
・体の中心(腰)の高さを監視
・急激な高さの低下を検出
・閾値: 通常の高さから50%以上低下
・体の傾き角度も考慮
【方法B: 機械学習ベース】
・過去16フレームの姿勢データを入力
・LSTM で転倒を分類
・訓練データ: 正常行動 vs 転倒
ステップ5: アラート
転倒検出時に通知を送信
誤検出防止のため数秒の確認期間
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3. 課題と対策
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
課題1: 誤検出
しゃがむ、座るを転倒と誤認識
対策:
・動きの速度を考慮(転倒は急激)
・姿勢の回復を監視(転倒後は起き上がらない)
・時系列情報を活用
課題2: 遮蔽
家具などで人物が隠れる
対策:
・複数カメラを使用
・見えているキーポイントのみで判定
・トラッキングで位置を予測
課題3: 照明変化
暗い環境での検出精度低下
対策:
・赤外線カメラの使用
・画像の明るさ正規化
課題4: リアルタイム処理
高解像度動画の処理が遅い
対策:
・軽量モデル(YOLOv8n、MediaPipe)
・解像度を下げる(720p以下)
・GPU使用
・フレームスキップ(5 FPS程度で十分)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4. 実装例(擬似コード)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 初期化
detector = YOLO(‘yolov8n.pt’)
pose = MediaPipe.Pose()
tracker = ByteTrack()
# 各人物の高さ履歴
height_history = {}
while True:
frame = camera.read()
# 人物検出
detections = detector(frame)
# トラッキング
tracked = tracker.update(detections)
for person_id, bbox in tracked:
# 姿勢推定
keypoints = pose.estimate(frame[bbox])
# 腰の高さを取得
hip_height = keypoints[‘hip’].y
# 履歴に追加
if person_id not in height_history:
height_history[person_id] = []
height_history[person_id].append(hip_height)
# 転倒判定
if len(height_history[person_id]) > 10:
# 過去10フレームで高さが50%以上低下
if detect_fall(height_history[person_id]):
send_alert(person_id, frame)
📝 STEP 23 のまとめ
✅ このステップで学んだこと
1. 動画の基礎
・フレーム、FPS、解像度、コーデックの概念
・OpenCVでの動画の読み込み・書き込み・フレーム抽出
2. 光学フロー
・Dense(Farneback法)とSparse(Lucas-Kanade法)
・行動認識での時間情報の抽出に活用
3. 行動認識の手法
・Two-Stream Networks: RGB + 光学フロー
・3D CNN(C3D、I3D): 時空間同時処理
・TSN: 長い動画への対応
4. 動画物体検出
・YOLOv8による検出とトラッキング
・時間的一貫性の維持
💡 重要ポイント
動画処理の特徴:
・空間情報 + 時間情報の両方を考慮
・計算コストが画像処理より大幅に増加
行動認識の選択指針:
・精度重視 → Two-Stream または I3D
・End-to-end → 3D CNN
・長い動画 → TSN
・リアルタイム → 軽量モデル
これでPart 6(応用タスク)が完了しました!
次のPart 7では、総合プロジェクトに取り組みます!