Pythonを用いた総当たりステッチ処理

## はじめに

このプログラムは、OpenCVライブラリを使用して、画像をステッチし、成功した画像と失敗した画像を分類します。
また、ステッチ成功した画像を保存し、最終的にすべての成功した画像を1つのフォルダにコピーします。

### 実行環境
python-version 3.8.8
cv2-version 4.7.0

## 必要なライブラリのインストール
まず、必要なライブラリをインストールします。
以下のコマンドを実行してください。

```console
pip install opencv-python-headless
```

## プログラムの全体像
以下がプログラムの全体像です。
このプログラムを順を追って説明していきます。

```python
import cv2
import os
import shutil
import glob
import copy
import re

# 総当たりでステッチ処理
def stich_round_robin(image_files, save_dir):
    # スキャンモードのStitcherクラスを作成する
    stitcher_scans = cv2.Stitcher_create(cv2.Stitcher_SCANS) 
    # ステッチ成功した画像パスリスト
    success_image_filed = []
    # ステッチ失敗した画像パスリスト
    false_image_files = copy.copy(image_files)
    
    for i in range(0, len(image_files) - 1):
        for j in range(i + 1, len(image_files)):
            # 対象画像読み込み
            images = [cv2.imread(image_files[i]), cv2.imread(image_files[j])]
            # スキャンモードで画像をステッチ（つなぎ合わせ）する
            status_scans, pano_scans = stitcher_scans.stitch(images)
            if status_scans == cv2.Stitcher_OK:
                file_name_i = os.path.splitext(os.path.basename(image_files[i]))[0]
                file_name_j = os.path.splitext(os.path.basename(image_files[j]))[0]
                # 成功したステッチ画像を保存
                new_file_name = re.sub(r'(_\w+)\1', r'\1', f'{file_name_i}_{file_name_j}')
                save_path = os.path.join(save_dir, f'{new_file_name}.jpg')
                cv2.imwrite(save_path, pano_scans)
                success_image_filed.append(save_path)
                # ステッチ失敗した画像パスリストから削除
                for image_file in [image_files[i], image_files[j]]:
                    if image_file in false_image_files:
                        false_image_files.remove(image_file)
                print(f'成功:{file_name_i}＋{file_name_j}')
            else:
                print(f'失敗:ステータスコード={status_scans}')
    
    return success_image_filed, false_image_files

# 画像ファイルを読み込んでリストにする
target_dir = r'./work/'
result_dir = r'./result/'
image_files = glob.glob(f'{target_dir}*.jpg')
cnt = 0
s_list, f_list = stich_round_robin(image_files, target_dir)
print('結果\n成功\n', s_list)
print('失敗\n', f_list)
for f_img in f_list:
    img_name = os.path.basename(f_img)
    shutil.copy(f_img, f'{result_dir}/{img_name}')

flag = len(s_list)
while flag > 1:
    s_list, f_list = stich_round_robin(s_list, target_dir)
    print('結果\n成功\n', s_list)
    print('失敗\n', f_list)
    for f_img in f_list:
        img_name = os.path.basename(f_img)
        shutil.copy(f_img, f'{result_dir}/{img_name}')
    flag = len(s_list)

img_name = os.path.basename(s_list[0])
shutil.copy(s_list[0], f'{result_dir}/{img_name}')
```

## コードの解説
### 1. ライブラリのインポート
必要なライブラリをインポートします。

```python
import cv2
import os
import shutil
import glob
import copy
import re
```

### 2. 総当たりでステッチ処理を行う関数 stich_round_robin
この関数は指定された画像ファイルリストに対して総当たりでステッチ処理を行います。

```python
def stich_round_robin(image_files, save_dir):
    stitcher_scans = cv2.Stitcher_create(cv2.Stitcher_SCANS) 
    success_image_filed = []
    false_image_files = copy.copy(image_files)
    
    for i in range(0, len(image_files) - 1):
        for j in range(i + 1, len(image_files)):
            images = [cv2.imread(image_files[i]), cv2.imread(image_files[j])]
            status_scans, pano_scans = stitcher_scans.stitch(images)
            if status_scans == cv2.Stitcher_OK:
                file_name_i = os.path.splitext(os.path.basename(image_files[i]))[0]
                file_name_j = os.path.splitext(os.path.basename(image_files[j]))[0]
                new_file_name = re.sub(r'(_\w+)\1', r'\1', f'{file_name_i}_{file_name_j}')
                save_path = os.path.join(save_dir, f'{new_file_name}.jpg')
                cv2.imwrite(save_path, pano_scans)
                success_image_filed.append(save_path)
                for image_file in [image_files[i], image_files[j]]:
                    if image_file in false_image_files:
                        false_image_files.remove(image_file)
                print(f'成功:{file_name_i}＋{file_name_j}')
            else:
                print(f'失敗:ステータスコード={status_scans}')
    
    return success_image_filed, false_image_files

```
### 3. 画像ファイルを読み込んでリストにする
指定されたディレクトリから画像ファイルを読み込みます。

```python
target_dir = r'./work/'
result_dir = r'./result/'
image_files = glob.glob(f'{target_dir}*.jpg')
cnt = 0
s_list, f_list = stich_round_robin(image_files, target_dir)
print('結果\n成功\n', s_list)
print('失敗\n', f_list)
for f_img in f_list:
    img_name = os.path.basename(f_img)
    shutil.copy(f_img, f'{result_dir}/{img_name}')
```

### 4. ステッチ処理を繰り返す
成功したステッチ画像をさらにステッチする処理を繰り返します。

```python
flag = len(s_list)
while flag > 1:
    s_list, f_list = stich_round_robin(s_list, target_dir)
    print('結果\n成功\n', s_list)
    print('失敗\n', f_list)
    for f_img in f_list:
        img_name = os.path.basename(f_img)
        shutil.copy(f_img, f'{result_dir}/{img_name}')
    flag = len(s_list)

img_name = os.path.basename(s_list[0])
shutil.copy(s_list[0], f'{result_dir}/{img_name}')

```


## 実行方法
1.必要なライブラリをインストールします。
2.スクリプトを保存し、Python環境で実行します。
3../work/ フォルダに画像ファイルを配置します。
4.スクリプトを実行すると、ステッチされた画像が ./result/ フォルダに保存されます。
## まとめ
この記事では、OpenCVを使用して画像を総当たりでステッチするPythonプログラムについて説明しました。このプログラムを応用することで、複数の画像を自動的に結合する処理を簡単に実現できます。ぜひ試してみてください。