Opencv-C++ ノート (13): opencv-image 畳み込み 1 (平均、中央値、ガウス、バイラテラル フィルタリング) とエッジ処理

1. 画像フィルタリングの概要

ヘッダー ファイルquick_opencv.h: クラスとパブリック関数を宣言します。

#pragma once
#include <opencv2\opencv.hpp>
using namespace cv;

class QuickDemo {
    
    
public:
	...
	void blur_Demo(Mat& image);
	void medianblur_Demo(Mat& image);
	void gaussian_Demo(Mat& image);
	void bilateralFilter_Demo(Mat& image);

	void custom_mask_Demo(Mat& image); //自定义掩膜运算
    void edge_process_Demo(Mat& image1);

};

メイン関数呼び出し

#include <opencv2\opencv.hpp>
#include <quick_opencv.h>
#include <iostream>
using namespace cv;


int main(int argc, char** argv) {
    
    
	Mat src = imread("D:\\Desktop\\pandas.jpg");
	if (src.empty()) {
    
    
		printf("Could not load images...\n");
		return -1;
	}
	namedWindow("input", WINDOW_NORMAL);
	imshow("input", src);

	QuickDemo qk;

	...
	qk.blur_Demo(src);
	qk.medianblur_Demo(src);
	qk.gaussian_Demo(src);
	qk.bilateralFilter_Demo(src);
	qk.custom_mask_Demo(src);
	qk.edge_process_Demo(src1);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

1.1. 平均値フィルタリング

blur( InputArray src,OutputArray dst, Size ksize, 
      Point anchor = Point(-1,-1)int borderType = BORDER_DEFAULT)

src: 入力画像。

dst: 出力画像。

ksize: カーネル サイズ、通常は Size(w,h)、w は幅、h は深さです。

アンカー: カーネルの中心が取られることを示す、平滑化される点。デフォルト値は Point(-1,-1) です。

boderType: 画像の外側のピクセルの境界パターンを推測します。デフォルト値 BORDER_DEFAULT

目的: 画像上の鋭いノイズを除去し、画像を滑らかにします。原理:平均値フィルタリングは線形フィルタリングに属し、その実現原理は近傍平均法である ソース
ここに画像の説明を挿入
ファイルquick_demo.cpp:実装クラスとパブリック関数

void QuickDemo::blur_Demo(Mat& image) {
    
    
	Mat dst, dst1, dst2;
	blur(image, dst, Size(5, 5), Point(-1, -1));
	blur(image, dst1, Size(15, 1), Point(-1, -1));
	blur(image, dst2, Size(1, 15), Point(-1, -1));
	imshow("均值滤波", dst);
	imshow("横向滤波", dst1);
	imshow("竖直滤波", dst2);
}

ここに画像の説明を挿入

1.2 メディアンフィルタリング

medianBlur(InputArray src,OutputArray dst,int ksize)

src: 入力画像。

dst: 出力画像。

ksize: 絞りの線形サイズ。このパラメータは 1 より大きい奇数でなければなりません。

統計的並べ替えフィルターの中央値は、塩胡椒ノイズに優れた抑制効果をもたらします。

void QuickDemo::medianblur_Demo(Mat& image) {
    
    
	Mat dst, dst1, dst2;
	medianBlur(image.clone(), dst, (3, 3));
	medianBlur(image.clone(), dst1, (5, 5));
	medianBlur(image.clone(), dst2, (7, 7));
	imshow("中值滤波", dst);
	imshow("中值滤波1", dst1);
	imshow("中值滤波2", dst2);
}

ここに画像の説明を挿入

1.3、ガウスフィルター

GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                   double sigmaX, double sigmaY = 0,
                   int borderType = BORDER_DEFAULT )

src: 入力画像。

dst: 出力画像。

ksize: ksize.width と ksize.height は異なっていてもかまいませんが、正の奇数、またはシグマで計算できる 0 でなければなりません。

sigmaX: X 方向のガウス カーネル関数の標準偏差

sigmaY: Y 方向のガウス カーネル関数の標準偏差

sigmaY が 0 の場合は、sigmaX に設定します。sigmaX と sigmaY の両方が 0 の場合は、ksize.width と ksize.height から計算されます。

ガウス フィルタリングは、ガウス ノイズの除去に適した線形平滑化フィルタであり、画像処理のノイズ低減プロセスで広く使用されています。

ガウスフィルタリングは画像全体の加重平均を行う処理であり、各ピクセルの値は自身と周囲の他のピクセル値の加重平均によって求められます。

ガウス フィルタリングの具体的な操作は次のとおりです。テンプレート (または畳み込み、マスク) を使用して画像内の各ピクセルをスキャンし、テンプレートによって決定された近傍のピクセルの加重平均グレー値を使用して、画像内のピクセルの値を置き換えます。テンプレートの中心。
ここに画像の説明を挿入
ここに画像の説明を挿入

void QuickDemo::gaussian_Demo(Mat& image) {
    
    
	Mat dst;
	GaussianBlur(image, dst, Size(5, 5), 15);
	imshow("高斯滤波", dst);
}

1.4. 双方向フィルタリング

エッジ保持フィルタリング アルゴリズム:

  • ガウス双方向フィルタリング
  • 平均シフト 平均シフト ファジー
  • 局所平均二乗誤差ファジー
  • ガイド付きフィルタリング

双方向フィルタリングの原理の概略図:
ここに画像の説明を挿入

void bilateralFilter(
InputArray src,    输入图像
OutputArray dst,   目标图像,需要和源图片有一样的尺寸和类型
int d,        在过滤期间使用的每个像素邻域的直径。如果输入d非0(没输入),则sigmaSpace由d计算得出。
double sigmaColor,  颜色空间滤波器的sigma值,这个参数的值越大,就表明该像素邻域内有更宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
double sigmaSpace, 坐标空间滤波器的sigma值,坐标空间的标注方差。他的数值越大,意味着越远的像素会相互影响,从而使更大的区域足够相似的颜色获取相同的颜色。当d>0,d指定了邻域大小且与sigmaSpace无关。否则,d正比于sigmaSpace。
int borderType = BORDER_DEFAULT  图像边界模式
)

src: 入力イメージ。Mat タイプにすることができ、イメージは 8 ビットまたは浮動小数点のシングル チャネル イメージまたは 3 チャネル イメージである必要があります。
dst: 元のイメージと同じサイズとタイプを持つ出力イメージ。
d: フィルタリング中の各ピクセル近傍の直径範囲を示します。この値が正でない場合、関数は 5 番目のパラメーター sigmaSpace から値を計算します。
sigmaColor: 色空間フィルタのシグマ値このパラメータの値が大きいほど、ピクセルの近傍の色がより広く混合​​され、その結果、より大きな半均等色領域が生成されます。
sigmaSpace: 座標空間におけるフィルターのシグマ値。値が大きい場合、類似した色の遠くのピクセルが相互に影響し、より広い領域内の十分に類似した色が同じ色になることを意味します。d>0 の場合、d は近傍のサイズを指定し、sigmaSpace に関連します。それ以外の場合、d は sigmaSpace に比例します。
borderType=BORDER_DEFAULT: 画像の外側のピクセルに対する特定の境界モードを推測するために使用され、デフォルト値は BORDER_DEFAULT です。 。

void QuickDemo::bilateralFilter_Demo(Mat& image) {
    
    
	Mat dst;
	bilateralFilter(image, dst, 0, 100.0, 10.0);
	imshow("双边滤波", dst);
}

ここに画像の説明を挿入

1.5、ボックスフィルタリング

boxFilter( InputArray src, OutputArray dst, int ddepth,
                Size ksize, Point anchor = Point(-1,-1),
                bool normalize = true,
                int borderType = BORDER_DEFAULT )

src: 入力画像

dst: 出力画像

d Depth: 出力イメージの深度、-1 は元のイメージの深度を表します

ksize: フィルター カーネルのサイズ。一般に、Size(w, h) はカーネルのサイズを表すように記述され、Size(10, 10) は 10x10 のカーネル サイズを表します。

アンカー = ポイント(-1,-1) : アンカー ポイント (つまり、平滑化されるポイント) を示します。デフォルト値は Point(-1,-1) であることに注意してください。このポイントの座標
が負の場合、コアが取られることを意味します。中心はアンカー ポイントであるため、デフォルト値 Point(-1,-1) は、アンカー ポイントがカーネルの中心にあることを意味します。

Normalize = true: デフォルト値は true、カーネルがその領域によって正規化されているかどうかを示す識別子 (正規化)

borderType=BORDER_DEFAULT : 画像の外側のピクセルの境界モードを推測するために使用されます。デフォルト値は BORDER_DEFAULT ですが、通常はそのままにしておきます。

2.カスタムマスク

ここに画像の説明を挿入

Mat kernel_ = (Mat_<char>(3, 3) << 0, -1, 0, 
							      -1, 5, -1, 
		                           0, -1, 0);
void filter2D(
InputArray src,     输入图像
OutputArray dst,     输出图像,和输入图像具有相同的尺寸和通道数量
int ddepth,      目标图像深度,为-1时保持输入图像通道深度
InputArray kernel,    卷积核
Point anchor=Point(-1,-1),表示过滤点在内核中的相对位置的内核锚;锚应位于内核内;默认值(-1-1)表示锚点位于内核中心。
double delta=0,     在储存目标图像前可选的添加到像素的值,默认值为0
int borderType=BORDER_DEFAULT   默认值是BORDER_DEFAULT,即对全部边界进行计算。
);

ここに画像の説明を挿入

void QuickDemo::custom_mask_Demo(Mat& image) {
    
    
	// 自定义filter函数实现
	int offset = image.channels();
	int cols = (image.cols - 1) * offset;
	int rows = image.rows;
	Mat dst = Mat::zeros(image.size(), image.type());
	for (int row = 1; row < (rows - 1); row++) {
    
    
		const uchar* previous = image.ptr<uchar>(row-1);
		const uchar* current = image.ptr<uchar>(row);
		const uchar* next = image.ptr<uchar>(row+1);
		uchar* output = dst.ptr<uchar>(row);
		for (int col = offset; col < cols; col++) {
    
    
			output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offset] + current[col + offset] + previous[col] + next[col]));
		}
	}
	imshow("dst", dst);

	// opencv自带filter2D函数实现
	Mat dst2;
	Mat kernel_ = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	filter2D(image.clone(), dst2, image.depth(), kernel_);
	imshow("dst2", dst2);
}

dst2 には黒枠がなく、エッジが処理されていることがわかります。
ここに画像の説明を挿入

3. エッジ処理

畳み込みが開始される前にエッジ ピクセルを増やし、塗りつぶされたピクセル値が 0 または RGB 黒になります (3x3 など)。
各周囲の 1 ピクセルのエッジを塗りつぶし、畳み込みプロセスの後にこれらのエッジを削除して、画像は加工されています

void copyMakeBorder(
InputArray src,     // 输入图像
OutputArray dst,  // 输出图像
int top,       // 填充边缘长度,一般上下左右都取相同值
int bottom,
int left,
int right,
int borderType,   // 填充类型
const Scalar& value = Scalar() // 边框颜色值,边框类型=BUALE_CONTER时才有用
)

openCVでの処理方法は以下の通りです。

BORDER_DEFAULT - デフォルト処理 (ミラー境界ピクセル: 1234|4321) BORDER_CONSTANT - 指定されたピクセル値でエッジを塗りつぶす
BORDER_REPLICATE - 既知のエッジ ピクセル値でエッジ ピクセルを塗りつぶします。(境界ピクセルのコピー: 1234|4444) BORDER_WRAP –
反対側のピクセルでパディングを補正します

BORDER_CONSTANT = 0
BORDER_REPLICATE = 1
BORDER_REFLECT = 2
BORDER_REFLECT_101 = 4、
BORDER_REFLECT101 = BORDER_REFLECT_101、
BORDER_DEFAULT = BORDER_REFLECT_101、
BORDER_WRAP = 3、
BORDER_TRANSPARENT = 5、

ソースファイルquick_demo.cpp: クラスとパブリック関数の実装

void QuickDemo::edge_process_Demo(Mat& image) {
    
    
	Mat dst0, dst1;
	copyMakeBorder(image, dst0, 5, 5, 5, 5, BORDER_REFLECT);
	copyMakeBorder(image, dst1, 5, 5, 5, 5, BORDER_CONSTANT,Scalar(0,255,255));
	imshow("REFLECT", dst0);
	imshow("CONSTANT", dst1);
}

ここに画像の説明を挿入

4. ソーベル演算子

実装手順:

x 軸方向の微分 - y 軸方向の微分 - 最終結果は 2 つの加算です。

関数プロトタイプ:

ソーベル(src,d Depth,dx,dy,ksize=3,…)

画像グレースケールの近似勾配を計算するために使用される離散微分演算子 (離散微分演算子)
ソーブル演算子関数セット ガウス平滑化と微分導出
水平方向と垂直方向の両方での一次微分演算子、導出演算子 ガイド、取得画像 X メソッドとY方向のグラデーション画像
ここに画像の説明を挿入

コード例:

chess = cv2.imread('chess.png')
# 求y方向边缘
dy = cv2.Sobel(chess, cv2.CV_64F, 1, 0, ksize=5)
# 求x方向边缘
dx = cv2.Sobel(chess, cv2.CV_64F, 0, 1, ksize=5)
# 二者相加
result = dy + dx

cv2.imshow('chess', chess)
cv2.imshow('dy', dy)
cv2.imshow('dx', dx)
cv2.imshow('result', result)
cv2.waitKey(0)

ここに画像の説明を挿入

上図から明らかなように、dx を 1 に設定すると y 方向のエッジ情報が取得され、その逆も同様で、最終的に 2 つを加算した結果がソーベル オペレーターの結果になります。最初から dx と dy を 1 に設定することはできないため、この効果は得られません。

5、シャール演算子

定義: Sobel と似ていますが、使用するカーネル値が異なり、3x3 のみであり、x 方向または y 方向のエッジ情報のみが取得できます。Sobel オペレーターは導関数の近似値を取得するため
、 kernel=3 の場合、画像内の弱いエッジ抽出効果はあまり正確ではありません。OpenCV は Scharr 関数の改良版を使用します。演算子は上の右の図に示すとおりです。

たとえば、3 * 3 Sobel 演算子の場合、勾配角度が水平または垂直の場合、その不正確さは非常に明白です。Scharr オペレーターは Sobel オペレーターの違いを拡張したものであり、画像のエッジを検出する原理と使用法は 2 つで同じです。

Scharr オペレーターの主なアイデアは、テンプレート内の重み係数を拡大することでピクセル値間の差を増やし、
x または y 方向の画像差分ソーベルを計算することです。Scharr オペレーターなどのオペレーターは、 : 出力画像深度 >= 入力画像深度。
ここに画像の説明を挿入

void Sobel(
InputArray src,     输入图像(单通道)
OutputArray dst,     输出图像
int ddepth,       输出图像深度 (>= 输入图像深度)
int dx,         x梯度(几阶导数)一般为012,其中0表示这个方向上没有求导
int dy,         y梯度(几阶导数)
int ksize = 3,      滤波窗口大小(357…)
double scale = 1,    是否缩放,默认=1.0
double delta = 0,     偏移常数
int borderType = BORDER_DEFAULT )  图像边缘处理方式。默认值=cv2.BORDER_DEFAULT

処理後、convertScaleAbs() 関数を使用して元の uint8 形式に変換する必要があります。そうしないと、画像は表示されず、灰色のウィンドウが表示されるだけになります。

void convertScaleAbs(
InputArray src,       输入图像
OutputArray dst,    输出uint8类型图片
double alpha = 1,     伸缩系数
double beta = 0     偏移值:加到结果上的一个值
)

Sobel オペレーターは 2 方向で計算されるため、最後に cv::addWeighted(src1, alpha, src2, beta, gamma, dst) 関数と組み合わせる必要があります。

//头文件 quick_opencv.h:声明类与公共函数
#pragma once
#include <opencv2\opencv.hpp>
using namespace cv;

class QuickDemo {
    
    
public:
	...
	void edge_extract_Demo(Mat& image1);
	void laplance_Demo(Mat& image1);
	void canny_Demo(Mat& image1);
};
//主函数调用该类的公共成员函数
#include <opencv2\opencv.hpp>
#include <quick_opencv.h>
#include <iostream>
using namespace cv;


int main(int argc, char** argv) {
    
    
	Mat src = imread("D:\\Desktop\\jianbian.png");
	if (src.empty()) {
    
    
		printf("Could not load images...\n");
		return -1;
	}
	QuickDemo qk;
	qk.edge_extract_Demo(src1);
	qk.laplance_Demo(src1);
	qk.canny_Demo(src1);
	waitKey(0);
	destroyAllWindows();
	return 0;
}
//源文件 quick_demo.cpp:实现类与公共函数
void QuickDemo::edge_extract_Demo(Mat& image) {
    
    
	Mat gau_dst, gray_dst, xgrad, ygrad;
	GaussianBlur(image, gau_dst, Size(3, 3), 10);
	cvtColor(gau_dst, gray_dst, COLOR_BGR2GRAY);

	//Sobel(gray_dst, xgrad, -1, 1, 0, 3);
	//Sobel(gray_dst, ygrad, -1, 0, 1, 3);

	//Sobel(gray_dst, xgrad, CV_16S, 1, 0, 3);
	//Sobel(gray_dst, ygrad, CV_16S, 0, 1, 3);

	Scharr(gray_dst, xgrad, CV_16S, 1, 0, 3);
	Scharr(gray_dst, ygrad, CV_16S, 0, 1, 3);
	convertScaleAbs(xgrad, xgrad);
	convertScaleAbs(ygrad, ygrad);

	Mat xygrad;
	addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad);
	imshow("xgrad", xgrad);
	imshow("ygrad", ygrad);
	imshow("xygrad", xygrad);

	Mat xy_grad = Mat::zeros(xgrad.size(),xgrad.type());
	int width = xgrad.cols;
	int height = xgrad.rows;
	for (int h = 0; h < height; h++) {
    
    
		uchar* current_x = xgrad.ptr<uchar>(h);
		uchar* current_y = ygrad.ptr<uchar>(h);
		uchar* current_d = xy_grad.ptr<uchar>(h);
		for (int w = 0; w < width; w++) {
    
    
			*current_d++ = saturate_cast<uchar>(*current_x++ + *current_y++);
		}
	}
	imshow("xy_grad", xy_grad);
}

出力イメージ深度: -1 (元のイメージと一致)
ここに画像の説明を挿入
出力イメージ深度: CV_16S 方向のグラデーションの詳細がさらに多くなります。

ここに画像の説明を挿入
カスタム関数を使用して結果を直接追加します (右): (addWeighted 関数を使用して、各方向に 0.5 の勾配を持つ重みの組み合わせが仮想です: 左)
ここに画像の説明を挿入
scharr() の効果をテストします: 詳細
ここに画像の説明を挿入

6. ラプラシアン演算子

利点: 2 方向のエッジを同時に取得できます。

欠点: ノイズの影響を受けやすいため、通常はラプラシアン オペレーターを呼び出す前にノイズを除去する必要があります。

関数プロトタイプ:

ラプラシアン (img, d Depth, ksize=1)
が 2 階導関数の場合、最大変化時の値は 0、つまりエッジは 0 になります。この理論によれば、二次微分の計算を通じて画像の二次微分を計算し、エッジを抽出することができます。
ここに画像の説明を挿入

void Laplacian(
InputArray src,      输入图像(单通道)
OutputArray dst,      输出图像
int ddepth,        输出图像深度 (>= 输入图像深度)
int ksize = 1,       滤波窗口大小(357…)
double scale = 1,     是否缩放,默认=1.0
double delta = 0,     偏移常数
int borderType = BORDER_DEFAULT   图像边缘处理方式
)

一般的な処理の流れ

ガウスぼかし – ノイズ除去 GaussianBlur()
グレースケール画像に変換 cvtColor()
ラプラシアン – 二次微分計算 Laplacian()
絶対値を取る ConvertScaleAbs()
結果を表示

void QuickDemo::laplance_Demo(Mat& image) {
    
    
	Mat gau_dst, gray_dst, laplance_dst, ygrad;

	GaussianBlur(image, gau_dst, Size(3, 3), 10);
	cvtColor(gau_dst, gray_dst, COLOR_BGR2GRAY);

	Laplacian(gray_dst, laplance_dst, CV_16S, 3, 1.0, 0);
	convertScaleAbs(laplance_dst, laplance_dst);

	threshold(laplance_dst, laplance_dst, 0, 255, THRESH_OTSU | THRESH_BINARY);
	imshow("laplance_dst", laplance_dst);
}

ここに画像の説明を挿入

chess = cv2.imread('chess.png')
result = cv2.Laplacian(chess, cv2.CV_64F, ksize=5)

cv2.imshow('chess', chess)
cv2.imshow('result', result)
cv2.waitKey(0)

ここに画像の説明を挿入
エフェクトの観点から見ると、ソーベルステップよりもシンプルで効果が優れていますが、ノイズが多すぎると効果が悪化するという欠点があります。

10.キャニーアルゴリズム

実装手順:

1. 5x5 ガウス フィルターを使用してノイズを除去します。

2. Sobel を使用して画像の勾配の方向 (0°、45°、90°、135°) を計算します。

3. 極大値を取得します。

4. 閾値の計算。

関数プロトタイプ:

キャニー(img、minVal、maxVal、…)

このうち、minVal と maxVal はエッジの閾値を表し、両者の差が大きすぎると特定のエッジ情報が失われます。

コード例:

img = cv2.imread('1.jpg')
result = cv2.Canny(img, 100, 200)

cv2.imshow('org', img)
cv2.imshow('result', result)
cv2.waitKey(0)

ここに画像の説明を挿入
Canny アルゴリズムの導入 - 非最大信号抑制
ここに画像の説明を挿入
T1、T2 はしきい値、T2 より大きいピクセルはすべて保持され、T1 より小さいピクセルはすべて破棄されます。T2 より大きいピクセルから開始し、T1 より大きく、互いに接続されているピクセルはすべて保持されます。 。最後に、出力バイナリ イメージが取得されます。
上限しきい値と下限しきい値の推奨比率は T2:T1 = 3:1/2:1 です。ここで、T2 は上限しきい値、T1 は下限しきい値です。

void Canny(
InputArray image,        输入图像(单通道)
OutputArray edges,     输出图像
double threshold1,      阈值T1
double threshold2,     阈值T2 一般为 2*T1
int apertureSize = 3,     Soble算子的size窗口大小 
bool L2gradient = false   默认false L1归一化,Ture则为L2归一化
);

一般的な処理の流れ

ガウスぼかし - GaussianBlur
グレースケール変換 - cvtColor
計算勾配 - Sobel/Scharr
非最大信号抑制
高閾値および低閾値出力バイナリ画像

void QuickDemo::canny_Demo(Mat& image) {
    
    
	Mat gau_dst, gray_dst, grad_dst;

	GaussianBlur(image, gau_dst, Size(3, 3), 10);
	cvtColor(gau_dst, gray_dst, COLOR_BGR2GRAY);

	Canny(gray_dst, grad_dst, 50.0, 100.0, 3, false);
	imshow("grad_dst", grad_dst);
}

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/jiyanghao19/article/details/131783149