Goal: Image filters and edge detectors
透過高斯、均值和中值模糊濾波器去除雜訊。
透過 sobel 和 laplacian 定向濾波器檢測邊緣。
透過 Canny 檢測器檢測邊緣
作業環境
Window 11
Visual Studio 2019
OpenCV 4.5.5
CMake 3.24.3
CPU:11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz 2.42 GHz
RAM:16.0 GB
對一張圖像執行高斯、均值和中值濾波器。並添加椒鹽噪聲(salt-and-pepper noise)並再次執行三個濾波器。
高斯濾波器Gaussian filters
高斯模糊是一種常用的圖像處理技術,要對圖像做高斯模糊,就是用一個符合高斯函數分佈的摺積核[1]對數字圖像做摺積[2]運算,可以平滑圖像並減少圖像中的噪聲。它基於常態分佈計算像素之間的權重,將每個像素的值設置為周圍像素值的加權平均值。高斯模糊濾波器是一種低通濾波器,可以用於圖像降噪和去除高頻細節。高斯模糊可以通過多次連續的高斯模糊來產生更加強烈的平滑效果(3x3 kernel多做幾次可以跟5x5、7x7效果差不多,而且更快)。
σ是常態分佈的標準偏差。σ越大,高斯濾波器的頻帶就越寬,平滑程度就越好,低通(高阻)濾波的效果越明顯。
一維高斯
二維高斯
在二維空間中,公式生成的曲面的等高線是從中心開始呈常態分佈的同心圓。
[1] 摺積核: 四方形網格結構,每個方格都有一個權重值
[2] 摺積(convolution): ㄒ使用摺積核對圖像每一個像素進行操作,呈現兩種或多種訊號相互疊加時改變訊號的現象。通常這些摺積計算都是線性,所以又叫線性濾波。
Function prototype:
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )
src: 輸入影像,可以有任意數量的通道,各個通道會被獨立處理。
dst: 輸出影像。影像大小、通道數以及深度都必須與輸入影像一樣。
ksize: kernel大小,可用cv::Size(x, y) 定義,x,y可以不一樣,但x,y必須為正奇數
sigmaX: Gaussian kernel在 X方向的標準差
sigmaY: Gaussian kernel在 Y方向的標準差,若為0則,sigmaX= sigmaY
borderType: 邊界類型
均值濾波器Mean filters
均值濾波是一種常用的圖像處理技術,它可以有效地去除圖像中的雜訊和平滑圖像紋理。該算法的原理是利用像素周圍的鄰域像素值的平均值來代替目標像素的值,因此可以消除一定程度上的高頻噪聲,使圖像更加平滑。在實際應用中,均值濾波常用於圖像的前處理,如邊緣檢測等。但是在處理某些具有複雜紋理或者特殊形狀的圖像時,均值濾波可能會產生模糊效果,因此需要根據實際應用場景選擇合適的濾波算法。
Function Prototype
void blur(InputArray src, OutputArray dst, Size ksize,Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
src: 輸入影像,可以有任意數量的通道,各個通道會被獨立處理。
dst: 輸出影像。影像大小、通道數以及深度都必須與輸入影像一樣。
ksize: kernel大小,可用cv::Size(x, y) 定義,x,y可以不一樣,但x,y必須為正奇數。
anchor: Kernel anchor預設為(-1, -1),表示anchor在kernel的中心。
borderType: 邊界類型。
中值濾波器Median filters
中值濾波法是一種非線性的圖像處理方法(對雜訊的抵抗最強),將每一像素點的像素值設置為該點某鄰域窗口內的所有像素點像素值的中值,對於去除椒鹽噪聲等雜訊有很好的效果。與鄰域平均法相比,中值濾波法具有更好的邊界保留能力,這使得它在許多實際應用中更為實用。通過選擇適當的窗口大小和形狀,中值濾波法可以適應不同的應用場景。
Function Prototype
void medianBlur(InputArray src, OutputArray dst, int ksize)
src:輸入影像,可以有任意數量的通道,各個通道會被獨立處理。
dst: 輸出影像。影像大小、通道數以及深度都必須與輸入影像一樣。
ksize: kernel大小,可用cv::Size(x, y) 定義,x,y可以不一樣,但x,y必須為正奇數。
denoise.cpp程式碼與註解
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <random>
using namespace std;
using namespace cv;
//椒鹽
void salt_pepper(cv::Mat image, int n) {
// C++11 random number generator 隨機數產生器
std::default_random_engine generator;
std::uniform_int_distribution<int> randomRow(0, image.rows - 1);
std::uniform_int_distribution<int> randomCol(0, image.cols - 1);
int i, j, ii, jj;
for (int k = 0; k < n; k++) {
//salt
// random image coordinate 隨機生成的位置
i = randomCol(generator);
j = randomRow(generator);
//用type區分灰階和彩色影像
if (image.type() == CV_8UC1) { // gray-level image 灰階影像
// single-channel 8-bit image 灰階只要改一個8-bit
image.at<uchar>(j, i) = 255;
}
else if (image.type() == CV_8UC3) { // color image 彩色影像
// 3-channel image 彩色要改3個(RGB)
image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
}
//pepper
ii = randomCol(generator);
jj = randomRow(generator);
//用type區分灰階和彩色影像
if (image.type() == CV_8UC1) { // gray-level image 灰階影像
// single-channel 8-bit image 灰階只要改一個8-bit
image.at<uchar>(jj, ii) = 0;
}
else if (image.type() == CV_8UC3) { // color image 彩色影像
// 3-channel image 彩色要改3個(RGB)
image.at<cv::Vec3b>(j, i) = cv::Vec3b(0, 0, 0);
}
}
}
void medianblur(const Mat& image) {
Mat result;
imshow("image", image);
}
int main()
{
// open the image
Mat image = imread("C:/Users/User/Desktop/CV_testimage/P3/woonwoodog.png", 1);
Mat gaussianblur_result,mean_result,median_result;
imshow("Image", image);
// call function to add noise
//salt_pepper(image, 10000);
//高斯模糊濾波
//kernel大小改變,simgaX、simgaY相同且不變
GaussianBlur(image, gaussianblur_result, Size(3, 3), 3);//沒有設定simgaY,則simgaY=simgaX
imshow("gaussianblur 3x3", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3);
imshow("gaussianblur 9x9", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 3);
imshow("gaussianblur 15x15", gaussianblur_result);
waitKey();
//kernel大小不變,simgaX、simgaY相同且改變
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3);
imshow("gaussianblur simgaXY=3", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9);
imshow("gaussianblur simgaXY=9", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 15);
imshow("gaussianblur simgaXY=15", gaussianblur_result);
waitKey();
//只改變simgaX
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 3);
imshow("gaussianblur simgaX=3", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9, 3);
imshow("gaussianblur simgaX=9", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 15, 3);
imshow("gaussianblur simgaX=15", gaussianblur_result);
waitKey();
//只改變simgaY
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 3);
imshow("gaussianblur simgaY=3", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 9);
imshow("gaussianblur simgaY=9", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 15);
imshow("gaussianblur simgaY=15", gaussianblur_result);
waitKey();
//kernel大小改變,simgaX、simgaY不變(維持 X > Y)
GaussianBlur(image, gaussianblur_result, Size(3, 3), 9, 3);
imshow("gaussianblur 3x3 simga X>Y", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9, 3);
imshow("gaussianblur 9x9 simga X>Y", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 9, 3);
imshow("gaussianblur 15x15 simga X>Y", gaussianblur_result);
waitKey();
//kernel大小改變,simgaX、simgaY不變(維持 X < Y)
GaussianBlur(image, gaussianblur_result, Size(3, 3), 3, 9);
imshow("gaussianblur 3x3 simga X<Y", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 9);
imshow("gaussianblur 9x9 simga X<Y", gaussianblur_result);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 3, 9);
imshow("gaussianblur 15x15 simga X<Y", gaussianblur_result);
waitKey();
//均值模糊濾波
//kernel大小改變
blur(image, mean_result, Size(3, 3));
imshow("blur 3x3", mean_result);
blur(image, mean_result, Size(5, 5));
imshow("blur 5x5", mean_result);
blur(image, mean_result, Size(7, 7));
imshow("blur 7x7", mean_result);
blur(image, mean_result, Size(9, 9));
imshow("blur 9x9", mean_result);
blur(image, mean_result, Size(15, 15));
imshow("blur 15x15", mean_result);
blur(image, mean_result, Size(21, 21));
imshow("blur 21x21", mean_result);
waitKey();
//中值模糊濾波
medianBlur(image, median_result, 3);
imshow("medianblur 3x3", median_result);
medianBlur(image, median_result, 5);
imshow("medianblur 5x5", median_result);
medianBlur(image, median_result, 7);
imshow("medianblur 7x7", median_result);
medianBlur(image, median_result, 9);
imshow("medianblur 9x9", median_result);
medianBlur(image, median_result, 15);
imshow("medianblur 15x15", median_result);
medianBlur(image, median_result, 21);
imshow("medianblur 21x21", median_result);
waitKey();
return 0;
}
原圖
原圖加椒鹽噪聲
salt_pepper(image, 10000);
高斯濾波器Gaussian filters
kernel大小改變,simgaX、simgaY相同且不變
結果:kernel越大越模糊
GaussianBlur(image, gaussianblur_result, Size(3, 3), 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 3);
kernel大小不變,simgaX、simgaY相同且改變
結果:simga值越大越模糊
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 15);
只改變simgaX
結果:simgaX值越大越模糊
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 15, 3);
只改變simgaY
結果:simgaY值越大越模糊
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 9);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 15);
結果:將simgaX=15和simgaY=15放在一起比較,可以看出simgaX的x軸較模糊,而simgaY的y軸較模糊
simgaX=15
simgaY=15
kernel大小改變,simgaX、simgaY不變(維持 X > Y)
結果:kernel越大越模糊
GaussianBlur(image, gaussianblur_result, Size(3, 3), 9, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9, 3);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 9, 3);
kernel大小改變,simgaX、simgaY不變(維持 X < Y)
結果:kernel越大越模糊
GaussianBlur(image, gaussianblur_result, Size(3, 3), 3, 9);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 9);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 3, 9);
結果:將kernel=15,X > Y、X < Y放在一起比較,可以看出X > Y的x軸較模糊,而X < Y的y軸較模糊
kernel=15,X > Y
kernel=15,X < Y
高斯濾波器Gaussian filters + 椒鹽噪聲(salt-and-pepper noise)
kernel大小改變,simgaX、simgaY相同且不變
結果:Kernel越大,越模糊,雜訊越少
GaussianBlur(image, gaussianblur_result, Size(3, 3), 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 3);
kernel大小不變,simgaX、simgaY相同且改變
結果:simga越大,越模糊,雜訊越少
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 15);
只改變simgaX
結果:simgaX越大,越模糊,雜訊越少
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 15, 3);
只改變simgaY
結果:simgaY越大,越模糊,雜訊越少
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 9);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 15);
結果:將simgaX=15和simgaY=15放在一起比較,可以看出simgaX的x軸較模糊、y軸的雜訊較明顯,而simgaY的y軸較模糊、x軸的雜訊較明顯
simgaX=15
simgaY=15
kernel大小改變,simgaX、simgaY不變(維持 X > Y)
結果:Kernel越大,越模糊,雜訊越少
GaussianBlur(image, gaussianblur_result, Size(3, 3), 9, 3);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 9, 3);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 9, 3);
kernel大小改變,simgaX、simgaY不變(維持 X < Y)
結果:Kernel越大,越模糊,雜訊越少
GaussianBlur(image, gaussianblur_result, Size(3, 3), 3, 9);
GaussianBlur(image, gaussianblur_result, Size(9, 9), 3, 9);
GaussianBlur(image, gaussianblur_result, Size(15, 15), 3, 9);
結果:將kernel=15,X > Y、X < Y放在一起比較,可以看出X > Y的x軸較模糊(有橫向延展的感覺),而X < Y的y軸較模糊(有縱向延展的感覺)
kernel=15,X > Y
kernel=15,X < Y
均值濾波器Mean filters
kernel大小改變
結果:Kernel越大,越模糊
blur(image, mean_result, Size(3, 3));
blur(image, mean_result, Size(5, 5));
blur(image, mean_result, Size(7, 7));
blur(image, mean_result, Size(9, 9));
blur(image, mean_result,
Size(15, 15));
blur(image, mean_result,
Size(21, 21));
均值濾波器Mean filters + 椒鹽噪聲(salt-and-pepper noise)
kernel大小改變
結果:Kernel越大,越模糊,雜訊越少
blur(image, mean_result, Size(3, 3));
blur(image, mean_result, Size(5, 5));
blur(image, mean_result, Size(7, 7));
blur(image, mean_result, Size(9, 9));
blur(image, mean_result,
Size(15, 15));
blur(image, mean_result,
Size(21, 21));
中值濾波器Median filters
kernel大小改變
結果:Kernel越大,越模糊
medianBlur(image, median_result, 3);
medianBlur(image, median_result, 5);
medianBlur(image, median_result, 7);
medianBlur(image, median_result, 9);
medianBlur(image, median_result, 15);
medianBlur(image, median_result, 21);
中值濾波器Median filters + 椒鹽噪聲(salt-and-pepper noise)
kernel大小改變
結果:Kernel越大,越模糊。幾乎看不到雜訊。
medianBlur(image, median_result, 3);
medianBlur(image, median_result, 5);
medianBlur(image, median_result, 7);
medianBlur(image, median_result, 9);
medianBlur(image, median_result, 15);
medianBlur(image, median_result, 21);
結果:幾乎看不到雜訊,只有kernel 3x3看到幾個黑點,如下圖所示。
II. Edge Detection程式實做
運用Sobel 和 Laplacian 濾波器來實現邊緣偵測
Sobel filter
Sobel是一種典型的用於邊緣檢測的線性濾波器,經過一次微分,濾波有方向性,它基於兩個簡單的 3×3 內核,內核結構如右所示。
如果把圖像看作二維函數,那麼 Sobel 就是圖像在垂直和水平方向變化的速度,這種速度稱為梯度,是一個二維向量,向量的元素是橫豎兩個方向的函數的一階導數(如右所示)。Sobel 在水平和垂直方向計算像素值的差分,得到圖像梯度的近似值。它在像素周圍的一定範圍內進行運算,以減少噪聲帶來的影響。
Function Prototype
void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
src: 輸入影像
dst: 輸出影像(影像大小、通道數以及深度都必須與輸入影像一樣)
ddepth: 輸出影像的通道數
dx: x方向的微分階數
dy: y方向的微分階數
scale: 輸出結果縮放系數
delta: 輸出結果位移系數
ksize: kernel大小,必須為1、3、5、7或CV_SCHARR(-1)
borderType:邊界類型
Laplacian filter
是一種基於二階微分的邊緣檢測算法,常用於圖像處理和計算機視覺中。通過計算圖像中每個像素周圍像素的二階導數,來檢測圖像中的邊緣和輪廓。將每個像素與其周圍八個像素(或四個像素,具體取決於所選的核大小)進行摺積,Laplacian內核的值的累加和等於0,得到該像素周圍像素的二階導數值,然後將這些值相加得到該像素的Laplacian值。在邊緣處,Laplacian值會發生較大的變化,因此可以用來檢測邊緣和輪廓。
與 Sobel相比,Laplacian在計算時可以使用更大的內核,並且對圖像噪聲更加敏感,因此是更理想的選擇(除非要求計算效率)。因為這些更大的內核是用高斯函數的二階導數計算的,因此這個也稱為Laplacian of Gaussian(LoG)。
LoG:
Function Prototype
void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
src: 輸入影像
dst: 輸出影像(影像大小、通道數以及深度都必須與輸入影像一樣)
ddepth: 輸出影像的通道數
ksize: kernel大小,必須為正奇數
scale: 輸出結果縮放系數
delta: 輸出結果位移系數
borderType: 邊界類型
edge_detection.cpp 程式碼與註解
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <random>
using namespace std;
using namespace cv;
int main() {
Mat image, sobel_result, laplacian_result;
image = imread("C:/Users/User/Desktop/CV_testimage/P3/woonwoodog.png", 1);//彩色
//Sobel
// x、y方向各微分1次、皆微分1次
Sobel(image, sobel_result, CV_8U, 1, 0);// 圖樣類型CV_8U:unsigned 8-bit(0~255)
imshow("Sobel dX", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 1);
imshow("Sobel dY", sobel_result);
Sobel(image, sobel_result, CV_8U, 1, 1);
imshow("Sobel dXdY", sobel_result);
waitKey();
//x、y方向微分1次
//kernel大小3、5、7
Sobel(image, sobel_result, CV_8U, 1, 0, 3);
imshow("Sobel dX 3x3", sobel_result);
Sobel(image, sobel_result, CV_8U, 1, 0, 5);
imshow("Sobel dX 5x5", sobel_result);
Sobel(image, sobel_result, CV_8U, 1, 0, 7);
imshow("Sobel dX 7x7", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 1, 3);
imshow("Sobel dY 3x3", sobel_result);
Sobel(image, sobel_result, CV_8U, 1, 0, 5);
imshow("Sobel dY 5x5", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 1, 7);
imshow("Sobel dY 7x7", sobel_result);
waitKey();
//x方向微分1、3、5次
//kernel大小3、5、7
Sobel(image, sobel_result, CV_8U, 1, 0, 3);
imshow("Sobel dX", sobel_result);
Sobel(image, sobel_result, CV_8U, 3, 0, 5);
imshow("Sobel dX^3", sobel_result);
Sobel(image, sobel_result, CV_8U, 5, 0, 7);
imshow("Sobel dX^5", sobel_result);
waitKey();
//y方向微分1、3、5次
//kernel大小3、5、7
Sobel(image, sobel_result, CV_8U, 0, 1, 3);
imshow("Sobel dY", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 3, 5);
imshow("Sobel dY^3", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 5, 7);
imshow("Sobel dY^5", sobel_result);
waitKey();
//x、y方向各微分1次
//kernel大小3x3
//scale 0.4、4.0
Sobel(image, sobel_result, CV_8U, 1, 0, 3, 0.4);
imshow("Sobel dX scale=0.4", sobel_result);
Sobel(image, sobel_result, CV_8U, 1, 0, 3, 4.0);
imshow("Sobel dX scale=4.0", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 1, 3, 0.4);
imshow("Sobel dY scale=0.4", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 1, 3, 4.0);
imshow("Sobel dY scale=4.0", sobel_result);
waitKey();
//x、y方向各微分1次
//kernel大小3x3
//scale 1.0
//delta 1.5、3.0
Sobel(image, sobel_result, CV_8U, 1, 0, 3, 1.0, 1.5);
imshow("Sobel dX delta=1.5", sobel_result);
Sobel(image, sobel_result, CV_8U, 1, 0, 3, 1.0, 3.0);
imshow("Sobel dX delta=3.0", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 1, 3, 1.0, 1.5);
imshow("Sobel dY delta=1.5", sobel_result);
Sobel(image, sobel_result, CV_8U, 0, 1, 3, 1.0, 3.0);
imshow("Sobel dY delta=3.0", sobel_result);
waitKey();
//Laplacian
Laplacian(image, laplacian_result, CV_8U, 1);
imshow("Laplacian", laplacian_result);
waitKey();
//改變kernel大小
Laplacian(image, laplacian_result, CV_8U, 3);
imshow("Laplacian 3x3", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 5);
imshow("Laplacian 5x5", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 9);
imshow("Laplacian 9x9", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 11);
imshow("Laplacian 11x11", laplacian_result);
waitKey();
//kernel大小3x3
//scale 2.5、3.0、4.0、5.0
Laplacian(image, laplacian_result, CV_8U, 3, 2.5);
imshow("Laplacian scale=2.5", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 3, 3.0);
imshow("Laplacian scale=3.0", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 3, 4.0);
imshow("Laplacian scale=4.0", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 3, 5.0);
imshow("Laplacian scale=5.0", laplacian_result);
waitKey();
//kernel大小3x3
//scale 1.0
//delta 2.5、3.0、4.0、5.0
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 2.5);
imshow("Laplacian delta=2.5", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 3.0);
imshow("Laplacian delta=3.0", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 4.0);
imshow("Laplacian delta=4.0", laplacian_result);
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 5.0);
imshow("Laplacian delta=5.0", laplacian_result);
waitKey();
}
原圖
Sobel filter
x、y方向各微分1次、皆微分1次
結果:x方向微1次的y方向邊緣比較明顯,而y方向微1次的x方向邊緣比較明顯
Sobel(image, sobel_result, CV_8U,
1, 0);
Sobel(image, sobel_result, CV_8U,
0, 1);
Sobel(image, sobel_result, CV_8U,
1, 1);
x方向微分1次,kernel大小3、5、7
結果:kernel越大,x方向微分1次的效果越明顯,越看不出原圖
Sobel(image, sobel_result, CV_8U, 1, 0, 3);
Sobel(image, sobel_result, CV_8U, 1, 0, 5);
Sobel(image, sobel_result, CV_8U, 1, 0, 7);
y方向微分1次,kernel大小3、5、7
結果:kernel越大,y方向微分1次的效果越明顯,越看不出原圖
Sobel(image, sobel_result, CV_8U,0 , 1, 3);
Sobel(image, sobel_result, CV_8U, 0, 1, 5);
Sobel(image, sobel_result, CV_8U, 0, 1, 7);
x方向微分1、3、5次,kernel大小3、5、7
結果:kernel越大,x方向微分次數越多,線條越多,y方向邊緣越明顯,越看不出原圖
Sobel(image, sobel_result, CV_8U, 1, 0, 3);
Sobel(image, sobel_result, CV_8U, 3, 0, 5);
Sobel(image, sobel_result, CV_8U, 5, 0, 7);
y方向微分1、3、5次,kernel大小3、5、7
結果:kernel越大,y方向微分次數越多,線條越多,x方向邊緣越明顯,越看不出原圖
Sobel(image, sobel_result, CV_8U, 0, 1, 3);
Sobel(image, sobel_result, CV_8U, 0, 3, 5);
Sobel(image, sobel_result, CV_8U, 0, 5, 7);
x、y方向各微分1次,kernel大小3x3,scale 0.4、4.0
結果:x方向微分1次,scale 4.0的邊緣較scale 0.4的邊緣明顯
y方向微分1次,scale 4.0的邊緣較scale 0.4的邊緣明顯
Sobel(image, sobel_result, CV_8U,
1, 0, 3, 0.4);
Sobel(image, sobel_result, CV_8U,
1, 0, 3, 4.0);
Sobel(image, sobel_result, CV_8U,
0, 1, 3, 0.4);
Sobel(image, sobel_result, CV_8U,
0, 1, 3, 4.0);
x、y方向各微分1次,kernel大小3x3,scale 1.0,delta 1.5、3.0
結果:x方向微分1次,delta 1.5和delta 3.0的差異沒有很大,仔細可看出delta 3.0的邊緣較白(亮)
y方向微分1次,delta 1.5和delta 3.0的差異沒有很大,仔細可看出delta 3.0的邊緣較白(亮)
Sobel(image, sobel_result, CV_8U,
1, 0, 3, 1.0, 1.5);
Sobel(image, sobel_result, CV_8U,
1, 0, 3, 1.0, 3.0);
Sobel(image, sobel_result, CV_8U,
0, 1, 3, 1.0, 1.5);
Sobel(image, sobel_result, CV_8U,
0, 1, 3, 1.0, 3.0);
Laplacian filter
Laplacian(image, laplacian_result, CV_8U, 1);
改變kernel大小
結果:kernel越大,邊緣越明顯,線條越多
Laplacian(image, laplacian_result, CV_8U, 3);
Laplacian(image, laplacian_result, CV_8U, 5);
Laplacian(image, laplacian_result, CV_8U, 7);
Laplacian(image, laplacian_result, CV_8U, 9);
kernel大小3x3,scale 2.5、3.0、4.0、5.0
結果:scale越大,邊緣強度越大越明顯
Laplacian(image, laplacian_result, CV_8U, 3, 2.5);
Laplacian(image, laplacian_result, CV_8U, 3, 3.0);
Laplacian(image, laplacian_result, CV_8U, 3, 4.0);
Laplacian(image, laplacian_result, CV_8U, 3, 5.0);
kernel大小3x3,scale 1.0,delta 2.5、3.0、4.0、5.0
結果:delta越大,跟delta=1.0比起來越白(亮)
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 2.5);
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 3.0);
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 4.0);
Laplacian(image, laplacian_result, CV_8U, 3, 1.0, 5.0);
III. Canny detector程式實做
Canny
是一個複合性的邊緣偵測演算法,基於 Sobel,結合了Gaussian Filter、梯度偵測(Gradient calculation)、非最大值抑制(Non-maximum suppression)、判斷邊界(雙閾值化(Double thresholding))四個演算法去實踐邊緣偵測。
實踐步驟:
1.圖片轉換成灰階,並利用 Gaussian Blur 去除雜訊
2. 梯度偵測(Gradient calculation),取得圖片每個 pixel 的梯度值和梯度方向(為了簡化計算,梯度的方向約略分成四種,0度、45度、90度、135度)
3.利用非極大值抑制(Non-maximum suppression)尋找可能的邊緣,把每個pixel 和梯度方向的鄰居比較梯度值,如果不是最大的,就去除。
4.根據兩個閾值選取 strong edge(確定的) 和 weak edge(進一步判斷)
a.高於高界線:一定是邊緣
b.低於低界線:一定不是邊緣
c.介於高界線與低界線:若附近有兩點高於高界線的點,則此點也視為邊緣
5.選取和 strong edge 相連的 weak edge 當作確定的 edge
Function Prototype
Canny ( IntputArray image, OutputArray edge, double threshold1, double threshold2, int apertureSize=3, bool L2gradient = false )
image: 輸入影像,灰階
edge: 輸出影像(影像大小、通道數以及深度都必須與輸入影像一樣)
threshold1: 低threshold
threshold2: 高threshold
apertureSize: Sobel Filter的kernel大小
L2gradient: 選擇要用 L1 norm(絕對值平均)還是 L2 norm(平方根)當作梯度的大小,預設是用 L1 norm
canny_detecor.cpp 程式碼與註解
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image, canny_result;
Mat gray, magn, lap_result;
Mat dx_result, dy_result;
image = imread("C:/Users/User/Desktop/CV_testimage/P3/woonwoodog.png", 0);
imshow("image", image);
short threshold1 = 20, threshold2 = 150;
Canny(image, canny_result, threshold1, threshold2);
imshow("canny", canny_result);
waitKey();
//跟其他比較
Sobel(image, dx_result, CV_32F, 1, 0);
Sobel(image, dy_result, CV_32F, 0, 1);
imshow("dx", dx_result / 255.0);
imshow("dy", dy_result / 255.0);
magnitude(dx_result, dy_result, magn);
Laplacian(image, lap_result, CV_32F, 1);
imshow("Magnitude", magn / 255.0);
imshow("Laplacian", lap_result / 255.0);
waitKey();
return 0;
}
原圖(灰階)
Canny
threshold1 = 20, threshold2 = 150
Canny(image, canny_result, threshold1, threshold2);
改變threshold
結果:threshold會影響線條多寡
threshold1 = 20
threshold2 = 150
threshold1 = 10
threshold2 = 75
threshold1 = 75
threshold2 = 150
跟其他比較
結果:比較後覺得canny對於threshold的選擇很重要
Sobel(image, dx_result, CV_32F, 1, 0);
x方向微分一次
Sobel(image, dy_result, CV_32F, 0, 1);
y方向微分一次
magnitude(dx_result, dy_result, magn);
Laplacian(image, lap_result, CV_32F, 1);
Reference
OpenCV 4 Computer Vision Application Programming Cookbook, by D. M. Escrivá, R. Laganiere, Fourth Edition, Packt Publishing, 2019
OpenCV计算机视觉编程攻略(第3版),2018年 5 月北京第 1 次印刷 ,R. Laganiere,翻譯 相银初
Resizing the output window of imshow function ,Jan.22,2016,sturkmen,
https://answers.opencv.org/question/84985/resizing-the-output-window-of-imshow-function/
邊緣偵測懶人包-Canny演算法,天道酬勤,Jul.2, 2019,https://medium.com/@bob800530/opencv-%E5%AF%A6%E4%BD%9C%E9%82%8A%E7%B7%A3%E5%81%B5%E6%B8%AC-canny%E6%BC%94%E7%AE%97%E6%B3%95-d6e0b92c0aa3
Canny edge detector 實作(OpenCV),Chin-yu Chien,Aug 4, 2018,https://medium.com/@pomelyu5199/canny-edge-detector-%E5%AF%A6%E4%BD%9C-opencv-f7d1a0a57d19
高斯模糊,維基百科 ,Oct.4,2018,https://zh.wikipedia.org/zh-tw/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A
索伯算子,維基百科,Nov.2,2022,https://zh.wikipedia.org/zh-tw/%E7%B4%A2%E4%BC%AF%E7%AE%97%E5%AD%90
OpenCV图像处理|1.9 平滑模糊滤波,Mengcius,July.23,2018,https://zhuanlan.zhihu.com/p/40325840
OpenCV计算机视觉学习(4)——图像平滑处理(均值滤波,高斯滤波,中值滤波,双边滤波),FlyAI ,Nov.2,2020,https://zhuanlan.zhihu.com/p/271995341