Opencv和C++实现canny边缘检测
生活随笔
收集整理的這篇文章主要介紹了
Opencv和C++实现canny边缘检测
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Canny邊緣檢測主要包括:
圖像的灰度化;
圖像的高斯濾波,來平滑圖像,同時消除和降低圖像噪聲的影響;
計算出每一個像素點位置的梯度(X方向梯度、Y方向梯度、已經該點的梯度幅值)和方向角度;Y方向和X方向梯度的比值,得出梯度方向,X梯度的平方和+Y梯度的平方和的值,再進行求平方得到該點的梯度幅值。(Sobel算子等)
局部非極大值抑制處理;梯度方向垂直于邊緣方向,在梯度方向上進行非極大值抑制可以細化邊緣,在梯度方向上比較該點前后兩個點的梯度的大小,如果大于兩個點則保留,小于任意一個點則置為0。
雙閾值處理和連接處理;指定高低閾值,然后高閾值直接賦值為255,低閾值為0,中間的值進行連接處理。如果中間的值八鄰域內有255,則該值也變為255,也就是說255往周圍進行擴張,收集邊緣加閉合邊緣。
Canny算法思路參考下面的博客:
https://blog.csdn.net/dcrmg/article/details/52344902
https://www.cnblogs.com/love6tao/p/5152020.html
我在下面直接給出可以運行的C++代碼(Opencv2.4.9)
#include <iostream> #include "opencv2/opencv.hpp"using namespace std; using namespace cv;/* 生成高斯卷積核 kernel */ void Gaussian_kernel(int kernel_size, int sigma, Mat &kernel) {const double PI = 3.1415926;int m = kernel_size / 2;kernel = Mat(kernel_size, kernel_size, CV_32FC1);float s = 2 * sigma*sigma;for (int i = 0; i < kernel_size; i++){for (int j = 0; j < kernel_size; j++){int x = i - m;int y = j - m;kernel.at<float>(i, j) = exp(-(x*x + y*y) / s) / (PI*s);}} }/* 計算梯度值和方向 imageSource 原始灰度圖 imageX X方向梯度圖像 imageY Y方向梯度圖像 gradXY 該點的梯度幅值 pointDirection 梯度方向角度 */ void GradDirection(const Mat imageSource, Mat &imageX, Mat &imageY,Mat &gradXY, Mat &theta) {imageX = Mat::zeros(imageSource.size(), CV_32SC1);imageY = Mat::zeros(imageSource.size(), CV_32SC1);gradXY = Mat::zeros(imageSource.size(), CV_32SC1);theta = Mat::zeros(imageSource.size(), CV_32SC1);int rows = imageSource.rows;int cols = imageSource.cols;int stepXY = imageX.step;int step = imageSource.step;/*Mat.step參數指圖像的一行實際占用的內存長度,因為opencv中的圖像會對每行的長度自動補齊(8的倍數),編程時盡量使用指針,指針讀寫像素是速度最快的,使用at函數最慢。*/uchar *PX = imageX.data;uchar *PY = imageY.data;uchar *P = imageSource.data;uchar *XY = gradXY.data;for (int i = 1; i < rows - 1; i++){for (int j = 1; j < cols - 1; j++){int a00 = P[(i - 1)*step + j - 1];int a01 = P[(i - 1)*step + j];int a02 = P[(i - 1)*step + j + 1];int a10 = P[i*step + j - 1];int a11 = P[i*step + j];int a12 = P[i*step + j + 1];int a20 = P[(i + 1)*step + j - 1];int a21 = P[(i + 1)*step + j];int a22 = P[(i + 1)*step + j + 1];double gradY = double(a02 + 2 * a12 + a22 - a00 - 2 * a10 - a20);double gradX = double(a00 + 2 * a01 + a02 - a20 - 2 * a21 - a22);//PX[i*stepXY + j*(stepXY / step)] = abs(gradX);//PY[i*stepXY + j*(stepXY / step)] = abs(gradY);imageX.at<int>(i, j) = abs(gradX);imageY.at<int>(i, j) = abs(gradY);if (gradX == 0){gradX = 0.000000000001;}theta.at<int>(i, j) = atan(gradY / gradX)*57.3;theta.at<int>(i, j) = (theta.at<int>(i, j) + 360) % 360;gradXY.at<int>(i, j) = sqrt(gradX*gradX + gradY*gradY);//XY[i*stepXY + j*(stepXY / step)] = sqrt(gradX*gradX + gradY*gradY);}}convertScaleAbs(imageX, imageX);convertScaleAbs(imageY, imageY);convertScaleAbs(gradXY, gradXY);}/* 局部非極大值抑制 沿著該點梯度方向,比較前后兩個點的幅值大小,若該點大于前后兩點,則保留, 若該點小于前后兩點任意一點,則置為0; imageInput 輸入得到梯度圖像 imageOutput 輸出的非極大值抑制圖像 theta 每個像素點的梯度方向角度 imageX X方向梯度 imageY Y方向梯度 */ void NonLocalMaxValue(const Mat imageInput, Mat &imageOutput, const Mat &theta, const Mat &imageX, const Mat &imageY) {imageOutput = imageInput.clone();int cols = imageInput.cols;int rows = imageInput.rows;for (int i = 1; i < rows - 1; i++){for (int j = 1; j < cols - 1; j++){if (0 == imageInput.at<uchar>(i, j))continue;int g00 = imageInput.at<uchar>(i - 1, j - 1);int g01 = imageInput.at<uchar>(i - 1, j);int g02 = imageInput.at<uchar>(i - 1, j + 1);int g10 = imageInput.at<uchar>(i , j - 1);int g11 = imageInput.at<uchar>(i, j);int g12 = imageInput.at<uchar>(i , j + 1);int g20 = imageInput.at<uchar>(i + 1, j - 1);int g21 = imageInput.at<uchar>(i + 1, j);int g22 = imageInput.at<uchar>(i + 1, j + 1);int direction = theta.at<int>(i, j); //該點梯度的角度值int g1 = 0; int g2 = 0;int g3 = 0;int g4 = 0;double tmp1 = 0.0; //保存亞像素點插值得到的灰度數double tmp2 = 0.0;double weight = fabs((double)imageY.at<uchar>(i, j) / (double)imageX.at<uchar>(i, j));if (weight == 0)weight = 0.0000001;if (weight > 1){weight = 1 / weight;}if ((0 <= direction && direction < 45) || 180 <= direction &&direction < 225){tmp1 = g10*(1 - weight) + g20*(weight);tmp2 = g02*(weight)+g12*(1 - weight);}if ((45 <= direction && direction < 90) || 225 <= direction &&direction < 270){tmp1 = g01*(1 - weight) + g02*(weight);tmp2 = g20*(weight)+g21*(1 - weight);}if ((90 <= direction && direction < 135) || 270 <= direction &&direction < 315){tmp1 = g00*(weight)+g01*(1 - weight);tmp2 = g21*(1 - weight) + g22*(weight);}if ((135 <= direction && direction < 180) || 315 <= direction &&direction < 360){tmp1 = g00*(weight)+g10*(1 - weight);tmp2 = g12*(1 - weight) + g22*(weight);}if (imageInput.at<uchar>(i, j) < tmp1 || imageInput.at<uchar>(i, j) < tmp2){imageOutput.at<uchar>(i,j) = 0;}}}}/* 雙閾值的機理是: 指定一個低閾值A,一個高閾值B,一般取B為圖像整體灰度級分布的70%,且B為1.5到2倍大小的A; 灰度值小于A的,置為0,灰度值大于B的,置為255; */ void DoubleThreshold(Mat &imageInput, const double lowThreshold, const double highThreshold) {int cols = imageInput.cols;int rows = imageInput.rows;for (int i = 0; i < rows; i++){for (int j = 0; j < cols; j++){double temp = imageInput.at<uchar>(i, j);temp = temp>highThreshold ? (255) : (temp);temp = temp < lowThreshold ? (0) : (temp);imageInput.at<uchar>(i, j) = temp;}}} /* 連接處理: 灰度值介于A和B之間的,考察該像素點臨近的8像素是否有灰度值為255的, 若沒有255的,表示這是一個孤立的局部極大值點,予以排除,置為0; 若有255的,表示這是一個跟其他邊緣有“接壤”的可造之材,置為255, 之后重復執行該步驟,直到考察完之后一個像素點。其中的鄰域跟蹤算法,從值為255的像素點出發找到周圍滿足要求的點,把滿足要求的點設置為255, 然后修改i,j的坐標值,i,j值進行回退,在改變后的i,j基礎上繼續尋找255周圍滿足要求的點。 當所有連接255的點修改完后,再把所有上面所說的局部極大值點置為0;(算法可以繼續優化)。參數1,imageInput:輸入和輸出的梯度圖像 參數2,lowTh:低閾值 參數3,highTh:高閾值 */ void DoubleThresholdLink(Mat &imageInput,double lowTh,double highTh) {int cols = imageInput.cols;int rows = imageInput.rows;for (int i = 1; i < rows - 1; i++){for (int j = 1; j < cols - 1; j++){double pix = imageInput.at<uchar>(i, j);if (pix != 255)continue;bool change = false;for (int k = -1; k <= 1; k++){for (int u = -1; u <= 1; u++){if (k == 0 && u == 0)continue;double temp = imageInput.at<uchar>(i + k, j + u);if (temp >= lowTh && temp <= highTh){imageInput.at<uchar>(i + k, j + u) = 255;change = true;}}}if (change){if (i > 1)i--;if (j > 2)j -= 2;}}}for (int i = 0; i < rows; i++){for (int j = 0; j < cols; j++){if (imageInput.at<uchar>(i, j) != 255){imageInput.at<uchar>(i, j) = 0;}}} }int main() {Mat image = imread("test.jpg");imshow("origin image", image);//轉換為灰度圖Mat grayImage;cvtColor(image, grayImage, CV_RGB2GRAY);imshow("gray image", grayImage);//高斯濾波Mat gausKernel;int kernel_size = 5;double sigma = 1;Gaussian_kernel(kernel_size, sigma, gausKernel);Mat gausImage;filter2D(grayImage, gausImage, grayImage.depth(), gausKernel);imshow("gaus image", gausImage);//計算XY方向梯度Mat imageX, imageY, imageXY;Mat theta;GradDirection(grayImage, imageX, imageY, imageXY , theta);imshow("XY grad", imageXY);//對梯度幅值進行非極大值抑制Mat localImage;NonLocalMaxValue(imageXY, localImage, theta,imageX,imageY);;imshow("Non local maxinum image", localImage);//雙閾值算法檢測和邊緣連接DoubleThreshold(localImage, 60, 100);DoubleThresholdLink(localImage, 60, 100);imshow("canny image", localImage);Mat temMat;Canny(image, temMat, 60, 100);imshow("opencv canny image", temMat);waitKey(0);return 0; }總結
以上是生活随笔為你收集整理的Opencv和C++实现canny边缘检测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++学习之路(一)
- 下一篇: 深度学习、图像识别的基本概念