Opencv和C++实现canny边缘检测_opencv边缘增强-CSDN博客
一、canny实现步骤
1、图像必须是单通道的,也就是说必须是灰度图像
2、图像进行高斯滤波,去掉噪点
3、sobel 算子过程的实现,计算x y方向 、梯度(用不到,但是可以看看xy 两个组合起来的结果)
以及梯度方向(很重要)
4、局部非极大值抑制
5、双阈值连接处理
具体可以分为上面的5个步骤,下面一起边看原理边实现。
二、原理与实现
1、图像灰度化
如果是一张3通道的图像,也就是我们常见的彩色图,那么们就需要将其转换成一个灰度图,其规则如下:
1.浮点算法:Gray = R*0.3 + G*0.59 + B*0.11
2.整数方法:Gray = (R*30+G*59+B*11)/100
3.移位方法:Gray = (R*28+G*151+B*77)>> 8
4.平均值法:Gray = (R+G+B)/3
5.仅取绿色:Gray = G
但是通常我们自己实现一般都是拿第一种实现的。
OpenCV转灰度图像特别简单,只需调用函数 cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 即可。
code:
void ConvertRGB2GRAY(const Mat& image, Mat& imageGray)
{
if (!image.data || image.channels() != 3)
{
return;
}
// 创建一个单通道的灰度图像
imageGray = Mat::zeros(image.size(), CV_8UC1);
// 取出存储图像的数组的指针
uchar* pointImage = image.data;
uchar* pointImageGray = imageGray.data;
int stepImage = image.step;
int stepImageGray = imageGray.step;
for (int i = 0; i < imageGray.rows; i++)
{
for (int j = 0; j < imageGray.cols; j++)
{
pointImageGray[i * stepImageGray + j] = 0.114 * pointImage[i * stepImage + 3 * j] + 0.587 * pointImage[i * stepImage + 3 * j + 1] + 0.299 * pointImage[i * stepImage + 3 * j + 2];
}
}
}
2、高斯滤波
在高斯滤波的时候先要生成一个2元高斯核,然后进行高斯滤波,其作用是去掉噪点,其图像变的平滑起来
二元高斯函数
随着sigma的增大,整个高斯函数的尖峰逐渐减小,整体也变的更加平缓,则对图像的平滑效果越来越明显。
高斯核
代码里面最后一定要归一化
void CreateGaussianKernel(int kernel_size, int sigma, Mat& kernel)
{
const double PI = 3.1415926;
int center = kernel_size / 2;
kernel = Mat(kernel_size, kernel_size,CV_32FC1);
float segma_pow = 2 * sigma * sigma;
float sum = 0;
// 二元高斯函数
for (size_t i = 0; i < kernel_size; i++)
{
for (size_t j= 0; j < kernel_size; j++)
{
float temp = ((i - center) * (i - center) + (j - center) * (j - center) )/ segma_pow;
kernel.at<float>(i, j) = 1 / (PI * segma_pow) * exp(-temp);
sum += kernel.at<float>(i, j);
}
}
// 归一化
for (size_t i = 0; i < kernel_size; i++)
{
for (size_t j = 0; j < kernel_size; j++)
{
kernel.at<float>(i, j) = kernel.at<float>(i, j)/sum;
}
}
}
5*5 的高斯核,那个核数一般是不能超过11 ,超过11 其效果均值一样了
高斯滤波
//******************高斯滤波*************************
//第一个参数imageSource是待滤波原始图像;
//第二个参数imageGaussian是滤波后输出图像;
//第三个参数 kernel 是一个指向含有N个double类型数组;
//第四个参数size是滤波核的尺寸
//*************************************************************
void GaussianFilter(const Mat& imageSource, Mat& imageGaussian, Mat& kernel, int size)
{
if (!imageSource.data|| imageSource.channels()!=1)
{
return;
}
imageGaussian = Mat::zeros(imageSource.size(),CV_8UC1);
float gaussArray[100];
// 将 kernel 的方阵 变成一个一维度数组 这样在循环的时候啊就少了一次内循环
int m = 0;
for (size_t i = 0; i < kernel.rows; i++)
{
for (size_t j = 0; j < kernel.cols; j++)
{
gaussArray[m] = kernel.at<float>(i,j);
m++;
}
}
//滤波
for (int i = 0; i < imageSource.rows; i++)
{
for (int j = 0; j < imageSource.cols; j++)
{
int k = 0;
for (int l = -size / 2; l <= size / 2; l++)
{
for (int g = -size / 2; g <= size / 2; g++)
{
//以下处理针对滤波后图像边界处理,为超出边界的值赋值为边界值
int row = i + l;
int col = j + g;
row = row < 0 ? 0 : row;
row = row >= imageSource.rows ? imageSource.rows - 1 : row;
col = col < 0 ? 0 : col;
col = col >= imageSource.cols ? imageSource.cols - 1 : col;
//卷积和
imageGaussian.at<uchar>(i, j) += gaussArray[k] * imageSource.at<uchar>(row, col);
k++;
}
}
}
}
}
void TestGaussian()
{
Mat kernel;
CreateGaussianKernel(5, 1, kernel);
// 打印 高斯核
for (int i = 0; i < kernel.rows; i++)
{
for (int j = 0; j < kernel.cols; j++)
{
cout << " " << kernel.at<float>(i, j);
}
cout << endl;
}
Mat src = imread("C:\\Users\\alber\\Desktop\\opencv_images\\529.jpg");
Mat dst, imageGaussian;
ConvertRGB2GRAY(src, dst);
imwrite("C:\\Users\\alber\\Desktop\\opencv_images\\1\\1.jpg", dst);
GaussianFilter(dst, imageGaussian, kernel, 5);
imwrite("C:\\Users\\alber\\Desktop\\GaussianFilter.jpg", imageGaussian);
}
3、实现sobel 算子
推导出X Y方向的核
【精选】Opencv 笔记5 边缘处理-canny、sobel、Laplacian、Prewitt_opencv 边缘处理_Σίσυφος1900的博客-CSDN博客
gradient =||dx||+||dy||
theta= atan(gradY / gradX) * 57.3 注意这里的角度转换
//******************Sobel算子计算X、Y方向梯度 以及 梯度方向角********************
//第一个参数imageSourc原始灰度图像;
//第二个参数imageSobelX是X方向梯度图像;
//第三个参数imageSobelY是Y方向梯度图像;
//第四个参数 theta 是梯度方向角数组指针 下一步很重要 就是要用这个值来计算
//*************************************************************
void SobelGradDirction(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);
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);
}
这个不明显,所以我打算换个图像test
三、halcon 效果对比
四、canny 的优劣性