OpenCVで静止画から動画を作成する方法

## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/ae931439-64d2-40f4-a96a-d01aae86a537.png)

複数の静止画から動画を作成したいケースは多いと思います。この記事では、OpenCVを使用して静止画から動画を作成する方法を解説します。シンプルで高速な処理が可能なため、大量の画像を扱う場合に特に有効です。

## 環境準備

必要なライブラリをインストールします：

```bash
pip install opencv-python
pip install tqdm  # 進捗表示用（オプション）
```

## 基本的な実装

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/34dfc2af-77b1-4dc3-8296-87d3da5aed33.png)

### 1. シンプルな動画作成

```python
import cv2
import glob
import os

def create_video(image_folder, output_path, fps=30):
    # 画像ファイルの取得（.jpg, .png対応）
    images = []
    for ext in ('*.jpg', '*.png'):
        images.extend(glob.glob(os.path.join(image_folder, ext)))
    images = sorted(images)
    
    if not images:
        print("画像が見つかりません")
        return
    
    # 最初の画像から動画サイズを取得
    frame = cv2.imread(images[0])
    height, width, layers = frame.shape
    
    # 動画ファイルの設定
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
    
    # 各画像を動画に追加
    for image in images:
        frame = cv2.imread(image)
        video.write(frame)
    
    video.release()
    print(f"動画を保存しました: {output_path}")
```

### 2. 進捗表示付きの実装

```python
from tqdm import tqdm

def create_video_with_progress(image_folder, output_path, fps=30):
    images = sorted(glob.glob(os.path.join(image_folder, '*.jpg')))
    
    if not images:
        print("画像が見つかりません")
        return
    
    frame = cv2.imread(images[0])
    height, width, layers = frame.shape
    
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
    
    for image in tqdm(images, desc="動画作成中"):
        frame = cv2.imread(image)
        video.write(frame)
    
    video.release()
```

### 3. リサイズ機能付きの実装

```python
import numpy as np

def resize_frame(frame, target_size=(1920, 1080)):
    """アスペクト比を保持しながらリサイズ"""
    h, w = frame.shape[:2]
    aspect = w / h
    
    if aspect > target_size[0] / target_size[1]:
        # 幅に合わせてリサイズ
        new_w = target_size[0]
        new_h = int(new_w / aspect)
    else:
        # 高さに合わせてリサイズ
        new_h = target_size[1]
        new_w = int(new_h * aspect)
    
    resized = cv2.resize(frame, (new_w, new_h))
    
    # 黒背景に中央配置
    result = np.zeros((target_size[1], target_size[0], 3), dtype=np.uint8)
    y_offset = (target_size[1] - new_h) // 2
    x_offset = (target_size[0] - new_w) // 2
    result[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized
    
    return result

def create_video_with_resize(image_folder, output_path, target_size=(1920, 1080), fps=30):
    images = sorted(glob.glob(os.path.join(image_folder, '*.jpg')))
    
    if not images:
        print("画像が見つかりません")
        return
    
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter(output_path, fourcc, fps, target_size)
    
    for image in tqdm(images, desc="動画作成中"):
        frame = cv2.imread(image)
        if frame is None:
            print(f"警告: {image} を読み込めませんでした")
            continue
        
        resized_frame = resize_frame(frame, target_size)
        video.write(resized_frame)
    
    video.release()
    print(f"動画を保存しました: {output_path}")
```

## 使用例

```python
# 基本的な使用方法
create_video("images", "output.mp4")

# リサイズ機能付きで使用（フルHD）
create_video_with_resize("images", "output_hd.mp4", target_size=(1920, 1080))

# 進捗表示付きで使用
create_video_with_progress("images", "output_with_progress.mp4")
```

<iframe width="560" height="315" src="https://www.youtube.com/embed/gQFuuysYALU?si=uWeJHVELxO1O17uB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## トラブルシューティング

### 1. 生成された動画が再生できない場合
コーデックを変更することで解決できる場合があります：

```python
# H264コーデックを使用
fourcc = cv2.VideoWriter_fourcc(*'avc1')  # または 'H264'
```

### 2. メモリエラーが発生する場合
バッチ処理を導入してメモリ使用量を抑えることができます：

```python
def create_video_memory_efficient(image_folder, output_path, batch_size=10):
    images = sorted(glob.glob(os.path.join(image_folder, '*.jpg')))
    
    frame = cv2.imread(images[0])
    height, width, layers = frame.shape
    
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter(output_path, fourcc, 30, (width, height))
    
    # バッチ処理でメモリ使用量を抑える
    for i in range(0, len(images), batch_size):
        batch = images[i:i+batch_size]
        for image in batch:
            frame = cv2.imread(image)
            video.write(frame)
    
    video.release()
```

### 3. よくある問題と解決策

1. **画像の読み込みエラー**
   - 画像パスに日本語が含まれていないか確認
   - ファイルのパーミッションを確認
   - 対応していない画像形式でないか確認

2. **出力動画のサイズが大きすぎる**
   - fpsを調整（30→24など）
   - リサイズ機能を使用して解像度を下げる
   - 圧縮率の高いコーデックを使用

3. **処理が遅い場合**
   - バッチサイズを調整
   - 画像を事前にリサイズ
   - マルチプロセス処理の導入を検討

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/18c8d459-3615-417b-8a26-aac77093f0b2.png)

OpenCVを使用することで、以下のような利点があります：
- シンプルなコードで動画作成が可能
- 処理が高速
- メモリ効率が良い
- カスタマイズ性が高い

## 参考リンク
- [OpenCV公式ドキュメント](https://docs.opencv.org/)
- [OpenCV-Python チュートリアル](https://docs.opencv.org/master/d6/d00/tutorial_py_root.html)

## 参考: 静止画像を生成するプログラム

```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image

# 画像サイズ
width, height = 300, 200
num_frames = 50  # フレーム数

# 猫の移動範囲（横方向）
x_positions = np.linspace(20, width - 80, num_frames)

# 画像を保存するリスト
image_files = []

# 画像を生成
for i in range(num_frames):
    fig, ax = plt.subplots(figsize=(width / 50, height / 50), dpi=50)
    ax.set_xlim(0, width)
    ax.set_ylim(0, height)
    ax.set_xticks([])
    ax.set_yticks([])
    ax.axis("off")

    # 背景色
    ax.set_facecolor("lightblue")

    # 猫の胴体
    body = patches.Ellipse((x_positions[i], 100), 60, 30, color="black")
    ax.add_patch(body)

    # 頭
    head = patches.Circle((x_positions[i] + 35, 110), 15, color="black")
    ax.add_patch(head)

    # 足（交互に動かす）
    leg1_y = 85 if i % 2 == 0 else 75
    leg2_y = 75 if i % 2 == 0 else 85
    ax.plot([x_positions[i] - 20, x_positions[i] - 15], [leg1_y, 60], color="black", linewidth=3)
    ax.plot([x_positions[i] + 20, x_positions[i] + 15], [leg2_y, 60], color="black", linewidth=3)

    # 尾
    tail = patches.Arc((x_positions[i] - 30, 110), 30, 30, angle=0, theta1=30, theta2=150, color="black", linewidth=3)
    ax.add_patch(tail)

    # 画像を保存
    filename = f"image{i+1:03d}.png"
    plt.savefig(filename, bbox_inches='tight', pad_inches=0.1)
    plt.close(fig)
    image_files.append(filename)

# 生成した画像を確認
image_files[:5]  # 最初の5枚を表示
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/16a57e55-5221-4534-a06b-ed2c55fa4f1c.png)