OpenCV学習の基本的な画像操作(10):畳み込みおよび畳み込み演算子

畳み込み

畳み込みの定義

簡単に言えば、カーネルは画像に対して加重加算を実行します。これは次のように表すことができます。

H(x、y)= \ sum _ {i = 0} ^ {M_i-1} \ sum _ {j = 0} ^ {M_j-1} I(x + i-a_i、y + j-a_j)K (i、j)

畳み込みカーネルのアンカーポイントは、畳み込みカーネルの更新出力ポイントを指します。これは通常、畳み込みカーネルの中心点です。

畳み込みのステップサイズは、畳み込みカーネルが画像上で1回移動する距離です。

畳み込み境界問題

コンボリューションカーネルが画像位相の境界に移動すると、コンボリューションカーネルの一部の位置が元の画像の境界を超えます。このとき、計算ができず、元の画像の境界を拡大してから計算する必要があります。

拡張方法

BORDER_DEFAULT:既知のエッジミラーリングで塗りつぶします

BORDER_CONSTANTP:指定されたピクセルでエッジを塗りつぶします

BORDER_REPLICATE:エッジを最も多くのエッジピクセルで塗りつぶします

BORDER_WRAP:エッジを反対側のピクセルで塗りつぶします

BORDER_ISOLATED:エッジを0で埋めます

畳み込み演算子

一次演算子

リバート

ロバーツ演算子はクロスディファレンシャル演算子とも呼ばれ、クロスディファレンスに基づく勾配アルゴリズムであり、ローカルディファレンス計算によってエッジラインを検出します。急勾配で低ノイズの画像を処理するためによく使用されます。画像のエッジがプラスまたはマイナス45度に近い場合、このアルゴリズムの処理効果がより理想的です。欠点は、エッジの位置が正確でなく、抽出されたエッジラインが太いことです。

G_x = \ begin {bmatrix} + 1&0 \\ 0&-1 \ end {bmatrix}    G_y = \ begin {bmatrix} 0&+ 1 \\ -1&0 \ end {bmatrix}

プレウィット

Prewittは、画像のエッジ検出の差分演算子です。その原理は、特定の領域のピクセルグレー値によって生成された差を使用して、エッジ検出を実現することです。Prewittオペレーターは3 * 3テンプレートを使用して領域内のピクセル値を計算し、Robertオペレーターのテンプレートは2 * 2であるため、Prewittオペレーターのエッジ検出結果は、Robertオペレーターよりも水平方向と垂直方向でより明確になります。 、Prewitt演算子は、ノイズが多く、グレースケールが緩やかな画像を識別するのに適しています。

G_x = \ begin {bmatrix} -1&0&1 \\ -1&0&1 \\ -1&0&1 \ end {bmatrix}   G_y = \ begin {bmatrix} -1&-1&-1 \\ 0&0&0 \\ 1&1&1 \ end {bmatrix}

古典的なPrewittオペレーターは、次のように考えています。しきい値以上の新しいグレー値を持つピクセルはすべてエッジポイントです。つまり、適切なしきい値Tを選択します。P(i、j)≥Tの場合、(i、j)はエッジポイントであり、P(i、j)はエッジイメージです。多くのノイズポイントのグレー値が大きく、振幅が小さいエッジポイントの場合、代わりにエッジが失われるため、この判断は不合理であり、エッジポイントの誤った判断を引き起こします。

  プレウィット演算子はノイズ抑制に影響を与えます。ノイズ抑制の原理はピクセル平均化によるものですが、ピクセル平均化は画像のローパスフィルタリングと同等であるため、プレウィット演算子はエッジの配置においてロバーツ演算子ほど優れていません。

Sobel

Sobel演算子は、ガウス平滑化と微分導出を組み合わせた、エッジ検出用の離散微分演算子です。この演算子は、画像の明るさのおおよその値を計算するために使用されます。画像のエッジの明るさに応じて、エリア内の特定の数を超える特定のポイントがエッジとして記録されます。Sobel演算子は、Prewitt演算子に基づいて重みの概念を追加します。隣接するポイント間の距離は、現在のピクセルに異なる影響を与えると考えられています。ピクセルが近いほど、現在のピクセルの影響が大きくなり、画像が実現されます。エッジの輪郭をシャープにしてハイライトします。

Sobelオペレーターのエッジ配置はより正確であり、ノイズが多く、グレースケールが緩やかな画像によく使用されます。

G_x = \ begin {bmatrix} -1&0&1 \\ -2&0&2 \\ -1&0&1 \ end {bmatrix}   G_y = \ begin {bmatrix} -1&-2&-1 \\ 0&0&0 \\ 1&2&1 \ end {bmatrix}

Sobelオペレーターは、ピクセルポイントの上下、左右の隣接ポイントのグレースケールの重みの差に基づいてエッジを検出し、エッジで極値に到達します。ノイズを平滑化する効果があり、より正確なエッジ方向情報を提供します。Sobel演算子は、ガウス平滑化と微分導出(微分)を組み合わせているため、ノイズ耐性が高くなります。精度要件がそれほど高くない場合は、Sobel演算子がより一般的に使用されるエッジ検出方法です。

画像の各ピクセルの水平および垂直勾配の概算値を次の式と組み合わせて、勾配の大きさを計算できます。

G = \ sqrt {G_x ^ 2 + Gy ^ 2}

ただし、実際のエンジニアリングでは、計算を簡略化するために、上記の式が次の式に置き換えられることがよくあります。

G = \ left |  G_x \ right |  + \ left |  G_y \ right |

等方性ソーベル算子

  Sobel演算子のもう1つの形式は、加重平均演算子である(Isotropic Sobel)演算子です。重みは、ゼロ点と中央のストアの間の距離に反比例します。エッジが異なる方向で検出される場合、勾配の振幅は同じであり、一般に等方性Sobelと呼ばれます。 (等方性ソーベル)演算子。2つのテンプレートもあります。1つは水平エッジを検出するためのもので、もう1つは垂直フラットエッジを検出するためのものです。通常のSobel演算子と比較して、等方性Sobel演算子はより正確な位置重み付け係数を持ち、異なる方向のエッジを検出する場合、勾配の大きさは同じです。

G_x = \ begin {bmatrix} -1&0&1 \\-\ sqrt {2}&0&\ sqrt {2} \\ -1&0&1 \ end {bmatrix}   G_y = \ begin {bmatrix} -1&-\ sqrt {2}&-1 \\ 0&0&0 \\ 1&\ sqrt {2}&1 \ end {bmatrix}

シャーオペレーター

Scharr演算子とSobel演算子の違いは、スムージング部分にあります。ここで使用されるスムージング演算子は、中央の1/4 * [1、2、1]に対して、1/16 * [3、10、3]です。要素の重みは重くなります。これは、よりランダムな信号である画像に関連している可能性があり、ドメインは関係ありません。したがって、近傍の平滑化では、標準偏差が比較的小さく、薄くて高いガウス関数を使用する必要があります。テンプレート。

  Sobel演算子は比較的小さなカーネルを計算するため、3 * 3 Sobel演算子のように、近似微分計算の精度は比較的低く、勾配角度が水平方向または垂直方向に近い場合、その不正確さがより明白になります。 。Scharr演算子は、Sobel演算子と同じくらい高速ですが、特にコアが小さいシナリオでは精度が高くなります。したがって、画像エッジ抽出を実現するために3 * 3フィルタを使用することを、Scharr演算子を使用することをお勧めします。

  • Sobel演算子は、エッジを抽出するためのフィルタ演算子の形式です。X方向とY方向のそれぞれがテンプレートを使用し、2つのテンプレートを組み合わせて勾配演算子を形成します。X方向テンプレートは垂直エッジに最大の影響を与え、Y方向テンプレートは水平エッジに最大の影響を与えます。

  • ロバート演算子は一種の勾配演算子であり、クロスチェックによって勾配を表現します。局所差分演算子を使用してエッジを見つける演算子です。急勾配でノイズの少ない画像に最も効果的です。

  • プレウィット演算子は加重平均演算子であり、ノイズを抑制する効果がありますが、ピクセル平均は画像の同じフィルタリングと同等であるため、プレウィット演算子はエッジの配置においてロバート演算子ほど優れていません。

二次演算子

ラプラス

ラプラシアン演算子は、n次元ユークリッド空間の2次微分演算子であり、画像強調やエッジ抽出の分野でよく使用されます。グレーの差で近傍のピクセルを計算します。基本的なプロセスは、画像の中央のピクセルのグレー値とその周囲の他のピクセルのグレー値を決定することです。中央のピクセルのグレー値が高い場合、中央のピクセルのグレー値が増加します。中央のピクセルのグレーレベルを下げて、画像の鮮明化操作を実現します。アルゴリズムの実装プロセスでは、ラプラシアンオペレーターは、近隣の中央ピクセルの4方向または8方向の勾配を計算し、勾配を追加して、中央ピクセルのグレーレベルと近隣の他のピクセルのグレーレベルとの関係を決定し、最後に勾配操作を行います。その結果、ピクセルのグレースケールが調整されます。

G_4 = \ begin {bmatrix} 0&1&0 \\ 1&-4&1 \\ 0&1&0 \ end {bmatrix}   G_8 = \ begin {bmatrix} 1&1&1 \\ 1&-8&1 \\ 1&1&1 \ end {bmatrix}

ラプラシアン演算子の基本的なプロセスは、画像の中央のピクセルのグレー値とその周囲の他のピクセルのグレー値を決定することです。中央のピクセルのグレーが高い場合は、中央のピクセルのグレーが増加します。それ以外の場合は、中央のピクセルのグレーが減少します。画像鮮明化動作を実現するために。アルゴリズムの実装プロセスでは、ラプラシアンオペレーターは、近隣の中央ピクセルの4方向または8方向の勾配を計算し、勾配を追加して、中央ピクセルのグレーレベルと近隣の他のピクセルのグレーレベルとの関係を決定し、最後に勾配操作を行います。その結果、ピクセルのグレースケールが調整されます。

         ラプラス演算子は、回転不変性を持つ等方性演算子、2次微分演算子です。周囲のピクセルのグレースケールの違いを考慮せずに、エッジの位置のみを気にする場合に適しています。孤立したピクセルに対するラプラス演算子の応答は、エッジまたはラインに対する応答よりも強いため、ノイズのない画像にのみ適しています。ノイズが存在する場合、ラプラシアン演算子を使用してエッジを検出する前に、ローパスフィルタリングが必要です。したがって、通常のセグメンテーションアルゴリズムは、ラプラシアン演算子とスムージング演算子を組み合わせて新しいテンプレートを生成します。

LOG(ガウスのラプラシアン)

LOG演算子は、最初に画像に対してガウスフィルタリングを実行し、次にそのラプラシアン2次導関数を見つけ、2次導関数のポットゼロ点に基づいて、つまりフィルタリングされた結果のゼロ交差を検出することにより、画像の境界を検出します。画像またはオブジェクトのエッジを取得します。

  LOGオペレーターは、ノイズ抑制とエッジ検出の2方向を総合的に検討し、ガウススムージングフィルターとラプラシアンシャープニングフィルターを組み合わせてノイズを平滑化し、次にエッジ検出を行うため、効果が向上します。 。この演算子は視覚生理学の数学モデルに似ているため、画像処理の分野で広く使用されています。強力な干渉防止能力、高い境界位置決め精度、良好なエッジ連続性の特徴を持ち、弱いコントラストで境界を効果的に抽出できます。

APIの紹介

端を埋める

copyMakeBorder(src,dst,top,bottom,left,right,borderType,color)
// src 原图   dst 目标图
// top,bottom,left,right 上下左右填充宽度
//borderType 填充类型 
//color 当使用BORDERTPYPE_CONSTANTP时,才用

Sobel(Scharr)演算子

Sobel(  //Scharr相同
InputArray Src,   //输入图像
QutputArray dst,  //输出图像
int depth,        //输出位深度 设为"-1"会自适应 选择合适的
int dx,    //x方向,几阶导数
int dy,    //y方向,几阶导数      
int ksize,        //kernel大小,必须是奇数
double scale = 1, //输出是否缩放,即乘因子
double delta = 0, //输出是否加偏差,即加因子
int borderType = BORDER_DEFAULT  //
)

ラプラシアン算子

Laplacian( 
src_gray,     //src_gray: 输入图像
dst,          //dst: 输出图像
ddepth,       //ddepth: 输出图像的深度因为输入图像的深度是 CV_8U ,这里我们必须定义 ddepth = CV_16S 以避免外溢。
kernel_size,  //kernel_size: 内部调用的 Sobel算子的内核大小,此例中设置为3。
scale, 
delta, 
BORDER_DEFAULT 
);

コードの練習

エッジサプリメント

#include <iostream>
#include <math.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char* argv[])
{
	//src = imread("src.jpg");
	Mat src = imread("cat.png"),dst;
	if (!src.data)
	{
		cout << "cannot open image" << endl;
		return -1;
	}
	namedWindow("input image", WINDOW_AUTOSIZE);
	imshow("input image", src);
	Mat d1, d2, d3, d4,d5,d6,d7,d8;
	int top = (int)(0.10 * src.rows);
	int bottom = (int)(0.10 * src.rows);
	int left = (int)(0.10 * src.cols);
	int right = (int)(0.10 * src.cols);

	copyMakeBorder(src, d1, top, bottom, left, right, BORDER_CONSTANT, Scalar(255, 255, 255));
	copyMakeBorder(src, d2, top, bottom, left, right, BORDER_DEFAULT);
	copyMakeBorder(src, d3, top, bottom, left, right, BORDER_REFLECT);
	copyMakeBorder(src, d4, top, bottom, left, right, BORDER_REPLICATE);
	copyMakeBorder(src, d5, top, bottom, left, right, BORDER_WRAP);
	copyMakeBorder(src, d6, top, bottom, left, right, BORDER_ISOLATED);
	copyMakeBorder(src, d7, top, bottom, left, right, BORDER_REFLECT101);

	imshow("BORDER_CONSTANT", d1);
	imshow("BORDER_DEFAULT", d2);
	imshow("BORDER_REFLECT", d3);
	imshow("BORDER_REPLICATE", d4);
	imshow("BORDER_WRAP", d5);
	imshow("BORDER_ISOLATED", d6);
	imshow("BORDER_REFLECT101", d7);

	waitKey(0);
	return 0;
}

オペレーター抽出エッジ

#include <iostream>
#include <math.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char* argv[])
{
	//src = imread("src.jpg");
	Mat src = imread("src.jpg"),gray_src;
	if (!src.data)
	{
		cout << "cannot open image" << endl;
		return -1;
	}
	namedWindow("input image", WINDOW_AUTOSIZE);
	imshow("input image", src);
	GaussianBlur(src, src, Size(3, 3), 0, 0);
	cvtColor(src,src,COLOR_BGR2GRAY);

	//Robert算子
	//X
	Mat dst_rx, dst_ry;
	Mat kernel_x1 = (Mat_<int>(2, 2) << 1, 0, 0, -1);
	filter2D(src, dst_rx, -1, kernel_x1, Point(-1, -1), 0.0);
	
	//Y
	Mat kernel_y1 = (Mat_<int>(2, 2) << 0 ,1, -1, 0);
	filter2D(src, dst_ry, -1, kernel_y1, Point(-1, -1), 0.0);

	//拼接两个方向的梯度
	Mat xygrad = Mat(src.size(), src.type());
	int width = xygrad.cols;
	int height = xygrad.rows;
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_rx.at<uchar>(row, col);
			int yg = dst_ry.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Rebort", xygrad);


	// prewitt算子
	Mat dst_sx, dst_sy;
	//X
	Mat kernel_x2 = (Mat_<int>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);
	filter2D(src, dst_sx, -1, kernel_x2, Point(-1, -1), 0.0);
	
	//Y
	Mat kernel_y2 = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
	filter2D(src, dst_sy, -1, kernel_y2, Point(-1, -1), 0.0);
	
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_sx.at<uchar>(row, col);
			int yg = dst_sy.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Prewitt", xygrad);

	//Sobel 算子
	Sobel(src, dst_rx, CV_16S, 1, 0, 3);
	convertScaleAbs(dst_rx, dst_rx);
	Sobel(src, dst_ry, CV_16S, 0, 1, 3);
	convertScaleAbs(dst_ry, dst_ry);
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_rx.at<uchar>(row, col);
			int yg = dst_ry.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Sobel", xygrad);

	//Scharr 算子
	Scharr(src, dst_rx, CV_16S, 1, 0);
	convertScaleAbs(dst_rx, dst_rx);
	Scharr(src, dst_ry, CV_16S, 0, 1);
	convertScaleAbs(dst_ry, dst_ry);
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			int xg = dst_rx.at<uchar>(row, col);
			int yg = dst_ry.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow("Scharr", xygrad);

	//Laplacian 算子
	Mat edge_image;
	Laplacian(src, edge_image, CV_16S, 3);
	convertScaleAbs(edge_image, edge_image);
	imshow("Laplacian", edge_image);
	

	waitKey(0);
	return 0;
}

 

おすすめ

転載: blog.csdn.net/fan1102958151/article/details/107343870