OpenCV での画像処理 - 画像勾配 + Canny エッジ検出 + 画像ピラミッド

OpenCV での画像処理 - 画像勾配 + Canny エッジ検出 + 画像ピラミッド


1. 画像のグラデーション

まず、画像の勾配とは何かを見てみましょう: 画像の勾配は、画像を 2 次元の離散関数とみなすことができます。画像の勾配は、この 2 次元の関数の導関数です。一般に、画像のエッジは画像にグラデーション操作を実行することで実現されます。

画像の勾配の部分では、画像の勾配、エッジなどを見つけるためにタッチします。この部分には、cv.Sobel ()、cv.Scharr ()、cv.Laplacian () の 3 つの主な関数が含まれます。対応する 3 つは、以下で提供されます。 OpenCV の勾配フィルター (ハイパス フィルター) の種類、つまり Sobel、Scharr、および Laplacian

2D コンボリューションの前の部分、つまり画像フィルタリングの内容では、ローパス フィルター (LPF) とハイパス フィルター (HPF) の主な適用方向について説明しました。LPF はノイズを除去するために使用され、HPF は使用されます。画像のグラデーションの部分では、画像内のエッジを見つけるために 3 つのハイパス フィルターを使用します

1.1 Sobel 演算子と Scharr 演算子

Sobel オペレーターはガウス平滑化と微分演算を組み合わせたものであるため、ノイズ耐性が非常に優れています。導出方向 (xorder または yorder) と使用するコンボリューション カーネル サイズ ksize を設定できます。

コンボリューションカーネルのサイズを-1に設定した場合、デフォルトで3x3 Scharrフィルタが使用されますが、3x3 Sobelよりも効果が高く、処理速度も同等であるため、Scharrフィルタを使用する場合はScharrを使用してください。 3x3Sobel フィルター。代わりにフィルターを使用します

上記の概念から、3x3 カーネルを使用したソーベル フィルターはシャール フィルターと同等ではないことがわかりますが、シャール フィルターは 3x3 カーネルを備えた高効率フィルターです。次に、Scharr フィルターを使用することをお勧めします。つまり、Sobel フィルターを使用する場合は、そのカーネル サイズを -1 に設定します。

Sobel および Scharr ハイパス フィルターのカーネルを理解した後、cv.Sobel() 関数と cv.Scharr() 関数のパラメーターを見てみましょう。 ,ksize) 関数を渡す必要があります。パラメータは元の画像です。cv.CV_64F は画像の深度で、一般に -1 として記述されます。dx と dy はそれぞれ x 軸方向と y 軸方向の演算子を表します。ksizeカーネルサイズです

Scharr ハイパス フィルターは 3x3 カーネルであるため、cv.Scharr() のパラメーターは、ksize パラメーターを渡すことによって cv.Sobel() 関数と比較されます。

1.2 ラプラシアン演算子

ラプラスは実際にはソーベル演算子の演算を利用しており、ソーベル演算子を通して画像のx方向とy方向の導関数を計算し、ラプラス変換の結果を得るというもので、ソーベル演算子のバージョンアップ版のようなものです。

理解を助けるために 2 つの例を示します

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread(r'E:\image\test06.png', 0)
laplacian = cv.Laplacian(img, cv.CV_64F)
sobelx = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
sobely = cv.Sobel(img, cv.CV_64F, 0, 1, ksize=5)
plt.subplot(2, 2, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap='gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 3), plt.imshow(sobelx, cmap='gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 4), plt.imshow(sobely, cmap='gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
plt.show()

ここに画像の説明を挿入

: 上記の例では、出力のデータ型は cv.CV_8U または np.uint8 ですが、ここに問題があります。黒から白への遷移は正の傾き (正の値) を持つように見えますが、白の遷移は正の傾きを持つように見えます。 -から黒への遷移は、負の勾配 (負の値を持つ) として扱われます。データを np.uint8 に変換すると、負の勾配はすべて 0 に設定されます。これは、このエッジ情報が欠落していることを意味します。

2 つのエッジが検出される場合、より良いオプションは、出力データ型を上位形式として保持し、その絶対値を取得してから cv.CV_8U に変換し直すことです。

これは無視できない必要かつ重要な問題なので、例を挙げて見てみましょう。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('E:/image/test07.png', 0)
sobelx8u = cv.Sobel(img, cv.CV_8U, 1, 0, ksize=5)

sobelx64f = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 2), plt.imshow(sobelx8u, cmap='gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 3), plt.imshow(sobel_8u, cmap='gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
plt.show()

ここに画像の説明を挿入

2.キャニーエッジ検出

Canny Edge Detection は、John F. Canny によって発明された一般的なエッジ検出アルゴリズムであり、主にガウス フィルタリング、勾配計算、非最大値抑制、および二重しきい値検出に分かれる多段階です。

2.1 多段階キャニーエッジ検出アルゴリズム

ガウスフィルタリング(ノイズリダクション)

エッジ検出は画像内のノイズの影響を受けやすいため、Canny エッジ検出アルゴリズムで画像を処理する最初のステップは、5x5 ガウス フィルターを使用して画像内のノイズを除去することです。

ガウス フィルタリングの具体的な方法は、ガウス テンプレートを生成し、畳み込みを使用して時間領域フィルタリングを実行することです。

勾配計算

平滑化された画像は、ソーベル カーネルを使用して水平方向と垂直方向にフィルタリングされ、水平方向と垂直方向の一次導関数が取得されます。

非最大抑制

勾配の大きさと方向を取得した後、画像を完全にスキャンして、エッジを形成しない可能性のある不要なピクセルを削除します。このため、ピクセルが勾配方向付近で極大値であるかどうかが各ピクセルでチェックされます。

ここに画像の説明を挿入

(画像は OpenCV4.1 の中国語公式ドキュメントからのものです)

点 A は端 (垂直) にあります。グラデーションの方向はエッジに対して垂直です。点 B と点 C は勾配の方向にあります。したがって、点 A が点 B および C に対してチェックされ、極大値が形成されるかどうかが確認されます。そうである場合は、次のステージで検討されますが、そうでない場合は抑制されます (ゼロに設定されます)。つまり、得られる結果は「細いエッジ」を持つバイナリ画像です。

ヒステリシス閾値(二重閾値検出)

この段階では、どのエッジが実際のエッジであるかが決定されます。このために、2 つのしきい値 minVal と maxVal を提供する必要があります。強度勾配が maxVal より大きいエッジはエッジである必要があり、minVal より小さいエッジはエッジであってはなりませんエッジ: エッジ ピクセルに接続されている場合。その後、それらをエッジの一部として扱うか、破棄します。

エッジ A は maxVal 上にあるため、「明確なエッジ」とみなされます。C は minVal を下回っていますが、A に接続されているため、有効とみなされ、完全な曲線が得られます。

しかし、B は minVal 上にあり、C とのユニティ領域内にありますが、保証されたエッジに接続されていないため、同様に破棄されます。

: 正しい結果を得るには、対応する minVal と maxVal を選択する必要があります。

2.2 OpenCV での Canny Edge 検出

OpenCV は、Canny エッジ検出アルゴリズムの 4 つの段階を単一の cv.Canny() に配置します。これを正しく使用するだけで、エッジ検出要件を取得できます。

cv.Canny() 関数のパラメーターを見てみましょう。最初のパラメーターは画像リソースで、2 番目と 3 番目のパラメーターは、それぞれヒステリシスしきい値 (二重しきい値検出) ステージの 2 つのしきい値 minVal と maxVal です。 4 つのパラメータは、picture_size で、画像グラデーションのソーベル カーネルのサイズを見つけるために使用されます。デフォルトは 3 です。5 番目のパラメータは L2gradient で、グラデーションを見つけるために使用される方程式を指定します。True の場合、より正確な値になります。式が使用されます。False の場合、デフォルトが使用されます。

例を見てみましょう

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('E:/image/test08.png', 0)
edges = cv.Canny(img, 100, 200)
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(edges, cmap='gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
plt.show()

ここに画像の説明を挿入

3. 画像ピラミッド

3.1 ピラミッド理論の基礎

これまでに学んだことでは、一定のピクセルサイズの画像が使用されていますが、場合によっては、画像に表示される画像を処理するときに、必要なオブジェクトの具体的なサイズ(またはオブジェクトがどのようなサイズであるか)がわからない場合があります)

この場合、解像度が異なる同一の画像のセットを作成し、これらの画像内のターゲット オブジェクトを検索する必要があります。ピクセル サイズが異なるこれらの画像のセットが画像ピラミッドです。

(最高解像度の画像を一番上に、最低解像度の画像を一番上に重ねて下に重ねると、ピラミッドのように見えるためです)

一般的に、ピラミッドにはガウス ピラミッドとラプラシアン ピラミッドの ​​2 種類があります。

3.1.1 ガウスピラミッド

ガウス ピラミッドのより高いレベル (より低い解像度) は、最初に画像をガウス カーネルで畳み込み、次に偶数の行と列を削除することによって形成されます。次に、より高いレベルの各ピクセルは、ベースからの 5 ピクセルの寄与によって形成されます。ガウス重みによるレベル, このような操作を通じて, M x N 画像は M/2 x N/2 画像になるので, 面積は元の 4 分の 1 に縮小されます. これをオクターブと呼びます. ピラミッドがより高い場合, これは継続するほどモードになります。

ダウンサンプリング方法: 1. 画像上のガウス カーネル コンボリューション; 2. すべての偶数行と列を削除

画像の下位レベル(高解像度)を上位レベル(低解像度)で各次元で2倍にし、新たに追加した行と列(偶数行と列)を0で埋めて使用します 指定されたフィルタで畳み込みを実行します欠落しているピクセルの近似値を推定します。

アップサンプリング方法: 1. 画像を各次元で元のサイズの 2 倍に拡張し、新しい行と列を 0 で埋めます; 2. 同じ元のカーネル (x4) を使用して、メソッドの後で画像と畳み込み、 new アップスケールされたピクセルの近似値

スケーリング プロセス中に一部の情報が失われます。スケーリング プロセス中の情報の損失を減らしたい場合は、ラプラシアン ピラミッドを使用する必要があります。

参照元: cv.pyrUp() および cv.pyrDown()

cv.pyrUp(src) 関数: 渡す必要があるパラメーターは 1 つだけです。これは画像リソースを表し、画像のアップサンプリングに使用されます。

cv.pyrDown() 関数: パラメーターの受け渡しは、画像のダウンサンプリングに使用される cv.pyrUp() と一致しており、通常は画像をぼかすためにも使用できます。

3.1.2 ラプラシアン ピラミッド

ラプラシアン ピラミッドはガウス ピラミッドによって形成されます。専用の関数はありません。ラプラシアン ピラミッド画像はエッジ画像のみを表します。その要素のほとんどは 0 です。通常、画像圧縮に使用されます。

ラプラシアン ピラミッドの層は、ガウス ピラミッドの層とガウス ピラミッドの上位レベルの拡張バージョンとの差によって形成されます。

例を通してラプラシアンピラミッドを示します

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread(r'E:\image\test06.png', )
loser_reso = cv.pyrDown(img)
higher_reso = cv.pyrUp(loser_reso)
lapPyr = img - higher_reso
test = higher_reso
loser_reso2 = cv.pyrDown(test)
higher_reso2 = cv.pyrUp(loser_reso2)
lapPyr2 = test - higher_reso2
cv.imshow('lapPyr', lapPyr)
cv.imshow('lapPyr2', lapPyr2)
cv.waitKey(0)
cv.destroyAllWindows()

ここに画像の説明を挿入

3.2 画像ピラミッドを使用した画像融合

画像ピラミッドの応用例の 1 つは画像の融合です。単純に画像を結合すると 2 枚の画像を重ね合わせますが、画像間の不連続性により効果が良くありません。この場合、ピラミッドを結合した画像を継ぎ目なしで使用できます。画像内に大量のデータを保持せずにブレンドする

画像ブレンドの効果を実現するには、次の手順を完了する必要があります。

  • ブレンドする必要がある 2 つの画像をロードします
  • 2 つの画像のガウス ピラミッドを見つけて、ガウス ピラミッド内のラプラシアン ピラミッドを見つけます。
  • 2 つの画像の半分を各ラプラシアン ピラミッド レベルに追加します。
  • 最後に、この結合画像ピラミッドから元の画像を再構成します。
import cv2 as cv
import numpy as np, sys

A = cv.imread('E:/image/horse.png')
B = cv.imread('E:/image/cow.png')
# 生成A的高斯金字塔
G = A.copy()
gpA = [G]
for i in range(6):
    G = cv.pyrDown(G)
    gpA.append(G)
# 生成B的高斯金字塔
G = B.copy()
gpB = [G]
for i in range(6):
    G = cv.pyrDown(G)
    gpB.append(G)
# 生成A的拉普拉斯金字塔
lpA = [gpA[5]]
for i in range(5, 0, -1):
    GE = cv.pyrUp(gpA[i])
    L = cv.subtract(gpA[i - 1], GE)
    lpA.append(L)
# 生成B的拉普拉斯金字塔
lpB = [gpB[5]]
for i in range(5, 0, -1):
    GE = cv.pyrUp(gpB[i])
    L = cv.subtract(gpB[i - 1], GE)
    lpB.append(L)
# 现在在每个级别中添加左右两半图像
LS = []
for la, lb in zip(lpA, lpB):
    rows, cols, dpt = la.shape
    ls = np.hstack((la[:, 0:cols / 2], lb[:, cols / 2:]))
    LS.append(ls)
# 现在重建
ls_ = LS[0]
for i in range(1, 6):
    ls_ = cv.pyrUp(ls_)
    ls_ = cv.add(ls_, LS[i])
# 图像与直接连接的每一半
real = np.hstack((A[:, :cols / 2], B[:, cols / 2:]))
cv.imwrite('Pyramid_blending2.jpg', ls_)
cv.imwrite('Direct_blending.jpg', real)

(注: 記事の内容については、OpenCV4.1 の中国語公式ドキュメントを参照してください)
記事が役に立った場合は、ワンクリックと 3 つのリンクでサポートしてください。

おすすめ

転載: blog.csdn.net/qq_50587771/article/details/123680995
おすすめ