この記事はOpenCV Advent Calendar 2025の24日目の記事です。
はじめに
X(旧Twitter)は、アウトプットを行うには最も手軽なプラットフォーム(の一つ)です。
しかし、OpenCV で何も考えずに VideoWriter()で動画を出力して、X にアップロードしようとすると、以下のようにアップロードできないことが多いですね👀
import cv2
import numpy as np
# 動画設定
WIDTH = 640
HEIGHT = 480
FPS = 30
DURATION_SEC = 5
# VideoWriter初期化
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter("videowriter01.mp4", fourcc, FPS, (WIDTH, HEIGHT))
# フレームごとに色を変化させて動画を書き出す
total_frames = FPS * DURATION_SEC
for i in range(total_frames):
hue = int((i / total_frames) * 180)
hsv = np.full((HEIGHT, WIDTH, 3), (hue, 255, 255), dtype=np.uint8)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
writer.write(bgr)
writer.release()
Q.なぜ?
A.OpenCVの mp4v 書き出しは(環境にもよるが)MPEG-4 Part2 で、X がサポートしているのは H.264 (MPEG-4 Part10 / AVC) だから
なので、FFmpegがインストールされている環境であれば、以下のように変換するか
import os
import cv2
import numpy as np
import subprocess
# 動画設定
WIDTH, HEIGHT = 640, 480
FPS = 30
DURATION_SEC = 5
TEMP_FILE = "videowriter01.mp4"
OUTPUT_FILE = "videowriter02.mp4"
# VideoWriter初期化
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter(TEMP_FILE, fourcc, FPS, (WIDTH, HEIGHT))
# フレームごとに色を変化させて動画を書き出す
total_frames = FPS * DURATION_SEC
for i in range(total_frames):
hue = int((i / total_frames) * 180)
hsv = np.full((HEIGHT, WIDTH, 3), (hue, 255, 255), dtype=np.uint8)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
writer.write(bgr)
writer.release()
# FFmpegでH.264(yuv420p)に変換
subprocess.run([
"ffmpeg", "-y", "-i", TEMP_FILE,
"-c:v", "libx264", "-pix_fmt", "yuv420p",
OUTPUT_FILE
], check=True)
# 一時ファイルを削除
os.remove(TEMP_FILE)
Windows環境であれば、Cisco の OpenH264(cisco/openh264/releases/tag/v1.8.0)からopenh264-1.8.0-win64.dllを取得してきて、スクリプトと同じディレクトリに配置し、以下を実行するのもありです(LinuxとかMacも同様だっけ?)
"""時間経過で色が変化する動画を生成(OpenCVのみ・Twitter対応)"""
import cv2
import numpy as np
# 動画設定
WIDTH, HEIGHT = 640, 480
FPS = 30
DURATION_SEC = 5
OUTPUT_FILE = "videowriter03.mp4"
# H.264コーデック(avc1)でVideoWriter初期化
fourcc = cv2.VideoWriter_fourcc(*"avc1")
writer = cv2.VideoWriter(OUTPUT_FILE, fourcc, FPS, (WIDTH, HEIGHT))
# フレームごとに色を変化させて書き出し
total_frames = FPS * DURATION_SEC
for i in range(total_frames):
hue = int((i / total_frames) * 180)
hsv = np.full((HEIGHT, WIDTH, 3), (hue, 255, 255), dtype=np.uint8)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
writer.write(bgr)
writer.release()
しかし、opencv-python が想定している OpenH264 のバージョンは古いですね、、、👀
※最新は2.5.1ですが、Xが要求するのは標準的なH.264のため、1.8.0で十分ではあります
ちなみに、横長動画
画像処理していると、以下のように入力動画と処理動画を横に並べて眺めたいことありますよね?
あるよね👀?
ちなみに以下は、LLIE(Low-Light Image Enhancement)で暗闇画像を補正した例です。
これを X にアップロードしようとすると「アップロードしようとした動画の縦横比が大きすぎます」となり、アップロードできません。
Q.なぜ?
A.エラー文の通りですが、X では 1:3 〜 3:1 の縦横比のみ対応しているため
3:1に収まるように縦方向に結合するか、トリミングして横方向に結合するとアップロードできます。
確認のため、以下のように複数解像度で動画を生成してみました。
import os
import cv2
import numpy as np
OUTPUT_DIR = "output"
FPS = 30
DURATION_SEC = 3
BASE = 720
# Twitter対応: 1:3 〜 3:1 の境界付近
RESOLUTIONS = [
(int(BASE * 2.9), BASE, "wide_2.9_ok"),
(int(BASE * 3.0), BASE, "wide_3.0_boundary"),
(int(BASE * 3.1), BASE, "wide_3.1_ng"),
(BASE, int(BASE * 2.9), "tall_2.9_ok"),
(BASE, int(BASE * 3.0), "tall_3.0_boundary"),
(BASE, int(BASE * 3.1), "tall_3.1_ng"),
]
os.makedirs(OUTPUT_DIR, exist_ok=True)
for width, height, name in RESOLUTIONS:
filepath = os.path.join(OUTPUT_DIR, f"{name}.mp4")
print(f"生成中: {name} ({width}x{height})")
fourcc = cv2.VideoWriter_fourcc(*"avc1")
writer = cv2.VideoWriter(filepath, fourcc, FPS, (width, height))
for i in range(FPS * DURATION_SEC):
hue = int((i / (FPS * DURATION_SEC)) * 180)
hsv = np.full((height, width, 3), (hue, 255, 255), dtype=np.uint8)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
writer.write(bgr)
writer.release()
| 比率 | 解像度例 | アップロード |
|---|---|---|
| 2.9:1 | 2088x720 | ✅ OK |
| 3.0:1 | 2160x720 | ✅ OK |
| 3.1:1 | 2232x720 | ❌ NG |
| 1:2.9 | 720x2088 | ✅ OK |
| 1:3.0 | 720x2160 | ✅ OK |
| 1:3.1 | 720x2232 | ❌ NG |
その他、動画要件など
X に動画をアップロードする場合は以下の要件👀
| 項目 | 要件 |
|---|---|
| 映像コーデック | H.264 |
| ピクセルフォーマット | YUV 4:2:0(yuv420p)のみ |
| フレームレート | 60fps以下 |
| 音声コーデック | AAC LC(音声がある場合) |
| アカウント/環境 | 最大動画長さ | 備考 |
|---|---|---|
| 通常アカウント | 140秒(2分20秒) | |
| Premium(Web/iOS) | 最大4時間 | 16GB上限など制限あり |
| Premium(Android) | 最大10分 | AndroidはiOSより厳しめ |
※さらに補足
GOP構造(極端に長いGOPやOpen GOP)や moov atom の位置などによって、
アップロード時点で弾かれるケースもありそうです。
ただし、OpenCV単体ではこれらの設定を細かく制御するのが難しく、検証対象外としています。
FFmpegで再エンコードするのが無難です。
以上。




