OpenCV數(shù)字圖像處理基于C++:邊緣檢測(cè)
1、概述
邊緣檢測(cè)是圖像處理和計(jì)算機(jī)視覺(jué)中的基本問(wèn)題,邊緣檢測(cè)的目的是標(biāo)識(shí)數(shù)字圖像中亮度變化明顯的點(diǎn)。圖像屬性中的顯著變化通常反映了屬性的重要事件和變化。
圖像邊緣檢測(cè)大幅度地減少了數(shù)據(jù)量,并且剔除了可以認(rèn)為不相關(guān)的信息,保留了圖像重要的結(jié)構(gòu)屬性。有許多方法用于邊緣檢測(cè),它們的絕大部分可以劃分為兩類:基于查找和基于零穿越。
基于查找:通過(guò)尋找圖像一階導(dǎo)數(shù)中的最大值和最小值來(lái)檢測(cè)邊界,然后利用計(jì)算結(jié)果估計(jì)邊緣的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值,代表算法是 Sobel 算子、Roberts 算子、 Scharr 算子和差分邊緣檢測(cè)。
基于零穿越︰通過(guò)尋找圖像二階導(dǎo)數(shù)零穿越來(lái)尋找邊界,代表算法是拉普拉斯(Laplacian)算子或者非線性差分表示的過(guò)零點(diǎn)。
邊緣一般是指圖像在某一局部強(qiáng)度劇烈變化的區(qū)域。強(qiáng)度變化一般有兩種情況:
階躍變化:
像數(shù)值從低到高變化,圖像從暗到亮
山頂變化:
- 找到有差異的相鄰像素(邊緣檢測(cè))
- 增加有差異的像素的對(duì)比度(圖像銳化)
邊緣檢測(cè)步驟:
(1)圖像獲取
(2)圖像濾波
邊緣檢測(cè)的算法主要是基于圖像強(qiáng)度的一階和二階導(dǎo)數(shù),但是導(dǎo)數(shù)通常對(duì)噪聲很敏感,因此必須采用濾波器來(lái)改善與噪聲有關(guān)的邊緣檢測(cè)器的性能。
(3)圖像增強(qiáng)
增強(qiáng)邊緣檢測(cè)的基礎(chǔ)是確定圖像各點(diǎn)的領(lǐng)域強(qiáng)度的變化值。增強(qiáng)算法可以將圖像灰度點(diǎn)領(lǐng)域強(qiáng)度值 有顯著變化的點(diǎn)凸顯出來(lái)。
(4)圖像檢測(cè)
(5)圖像定位
2、差分邊緣檢測(cè)
2.1 原理
使用圖像的一階差分代替圖像函數(shù)的導(dǎo)數(shù)。二維離散圖像函數(shù)在x方向上的一階差分定義為,y方向上為
,其中前者計(jì)算垂直邊邊緣,后者計(jì)算水平邊緣。
2.2 實(shí)現(xiàn)差分邊緣檢測(cè)
// 圖像差分操作
void diffOperation(const cv::Mat srcImage, cv::Mat& edgeXImage, cv::Mat& edgeYImage)
{
cv::Mat tempImage = srcImage.clone();
int nRows = tempImage.rows;
int nCols = tempImage.cols;
for (int i = 0; i < nRows - 1; i++)
{
for (int j = 0; j < nCols - 1; j++)
{
// 計(jì)算垂直邊邊緣
edgeXImage.at<uchar>(i, j) =
abs(tempImage.at<uchar>(i + 1, j) -
tempImage.at<uchar>(i, j));
// 計(jì)算水平邊緣
edgeYImage.at<uchar>(i, j) =
abs(tempImage.at<uchar>(i, j + 1) -
tempImage.at<uchar>(i, j));
}
}
}
int main()
{
cv::Mat srcImage = cv::imread("E:\\Lena.jpg");
if (!srcImage.data)
return -1;
cv::imshow("srcImage", srcImage);
cv::Mat edgeXImage(srcImage.size(), srcImage.type());
cv::Mat edgeYImage(srcImage.size(), srcImage.type());
// 計(jì)算差分圖像
diffOperation(srcImage, edgeXImage, edgeYImage);
cv::imshow("edgeXImage1", edgeXImage);
cv::imshow("edgeYImage2", edgeYImage);
cv::Mat edgeImage(srcImage.size(), srcImage.type());
// 水平與垂直邊緣圖像疊加
addWeighted(edgeXImage, 0.5, edgeYImage,
0.5, 0.0, edgeImage);
cv::imshow("edgeImage3", edgeImage);
cv::waitKey(0);
return 0;
}
3、Roberts算子邊緣檢測(cè)
3.1 原理
Roberts算子與普通梯度算子類似,都是取一階的差分作為梯度,區(qū)別在于取值的位置:
正對(duì)角為水平方向,斜對(duì)角為垂直方向:
Roberts算法優(yōu)缺點(diǎn)
優(yōu)點(diǎn):Roberts算子能較好的增強(qiáng)正負(fù)45度的圖像邊緣
缺點(diǎn):對(duì)邊緣的定位不太準(zhǔn)確,提取的邊緣線條較粗
3.2 實(shí)現(xiàn)Roberts算子邊緣檢測(cè)
//Roberts算子實(shí)現(xiàn)
Mat roberts(Mat srcImage)
{
Mat dstImage = srcImage.clone();
int nRows = dstImage.rows;
int nCols = dstImage.cols;
for (int i = 0; i < nRows - 1; i++) {
for (int j = 0; j < nCols - 1; j++) {
//根據(jù)公式計(jì)算
int t1 = (srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1)) *
(srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1));
int t2 = (srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1)) *
(srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1));
//計(jì)算g(x,y)
dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);
}
}
return dstImage;
}
int main()
{
Mat srcImage = imread("E:\\la.jpg");
if (!srcImage.data) {
cout << "falied to read" << endl;
system("pause");
return -1;
}
Mat srcGray;
cvtColor(srcImage, srcGray, CV_BGR2GRAY);
//高斯濾波
GaussianBlur(srcGray, srcGray, Size(3, 3),
0, 0, BORDER_DEFAULT);
Mat dstImage = roberts(srcGray);
imshow("srcImage", srcImage);
imshow("dstImage", dstImage);
waitKey(0);
return 0;
}
4、Prewitt算子邊緣檢測(cè)
4.1 原理
? Prewitt算子是一種一階微分算子的邊緣檢測(cè),利用像素點(diǎn)上下、左右鄰點(diǎn)的灰度差,在邊緣處達(dá)到極值檢測(cè)邊緣,去掉部分偽邊緣,對(duì)噪聲具有平滑作用 。其原理是在圖像空間利用兩個(gè)方向模板與圖像進(jìn)行鄰域卷積來(lái)完成的,這兩個(gè)方向模板一個(gè)檢測(cè)水平邊緣,一個(gè)檢測(cè)垂直邊緣。
? prewitt算子是加權(quán)平均算子,對(duì)噪聲有抑制作用,但是像素平均相當(dāng)于對(duì)圖像進(jìn)行的同濾波,所以prewitt算子對(duì)邊緣的定位不如robert算子。
標(biāo)準(zhǔn)的 Prewitt 邊緣檢測(cè)算子由以下兩個(gè)卷積核組成。
4.2 手工實(shí)現(xiàn) Prewitt 算子邊緣檢測(cè)
//Prewitt輪廓提取算法
Mat myPrewitt(Mat src,int x,int y) {
//獲取圖像屬性
int nRows = src.rows;
int nCols = src.cols;
int dx = 0, dy = 0;
//定義空白圖像,用于存放Roberts算法提取出來(lái)的輪廓圖
Mat dst(src.size(), src.type());
//對(duì)閾值化圖像進(jìn)行遍歷,進(jìn)行Sober算法
for (int i = 1; i < nRows - 1; i++) {
for (int j = 1; j < nCols - 1; j++) {
dx = abs((src.at<uchar>(i + 1, j - 1) + src.at<uchar>(i + 1, j ) + src.at<uchar>(i + 1, j + 1))-(src.at<uchar>(i - 1, j - 1) + src.at<uchar>(i - 1, j) + src.at<uchar>(i - 1, j + 1))) ;
dy = abs((src.at<uchar>(i - 1, j + 1) + src.at<uchar>(i , j + 1 ) + src.at<uchar>(i + 1, j + 1))-(src.at<uchar>(i - 1, j - 1) + src.at<uchar>(i , j - 1) + src.at<uchar>(i + 1, j - 1))) ;
if (x == 0) {
dst.at<uchar>(i, j) = dy;
}
else {
dst.at<uchar>(i, j) = dx;
}
}
}
return dst;
}
int main()
{
//讀取圖像,黑白圖像邊緣檢測(cè)結(jié)果較為明顯
Mat img = imread("E:\\la.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
//顯示圖像
imshow("原圖", img);
cvtColor(img, img, COLOR_RGB2GRAY);
imshow("灰度圖", img);
GaussianBlur(img, img, Size(3, 3), 0); //高斯濾波器(模糊/平滑/近似)消除噪點(diǎn)
Mat dstX = myPrewitt(img,1,0);
imshow("SoberX", dstX);
Mat dstY = myPrewitt(img, 0, 1);
imshow("SoberY", dstY);
convertScaleAbs(dstX, dstX);
//imshow("SoberX2", dstX);
convertScaleAbs(dstY, dstY);
Mat dst;
addWeighted(dstX, 0.5, dstY, 0.5, 0, dst);
imshow("Sober", dst);
waitKey(0);
return 0;
}
convertScaleAbs()用于實(shí)現(xiàn)對(duì)整個(gè)圖像數(shù)組中的每一個(gè)元素,進(jìn)行如下操作:
4.3 函數(shù)實(shí)現(xiàn) Prewitt 算子邊緣檢測(cè)
void getPrewitt_oper(Mat& getPrewitt_horizontal, Mat& getPrewitt_vertical, Mat& getPrewitt_Diagonal1, Mat& getPrewitt_Diagonal2) {
//水平方向
getPrewitt_horizontal = (Mat_<float>(3, 3) << -1, -1, -1, 0, 0, 0, 1, 1, 1);
//垂直方向
getPrewitt_vertical = (Mat_<float>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);
//對(duì)角135°
getPrewitt_Diagonal1 = (Mat_<float>(3, 3) << 0, 1, 1, -1, 0, 1, -1, -1, 0);
//對(duì)角45°
getPrewitt_Diagonal2 = (Mat_<float>(3, 3) << -1, -1, 0, -1, 0, 1, 0, 1, 1);
//逆時(shí)針?lè)崔D(zhuǎn)180°得到卷積核
flip(getPrewitt_horizontal, getPrewitt_horizontal, -1);
flip(getPrewitt_vertical, getPrewitt_vertical, -1);
flip(getPrewitt_Diagonal1, getPrewitt_Diagonal1, -1);
flip(getPrewitt_Diagonal2, getPrewitt_Diagonal2, -1);
}
void edge_Prewitt(Mat& src, Mat& dst1, Mat& dst2, Mat& dst3, Mat& dst4, Mat& dst, int ddepth, double delta = 0, int borderType = BORDER_DEFAULT) {
//獲取Prewitt算子
Mat getPrewitt_horizontal;
Mat getPrewitt_vertical;
Mat getPrewitt_Diagonal1;
Mat getPrewitt_Diagonal2;
getPrewitt_oper(getPrewitt_horizontal, getPrewitt_vertical, getPrewitt_Diagonal1, getPrewitt_Diagonal2);
//卷積得到水平方向邊緣
filter2D(src, dst1, ddepth, getPrewitt_horizontal, Point(-1, -1), delta, borderType);
//卷積得到4垂直方向邊緣
filter2D(src, dst2, ddepth, getPrewitt_vertical, Point(-1, -1), delta, borderType);
//卷積得到45°方向邊緣
filter2D(src, dst3, ddepth, getPrewitt_Diagonal1, Point(-1, -1), delta, borderType);
//卷積得到135°方向邊緣
filter2D(src, dst4, ddepth, getPrewitt_Diagonal2, Point(-1, -1), delta, borderType);
//邊緣強(qiáng)度(近似)
convertScaleAbs(dst1, dst1); //求絕對(duì)值并轉(zhuǎn)為無(wú)符號(hào)8位圖
convertScaleAbs(dst2, dst2);
convertScaleAbs(dst3, dst3); //求絕對(duì)值并轉(zhuǎn)為無(wú)符號(hào)8位圖
convertScaleAbs(dst4, dst4);
dst = dst1 + dst2;
}
int main()
{
Mat dst, dst1, dst2, dst3, dst4;
Mat img = imread("E:\\la.jpg");
cvtColor(img, img, COLOR_BGR2GRAY);
//注意:要采用CV_32F,因?yàn)橛行┑胤骄矸e后為負(fù)數(shù),若用8位無(wú)符號(hào),則會(huì)導(dǎo)致這些地方為0
edge_Prewitt(img, dst1, dst2, dst3, dst4, dst, CV_32F);
imshow("原圖", img);
namedWindow("水平邊緣", WINDOW_NORMAL);
imshow("水平邊緣", dst1);
namedWindow("垂直邊緣", WINDOW_NORMAL);
imshow("垂直邊緣", dst2);
namedWindow("45°邊緣", WINDOW_NORMAL);
imshow("45°邊緣", dst3);
namedWindow("135°邊緣", WINDOW_NORMAL);
imshow("135°邊緣", dst4);
namedWindow("邊緣強(qiáng)度", WINDOW_NORMAL);
imshow("邊緣強(qiáng)度", dst);
waitKey(0);
return 0;
}
5、Sobel算子邊緣檢測(cè)
5.1 原理
Sobel算法(索貝爾算子)是一種用于邊緣檢測(cè)的離散微分算子,它結(jié)合了高斯平滑和微分求導(dǎo)。該算子用于計(jì)算圖像明暗程度近似值,根據(jù)圖像邊緣旁邊明暗程度把該區(qū)域內(nèi)超過(guò)某個(gè)數(shù)的特定點(diǎn)記為邊緣。Sobel算子在Prewitt算子的基礎(chǔ)上增加了權(quán)重的概念,認(rèn)為相鄰點(diǎn)的距離遠(yuǎn)近對(duì)當(dāng)前像素點(diǎn)的影響是不同的,距離越近的像素點(diǎn)對(duì)應(yīng)當(dāng)前像素的影響越大,從而實(shí)現(xiàn)圖像銳化并突出邊緣輪廓。當(dāng)對(duì)精度要求不是很高時(shí),Sobel算子是一種較為常用的邊緣檢測(cè)方法。
Sobel算法提取圖像邊緣的三大步驟:
(1)提取X方向的邊緣,即Gx;
以卷積核的中心為中心,將卷積核與圖像上像素值一一對(duì)應(yīng),卷積核上的數(shù)字相當(dāng)于系數(shù)。利用如下公式即可計(jì)算出卷積核中心的x方向梯度。
(2)提取Y方向的邊緣,即Gy;
同理,如果想要計(jì)算y方向的梯度,卷積核應(yīng)該是這樣的,公式也是同理。
(3)綜合兩個(gè)方向的邊緣信息得到整幅圖的邊緣。
Sobel算法優(yōu)缺點(diǎn)
優(yōu)點(diǎn):Sobel算子的邊緣定位更準(zhǔn)確,會(huì)具有更多的抗噪性,不但產(chǎn)生較好的檢測(cè)效果,而且對(duì)噪聲具有平滑抑制作用;方法簡(jiǎn)單、處理速度快,并且所得的邊緣光滑、連續(xù)
缺點(diǎn):得到的邊緣較粗,且可能出現(xiàn)偽邊緣
5.2 手工實(shí)現(xiàn) Sobel 算子邊緣檢測(cè)
//Sobel輪廓提取算法
Mat mySobel(Mat src) {
//獲取圖像屬性
int nRows = src.rows;
int nCols = src.cols;
int dx = 0, dy = 0;
//定義空白圖像,用于存放Roberts算法提取出來(lái)的輪廓圖
Mat dst(src.size(), src.type());
//對(duì)閾值化圖像進(jìn)行遍歷,進(jìn)行Sober算法
for (int i = 1; i < nRows - 1; i++) {
for (int j = 1; j < nCols - 1; j++) {
dx = (src.at<uchar>(i - 1, j + 1) - src.at<uchar>(i - 1, j - 1)) + 2 * (src.at<uchar>(i, j + 1) - src.at<uchar>(i, j - 1)) + (src.at<uchar>(i + 1, j + 1) - src.at<uchar>(i + 1, j - 1));
dy = (src.at<uchar>(i + 1, j - 1) - src.at<uchar>(i - 1, j - 1)) + 2 * (src.at<uchar>(i + 1, j) - src.at<uchar>(i -1 , j)) + (src.at<uchar>(i + 1, j + 1) - src.at<uchar>(i - 1, j + 1));
dst.at<uchar>(i, j) = sqrt(dx * dx + dy * dy);
}
}
return dst;
}
int main()
{
//讀取圖像,黑白圖像邊緣檢測(cè)結(jié)果較為明顯
Mat img = imread("E:\\la.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
//顯示圖像
imshow("原圖", img);
cvtColor(img, img, COLOR_RGB2GRAY);
imshow("灰度圖", img);
Mat dst = mySobel(img);
imshow("Sober", dst);
waitKey(0);
return 0;
}
Mat img = imread("E:\\la.jpg");
cvtColor(img, img, COLOR_BGR2GRAY);
Mat imageX = Mat::zeros(img.size(), CV_16SC1);
Mat imageY = Mat::zeros(img.size(), CV_16SC1);
Mat imageXY = Mat::zeros(img.size(), CV_16SC1);
Mat imageX8UC;
Mat imageY8UC;
Mat imageXY8UC;
GaussianBlur(img, img, Size(3, 3), 0); //高斯濾波器(模糊/平滑/近似)消除噪點(diǎn)
uchar* P = img.data;
uchar* PX = imageX.data;
uchar* PY = imageY.data;
int step = img.step;
int stepXY = imageX.step;
for (int i = 1; i < img.rows - 1; i++)
{
for (int j = 1; j < img.cols - 1; j++)
{
// 通過(guò)指針遍歷圖像上每一個(gè)像素
// 求出X,Y方向的導(dǎo)數(shù)(梯度) sobel算子加權(quán)的結(jié)果
PX[i * imageX.step + j * (stepXY / step)] = abs(P[(i - 1) * step + j + 1] + P[i * step + j + 1] * 2 + P[(i + 1) * step + j + 1] - P[(i - 1) * step + j - 1] - P[i * step + j - 1] * 2 - P[(i + 1) * step + j - 1]);
PY[i * imageX.step + j * (stepXY / step)] = abs(P[(i + 1) * step + j - 1] + P[(i + 1) * step + j] * 2 + P[(i + 1) * step + j + 1] - P[(i - 1) * step + j - 1] - P[(i - 1) * step + j] * 2 - P[(i - 1) * step + j + 1]);
}
}
addWeighted(imageX, 0.5, imageY, 0.5, 0, imageXY);//融合X、Y方向的梯度
convertScaleAbs(imageX, imageX8UC);
convertScaleAbs(imageY, imageY8UC);
convertScaleAbs(imageXY, imageXY8UC); //轉(zhuǎn)換為8bit圖像
Mat imageSobel;
Mat x_grad, y_grad;
Sobel(img, x_grad, CV_16S, 1, 0, 3);
Sobel(img, y_grad, CV_16S, 0, 1, 3);
convertScaleAbs(x_grad, x_grad);
convertScaleAbs(y_grad, y_grad);
addWeighted(x_grad, 0.5, y_grad, 0.5, 0, imageSobel);
imshow("Source Image", img);
imshow("X Direction", imageX8UC);
imshow("Y Direction", imageY8UC);
imshow("XY Direction", imageXY8UC);
imshow("Opencv Soble", imageSobel);
waitKey(0);
addWeighted()函數(shù)是將兩張相同大小,相同類型的圖片融合的函數(shù)。他可以實(shí)現(xiàn)圖片的特效,不多說(shuō)了,直接上圖。
void addWeighted( const CvArr* src1, double alpha,const CvArr* src2, double beta,double gamma, CvArr* dst );
參數(shù)1:src1,第一個(gè)原數(shù)組.
參數(shù)2:alpha,第一個(gè)數(shù)組元素權(quán)重
參數(shù)3:src2第二個(gè)原數(shù)組
參數(shù)4:beta,第二個(gè)數(shù)組元素權(quán)重
參數(shù)5:gamma,圖1與圖2作和后添加的數(shù)值。不要太大,不然圖片一片白。總和等于255以上就是純白色了。
參數(shù)6:dst,輸出圖片
convertScaleAbs()用于實(shí)現(xiàn)對(duì)整個(gè)圖像數(shù)組中的每一個(gè)元素,進(jìn)行如下操作:
convertScaleAbs(
cv::InputArray src, // 輸入數(shù)組
cv::OutputArray dst, // 輸出數(shù)組
double alpha = 1.0, // 乘數(shù)因子
double beta = 0.0 // 偏移量
);
5.3 函數(shù)實(shí)現(xiàn) Sobel 算子邊緣檢測(cè)
int main()
{
//讀取圖像,黑白圖像邊緣檢測(cè)結(jié)果較為明顯
Mat img = imread("E:\\la.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
Mat resultX, resultY, resultXY;
Sobel(img, resultX, CV_16S, 2, 0, 1);//X方向一階邊緣
convertScaleAbs(resultX, resultX);
Sobel(img, resultY, CV_16S, 0, 1, 3);//Y方向一階邊緣
convertScaleAbs(resultY, resultY);
resultXY = resultX + resultY;//整幅圖像的一階邊緣
//顯示圖像
imshow("原圖", img);
imshow("resultX", resultX);
imshow("resultY", resultY);
imshow("resultXY", resultXY);
waitKey(0);
return 0;
}
CV_EXPORTS_W 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
);
參數(shù)說(shuō)明:
src :輸入圖像;
dst : 輸出與src大小、通道數(shù)相同的圖像;、
ddepth :輸出圖像的深度,必須大于輸入的圖像數(shù)據(jù)類型,參見(jiàn)@ref filter_depth " combined ";在輸入圖像為8位的情況下會(huì)導(dǎo)致導(dǎo)數(shù)被截?cái)唷?dx : x的導(dǎo)數(shù)的階次;
dy : y的導(dǎo)數(shù)的階次;
ksize : 擴(kuò)展Sobel內(nèi)核的大小;它一定是1 3 5或7。
scale : 計(jì)算出的導(dǎo)數(shù)值選擇尺度因子;默認(rèn)情況下是1,不縮放;
delta : 表示在結(jié)果存入目標(biāo)圖(第二個(gè)參數(shù)dst)之前可選的delta值,有默認(rèn)值0。
borderType : 邊界的處理模式,默認(rèn)值:BORDER_DEFAULT。
6、Laplace算子邊緣檢測(cè)(拉普拉斯)
6.1 原理
Laplacian算子具有各方向同性的特點(diǎn),能夠?qū)θ我夥较虻倪吘夁M(jìn)行提取,具有無(wú)方向性的優(yōu)點(diǎn),因此使用Laplacian算子提取邊緣不需要分別檢測(cè)X方向的邊緣和Y方向的邊緣,只需要一次邊緣檢測(cè)即可。利用拉普拉斯算子作邊緣檢測(cè)前最好先對(duì)圖像作一個(gè)高斯濾波,效果會(huì)好不少。
Laplace 是導(dǎo)數(shù)算子,會(huì)突出圖像中的急劇灰度變化,抑制灰度緩慢變化區(qū)域,往往會(huì)產(chǎn)生暗色背景下的灰色邊緣和不連續(xù)圖像。將拉普拉斯圖像與原圖疊加,可以得到保留銳化效果的圖像。
6.2 手工實(shí)現(xiàn) Laplace 算子邊緣檢測(cè)
//銳化算法
Mat& imgSharpen(const Mat& img, char* arith) //arith為3*3模板算子
{
int rows = img.rows; //原圖的行
int cols = img.cols * img.channels(); //原圖的列
int offsetx = img.channels(); //像素點(diǎn)的偏移量
static Mat dst = Mat::ones(img.rows - 2, img.cols - 2, img.type());
for (int i = 1; i < rows - 1; i++)
{
const uchar* previous = img.ptr<uchar>(i - 1);
const uchar* current = img.ptr<uchar>(i);
const uchar* next = img.ptr<uchar>(i + 1);
uchar* output = dst.ptr<uchar>(i - 1);
for (int j = offsetx; j < cols - offsetx; j++)
{
output[j - offsetx] =
saturate_cast<uchar>(previous[j - offsetx] * arith[0] + previous[j] * arith[1] + previous[j + offsetx] * arith[2] +
current[j - offsetx] * arith[3] + current[j] * arith[4] + current[j + offsetx] * arith[5] +
next[j - offsetx] * arith[6] + next[j] * arith[7] + next[j - offsetx] * arith[8]);
}
}
return dst;
}
//Laplace輪廓提取算法
Mat myLaplace(Mat src) {
//獲取圖像屬性
int nRows = src.rows;
int nCols = src.cols;
int dx = 0, dy = 0;
//定義空白圖像,用于存放Roberts算法提取出來(lái)的輪廓圖
Mat dst(src.size(), src.type());
//對(duì)閾值化圖像進(jìn)行遍歷,進(jìn)行Sober算法
for (int i = 1; i < nRows - 1; i++) {
for (int j = 1; j < nCols - 1; j++) {
//防止越界
dst.at<uchar>(i, j) = saturate_cast<uchar>(src.at<uchar>(i - 1,j) + src.at<uchar>(i + 1, j) + src.at<uchar>(i, j - 1) + src.at<uchar>(i , j - 1) - 4 * src.at<uchar>(i, j));
}
}
return dst;
}
int main()
{
char arith[9] = { 0, -1, 0, -1, 5, -1, 0, -1, 0 }; //使用拉普拉斯算子
//讀取圖像,黑白圖像邊緣檢測(cè)結(jié)果較為明顯
Mat img = imread("E:\\la.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "請(qǐng)確認(rèn)圖像文件名稱是否正確" << endl;
return -1;
}
//顯示圖像
imshow("原圖", img);
/*img = imgSharpen(img, arith);
imshow("Sharpen", img);*/
cvtColor(img, img, COLOR_RGB2GRAY);
imshow("灰度圖", img);
//高斯濾波器(模糊/平滑/近似)消除噪點(diǎn)
GaussianBlur(img, img, Size(3, 3), 0);
img = imgSharpen(img,arith);
imshow("Sharpen2", img);
convertScaleAbs(img, img);
Mat dst = myLaplace(img);
imshow("Laplace", dst);
waitKey(0);
return 0;
}
(1)未使用高斯濾波進(jìn)行降噪
(2)使用高斯濾波進(jìn)行降噪,但未銳化
(3)使用高斯濾波進(jìn)行降噪,且銳化
6.3 函數(shù)實(shí)現(xiàn) Laplace 算子邊緣檢測(cè)
int main() {
Mat img = imread("E:\\la.jpg", IMREAD_GRAYSCALE);
if (img.empty()) {
cerr << "image file read error" << endl;
return -1;
}
// resize(im, im, Size(0, 0), 0.5, 0.5);
Mat result1, resultGauss, result2;
// 未使用高斯濾波進(jìn)行邊緣檢測(cè)
Laplacian(img, result1, -1, 3);
convertScaleAbs(result1, result1);
// 先用高斯濾波器進(jìn)行濾波后再進(jìn)行邊緣檢測(cè)
GaussianBlur(img, resultGauss, Size(3, 3), 0);
Laplacian(resultGauss, result2, -1, 3);
convertScaleAbs(result2, result2);
imshow("原圖", img);
imshow("result1", result1);
imshow("result2", result2);
waitKey(0);
destroyAllWindows();
return 0;
}
void cv::Laplacian( InputArray src,
OutputArray dst,
int ddepth,
int ksize = 1,
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)
參數(shù)的意義如下:
src---待提取邊緣的圖像。
dst---輸出圖像,與輸入圖像src具有相同的尺寸和通道數(shù),數(shù)據(jù)類型由第三個(gè)參數(shù)ddepth控制。
ddepth---輸出圖像的數(shù)據(jù)類型(深度)。
ksize---表示Laplacian核的大小。
scale---對(duì)導(dǎo)數(shù)計(jì)算結(jié)果進(jìn)行縮放的縮放因子,默認(rèn)系數(shù)為1,不進(jìn)行縮放。
delta---偏移值,在計(jì)算結(jié)果中加上偏移值。
7、LoG算子邊緣檢測(cè)(高斯拉普拉斯)
7.1 原理
LoG邊緣檢測(cè)算子是David Courtnay Marr和Ellen Hildreth(1980)共同提出的。因此,也稱為邊緣檢測(cè)算法或Marr & Hildreth算子。該算法首先對(duì)圖像做高斯濾波,然后再求其拉普拉斯(Laplacian)二階導(dǎo)數(shù)。即圖像與 Laplacian of the Gaussian function 進(jìn)行濾波運(yùn)算。最后,通過(guò)檢測(cè)濾波結(jié)果的零交叉(Zero crossings)可以獲得圖像或物體的邊緣。因而,也被業(yè)界簡(jiǎn)稱為L(zhǎng)aplacian-of-Gaussian (LoG)算子。
算法描述:LoG算子也就是 Laplace of Gaussian function(高斯拉普拉斯函數(shù))。常用于數(shù)字圖像的邊緣提取和二值化。LoG 算子源于D.Marr計(jì)算視覺(jué)理論中提出的邊緣提取思想, 即首先對(duì)原始圖像進(jìn)行最佳平滑處理, 最大程度地抑制噪聲, 再對(duì)平滑后的圖像求取邊緣。
由于噪聲點(diǎn)(灰度與周圍點(diǎn)相差很大的像素點(diǎn))對(duì)邊緣檢測(cè)有一定的影響,所以效果更好的邊緣檢測(cè)器是LoG算子,也就是Laplacian-Gauss算子。它把的Gauss平滑濾波器和Laplacian銳化濾波器結(jié)合了起來(lái),先平滑掉噪聲,再進(jìn)行邊緣檢測(cè),所以效果會(huì)更好。
基于模板的LoG算子:
7.2 手工實(shí)現(xiàn) LoG 算子邊緣檢測(cè)
//x,y方向聯(lián)合實(shí)現(xiàn)獲取高斯模板
void generateGaussMask(cv::Mat& Mask, cv::Size wsize, double sigma) {
Mask.create(wsize, CV_64F);
int h = wsize.height;
int w = wsize.width;
int center_h = (h - 1) / 2;
int center_w = (w - 1) / 2;
double sum = 0.0;
double x, y;
for (int i = 0; i < h; ++i) {
y = pow(i - center_h, 2);
for (int j = 0; j < w; ++j) {
x = pow(j - center_w, 2);
//因?yàn)樽詈蠖家獨(dú)w一化的,常數(shù)部分可以不計(jì)算,也減少了運(yùn)算量
double g = exp(-(x + y) / (2 * sigma * sigma));
Mask.at<double>(i, j) = g;
sum += g;
}
}
Mask = Mask / sum;
}
//按二維高斯函數(shù)實(shí)現(xiàn)高斯濾波
///
void GaussianFilter(cv::Mat& src, cv::Mat& dst, cv::Mat window) {
int hh = (window.rows - 1) / 2;
int hw = (window.cols - 1) / 2;
dst = cv::Mat::zeros(src.size(), src.type());
//邊界填充
cv::Mat Newsrc;
cv::copyMakeBorder(src, Newsrc, hh, hh, hw, hw, cv::BORDER_REPLICATE);//邊界復(fù)制
//高斯濾波
for (int i = hh; i < src.rows + hh; ++i) {
for (int j = hw; j < src.cols + hw; ++j) {
double sum[3] = { 0 };
for (int r = -hh; r <= hh; ++r) {
for (int c = -hw; c <= hw; ++c) {
if (src.channels() == 1) {
sum[0] = sum[0] + Newsrc.at<uchar>(i + r, j + c) * window.at<double>(r + hh, c + hw);
}
else if (src.channels() == 3) {
cv::Vec3b rgb = Newsrc.at<cv::Vec3b>(i + r, j + c);
sum[0] = sum[0] + rgb[0] * window.at<double>(r + hh, c + hw);//B
sum[1] = sum[1] + rgb[1] * window.at<double>(r + hh, c + hw);//G
sum[2] = sum[2] + rgb[2] * window.at<double>(r + hh, c + hw);//R
}
}
}
for (int k = 0; k < src.channels(); ++k) {
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (src.channels() == 1)
{
dst.at<uchar>(i - hh, j - hw) = static_cast<uchar>(sum[0]);
}
else if (src.channels() == 3)
{
cv::Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<cv::Vec3b>(i - hh, j - hw) = rgb;
}
}
}
}
//DOG高斯差分
///
void DOG1(cv::Mat& src, cv::Mat& dst, cv::Size wsize, double sigma, double k = 1.6) {
cv::Mat Mask1, Mask2, gaussian_dst1, gaussian_dst2;
generateGaussMask(Mask1, wsize, k * sigma);//獲取二維高斯濾波模板1
generateGaussMask(Mask2, wsize, sigma);//獲取二維高斯濾波模板2
//高斯濾波
GaussianFilter(src, gaussian_dst1, Mask1);
GaussianFilter(src, gaussian_dst2, Mask2);
dst = gaussian_dst1 - gaussian_dst2 - 1;
cv::threshold(dst, dst, 0, 255, cv::THRESH_BINARY);
}
//DOG高斯差分--使用opencv的GaussianBlur
void DOG2(cv::Mat& src, cv::Mat& dst, cv::Size wsize, double sigma, double k = 1.6) {
cv::Mat gaussian_dst1, gaussian_dst2;
//高斯濾波
cv::GaussianBlur(src, gaussian_dst1, wsize, k * sigma);
cv::GaussianBlur(src, gaussian_dst2, wsize, sigma);
dst = gaussian_dst1 - gaussian_dst2;
cv::threshold(dst, dst, 0, 255, cv::THRESH_BINARY);
}
int main() {
cv::Mat src = cv::imread("E:\\la.jpg");
if (src.empty()) {
return -1;
}
if (src.channels() > 1) cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat edge1, edge2;
DOG1(src, edge1, cv::Size(7, 7), 2);
DOG2(src, edge2, cv::Size(7, 7), 2);
cv::namedWindow("src", CV_WINDOW_NORMAL);
imshow("src", src);
cv::namedWindow("My_DOG", CV_WINDOW_NORMAL);
imshow("My_DOG", edge1);
cv::namedWindow("Opencv_DOG", CV_WINDOW_NORMAL);
imshow("Opencv_DOG", edge2);
cv::waitKey(0);
return 0;
}
int main(int argc, char** argv)
{
Mat src, gray_src, edge, LOGdst;
src = imread("E:\\la.jpg");
if (!src.data) {
printf("could not load image...");
return -1;
}
imshow("src", src);
cvtColor(src, gray_src, CV_BGR2GRAY);
Mat gauss_output, gauss_output_2;
//定義x方向的模糊因子
float sigma_x = 20.0; //該參數(shù)決定了鄰接像素的權(quán)重
float sigma_y = sigma_x;
//不同的高斯核卷積,實(shí)現(xiàn)了不同尺度特征,可以近似LoG
GaussianBlur(gray_src, gauss_output, Size(3, 3), sigma_x, sigma_y);
GaussianBlur(gray_src, gauss_output_2, Size(11, 11), sigma_x, sigma_y);
imshow("gauss_output", gauss_output);
//基于LoG方法
Laplacian(gauss_output, LOGdst, -1, 3, 1.0, 0.0);
imshow("LoGdst", LOGdst);
//基于DoG 近似
Mat DOGdst(src.size(), CV_32S);
subtract(gauss_output_2, gauss_output, DOGdst);
convertScaleAbs(DOGdst, DOGdst);
normalize(DOGdst, DOGdst, 0, 255, NORM_MINMAX, CV_8UC1);
imshow("DoGdst", DOGdst);
//基于指針的操作比采用at會(huì)快一個(gè)數(shù)量級(jí)
//基于自定義模板卷積核的實(shí)現(xiàn),在經(jīng)過(guò)NMS后效果或許會(huì)更好
Mat LoG_kernel = (Mat_<signed>(5, 5) << 0, 0, -1, 0, 0,
0, -1, -2, -1, 0,
-1, -2, 16, -2, -1,
0, -1, -2, -1, 0,
0, 0, -1, 0, 0);
Mat self_define, gauss_output2;
GaussianBlur(gray_src, gauss_output2, Size(5, 5), 0, 0);
filter2D(gauss_output2, self_define, CV_32FC1, LoG_kernel);
convertScaleAbs(self_define, self_define);
normalize(self_define, self_define, 0, 255, NORM_MINMAX, CV_8UC1);
imshow("self_define", self_define);
waitKey(0);
return 0;
}
7.3 函數(shù)實(shí)現(xiàn) LoG 算子邊緣檢測(cè)
//DOG高斯差分--使用opencv的GaussianBlur
void DOG2(cv::Mat& src, cv::Mat& dst, cv::Size wsize, double sigma, double k = 1.6) {
cv::Mat gaussian_dst1, gaussian_dst2;
//高斯濾波
cv::GaussianBlur(src, gaussian_dst1, wsize, k * sigma);
cv::GaussianBlur(src, gaussian_dst2, wsize, sigma);
dst = gaussian_dst1 - gaussian_dst2;
cv::threshold(dst, dst, 0, 255, cv::THRESH_BINARY);
}
8、Canny算子邊緣檢測(cè)
8.1 原理
Canny算法也被許多人稱為最佳探測(cè)器,旨在滿足三個(gè)主要標(biāo)準(zhǔn):
(1)低錯(cuò)誤率:這意味著只能很好地檢測(cè)存在的邊緣。
(2)良好的本地化:必須將檢測(cè)到的邊緣像素與實(shí)際邊緣像素之間的距離降至最低。
(3)最小響應(yīng):每條邊距只有一個(gè)檢測(cè)器響應(yīng)。
Canny算子邊緣檢測(cè)有以下步驟:
(1)用高斯濾波器過(guò)濾噪音,平滑圖像
(2)用Sobel等梯度算子計(jì)算梯度幅值和方向
(3)對(duì)梯度幅值進(jìn)行非極大值抑制(細(xì)化邊緣)
(4)用雙閾值算法檢測(cè)和連接邊緣
如果像素漸變高于閾值上限,則該像素被接受為邊緣
如果像素漸變值低于下限閾值,則會(huì)拒絕該值。
如果像素漸變介于兩個(gè)閾值之間,則僅當(dāng)它連接到高于閾值上限的像素時(shí),才會(huì)接受它。
8.2 手工實(shí)現(xiàn)Canny 算子邊緣檢測(cè)
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//1 高斯濾波
void Gaussfilter_ly(Mat input_image, Mat& output_image, int Gauss_size, double Sigma)
{
//保證高斯核大小為大于等于3的奇數(shù)
if (Gauss_size < 3) Gauss_size = 3;
else Gauss_size = (int)(Gauss_size / 2) * 2 + 1;
//生成高斯卷積核
double** Gausskernel = new double* [Gauss_size];
for (int i = 0; i < Gauss_size; i++)
{
Gausskernel[i] = new double[Gauss_size];
}
int center = Gauss_size / 2;
double sum = 0;
for (int i = 0; i < Gauss_size; i++)
{
for (int j = 0; j < Gauss_size; j++)
{
Gausskernel[i][j] = exp(-((i - center) * (i - center) + (j - center) * (j - center)) / (2 * Sigma * Sigma));
sum += Gausskernel[i][j];
}
}
//高斯卷積核歸一化
double sum1 = 1 / sum;
for (int i = 0; i < Gauss_size; i++)
{
for (int j = 0; j < Gauss_size; j++)
{
Gausskernel[i][j] *= sum1;
}
}
//濾波
Mat tem_image = input_image.clone();
int rows = input_image.rows - center;
int cols = input_image.cols - center;
for (int i = center; i < rows; i++)
{
for (int j = center; j < cols; j++)
{
double sum = 0;
for (int m = -center; m <= center; m++)
{
for (int n = -center; n <= center; n++)
{
sum += Gausskernel[center + m][center + n] * input_image.at<uchar>(i + m, j + n);
}
}
tem_image.at<uchar>(i, j) = static_cast<uchar>(sum);
}
}
output_image = tem_image;
//釋放內(nèi)存
for (int i = 0; i < Gauss_size; i++) delete[] Gausskernel[i];
delete[] Gausskernel;
}
//2 計(jì)算梯度幅值圖像,方向圖像和邊緣圖像
void Grad_dire_ly(Mat input, Mat& Gradimage, Mat& Direimage)
{
Mat tempGrad = Mat(input.size(), CV_16U, Scalar(0));
Mat tempDire = Mat(input.size(), CV_8U, Scalar(0));
int width = input.cols;
int height = input.rows;
for (int i = 1; i < height - 1; i++)
{
for (int j = 1; j < width - 1; j++)
{
//計(jì)算梯度及梯度幅值
int gx = input.at<uchar>(i + 1, j - 1) + input.at<uchar>(i + 1, j) + input.at<uchar>(i + 1, j + 1)
- input.at<uchar>(i - 1, j - 1) - input.at<uchar>(i - 1, j) - input.at<uchar>(i - 1, j + 1);
int gy = input.at<uchar>(i - 1, j + 1) + input.at<uchar>(i, j + 1) + input.at<uchar>(i + 1, j + 1)
- input.at<uchar>(i - 1, j - 1) - input.at<uchar>(i, j - 1) - input.at<uchar>(i + 1, j - 1);
int sum = gx + gy;
//梯度幅值圖像
tempGrad.at<ushort>(i, j) = abs(sum);
//方向圖像,圖像中的坐標(biāo)軸
double dire = atan2(gy, gx) * 180 / 3.1415926;
if (dire <= -67.5 || dire >= 67.5) tempDire.at<uchar>(i, j) = 1; //1:水平
else if (dire > -67.5 && dire < -22.5) tempDire.at<uchar>(i, j) = 2; //2:45
else if (dire > -22.5 && dire < 22.5) tempDire.at<uchar>(i, j) = 3; //3:垂直
else tempDire.at<uchar>(i, j) = 4; //4:-45
}
}
Gradimage = tempGrad;
Direimage = tempDire;
}
//3 非極大值抑制圖像
void Nonmax_suppression_ly(Mat Gradimage, Mat Direimage, Mat& Suppimage)
{
Mat tempSupp = Mat(Gradimage.size(), Gradimage.type(), Scalar(0));
int width = Gradimage.cols;
int height = Gradimage.rows;
for (int i = 1; i < height - 1; i++)
{
for (int j = 1; j < width - 1; j++)
{
switch (Direimage.at<uchar>(i, j))
{
case 1:
if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i, j + 1))
tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
else
tempSupp.at<ushort>(i, j) = 0;
break;
case 2:
if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j + 1))
tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
else
tempSupp.at<ushort>(i, j) = 0;
break;
case 3:
if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j))
tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
else
tempSupp.at<ushort>(i, j) = 0;
break;
case 4:
if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j + 1))
tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
else
tempSupp.at<ushort>(i, j) = 0;
break;
default:
break;
}
}
}
Suppimage = tempSupp;
}
//4 滯后閾值處理(雙閾值)
void doubleThread_ly(Mat Suppimage, Mat& Edgeimage, int th_high, int th_low)
{
int temp;
if (th_high < th_low)
{
temp = th_high;
th_high = th_low;
th_low = temp;
}
Mat bw_h = Mat(Suppimage.size(), CV_8UC1, Scalar(0));
Mat bw_l = Mat(Suppimage.size(), CV_8UC1, Scalar(0));
int width = Suppimage.cols;
int height = Suppimage.rows;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
if (Suppimage.at<ushort>(i, j) >= th_high)
bw_h.at<uchar>(i, j) = 255;
else
bw_h.at<uchar>(i, j) = 0;
if (Suppimage.at<ushort>(i, j) >= th_low && Suppimage.at<ushort>(i, j) < th_high)
bw_l.at<uchar>(i, j) = 255;
else
bw_l.at<uchar>(i, j) = 0;
}
}
Mat bw = bw_h.clone();
for (int i = 1; i < height - 1; i++)
{
for (int j = 1; j < width - 1; j++)
{
if (bw_h.at<uchar>(i, j) == 255)
{
if (bw_l.at<uchar>(i - 1, j - 1) == 255)
bw.at<uchar>(i - 1, j - 1) = 255;
if (bw_l.at<uchar>(i - 1, j) == 255)
bw.at<uchar>(i - 1, j) = 255;
if (bw_l.at<uchar>(i - 1, j + 1) == 255)
bw.at<uchar>(i - 1, j + 1) = 255;
if (bw_l.at<uchar>(i, j - 1) == 255)
bw.at<uchar>(i, j - 1) = 255;
if (bw_l.at<uchar>(i, j + 1) == 255)
bw.at<uchar>(i, j + 1) = 255;
if (bw_l.at<uchar>(i + 1, j - 1) == 255)
bw.at<uchar>(i + 1, j - 1) = 255;
if (bw_l.at<uchar>(i + 1, j) == 255)
bw.at<uchar>(i + 1, j) = 255;
if (bw_l.at<uchar>(i + 1, j + 1) == 255)
bw.at<uchar>(i + 1, j + 1) = 255;
}
}
}
Edgeimage = bw;
}
//5 canny函數(shù)
void canny_ly(Mat input_image, Mat& output_image, int th_high, int th_low, int Gauss_size, double sigmma)
{
Mat Gaussimage, Gradimage, Direimage, Suppimage, Edgeimage;
//1 高斯濾波函數(shù)
Gaussfilter_ly(input_image, Gaussimage, Gauss_size, sigmma);
//2 計(jì)算梯度幅值圖像和方向圖像
Grad_dire_ly(Gaussimage, Gradimage, Direimage);
//3 非極大值抑制圖像
Nonmax_suppression_ly(Gradimage, Direimage, Suppimage);
//4 滯后閾值處理(雙閾值)
doubleThread_ly(Suppimage, Edgeimage, th_high, th_low);
output_image = Edgeimage;
}
int main()
{
Mat src = imread("E:\\la.jpg", 1);//讀取灰度圖像
if (src.empty())
{
cout << "讀取錯(cuò)誤" << endl;
return -1;
}
imshow("原圖", src);
Mat dst;
//轉(zhuǎn)灰度圖像
cvtColor(src, dst, COLOR_BGRA2GRAY);
imshow("灰度", dst);
Mat img2;
canny_ly(dst, img2, 50, 20, 3, 1);
imshow("Canny", img2);
waitKey();
return 0;
}
8.3 函數(shù)實(shí)現(xiàn)Canny 算子邊緣檢測(cè)
int main()
{
Mat src = imread("E:\\la.jpg", 1);//讀取灰度圖像
if (src.empty())
{
cout << "讀取錯(cuò)誤" << endl;
return -1;
}
imshow("原圖", src);
Mat dst;
//轉(zhuǎn)灰度圖像
cvtColor(src, dst, COLOR_BGRA2GRAY);
imshow("灰度", dst);
//均值濾波過(guò)濾
blur(dst, dst, Size(3, 3));
imshow("高斯濾波", dst);
//opencv自帶canny檢測(cè)函數(shù)
Canny(src, dst, 50, 150);
imshow("Canny", dst);
waitKey(0);
return 0;
}
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-453461.html
Canny(
InputArray src, // 8-bit的輸入圖像,也就是單通道圖像
OutputArray edges,// 輸出邊緣圖像, 一般都是二值圖像,背景是黑色
double threshold1,// 低閾值,常取高閾值的1/2或者1/3
double threshold2,// 高閾值
int aptertureSize,// Soble算子的size,通常3x3,取值3
bool L2gradient // 選擇 true表示是L2來(lái)歸一化,否則用L1歸一化,一般我們選擇L1,性能更好
)
部分參考來(lái)源數(shù)字圖像處理(c++ opencv):圖像分割-基本邊緣檢測(cè)–canny邊緣檢測(cè) - 知乎 (zhihu.com)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-453461.html
到了這里,關(guān)于OpenCV數(shù)字圖像處理基于C++:邊緣檢測(cè)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!