Opencv和C++实现canny边缘检测
Canny邊緣檢測(cè)主要包括:
圖像的灰度化;
圖像的高斯濾波,來平滑圖像,同時(shí)消除和降低圖像噪聲的影響;
計(jì)算出每一個(gè)像素點(diǎn)位置的梯度(X方向梯度、Y方向梯度、已經(jīng)該點(diǎn)的梯度幅值)和方向角度;Y方向和X方向梯度的比值,得出梯度方向,X梯度的平方和+Y梯度的平方和的值,再進(jìn)行求平方得到該點(diǎn)的梯度幅值。(Sobel算子等)
局部非極大值抑制處理;梯度方向垂直于邊緣方向,在梯度方向上進(jìn)行非極大值抑制可以細(xì)化邊緣,在梯度方向上比較該點(diǎn)前后兩個(gè)點(diǎn)的梯度的大小,如果大于兩個(gè)點(diǎn)則保留,小于任意一個(gè)點(diǎn)則置為0。
雙閾值處理和連接處理;指定高低閾值,然后高閾值直接賦值為255,低閾值為0,中間的值進(jìn)行連接處理。如果中間的值八鄰域內(nèi)有255,則該值也變?yōu)?55,也就是說255往周圍進(jìn)行擴(kuò)張,收集邊緣加閉合邊緣。
Canny算法思路參考下面的博客:
https://blog.csdn.net/dcrmg/article/details/52344902
https://www.cnblogs.com/love6tao/p/5152020.html
我在下面直接給出可以運(yùn)行的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);}} }/* 計(jì)算梯度值和方向 imageSource 原始灰度圖 imageX X方向梯度圖像 imageY Y方向梯度圖像 gradXY 該點(diǎn)的梯度幅值 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參數(shù)指圖像的一行實(shí)際占用的內(nèi)存長(zhǎng)度,因?yàn)閛pencv中的圖像會(huì)對(duì)每行的長(zhǎng)度自動(dòng)補(bǔ)齊(8的倍數(shù)),編程時(shí)盡量使用指針,指針讀寫像素是速度最快的,使用at函數(shù)最慢。*/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);}/* 局部非極大值抑制 沿著該點(diǎn)梯度方向,比較前后兩個(gè)點(diǎn)的幅值大小,若該點(diǎn)大于前后兩點(diǎn),則保留, 若該點(diǎn)小于前后兩點(diǎn)任意一點(diǎn),則置為0; imageInput 輸入得到梯度圖像 imageOutput 輸出的非極大值抑制圖像 theta 每個(gè)像素點(diǎn)的梯度方向角度 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); //該點(diǎn)梯度的角度值int g1 = 0; int g2 = 0;int g3 = 0;int g4 = 0;double tmp1 = 0.0; //保存亞像素點(diǎn)插值得到的灰度數(shù)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;}}}}/* 雙閾值的機(jī)理是: 指定一個(gè)低閾值A(chǔ),一個(gè)高閾值B,一般取B為圖像整體灰度級(jí)分布的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之間的,考察該像素點(diǎn)臨近的8像素是否有灰度值為255的, 若沒有255的,表示這是一個(gè)孤立的局部極大值點(diǎn),予以排除,置為0; 若有255的,表示這是一個(gè)跟其他邊緣有“接壤”的可造之材,置為255, 之后重復(fù)執(zhí)行該步驟,直到考察完之后一個(gè)像素點(diǎn)。其中的鄰域跟蹤算法,從值為255的像素點(diǎn)出發(fā)找到周圍滿足要求的點(diǎn),把滿足要求的點(diǎn)設(shè)置為255, 然后修改i,j的坐標(biāo)值,i,j值進(jìn)行回退,在改變后的i,j基礎(chǔ)上繼續(xù)尋找255周圍滿足要求的點(diǎn)。 當(dāng)所有連接255的點(diǎn)修改完后,再把所有上面所說的局部極大值點(diǎn)置為0;(算法可以繼續(xù)優(yōu)化)。參數(shù)1,imageInput:輸入和輸出的梯度圖像 參數(shù)2,lowTh:低閾值 參數(shù)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);//轉(zhuǎn)換為灰度圖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);//計(jì)算XY方向梯度Mat imageX, imageY, imageXY;Mat theta;GradDirection(grayImage, imageX, imageY, imageXY , theta);imshow("XY grad", imageXY);//對(duì)梯度幅值進(jìn)行非極大值抑制Mat localImage;NonLocalMaxValue(imageXY, localImage, theta,imageX,imageY);;imshow("Non local maxinum image", localImage);//雙閾值算法檢測(cè)和邊緣連接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; }總結(jié)
以上是生活随笔為你收集整理的Opencv和C++实现canny边缘检测的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++学习之路(一)
- 下一篇: QT给文本添加链接事件