STEP 23:動画処理と行動認識

🎬 STEP 23: 動画処理と行動認識

動画の扱い方、フレーム抽出、光学フロー、行動認識(Action Recognition)、
Two-Stream Networks、3D CNN、動画物体検出を学びます

📋 このステップで学ぶこと

  • 動画の基礎知識(フレーム、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では、総合プロジェクトに取り組みます!

📝

学習メモ

コンピュータビジョン(CV) - Step 23

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE