圖像的邊界信息一般通過灰度值突變來體現,所以圖像邊緣提取一般通過捕捉灰度突變的方法來實現,捕捉灰度突變可以通過求微分來實現
導數越大說明變化越大,邊緣信號越強
1.Sobel算子
也叫離散微分算子,一階微分算子,求導算子,先做高斯平滑在做微分求導
可以在各個方向上求圖像的梯度
如水平方向 Gx=[-1,0,1,-2,0,2,-1,0,1],垂直方向Gy=[-1,-2,-1,0,0,0,1,2,1]
最終G=sqrt(Gx^2+Gy^2),或者G=|Gx|+|Gy|
第二種的運算速度要快於第一種,所以一般采用第二種方法
Sobel算子的改進版叫Scharr算子[-3,0,3,-10,0,10,-3,0,3]
#include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; int main(int argc, char **argv) { Mat src, dst; src = imread("b.png"); if (src.empty()) { cout << "load img failed" << endl; return -1; } imshow("input img", src); Mat gaussian,gray_src; GaussianBlur(src, gaussian, Size(3, 3), 0, 0); cvtColor(gaussian, gray_src, CV_BGR2GRAY); imshow("blur gray", gray_src); Mat xgrad, ygrad; Sobel(gray_src, xgrad, CV_16S, 1, 0, 3); Sobel(gray_src, ygrad, CV_16S, 0, 1, 3); convertScaleAbs(xgrad, xgrad); convertScaleAbs(ygrad, ygrad); imshow("x grade", xgrad); imshow("y grade", ygrad); addWeighted(xgrad, 0.5, ygrad, 0.5, 0, dst); imshow("output img", dst); /* dst = Mat(xgrad.size(), xgrad.type()); int width = dst.cols; int height = dst.rows; for(int i=0;i<height;++i) for (int j = 0; j < width; ++j) { int xg = xgrad.at<char>(i, j); int yg = ygrad.at<char>(i, j); int xy = xg + yg; dst.at<char>(i, j) = saturate_cast<uchar>(xy); } imshow("output img", dst);*/ waitKey(0); return 0; }
2.Laplance算子
求二階導數,在二階導數的時候,最大變化處的值為0,即邊緣的二階導數是0
流程:
高斯模糊去噪GaussianBlur()
轉為灰度值cvtColor()
Laplance二階導數計算Laplancian()
取絕對值convertScaleAbs()
顯示結果
#include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; int main(int argc, char **argv) { Mat src, dst; src = imread("b.png"); if (src.empty()) { cout << "load img failed" << endl; return -1; } imshow("input img", src); Mat gaussian,gray_src; GaussianBlur(src, gaussian, Size(3, 3), 0, 0); cvtColor(gaussian, gray_src, CV_BGR2GRAY); imshow("blur gray", gray_src); Laplacian(gray_src, dst, CV_16S,3); convertScaleAbs(dst, dst); imshow("Laplacian", dst); threshold(dst, dst, 0, 255, THRESH_OTSU | THRESH_BINARY); imshow("output img", dst); /* dst = Mat(xgrad.size(), xgrad.type()); int width = dst.cols; int height = dst.rows; for(int i=0;i<height;++i) for (int j = 0; j < width; ++j) { int xg = xgrad.at<char>(i, j); int yg = ygrad.at<char>(i, j); int xy = xg + yg; dst.at<char>(i, j) = saturate_cast<uchar>(xy); } imshow("output img", dst);*/ waitKey(0); return 0; }
3.Canny邊緣檢測
步驟:
高斯模糊 GaussianBlur
灰度轉換cvtColor
計算梯度Sobel/Scharr
非最大信號抑制
高低閾值輸出二值圖像
非最大信號抑制需要計算梯度方向
T1為低閾值,T2為高閾值,凡是高於T2的都保留,凡是低於T1的都丟棄,從高於T2的像素出發,凡是大於T1且相互連接的都保留,最終得到一個輸出二值圖像
推薦的高低閾值比為3:1或2:1
Canny(src,dst,threshold_low,threshold_high,Sobel_size,Lwgradient)
最后一個如果是true就用L2歸一化(開根),如果不是就L1歸一化(絕對值),一般用L1
#include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; Mat src, dst, gray_src, gaussian; int t1_value = 50; int max_value = 255; const char* OUTPUT_TITLE = "Canny Result"; void Canny_Demo(int,void*); int main(int argc, char **argv) { //Mat src, dst; src = imread("b.png"); if (src.empty()) { cout << "load img failed" << endl; return -1; } imshow("input img", src); //Mat gaussian,gray_src; //GaussianBlur(src, gaussian, Size(3, 3), 0, 0); namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE); cvtColor(src, gray_src, CV_BGR2GRAY); createTrackbar("Threshold Value :", OUTPUT_TITLE, &t1_value, max_value, Canny_Demo); Canny_Demo(0, 0); waitKey(0); return 0; } void Canny_Demo(int, void*) { Mat edge_output; blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT); Canny(gray_src, edge_output, t1_value, t1_value * 2, 3, false); //dst.create(src.size(), src.type()); //src.copyTo(dst, edge_output); imshow(OUTPUT_TITLE, ~edge_output); }
去掉注釋會變成彩色圖,注意修改imshow中的輸出變量