一、功能簡介及其運行
(一)、功能:該程序實現了canny算子求圖片的邊緣,其中主要包括七大部分:
1、對傳入的彩色圖片二值化
2、對二值化后的圖片進行高斯濾波
3、使用sobel算子對濾波之后的圖形分別求x,y方向上的梯度
4、計算出梯度幅值
5、對梯度圖像做非極大抑制
6、對非極大抑制后的圖像做雙閾值處理
7、對雙閾值處理后的圖像進行連接操作(處理閾值中間的點)
(二)、運行:注意要在release文件夾下運行exe
運行程序后終端會顯示所用到的sobel算子矩陣和高斯濾波矩陣,同時會彈出多張圖片,他包括:
1、原圖片:Lena.jpg
2、二值化后的圖片:gray.jpg
3、高斯濾波后的圖片:guss,jpg
4、x,y方向梯度的圖片:gradit_x.jpg gradint_y.jpg
5、梯度圖片:gradint.jpg
6、非極大抑制后的圖片:afterNms.jpg
7、雙閾值后的圖片:afterDT.jpg
8、連接操作后的圖片(即最終的canny算法圖片)canny.jpg
9、使用opencv自帶函數得到的canny算子圖片(用於對比)
彈出的同時,會將這些圖片保存在當前路徑的文件下,命名為上面的命名。
二、開發環境
操作系統:windows
Opencv版本:2.4
編譯器:vs2015
語言:c++
請助教運行release下的exe。
三、程序思路
大體思路其實與上面七大部分相同,這里做一個概述,並說明大體實現,具體通過這個簡單流程圖來說明(流程圖viso文件附在壓縮包內))
一、具體實現
(1)圖像灰度化:
圖像灰度畫函數:Mat getGray(Mat &image)
作用:返回一張彩色圖片的灰度圖片
實現方式:才用標准的轉化公式:Gray = R*0.299 + G*0.587 + B*0.114
循環的取出每一個像素,對每一個彩色像素做該運算就可得到灰度圖片。
(2)獲取高斯核
圖像灰度畫函數:double **getGussKernel(int size, double sigma)
作用:返回一個二維double類型指針,里面是對應size和sigma的高斯卷積核
實現方式:通過查閱資料和博客我們知道高斯卷積核公式:
由這個公式,我們可以容易寫出一下核心代碼:
最后記得要做歸一化,之后返回指針g即可。
(3)卷積函數的兩種實現
現在我們得到了一個高斯核,要做卷積了,由於我們知道在求梯度圖片的時候同樣要用到卷積,所以我們寫一個卷積函數。
卷積函數:Mat Convolution1(Mat &image, double **kernel,int size)
Mat Convolution2(Mat &image, double **kernel,int size)
參數:帶卷積圖像 卷積核 卷積核大小
這兩種方法大體上相同,就是在對邊緣的處理上一個視邊緣的無效鄰域視為邊緣本身;一個視為0,根據實驗效果來看,兩者沒有太大差別(邊緣本身就看不出來)
作用:將圖像與卷積核做卷積處理並返回卷積后的圖像(不改變原圖)
具體實現:采用循環的方式,對每一個像素都對他的八鄰域做分別做乘積后相加。需要注意的是不同的邊緣控制代碼有一些不同。
(需要注意的是,為了方便計算,我們事先把卷積核轉化為1維,這樣省了不少事,這個主要感謝http://blog.csdn.net/dcrmg/article/details/52344902這篇博客上的靈感,不然處理起來有點混亂。)
最后返回Mat 圖像就是卷積之后的圖像。
有了這個工具,我們只需要使用這個工具與步驟而獲得的高斯核卷積就可以得到高斯濾波后的圖像。
(4)獲得梯度圖像
這里分三步:
(1)獲得x方向的梯度圖像
方法:使用sobel算子:
sobel_x[3][3] = { { -1,-2,-1 } ,{ 0,0,0 } ,{ 1,2,1 } };
與我們之前寫好的卷積函數做卷積即可。
(2)獲得y方向上的梯度圖像
方法:使用sobel算子:
sobel_y[3][3] = { { -1,0,1 } ,{ -2,0,2 } ,{ -1,0,1 } };
與我們之前寫好的卷積函數做卷積即可。
(3)獲得綜合梯度的幅值
Sobel函數:
Mat Sobel(Mat &image_x,Mat &image_y)
參數:x方向梯度圖像 y方向梯度圖像
作用:返回梯度圖像
具體實現:根據公式
可以知道新圖片的每一像素點都等於x,y梯度方向上的歐氏距離,由此思路可以清晰的寫出代碼:
最后返回Mat圖像即可
(5)非極大抑制
非極大值抑制就是要消除梯度方向上非極大值的點,避免邊緣非常“厚”的問題,是canny算法的精髓之一。
Nms函數:
Mat Nms(Mat &image, Mat &image_x, Mat &image_y)
參數:梯度圖像 x方向梯度圖像 y方向梯度圖像
作用:通過計算梯度方向和插值對圖像進行非極大抑制
具體實現:我們知道非極大抑制的難點就在於找和當前點梯度方向相同的相鄰點,由於很有可能該點處於兩個像素的中間,即實際該點沒有像素值,但是梯度方向指向該值,所以我們在這里使用插值的方法。通俗的解釋就是
假設這里我們以c點為中心,那么c點的梯度方向的鄰域點可能落在上圖中四個小正方體的外接邊上的八個邊上,而我們只知道八個頂點的信息,為了取得亞像素的信息,我們采用線性插值的方法,插值點有四種位置,我們需要根據梯度方向的不同來確定具體的位置。根據線性插值的一般公式:
M(dtmp1)=w*M(g2)+(1-w)*M(g1)
其中w=|x方向梯度|/|y方向梯度|(靠x方向時)
或w=|y方向梯度|/|x方向梯度|(靠y方向時)
基於這種考慮,我們通過判斷x與y的絕對值大小來判斷c2與c4的位置
通過比較x與y是否同號來確定c1,c3的位置。
其他方向同理
在找到其梯度方向上的插值點之后,剩下的就是要與這兩個插值點進行比較,若當前點比他們都大,說明當前點為極大值點,保留。否則就置成0。代碼實現較簡單使用簡單的判斷即可。(這里沒有使用梯度角,而是直接用了x,y方向上的梯度圖像來比較主要是為了理解方便,而且梯度角非常容易出錯)
(6)雙閾值
到上一步,我們得到了大致的邊緣圖片,但是這幅圖片邊緣處太密,我們想通過雙閾值的方法來處理圖片,突出灰度值較大的邊緣部分,將灰度值較小的部分置為0.
雙閾值函數:
Mat DoubleThreshold(Mat &image, double high_threshold, double low_threshold)
參數: Nms后的圖像 高閾值 低閾值
作用:輸出對應雙閾值之后的圖像(沒有對中間閾值部分進行連接)
連接函數:
Mat Link1(Mat &image)
作用:對執行完DoubleThreshold函數的圖像的中間部分做中間的連接處理,返回最終的邊緣函數。
具體實現:實現分兩步。
首先第一步很簡單,就是遍歷圖像的每一個像素,並對他的值進行判斷,若該值小於低閾值就為0;若高於高閾值就置為255。對於中間的點先不理他,在后面做連接的時候再處理。
第二步 連接:在這里實現了兩種連接。后面在實驗結果分析會比較哪種比較好。第一種連接方式的原則很簡單:
遍歷一個經過DoubleThreshold后的圖像,對於每一個點再去遍歷他的八連通鄰域,若其八連通鄰域全是0,說明這是一個孤立的邊緣點,可以將其去掉;若其八鄰域內有非0的點,不管是255還是處於中間的點,則說明這個點可以作為邊緣連接點,就將其置為255。這樣循環遍歷,然后返回。
核心程序截圖如下:
第二種才用遞歸的方式,稱其為Link2。
遞歸的主要特點使每次從當前剛剛變為邊緣的圖像開始深度優先搜索,這樣有不會出現“誤傷”的情況。具體實現分為遞歸函數和主控函數,核心遞歸函數如圖所示:
最終canny為
opencv自帶的為
最后附上源代碼:
//#include <stdafx.h> #include "cv.h" #include "cxcore.h" #include "highgui.h" #include<cstring> #include<math.h> #include<iostream> #define PI 3.1415926 using namespace cv; using namespace std; char path[20]; double sobel_y[3][3] = { { -1,0,1 } ,{ -2,0,2 } ,{ -1,0,1 } };//sobel算子 double sobel_x[3][3] = { { -1,-2,-1 } ,{ 0,0,0 } ,{ 1,2,1 } }; int xNum[8] = { 1,1,0,-1,-1,-1,0,1 }; int yNum[8] = { 0,1,1,1,0,-1,-1,-1 };//方向 /*返回該函數的灰度值*/ Mat getGray(Mat &image) { if (!image.data || image.channels() != 3) { printf("圖片屬性錯誤"); return image; } Mat gray_image = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 //Mat gray_image(2, image.size, CV_8UC1, Scalar::all(0)); int image_step = image.step; for (int i = 0; i < gray_image.rows; i++) { for (int j = 0; j < gray_image.cols; j++) { int base = image_step*i;//基地址 gray_image.data[i*gray_image.step + j] = 0.114*image.data[base + j * 3 + 0] \ + 0.587*image.data[base + j * 3 + 1] + 0.299*image.data[base + j * 3 + 2]; } } return gray_image; } /*獲取尺寸為size 標准差為sigma的高斯核*/ double **getGussKernel(int size, double sigma) { double temp = 1 / (2 * PI*sigma*sigma); int x0 = size / 2; int y0 = size / 2; double sum = 0;//歸一化做准備 double **g = new double *[size]; for (int i =0; i < size; i++) { g[i] = new double[size]; } for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { g[x][y] = temp*exp(-((x - x0)*(x - x0) + (y - y0)*(y - y0)) / (2 * sigma*sigma)); sum += g[x][y]; } } for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { g[x][y] = g[x][y] / sum; cout << g[x][y] << " "; } cout << endl << endl; } return g; } /*對圖片做kernel卷積 邊緣處理方案一*/ Mat Convolution1(Mat &image, double **kernel,int size) { Mat after_convolution = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 int count = 0; double kernel_temp[100]; for (int i = 0; i<size*size; i++) { kernel_temp[i] = 0; //賦初值,空間分配 } for (int i = 0; i < size; i++)//將核轉化為一維,方便卷積*** { for (int j = 0; j < size; j++) { kernel_temp[count] = kernel [i][j]; count++; } } /*邊緣處理才用邊緣的點代替值*/ for (int i = 0; i < image.rows; i++) { for (int j = 0; j < image.cols; j++) { int k = 0;//卷積核的索引 for (int x = -size / 2; x <=size / 2; x++) { for (int y = -size / 2; y <= size / 2; y++) { int r = i + x; int c = j + y; r = r < 0 ? 0 : r;//處理頂部邊緣 r = r >= image.rows ? image.rows - 1 : r;//處理底部邊緣 c = c < 0 ? 0 : c; c = c >= image.cols ? image.cols - 1 : c; after_convolution.at<uchar>(i, j) += kernel_temp[k] * image.at<uchar>(r, c); k++; } } } } return after_convolution; } Mat Convolution2(Mat &image, double **kernel, int size) { Mat after_convolution = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 int count = 0; double kernel_temp[100]; for (int i = 0; i<size*size; i++) { kernel_temp[i] = 0; //賦初值,空間分配 } for (int i = 0; i < size; i++)//將核轉化為一維,方便卷積*** { for (int j = 0; j < size; j++) { kernel_temp[count] = kernel[i][j]; count++; } } /*邊緣處理用0代替*/ for (int i = 0; i < image.rows; i++) { for (int j = 0; j < image.cols; j++) { int k = 0;//卷積核的索引 for (int x = -size / 2; x <= size / 2; x++) { for (int y = -size / 2; y <= size / 2; y++) { int r = i + x; int c = j + y; r = r < 0 ? -1 : r;//處理頂部邊緣 r = r >= image.rows ? - 1 : r;//處理底部邊緣 c = c < 0 ? -1: c; c = c >= image.cols ? -1 : c; if (r == -1 || c == -1) after_convolution.at<uchar>(i, j) += kernel_temp[k] * 0; else after_convolution.at<uchar>(i, j) += kernel_temp[k] * image.at<uchar>(r, c); k++; } } } } return after_convolution; } //卷積公式加絕對值 Mat Convolution3(Mat &image, double (*kernel)[3], int size) { double sum=0; Mat after_convolution = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 int count = 0; double kernel_temp[100]; for (int i = 0; i<size*size; i++) { kernel_temp[i] = 0; //賦初值,空間分配 } for (int i = 0; i < size; i++)//將核轉化為一維,方便卷積*** { for (int j = 0; j < size; j++) { kernel_temp[count] = kernel[i][j]; count++; printf("%f ", kernel[i][j]); } printf("\n\n"); } /*邊緣處理才用邊緣的點代替值*/ for (int i = 0; i < image.rows; i++) { for (int j = 0; j < image.cols; j++) { sum = after_convolution.at<uchar>(i, j); int k = 0;//卷積核的索引 for (int x = -size / 2; x <= size / 2; x++) { for (int y = -size / 2; y <= size / 2; y++) { int r = i + x; int c = j + y; r = r < 0 ? 0 : r;//處理頂部邊緣 r = r >= image.rows ? image.rows - 1 : r;//處理底部邊緣 c = c < 0 ? 0 : c; c = c >= image.cols ? image.cols - 1 : c; sum += (kernel_temp[k] * image.at<uchar>(r, c)); k++; } } after_convolution.at<uchar>(i, j) = abs(sum); } } convertScaleAbs(after_convolution, after_convolution); return after_convolution; } Mat Sobel(Mat &image_x,Mat &image_y, double *gradint_angle) { Mat after_sobel = Mat::zeros(image_x.size(), CV_32FC1);//創建一幅空圖片注意圖片類型 int k = 0; for (int i = 0; i < after_sobel.rows; i++) { for (int j = 0; j < after_sobel.cols; j++) { double x = image_x.at<uchar>(i, j); double y = image_y.at<uchar>(i, j); after_sobel.at<float>(i, j) = sqrt(x*x + y*y); } } convertScaleAbs(after_sobel, after_sobel);//轉為8位無符號整形 return after_sobel; } Mat Nms1(Mat &image, Mat &image_x, Mat &image_y) { Mat after_nms = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 after_nms = image.clone(); double center, temp1, temp2; int c1, c2, c3, c4; double x, y;//圖片在x,y方向的梯度 double k; for (int i = 1; i < image.rows-1; i++) { for (int j = 1; j < image.cols-1; j++) { center = image.at<uchar>(i, j); x = image_x.at<uchar>(i, j); y = image_y.at<uchar>(i, j); if (abs(y) > abs(x))//確定c2c4位置 考y軸 { c2 = image.at<uchar>(i - 1, j);//上 c4 = image.at<uchar>(i - 1, j); if (y == 0) k = 0; else { k = fabs(x) /fabs(y);//比例 } if (x*y < 0)//兩方向不相同,13象限 { c1 = image.at<uchar>(i - 1, j + 1); c3 = image.at<uchar>(i + 1, j - 1); } else//否則24象限 { c3 = image.at<uchar>(i + 1, j + 1); c1 = image.at<uchar>(i - 1, j - 1); } } else { c2 = image.at<uchar>(i , j+1);//上 c4 = image.at<uchar>(i - 1, j-1); if (x == 0) k = 0; else { k = fabs(y) / fabs(x);//比例 } if (x*y < 0)//兩方向不相同,13象限 { c1 = image.at<uchar>(i - 1, j - 1); c3 = image.at<uchar>(i + 1, j + 1); } else//否則1,3象限 { c1 = image.at<uchar>(i + 1, j - 1); c3 = image.at<uchar>(i - 1, j + 1); } } temp1 = k*c1 + (1 - k)*c2; temp2 = k*c3 + (1 - k)*c4; if (center >= temp1&¢er >= temp2) { //是極大值 ; } else { //不是最大值置0 after_nms.at<uchar>(i, j) = 0; } } } return after_nms; } Mat Nms2(Mat &image, Mat &image_x, Mat &image_y) { Mat after_nms = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 after_nms = image.clone(); double center, temp1, temp2; int c1, c2, c3, c4; double x, y;//圖片在x,y方向的梯度 double k; for (int i = 1; i < image.rows - 1; i++) { for (int j = 1; j < image.cols - 1; j++) { center = image.at<uchar>(i, j); x = image_x.at<uchar>(i, j); y = image_y.at<uchar>(i, j); if (abs(y) > abs(x))//確定c2c4位置 考y軸 { c2 = image.at<uchar>(i, j + 1);//上 c4 = image.at<uchar>(i, j - 1); if (y == 0) k = 0; else { k = fabs(x) / fabs(y);//比例 } if (x*y < 0)//兩方向不相同,13象限 { c1 = image.at<uchar>(i - 1, j + 1); c3 = image.at<uchar>(i + 1, j - 1); } else//否則24象限 { c3 = image.at<uchar>(i + 1, j + 1); c1 = image.at<uchar>(i - 1, j - 1); } } else { c2 = image.at<uchar>(i - 1, j);//上 c4 = image.at<uchar>(i + 1, j); if (x == 0) k = 0; else { k = fabs(y) / fabs(x);//比例 } if (x*y < 0)//兩方向不相同,13象限 { c1 = image.at<uchar>(i - 1, j - 1); c3 = image.at<uchar>(i + 1, j + 1); } else//否則1,3象限 { c1 = image.at<uchar>(i + 1, j - 1); c3 = image.at<uchar>(i - 1, j + 1); } } temp1 = k*c1 + (1 - k)*c2; temp2 = k*c3 + (1 - k)*c4; if (center >= temp1&¢er >= temp2) { //是極大值 ; } else { //不是最大值置0 after_nms.at<uchar>(i, j) = 0; } } } return after_nms; } Mat Nms3(Mat &image, Mat &image_x, Mat &image_y) { Mat after_nms = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 after_nms = image.clone(); double center, temp1, temp2; int c1, c2, c3, c4; double x, y;//圖片在x,y方向的梯度 double k; for (int i = 1; i < image.rows - 1; i++) { for (int j = 1; j < image.cols - 1; j++) { center = image.at<uchar>(i, j); x = image_x.at<uchar>(i, j); y = image_y.at<uchar>(i, j); if (abs(y) < abs(x))//確定c2c4位置 考y軸 { c2 = image.at<uchar>(i, j + 1);//上 c4 = image.at<uchar>(i, j - 1); if (y == 0) k = 0; else { k = fabs(y) / fabs(x);//比例 } if (x*y < 0)//兩方向不相同,13象限 { c1 = image.at<uchar>(i - 1, j + 1); c3 = image.at<uchar>(i + 1, j - 1); } else//否則24象限 { c3 = image.at<uchar>(i - 1, j - 1); c1 = image.at<uchar>(i + 1, j + 1); } } else { c2 = image.at<uchar>(i - 1, j);//上 c4 = image.at<uchar>(i + 1, j); if (x == 0) k = 0; else { k = fabs(x) / fabs(y);//比例 } if (x*y < 0)//兩方向不相同,13象限 { c1 = image.at<uchar>(i - 1, j - 1); c3 = image.at<uchar>(i + 1, j + 1); } else//否則1,3象限 { c1 = image.at<uchar>(i - 1, j + 1); c3 = image.at<uchar>(i + 1, j - 1); } } temp1 = k*c1 + (1 - k)*c2; temp2 = k*c3 + (1 - k)*c4; if (center >= temp1&¢er >= temp2) { //是極大值 ; } else { //不是最大值置0 after_nms.at<uchar>(i, j) = 0; } } } return after_nms; } Mat DoubleThreshold(Mat &image, double high_threshold, double low_threshold) { Mat after_threshold = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 after_threshold = image.clone(); for (int i = 0; i < image.rows; i++) { for (int j = 0; j < image.cols; j++) { double temp = image.at<uchar>(i, j); if (temp < low_threshold) { after_threshold.at<uchar>(i, j) = 0; } if (temp > high_threshold) { after_threshold.at<uchar>(i, j) = 255; } } } return after_threshold; } Mat Link1(Mat &image) { bool flag = false; int x = 0; int y = 0; Mat after_link = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 after_link = image.clone(); for (int i = 1; i < image.rows-1; i++) { for (int j = 1; j < image.cols-1; j++) { flag = false; double temp = image.at<uchar>(i, j); if (temp > 0 && temp < 255)//處於閾值中間的 { for (int k = 0; k < 8; k++)//循環判斷 { x = i + xNum[k]; y = j + yNum[k]; if (after_link.at<uchar>(x, y) != 0) { flag = true; break; } } if (flag)//周圍有不為0的點 { after_link.at<uchar>(i, j) = 255; } else { after_link.at<uchar>(i, j) = 0; } } } } return after_link; } void Link2(Mat &image,int i,int j) { //printf("i=%d j=%d\n", i, j); bool flag = false; int x = 0; int y = 0; double temp = image.at<uchar>(i, j); if (temp > 0 && temp < 254)//處於閾值中間的 { for (int k = 0; k < 8; k++)//循環判斷 { x = i + xNum[k]; y = j + yNum[k]; if (image.at<uchar>(x, y) != 0) { flag = true; break; } } if (flag)//周圍有不為0的點 { image.at<uchar>(i, j) = 255; Link2(image, x, y); } else { image.at<uchar>(i, j) = 0; } } return; } Mat Link3(Mat &image) { Mat after_link = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 after_link = image.clone(); int k = 0; for (int i = 1; i < after_link.rows-1; i++) { for (int j = 1; j<after_link.cols-1; j++) { double temp = image.at<uchar>(i, j); if (temp > 0 && temp < 255) { //printf("%d\n", k); Link2(after_link, i, j); k++; } } } return after_link; } int main() { Mat image = imread("G:\\image\\Lena.jpg"); Mat test_image = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 Mat test_image2 = Mat::zeros(image.size(), CV_8UC1);//創建一幅空圖片 if (image.empty()) { printf("圖片加載出錯"); return 0; } double *point; Mat gray_image = getGray(image); imwrite("..\\gray.jpg", gray_image); double *gradint_angle=nullptr; double **guss = getGussKernel(5, 5); Mat after_guss = Convolution2(gray_image, guss, 5); imwrite("..\\guss.jpg", after_guss); Mat x = Convolution3(after_guss, sobel_x, 3); imwrite("..\\gradint_x.jpg", x); Mat y = Convolution3(after_guss, sobel_y,3); imwrite("..\\gradint_y.jpg", y); Mat z = Sobel(x, y, gradint_angle); imwrite("..\\gradint.jpg", z); Mat nms = Nms2(z, x, y); imwrite("..\\afterNms.jpg", nms); Mat t = DoubleThreshold(nms, 120, 60); imwrite("..\\afterDT.jpg", t); Mat canny = Link1(t); imwrite("..\\canny.jpg", canny); Mat canny2 = Link3(t); GaussianBlur(gray_image, test_image,Size(5,5),10); imshow("原圖片為", image); imshow("灰度圖片為", gray_image); imshow("使用自己寫的高斯濾波后的圖片為", after_guss); imshow("x方向的梯度", x); imshow("y方向的梯度", y); imshow("合並的梯度", z); imshow("非極大抑制后的結果為", nms); imshow("雙閾值結果為", t); imshow("連接后的結果為", canny); imshow("2連接后的結果為", canny2); imshow("cv高斯濾波", test_image); ///*opencv 自帶梯度算法*/ //Sobel(after_guss, test_image, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT); //convertScaleAbs(test_image, test_image);//使用線性變換轉換輸入數組元素成8位無符號整型 //imshow("oX方向Sobel", test_image); //Sobel(after_guss, test_image2, CV_16S, 1, 1, 3, 1, 1, BORDER_DEFAULT); //convertScaleAbs(test_image2, test_image2);//使用線性變換轉換輸入數組元素成8位無符號整型 //imshow("oY方向Sobel", test_image2); //imshow("使用opencv高斯濾波后的圖片為", test_image); //addWeighted(test_image, 0.5, test_image2, 0.5, 0, test_image); //imshow("整體方向Sobel", test_image); ///****************************************/ Canny(image, test_image, 150, 50,3); imshow("cvcanny", test_image); waitKey(); return 0; }