このチュートリアルでは、Python と OpenCV、および cv2.createStitcher
と cv2.Stitcher_create
関数を使用して画像のステッチングを実行する方法を学習します。 今日のコードを使用すると、複数の画像をつなぎ合わせて、つなぎ合わせた画像のパノラマを作成することができるようになります。
ちょうど 2 年弱前、私は画像のステッチングとパノラマ構築に関する 2 つのガイドを公開しました。
- Fundamentals of image stitching
- Real-time panorama and image stitching
これらのチュートリアルでは、典型的な画像ステッチング アルゴリズムの基本を説明しましたが、最低限、次の 4 つの主要ステップが必要です:
- キーポイント (DoG, Harris, etc.) を検出すること。を検出し、局所不変記述子(SIFT、SURF など)を抽出する。
- 画像間の記述子のマッチング
- RANSACアルゴリズムを用いて、マッチした特徴ベクトルを用いてホモグラフィ行列を推定
- ステップ3で得られたホモグラフィ行列を用いてワープ変換を適用
しかし私のオリジナルの実装の最大の問題は、2枚以上の入力画像に対して対応できないことでした。
今日のチュートリアルでは、OpenCV による画像のステッチングを再考し、2 つ以上の画像をステッチしてパノラマ画像にする方法を紹介します。
OpenCV と Python による画像のステッチング方法を学ぶために、読み続けてください!
- この投稿のソース コードをお探しですか?
- Image Stitching with OpenCV and Python
- OpenCV のイメージ スティッチング アルゴリズム
- プロジェクトの構造
- The cv2.createStitcher and cv2.Stitcher_create functions
- Implementing image stitching with Python
- 基本的なイメージ スティッチングの結果
- A better image stitcher with OpenCV and Python
- 改良されたイメージ スティッチング結果
- 制限と欠点
- OpenCV を使用して画像のステッチングを行う際にエラーに遭遇することはありますか。
この投稿のソース コードをお探しですか?
ダウンロードセクションへジャンプ
Image Stitching with OpenCV and Python
今日のチュートリアルの最初のパートでは、OpenCV のイメージステッチングアルゴリズムを簡単にレビューします。
そこからプロジェクトの構造を見直し、イメージのステッチングに使用できる Python スクリプトを実装します。
この最初のスクリプトの結果を見直し、その制限に注意し、次に、より美的に美しいイメージ ステッチング結果に使用できる、2 番目の Python スクリプトを実装します。
OpenCV のイメージ スティッチング アルゴリズム
今日ここで使用するアルゴリズムは、Brown と Lowe が 2007 年の論文、Automatic Panoramic Image Stitching with Invariant Feature で提案した方法と同様なものです。
入力画像の順序に敏感な以前の画像ステッチング アルゴリズムとは異なり、Brown and Lowe の方法はより堅牢で、次のようなことに影響を受けません。
- 画像の順序
- 画像の向き
- 照明の変化
- 実際にはパノラマの一部ではないノイズ画像
さらに、彼らの画像ステッチング方法は、利得補償と画像ブレンドを使用し、より審美的に好ましい出力パノラマ画像を作り出すことが可能です。
アルゴリズムの完全で詳細なレビューはこの投稿の範囲外ですので、さらに詳しく知りたい場合は、オリジナルの出版物を参照してください。
プロジェクトの構造
このプロジェクトが tree
コマンドでどのように構成されているかを見てみましょう。
Today we’re reviewing two Python scripts:
-
image_stitching_simple.py
: このシンプルなイメージスティッチングは、50 行未満の Python コードで完了します。 -
image_stitching.py
: このスクリプトには、ステッチされた画像の ROI を抽出し、美的な結果を得るための私のハックが含まれています。
The cv2.createStitcher and cv2.Stitcher_create functions
OpenCV はすでに
cv2.createStitcher
(OpenCV 3.) を通じて Brown and Lowe の論文と同様の方法を実装しています。OpenCV が正しく設定されインストールされていると仮定すると、OpenCV 3.x の
cv2.createStitcher
関数のシグネチャを調査することができます:createStitcher(...) createStitcher() -> retval
この関数には
try_gpu
という単一のパラメーターしかなく、画像ステッチング パイプライン全体を改善するために使用できることに注意してください。 OpenCVのGPUサポートは限られており、私はこのパラメータを動作させることができなかったので、常にFalse
のままにしておくことをお勧めします。OpenCV 4 の
cv2.Stitcher_create
関数も同様のシグネチャを持っています。Stitcher_create(...) Stitcher_create() -> retval . @brief Creates a Stitcher configured in one of the stitching .modes. . . @param mode Scenario for stitcher operation. This is usually .determined by source of images to stitch and their transformation. .Default parameters will be chosen for operation in given scenario. . @return Stitcher class instance.
実際の画像のスティッチングを行うには、
.stitch
メソッドを呼び出す必要があります:OpenCV 3.x:stitch(...) method of cv2.Stitcher instance stitch(images) -> retval, panoOpenCV 4.x:stitch(...) method of cv2.Stitcher instance stitch(images, masks) -> retval, pano . @brief These functions try to stitch the given images. . . @param images Input images. . @param masks Masks for each input image specifying where to .look for keypoints (optional). . @param pano Final pano. . @return Status code.
このメソッドは入力
images
を受け取り、パノラマへのスティッチングを行って出力画像を呼び出し関数に返します。status
変数は画像のスティッチングが成功したかどうかを示し、次の4つの変数のうちの1つを取ることができます:-
OK = 0
:画像のスティッチングは成功しました。 -
ERR_NEED_MORE_IMGS = 1
:このステータスコードを受け取った場合、パノラマ画像を作成するにはさらに入力画像を必要とします。 -
ERR_HOMOGRAPHY_EST_FAIL = 2
: このエラーは、RANSACホモグラフィの推定に失敗した場合に発生します。 -
ERR_CAMERA_PARAMS_ADJUST_FAIL = 3
: このエラーに遭遇したことがないため、あまり知識がありませんが、要は入力画像からカメラの内部構造/外部構造を適切に推定できていないことに関連していると思われます。 このエラーが発生した場合は、OpenCV のドキュメントを参照するか、OpenCV C++ のコードに飛び込む必要があるかもしれません。
ここまでで、
cv2.createStitcher
、cv2.Stitcher_create
、.stitch
メソッドを確認しましたが、次は実際に OpenCV と Python で画像ステッチを実装しましょう。Implementing image stitching with Python
Let’s go ahead and get started implementing our image stitching algorithm!
image_stitching_simple.py
ファイルを開き、次のコードを挿入します:# import the necessary packagesfrom imutils import pathsimport numpy as npimport argparseimport imutilsimport cv2# construct the argument parser and parse the argumentsap = argparse.ArgumentParser()ap.add_argument("-i", "--images", type=str, required=True,help="path to input directory of images to stitch")ap.add_argument("-o", "--output", type=str, required=True,help="path to the output image")args = vars(ap.parse_args())
Lines 2-6 で必要なパッケージは import されています。 特に、OpenCVとimutilsを使用する予定です。 OpenCV をインストールするには、私の OpenCV インストール ガイドの 1 つに従ってください。
pip install --upgrade imutils
. -
そこから、9-14行目で2つのコマンドライン引数を解析します:
-
--images
:スティッチする入力画像のディレクトリへのパス。 -
--output
: 結果を保存する出力画像へのパス。
argparse
とコマンドライン引数の概念に慣れていない場合は、このブログ記事を読んでみてください。
入力画像をロードしましょう。
# grab the paths to the input images and initialize our images listprint(" loading images...")imagePaths = sorted(list(paths.list_images(args)))images = # loop over the image paths, load each one, and add them to our# images to stitch listfor imagePath in imagePaths:image = cv2.imread(imagePath)images.append(image)
ここで imagePaths
を取得します (行 18)。
それから各 imagePath
に対して、image
をロードして images
リストに追加します (行 19-25).
これで images
がメモリに入ったので、OpenCV の組み込み機能を使ってパノラマにつなぎ合わせてみましょう:
# initialize OpenCV's image stitcher object and then perform the image# stitchingprint(" stitching images...")stitcher = cv2.createStitcher() if imutils.is_cv3() else cv2.Stitcher_create()(status, stitched) = stitcher.stitch(images)
30行目で stitcher
オブジェクトが作成されています。 OpenCV 3 か 4 かによって、異なるコンストラクタが呼び出されることに注意してください。
その後、images
を .stitch
メソッド (ライン 31) に渡すことができます。 .stitch
の呼び出しにより、status
と stitched
の画像が返されます (スティッチングが成功したと仮定しています)。
最後に、(1) ステッチしたイメージをディスクに書き込み、(2) 画面に表示します。
# if the status is '0', then OpenCV successfully performed image# stitchingif status == 0:# write the output stitched image to diskcv2.imwrite(args, stitched)# display the output stitched image to our screencv2.imshow("Stitched", stitched)cv2.waitKey(0)# otherwise the stitching failed, likely due to not enough keypoints)# being detectedelse:print(" image stitching failed ({})".format(status))
status
フラグが成功を示していると仮定すると (35 行目)、stitched
イメージをディスクに書き込み (37 行目)、キーが押されるまで (40 と 41 行目) それを表示します (
さもなければ、単に失敗メッセージを表示します (45 と 46 行目)。
基本的なイメージ スティッチングの結果
このイメージ スティッチング スクリプトを試すには、チュートリアルの「ダウンロード」セクションでソース コードとサンプル画像をダウンロードするようにしてください。
images/scottsdale/
ディレクトリの中に、アリゾナ州スコッツデールにあるフランク ロイド ライトの有名な Taliesin West ハウスを訪れた際に撮った 3 枚の写真があります。 これらの画像は、私がアリゾナ州スコッツデールのフランク・ロイド・ライトの有名な Taliesin West 家で撮影したものです。
我々の目標は、これら 3 つの画像を 1 つのパノラマ画像にステッチすることです。 スティッチングを実行するには、ターミナルを開き、コードと画像をダウンロードした場所に移動して、次のコマンドを実行します。 この画像はスティッチングが行われましたが、まだトリミングされていません。
画像スティッチングがうまくいったことに注目してください!
しかし、パノラマを囲む黒い領域はどうでしょうか?
これらの領域は、パノラマを構築するために必要なパースペクティブ ワープを実行したことによるものです。
A better image stitcher with OpenCV and Python
最初の画像ステッチング スクリプトは良いスタートでしたが、パノラマ自体を囲むそれらの黒い領域は「美的に好ましい」と呼ぶべきものではありません。
さらに言えば、iOS や Android などに組み込まれている一般的なイメージ スティッチング アプリケーションでは、このような出力イメージは見られません。
そこで、このスクリプトを少しハックして、より美的に楽しいパノラマを作成するための追加ロジックをいくつか追加します。
私たちが望む結果を得るために、閾値、輪郭抽出、モルフォロジー操作などの基本的な画像処理操作を確認します。
私の知る限り、OpenCV の Python バインディングは、パノラマの内側の最大長方形領域を手動で抽出するための必要情報を提供してくれません。 OpenCV が提供している場合は、ぜひコメントで教えてください。
さっそく始めましょう。image_stitching.py
スクリプトを開き、次のコードを挿入してください:
# import the necessary packagesfrom imutils import pathsimport numpy as npimport argparseimport imutilsimport cv2# construct the argument parser and parse the argumentsap = argparse.ArgumentParser()ap.add_argument("-i", "--images", type=str, required=True,help="path to input directory of images to stitch")ap.add_argument("-o", "--output", type=str, required=True,help="path to the output image")ap.add_argument("-c", "--crop", type=int, default=0,help="whether to crop out largest rectangular region")args = vars(ap.parse_args())# grab the paths to the input images and initialize our images listprint(" loading images...")imagePaths = sorted(list(paths.list_images(args)))images = # loop over the image paths, load each one, and add them to our# images to stich listfor imagePath in imagePaths:image = cv2.imread(imagePath)images.append(image)# initialize OpenCV's image sticher object and then perform the image# stitchingprint(" stitching images...")stitcher = cv2.createStitcher() if imutils.is_cv3() else cv2.Stitcher_create()(status, stitched) = stitcher.stitch(images)
このコードはすべて以前のスクリプトと同じですが、ひとつだけ例外があります。
次のステップでは、追加の機能を実装します。
# if the status is '0', then OpenCV successfully performed image# stitchingif status == 0:# check to see if we supposed to crop out the largest rectangular# region from the stitched imageif args > 0:# create a 10 pixel border surrounding the stitched imageprint(" cropping...")stitched = cv2.copyMakeBorder(stitched, 10, 10, 10, 10,cv2.BORDER_CONSTANT, (0, 0, 0))# convert the stitched image to grayscale and threshold it# such that all pixels greater than zero are set to 255# (foreground) while all others remain 0 (background)gray = cv2.cvtColor(stitched, cv2.COLOR_BGR2GRAY)thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
40 行目で --crop
フラグが設定されたときのために新しいブロックを作成したことに注目してください。
- 最初に、
stitched
イメージのすべての側面に10
ピクセルの境界線を追加し (行 43 と行 44)、このセクションの後半でパノラマ全体の輪郭を見つけることができるようにします。 - 次に、
stitched
イメージのgray
バージョンを作成します(49行目)。 - そこから、
gray
イメージにしきい値を設定します(50行目)。
これらの 3 つのステップの結果 (thresh
) は次のとおりです。
これで、白いピクセル (255) が前景、黒いピクセル (0) が背景となる、パノラマのバイナリ画像が得られました。
# find all external contours in the threshold image then find# the *largest* contour which will be the contour/outline of# the stitched imagecnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)cnts = imutils.grab_contours(cnts)c = max(cnts, key=cv2.contourArea)# allocate memory for the mask which will contain the# rectangular bounding box of the stitched image regionmask = np.zeros(thresh.shape, dtype="uint8")(x, y, w, h) = cv2.boundingRect(c)cv2.rectangle(mask, (x, y), (x + w, y + h), 255, -1)
輪郭は、55-57 行目で抽出され、解析されます。
注意: imutils.grab_contours
関数は、OpenCV 2.4, OpenCV 3, OpenCV 4 と cv2.findContours
の異なる戻り値に対応するために imutils==0.5.2
で新たに追加されました。 そして,63行目では,最大の輪郭のバウンディングボックスを計算しています.
上記のコードブロックの出力は次のようになります:
This bounding box is the smallest rectangular region that the entire panorama can fit in.この境界は、パノラマ全体に収まる最小限の四角形領域となります。
さて、ここで、私がブログ投稿用にまとめた最大のハックの 1 つを紹介します。
# create two copies of the mask: one to serve as our actual# minimum rectangular region and another to serve as a counter# for how many pixels need to be removed to form the minimum# rectangular regionminRect = mask.copy()sub = mask.copy()# keep looping until there are no non-zero pixels left in the# subtracted imagewhile cv2.countNonZero(sub) > 0:# erode the minimum rectangular mask and then subtract# the thresholded image from the minimum rectangular mask# so we can count if there are any non-zero pixels leftminRect = cv2.erode(minRect, None)sub = cv2.subtract(minRect, thresh)
70 行目と 71 行目で、mask
画像のコピーを 2 つ作成します。
- 最初のマスク
minMask
は、パノラマ画像の内側にフィットするまで徐々にサイズが縮小されます(本セクション上部にある図 5 を参照ください)。 - 2番目のマスクである
sub
は、minMask
のサイズを縮小し続ける必要があるかどうかを判断するために使用されます。
ライン75はsub
に前景ピクセルがなくなるまでループし続けるwhile
ループを開始します。
ライン79はminRect
のサイズを縮小する浸食形態演算を実行します。
行80はminRect
からthresh
を引き、minRect
に前景画素がなくなったら、ループから抜け出すことができるようになります。
以下に、このハックのアニメーションを示します。
上に sub
画像、下に minRect
画像が表示されているのが、このアニメーションの結果です。
sub
に前景ピクセルがなくなるまで、minRect
のサイズがだんだん小さくなっていることに注目してください – この時点で、パノラマの最大の矩形領域に収まる最小の矩形マスクを見つけたことがわかります。
最小の内側の矩形が与えられたら、再び輪郭を見つけ、バウンディングボックスを計算しますが、今回は単に stitched
画像から ROI を抽出します:
# find contours in the minimum rectangular mask and then# extract the bounding box (x, y)-coordinatescnts = cv2.findContours(minRect.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)cnts = imutils.grab_contours(cnts)c = max(cnts, key=cv2.contourArea)(x, y, w, h) = cv2.boundingRect(c)# use the bounding box coordinates to extract the our final# stitched imagestitched = stitched
ここで得られた結果:
-
minRect
で輪郭を発見(ライン 84 と 85)、 - 複数の OpenCV バージョンに対する輪郭の解析の処理(ライン 86). この関数を使用するには、
imutils>=0.5.2
が必要です。 - 最大の輪郭を描画します(87行目)。
- 最大の輪郭の境界ボックスを計算します(88行目)。
- 境界ボックス情報を使用して、
stitched
からROIを抽出します(92行目)。
最終的なstitched
イメージを画面に表示し、ディスクに保存します。
# write the output stitched image to diskcv2.imwrite(args, stitched)# display the output stitched image to our screencv2.imshow("Stitched", stitched)cv2.waitKey(0)# otherwise the stitching failed, likely due to not enough keypoints)# being detectedelse:print(" image stitching failed ({})".format(status))
95-99行は、クロッピングハックを実行したかどうかにかかわらず、イメージの保存と表示を行います。
以前と同様に、status
フラグの成功として戻ってこなかった場合は、エラーメッセージを表示します(行103と行104)。
それでは、改良されたイメージ スティッチング + OpenCV パイプラインの結果を確認してみましょう。
改良されたイメージ スティッチング結果
繰り返しますが、今日のチュートリアルの「ダウンロード」セクションを使って、ソース コードとサンプル画像をダウンロードしたことを確認しておいてください。
そこからターミナルを開き、次のコマンドを実行します。
$ python image_stitching.py --images images/scottsdale --output output.png \--crop 1 loading images... stitching images... cropping...
今回は、上記のセクションで説明したハックを適用し、ステッチング出力画像から黒い領域を削除したことに注意してください (ワープ変換により発生)。
制限と欠点
以前のチュートリアルで、リアルタイムのパノラマおよび画像のステッチング アルゴリズムを構築する方法を示しました。
OpenCV の組み込みの cv2.createStitcher
および cv2.Stitcher_create
関数は、確かに正確で美しいパノラマを構築できますが、この方法の主な欠点の 1 つは、ホモグラフィ行列へのアクセスを一切抽象化していることです。
一度、最初のホモグラフィー推定を計算したら、時々行列を再計算する必要があるだけです。
本格的なキーポイント マッチングと RANSAC 推定を行う必要がないため、パノラマ構築時に驚異的なスピードアップをもたらします。したがって、生のホモグラフィ行列にアクセスできなければ、OpenCV の組み込み画像ステッチング アルゴリズムを取り出してリアルタイムに変更することは困難でしょう。
OpenCV を使用して画像のステッチングを行う際にエラーに遭遇することはありますか。
cv2.createStitcher
関数またはcv2.Stitcher_create
関数のいずれかを使用しようとすると、エラーに遭遇する可能性があります。
たとえば、OpenCV 4 を使用していて、cv2.createSticher
を呼び出そうとすると、次のエラーメッセージが表示されます:
>>> cv2.createStitcherTraceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: module 'cv2' has no attribute 'createStitcher'
代わりに cv2.Stitcher_create
関数を使用する必要があります。
OpenCV のバージョンがわからない場合は、cv2.__version__
を使って確認することができます :
>>> cv2.__version__'4.0.0'
ここで、私は OpenCV 4.0.0 を使用していることがわかります。
遭遇する可能性があり、おそらく最も一般的な最後のエラーは、OpenCV の (1) contrib サポートがない、および (2) OPENCV_ENABLE_NONFREE=ON
オプションを有効にしないでコンパイルされたことに関連するものです。
このエラーを解決するには、opencv_contrib
モジュールをインストールし、OPENCV_ENABLE_NONFREE
オプションを ON
に設定しなければなりません。
OpenCV の non-free および contrib モジュールに関するエラーが発生した場合は、私の OpenCV インストール ガイドを参照して OpenCV を完全にインストールしていることを確認してください。
注意: 私のインストールガイドに従っていない場合、私はあなた自身の OpenCV インストールのデバッグを手助けできないので、システムを構成する際には、私の OpenCV インストールガイドを使用していることを確認してください。
OpenCV と Python の両方を使用して、複数の画像をつなぎ合わせてパノラマ画像を作成することができました。
私たちの出力パノラマ画像は、つなぎ合わせた配置が正確なだけでなく、美的にも優れていました。
しかし、OpenCV の組み込み画像ステッチング クラスを使用する最大の欠点の 1 つは、結果のホモグラフィ行列自体を含む内部計算の多くを抽象化していることです。
以前の投稿で行ったように、リアルタイム画像ステッチングを実行しようとしている場合、ホモグラフィ行列をキャッシュして、キーポイント検出、特徴抽出、特徴照合をたまにしか行わないことが有効だと思われるでしょう。
これらのステップをスキップして、キャッシュされた行列を使用して透視投影を実行すると、パイプラインの計算負荷を軽減し、最終的にリアルタイム画像ステッチング アルゴリズムを高速化できますが、残念ながら OpenCV の cv2.createStitcher
Python バインディングは生の行列へのアクセスを提供してくれません。
リアルタイムパノラマ構築についてもっと学びたい場合は、私の以前の投稿を参照してください。
画像ステッチングに関する今日のチュートリアルを楽しんでいただければ幸いです!
今日の投稿のソースコードをダウンロードしたり、この PyImageSearch でチュートリアルを公開する通知を受け取るには、以下のフォームにあなたの電子メールアドレスを入力してください!
PySearch で公開されている画像ステッチングのソースコードをダウンロードするには、以下のフォームに入力してください!
PySearch で公開されているリアルタイムパノラマのソースコードをダウンロードするには、以下のフォームに電子メールアドレスを入力してください。