0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonでパノラマ生成だ!

Posted at

はじめに

いきなりですが、Pythonでパノラマ画像を作成してみます。

素材

今回は以下の4つの画像を合成して1枚の画像にします。

4枚の画像

結果

先に完成品をお見せします。
おお...、これはかなりいい感じで合成できますね。

panorama.jpg

環境

Windows11+Python3.12
CPUのみで合成を行います。

opencv-contrib-python 4.10.0.84
opencv-python 4.11.0.86
stitching 0.6.1

ライブラリ

CV2のStitcher_createなどが有名ですが、パラメータの解説が少ないのと、自由度が少ないので、今回はcv2をラッパーしたようなライブラリstitchingを使用します。

stitchingは日本語の解説記事がほとんど無いのですが、実装の中身がわかりやすく、パラメータ自由度も高いです。

実行

インストール

pip install stitching

コード

まずは公式の指定通りに実行してみます。

import sys
import glob
import os
import cv2
from stitching import Stitcher

# ──────────────────────────────────────────
# 設定値
FOLDER = r"img"  # 画像フォルダ
SAVE_IMAGE_NAME = r"panorama.jpg"  # 出力ファイル名

STITCH_SETTINGS = {
    "detector":     "sift",
    "confidence_threshold": 0.2,
}


if __name__ == "__main__":
    # 1. フォルダ内の画像を読み込む
    paths = sorted(glob.glob(os.path.join(FOLDER, "*.jpg"))
                   + glob.glob(os.path.join(FOLDER, "*.png")))
    images = [cv2.imread(p) for p in paths if cv2.imread(p) is not None]

    # 3. Stitcher で合成
    try:
        stitcher = Stitcher(**STITCH_SETTINGS)
        panorama = stitcher.stitch(images)
    except Exception as e:
        sys.exit(f"Stitch 失敗: {e}")

    # 4. 保存
    if cv2.imwrite(str(SAVE_IMAGE_NAME), panorama):
        print(f"保存完了 → {SAVE_IMAGE_NAME}")
    else:
        sys.exit("ERROR: 保存に失敗しました")

うまく合成されましたが、少し山頂部分が見切れていますね。
合成後の画像

分析

とりあえず分析してみましょう。
stitcher.stitch_verboseを使うことで、合成途中の経過をログ出力してくれます。
VERBOSE_DIRでフォルダ指定してください。なお、先にフォルダを作成しておかないとエラーで止まります。

- panorama = stitcher.stitch(images)
+ panorama = stitcher.stitch_verbose(images, verbose_dir=VERBOSE_DIR)

image.png

それらしいファイルが表示されました。
ポイントは以下の2枚ですね。途中の黒帯部をクロップする過程で山頂部が消えてしまったようですね。
黄・緑帯の画像が悪さをしていそうです。

06_lir.jpg

09_result_with_seam_polygons.jpg

ステップアップの改善

いくつかの改善パターンを紹介します。

detector変更

とりあえず、特徴検出器(detector)をsiftorbに変更してみます。

STITCH_SETTINGS = {
-   "detector":     "sift",
+   "detector":     "orb",
    "confidence_threshold": 0.4,
}

実行してみましたが、エラーが出ました。

Stitch 失敗: Camera parameters adjusting failed.

うまくカメラ位置が認識できていませんね。
このような場合は、verbose_dirで指定したフォルダを確認してみましょう。
03_matches_graph.txtを開いてみてください。

ダメな例:

03_matches_graph.txt
graph matches_graph{
"1" -- "2"[label="Nm=163, Ni=99, C=1.73989"];
"1" -- "3"[label="Nm=28, Ni=8, C=0.487805"];
"1" -- "4"[label="Nm=19, Ni=5, C=0.364964"];
}

正常な例:

03_matches_graph.txt
graph matches_graph{
"1" -- "2"[label="Nm=1869, Ni=1144, C=2.01161"];
"2" -- "3"[label="Nm=605, Ni=235, C=1.24011"];
"3" -- "4"[label="Nm=460, Ni=200, C=1.36986"];
}

通常、うまく認識した場合、
1→2
2→3
3→4
のように画像のつながりが認識されます。

ダメな例では、
1→2
1→3
1→4
のように、すべての画像が1枚目の画像と関連づけられています。
これではパノラマになりませんね。

match_conf 変更

ということで、特徴の関連付けがうまくできていないと仮定して、設定を変えてみます。
match_conf(マッチ信頼度閾値)を0.15に設定してみます。
orbの場合はデフォルトで0.3です。

STITCH_SETTINGS = {
    "detector":     "orb",
    "confidence_threshold": 0.4,
+   "match_conf":   0.15,
}
03_matches_graph.txt
graph matches_graph{
"1" -- "2"[label="Nm=323, Ni=174, C=1.65872"];
"2" -- "3"[label="Nm=156, Ni=38, C=0.693431"];
"3" -- "4"[label="Nm=179, Ni=84, C=1.36143"];
}

やりました!うまく合成できました。
しかし合成部の色味(空の色)の変化が気になりますね。

panorama.jpg

ブレンド変更

ブレンドの設定を変更してみます。
ひとまず以下の設定を追加してみました。

STITCH_SETTINGS = {
    "detector":     "orb",
    "confidence_threshold": 0.4,
    "match_conf":   0.15,
+   "finder": "gc_colorgrad", # シーム(重なり境界)検出器
+   "blender_type": "multiband",# ブレンダー方式
+   "blend_strength": 40, # ブレンド幅係数。大きいほど滑らか&ぼやける
}

いいですね!かなり自然な感じになりました。

panorama.jpg

設定できるパラメータ一覧

デフォルト値は★でマーキングしています。
ドキュメントが少なく、調べた限りの内容ですので、解説に間違いもあるかもしれません。

選択系のパラメータ

detector

特徴点検出アルゴリズムの選択

特徴
orb 高速・軽量。回転・照度変化に強いが、スケール変化にはやや弱い。nfeatures が有効。
sift スケール・回転・照度変化に強いが、やや遅い。nfeatures が有効。
brisk 回転・スケール両対応の高速処理。
akaze 速度と耐性のバランス型。

※ sift,brisk,akazeも割と良い。verbose_dir内の画像の緑点を確認して、適切なものを選択。


matcher_type

特徴matcherの種類 (射影/アフィン)

特徴
homography 通常のパノラマ。オーバーヘッド少なく高速。
affine アフィン変換。平面シーンやドローン俯瞰など傾きが小さい場合に有利。

estimator

カメラパラメータ初期推定器

特徴
homography 一般的パノラマ用。遠方風景や旋回撮影向け。
affine シーン深度が浅い・平面基準のとき安定(例: スキャナ画像合成)。

adjuster

バンドル調整器の種類

特徴
ray 球面射影に基づくレイ最小化。汎用的で収束性良好。
reproj 再投影誤差を直接最小化。精度は高いが失敗しやすい。
もう、ほんと失敗しやすい...。
ズレもしっかりマッチするが、アスペクト比が狂いやすい。
affine アフィンモデル専用。estimator=affine と組み合わせ必須。
no 調整を行わない。高速だがブレンドずれが残りやすい。

wave_correct_kind

回転行列のウェーブ補正方向

特徴
horiz カメラを横方向に振った普通のパノラマ。水平線が直線になる。
vert 縦方向に振った場合(上下パノラマ)。
auto 自動判別。複雑な並びでも失敗少なめ。
no 補正なし。建物が歪む/波打つことがあるが計算は速い。

※カメラの振った向きがわかっていれば、事前指定が吉


warper_type

画像の射影モデル

特徴
spherical 最も一般的。全周囲撮影に強いが両端が圧縮される。
cylindrical 地平線が水平に保たれ、上下の歪みが小さい。屋外風景で良い。
plane 透視射影。視野が狭い/ブレンド数が少ないときに自然。
mercator メルカトル。引き伸ばされるので、ゆがみが大きい
affine アフィン前提パノラマ。estimator=affine とペアで使用。

※ほかにも以下あり。
fisheye,
transverseMercator,
stereographic,
compressedPlaneA2B1,
compressedPlaneA1.5B1,
compressedPlanePortraitA2B1,
compressedPlanePortraitA1.5B1,
paniniA2B1,
paniniA1.5B1,
paniniPortraitA2B1,
paniniPortraitA1.5B1


compensator

露光誤差補正方式

特徴
gain_blocks ブロック単位で輝度ゲインを調整。周辺光量落ちに強い。
gain 画像全体で 1 値のゲインを推定。高速だがムラに弱い。
channel RGB 各チャネル毎にゲイン補正。色被りも補正。
channel_blocks channel+blocks のハイブリッド。高精度だが重い。
no 補正無効。照度差が小さい場合や処理を早めたい場合。

finder

シーム(重なり境界)検出器

特徴
dp_color Dynamic-Programming (DP) + 色差。高速で滑らかなシーム。
dp_colorgrad 色 + 勾配差を併用。テクスチャが複雑なとき縫い目が目立ちにくい。
gc_color Graph-Cut + 色差。計算コストup、テクスチャ変化大きくても比較的良好
gc_colorgrad Graph-Cut + 色+勾配。品質は良いが遅い。
voronoi 単純分割。高速だがシームが直線的で目立ちやすい。
no シーム検出なし (マスク重ね合わせのみ)。高速

blender_type

ブレンダー方式

特徴
multiband ピラミッド階層ブレンド。照度差・パララックスに強い。
feather ガウスぼかしの重み付け。高速だがゴーストが出やすい。
no マスクで単純合成。超高速だが境界がそのまま残る。

timelapse

タイムラプスモード

特徴
no 通常のパノラマ生成。
as_is 各フレームを歪ませずにパノラマ座標へ配置して保存。
crop as_is と同じだが不要領域を自動クロップして保存。

数値系パラメータ

nfeatures

detectorでorbおよびsift 選択時のみ有効。
最大キーポイント数(特徴ポイント数)
verbose_dir内の画像の緑点のこと。

設定値 説明
500 整数で指定。大きいほど特徴点が増えマッチ精度が上がるが処理時間も上がる
特徴点が多すぎても、変なマッチをすることがある。

range_width

マッチ対象の隣接画像を窓で制限

設定値 説明
-1 すべての画像ペアをマッチ
1 以上の整数 その指定枚数±n枚だけ隣接画像(フレーム)だけをマッチ。
少ない方が高速
渡す画像の順番が正しければ、異常なマッチを低減できる

match_conf

画像ペア同士の特徴マッチの閾値

設定値 説明
None アルゴリズムで自動選択(ORB: 0.3, それ以外: 0.65)
0.01.0 小さくすると低品質マッチも採用し画像が多く残る

confidence_threshold

マッチ後の画像ペアから採用するか否かを決める閾値。

設定値 説明
1.0 0–1.0。値を下げると弱いリンクも許容し枚数を優先

※ match_confでペア推定 → confidence_thresholdはそのペアの信頼度の閾値。


refinement_mask

調整する内部パラメータ
焦点距離・skew・aspectなどの調整する5個のパラメータ。xで有効
1個ずつ無効化して効果を確認。

設定値 説明
xxxxx デフォルトはすべて有効。設定例:x0x0x

medium_megapix

特徴検出・マッチング用に画像を中解像度へ縮小するときの総画素数 (メガピクセル)

設定値 説明
0.6 -1 でリサイズ無し
小さくすると速度up精度down

low_megapix

マスク・露光補正・シーム検出用の低解像度の総画素数 (メガピクセル)

設定値 説明
0.1 小さくすると速度up精度down

final_megapix

最終出力の総画素数 (メガピクセル)

設定値 説明
-1 リサイズせずフル解像度で出力
整数 指定に合わせてリサイズ。小さくすると速度up精度down

blend_strength

ブレンダーのブレンド幅係数

設定値 説明
5 0–100。大きいほど継ぎ目は滑らか・ぼやける。
20ぐらいでもいい感じ。

nr_feeds

露光誤差補正方式 (compensator)で、channelおよび"channel_block"を指定したときに有効。
更新パス回数。ムラが残るとき増やす。

設定値 説明
1 正の整数。2 以上にするとムラが安定するがその分遅くなる

block_size

同じく露光誤差補正方式 (compensator)でchannel_blocksを選択したときの補正器のブロック辺長 (px)

設定値 説明
32 大きいほど補正は滑らか,小さいと局所適応力が良くなる

crop

自動クロップ設定

特徴
True 合成後、Largest Interior Rectangle アルゴリズムで黒帯を除去。
False オリジナル範囲をそのまま出力。

try_use_gpu

CUDA の選択

設定値 説明
False GPU を使わない
True GPUが使えればシーム検出・ブレンドが高速化

設定例(フルコード)

from pathlib import Path
import sys
import glob
import os
import cv2
from stitching import Stitcher

# ──────────────────────────────────────────
# 設定値
FOLDER = r"img"  # 画像フォルダ
SAVE_IMAGE_NAME = r"panorama.jpg"  # 出力ファイル名
VERBOSE_DIR = r"debug" # 途中経過のログ出力。事前にフォルダ作成

STITCH_SETTINGS = {
    # 特徴点検出・マッチング
    "detector":     "orb",
    "nfeatures":    2000,
    "matcher_type": "homography",
    "range_width":  2,
    "match_conf":   0.15,
    # 画像選別
    "confidence_threshold": 0.4,  # 指定値
    # カメラ推定・調整
    "estimator":       "homography",
    "adjuster":        "ray",
    "refinement_mask": "x0x0x",
    "wave_correct_kind": "horiz",
    # ワーピング
    "warper_type": "cylindrical",
    # 露光補正
    "compensator": "gain_blocks",
    "nr_feeds":    2,
    "block_size":  32,
    # シーム検出
    "finder": "gc_colorgrad",
    # ブレンディング
    "blender_type":   "multiband",
    "blend_strength": 20,
    # タイムラプス
    "timelapse":        "no",
    "timelapse_prefix": "fixed_",
    # そのほか
    "crop": True,
    "try_use_gpu": False,
}


if __name__ == "__main__":
    # 1. フォルダ内の画像を読み込む(拡張子は適当に変更可)
    paths = sorted(glob.glob(os.path.join(FOLDER, "*.jpg"))
                   + glob.glob(os.path.join(FOLDER, "*.png")))
    images = [cv2.imread(p) for p in paths if cv2.imread(p) is not None]

    # 3. Stitcher で合成
    try:
        """Stitcher でパノラマ作成"""
        stitcher = Stitcher(**STITCH_SETTINGS)
        panorama = stitcher.stitch_verbose(images, verbose_dir=VERBOSE_DIR)
    except Exception as e:
        sys.exit(f"Stitch 失敗: {e}")

    # 4. 保存
    if cv2.imwrite(str(SAVE_IMAGE_NAME), panorama):
        print(f"保存完了 → {SAVE_IMAGE_NAME}")
    else:
        sys.exit("ERROR: 保存に失敗しました")

Tips

iPhoneなどのパノラマ撮影は、スマホ内の加速度センサなどを組み合わせ、カメラ位置・角度を推定しながら、高精度に合成しています。
しかし、今回の方法では画像内の特徴量だけを頼りにパノラマ化を行うので、背景に特徴がなかったり、同じような構図が続く場合はうまく合成が上手くいきません。

  • 特徴的な物体を配置して撮影するなど、撮影時点から工夫
  • オーバーラップを十分に取る(30%〜50%程度)
  • カメラの視点(レンズの中心)を動かさず、その場で回転するように撮影する(視差を減らす)

といったテクニックが有効です。

こちらの記事が非常に参考になりました。

おわりに

stitchingを使ったパノラマ画像の生成を解説しました。

パノラマ化の専用ソフトは多々あれど、画像検査の工程など、パノラマ化を自動工程に組み込みたいケースも多々あると思います。
そんなときにpython+stitchingで自動化してみましょう!

難しさもありますが、うまく合成できると非常に面白いので、みなさんもぜひ試してみてください。

次回記事では動画からパノラマ化までをトライしてみようと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?