画像用ツールを動画に拡張したい
これまでの記事群で、Adobe Stockへの画像投稿を自動化するツール(CLIP解析・Ollamaメタデータ生成・審査結果収集・Excel管理)を構築してきました。 アップスケール済みの動画も投稿できる状態になったので、この画像用パイプラインを動画にも展開することにしました。
幸い、動画のアップスケール自体はffmpeg + Real-ESRGANの自動化パイプラインとして既に別記事で構築済みです。 今回のテーマは「アップスケール後の動画をどう解析し、どうAdobe Stock申請の準備をするか」という部分になります。
この記事で書くこと
- CLIPが動画を直接扱えない問題と、代表フレーム抽出による解決策
- 手ブレ・シーン数・無音区間など動画特有の技術チェック項目
- 設定ファイル(stockphoto.json)とExcel実体、それぞれ分けるか一体にするかの判断
- 画像用Excelと動画用Excelの列が衝突しないようにする設計方針
CLIPは1枚の画像しか評価できない問題
画像側では、CLIP(美的評価スコア)とOpenCVによる画質指標(シャープネス・コントラスト・ノイズなど)を1枚の静止画に対して計算していました。 しかし動画は時間軸を持つデータなので、そのままこのロジックを適用することはできません。
単純に「動画の最初の1フレームだけ」を評価する案も考えましたが、これでは動画中盤に手ブレやピンボケが混入していても検出できません。 逆に全フレームを解析するのは計算コストが高すぎて現実的ではありません。そこで「意味のある変化があったフレームだけを代表として抜き出す」方式を採用しました。
代表フレーム抽出という解決策
ffmpegのselectフィルタにはシーン検出機能があり、フレーム間の変化量(シーンスコア)が閾値を超えた瞬間だけを抽出できます。
これを使えば、動きの少ない動画は少ない枚数、カットの多い動画は多めの枚数、というように動画の中身に応じて自然に代表フレーム数が変わります。
ffmpeg -i input.mp4 -vf "select='gt(scene,0.28)',showinfo" -vsync vfr scene_%03d.jpg -y
ただし、シーン変化がほとんどない静的な動画(ゆっくりズームするだけの背景動画など)ではフレームがほぼ抽出されないケースがあります。 そのため、抽出数が最低枚数(既定3枚)を下回った場合は、尺を均等分割して機械的にフレームを抜き出すフォールバック処理を用意しました。
def extract_uniform_frames(video_path, out_dir, duration, count=6):
"""尺を等分した時刻でフレームを1枚ずつ抽出(シーン検出の補完用)"""
for i in range(count):
t = duration * (i + 0.5) / count # 各区間の中間時刻
cmd = [FFMPEG_PATH, "-ss", f"{t:.2f}", "-i", video_path,
"-frames:v", "1", "-q:v", "2", out_path, "-y"]
subprocess.run(cmd, capture_output=True, timeout=30)
抽出した代表フレーム(最大8枚)それぞれに、画像版と全く同じCLIP解析・追加チェック関数をそのまま適用し、平均値と最低値の両方を集約します。 平均値は動画全体の「だいたいの品質」を、最低値は「一番悪いカットの品質」を表すため、両方持たせることで審査落ち傾向の分析に活かせると考えています。
動画特有の技術チェック項目
代表フレームのCLIP解析に加えて、動画ならではの技術指標も必要です。以下の項目を追加しました。
| 指標 | 算出方法 | 目的 |
|---|---|---|
| 解像度・fps・尺・コーデック | ffprobeでメタ情報取得 | Adobe Stockの規定解像度を満たすか確認 |
| シーン数 | select フィルタの検出回数 | 動きの少ない静止画同然の動画を検出 |
| ブラースコア | 均等間隔サンプルのラプラシアン分散 | ピンボケフレームの有無 |
| 手ブレスコア | オプティカルフロー(Farneback法)の平均移動量 | 過度な揺れの検出 |
| 無音比率 | ffmpegのsilencedetectフィルタ | 音声トラブルの検知 |
手ブレ判定は、ブラー判定とは別に一定間隔でフレームを取得し、連続するフレーム間のオプティカルフローの平均移動量を計算する方式にしました。 シーン検出で抽出したフレーム同士は時間的に飛び飛びなので、手ブレ判定には向きません。均等間隔サンプリングと使い分けています。
flow = cv2.calcOpticalFlowFarneback(prev_gray, gray, None,
0.5, 3, 15, 3, 5, 1.2, 0)
mag = np.sqrt(flow[..., 0] ** 2 + flow[..., 1] ** 2)
shake_score = float(np.clip(1.0 - mag.mean() / 5.0, 0.0, 1.0))
Excelは分けるか、一体にするか
設計時に最初に悩んだのが「動画のExcel管理は画像と同じファイルにするか、別ファイルにするか」でした。
画像用の解析結果シートはラプラシアンや中央シャープなど静止画1枚に対する列構成が前提になっており、
動画特有の技術メタ情報(解像度・fps・尺・音声)を無理に同じシートに詰め込むと、空欄だらけの列が増えて集計しづらくなります。
一方で、設定ファイル(stockphoto.json)については、画像側が既に「工程ごとにトップレベルのセクションを追加していく」設計だったため、
動画もこの延長でanalyze_videoのような新セクションを追加する方が一貫性がありました。
結論:設定は一体化、Excel実体は分離
stockphoto.jsonは今まで通り1ファイルのまま、analyze_videoのような新セクションを追加。
Excel実体だけanalysis_results_video.xlsxとして画像用と分離することにしました。
列衝突を避けるExcel設計
もう一つ重要な設計判断が「審査結果収集(EXAM/OK/NG)を書き込む列位置」です。 画像用Excelでは、解析結果の列(A〜S、19列)の直後にSTATUS列(T列)、続けてEXAM情報(U列〜)、NG情報(AA列〜)、OK情報(AC列〜)という配置になっています。
動画用Excelは、解析結果自体の列数が画像より多くなります(動画情報グループが追加されるため、A〜AC=29列)。 もし画像と同じ「T列=STATUS」という固定位置を動画側にも使うと、動画側では既に別のデータ(シーン数など)が入っている列を上書きしてしまいます。
| 画像用Excel | 動画用Excel | |
|---|---|---|
| 解析結果の列数 | 19列(A〜S) | 29列(A〜AC) |
| STATUS列 | T列(20) | AD列(30) |
| EXAM情報 開始列 | U列(21) | AE列(31) |
| NG情報 開始列 | AA列(27) | AK列(37) |
| OK情報 開始列 | AC列(29) | AM列(39) |
この設計により、審査結果収集スクリプト(update_exam_excel.pyなど)は「どのExcelファイルの、どの列位置に書くか」だけを設定ファイルのセクション切り替えで指定でき、
スクレイピング処理自体(Adobe Stockの審査一覧ページを見に行く部分)は画像・動画で共有したまま、書き込み先だけを正しく振り分けられるようになりました。
画像Excel管理のサンプルは以下のようになります。
そして、以下が動画Excel管理のサンプルです。
まとめと次回予告
今回は設計方針の決定までをまとめました。ポイントは「代表フレーム抽出でCLIPの制約を回避する」「動画特有の技術指標を別レイヤーとして追加する」「設定は一体化・Excel実体は分離・列位置は衝突しないよう計算で確保する」の3点です。
次回は、実際にvideo_analyzer.pyを実装し、初回実行で遭遇したエラー(ffprobeのパス設定ミス、文字コードエラー、Ollamaサーバーの競合問題)とその解決策を記録します。
今回のポイントまとめ
- CLIPは1枚画像前提。動画はシーン検出で代表フレームを複数抽出し、平均・最低値に集約
- 手ブレ・シーン数・無音区間など、動画特有の技術チェックを追加のレイヤーとして実装
- 設定ファイルは1ファイルのままセクション追加、Excel実体だけ画像・動画で分離
- Excel列位置は「解析結果の列数+α」で機械的に計算し、画像・動画で衝突しないようにする