canny检测的思路也比较简单
首先对整幅图像高斯平滑,然后求梯度的赋值以及相角
然后进行非极大抑制;然后阈值分割,为了防止阈值不合适出现的信息缺失,对强边缘进行八邻域搜索是否有弱边缘,然后对弱边缘也进行邻域搜索,然后对图像进行二值化
第一步
高斯平滑
对图像以行的形式平滑,然后进行转置,按同样的方向进行卷积,代码如下
void gauss(Mat& image)//高斯滤波器
{
int nr = image.rows;
int nl = image.cols;
for (int i = 0; i < nr; i++)//对图像横向高斯滤波,模板为1/4[1,2,1]
{
float* p = image.ptr<float>(i);
for (int j = 1; j < nl - 1; j++)
{
p[j] = (p[j - 1] + 2 * p[j] + p[j + 1]) / 4;
}
}
image = image.t();//转置之后再进行一次滤波,达到二维滤波的效果
for (int i = 0; i < nl; i++)
{
float* p = image.ptr<float>(i);
for (int j = 1; j < nr - 1; j++)
{
p[j] = (p[j - 1] + 2 * p[j] + p[j + 1]) / 4;
}
}
image = image.t();//再转置
}
计算梯度幅值比较简单,直接上代码
void countPQMY(const Mat& image, Mat& P, Mat& Q, Mat& M, Mat& Y)//这一部分计算横向梯度P(x,y) 纵向梯度Q(x,y) 角度Y(x,y) 幅值M(x,y)
{
int nr = image.rows;
int nl = image.cols;
for (int i = 0; i < nr - 1; i++)
{
const float* up = image.ptr<float>(i);
const float* down = image.ptr<float>(i + 1);
float* p = P.ptr<float>(i);
float* q = Q.ptr<float>(i);
float* m = M.ptr<float>(i);
float* y = Y.ptr<float>(i);
for (int j = 0; j < nl - 1; j++)
{
p[j] = (-up[j] + up[j + 1] - down[j] + down[j + 1]) / 2;
q[j] = -(-up[j] - up[j + 1] + down[j] + down[j + 1]) / 2;
m[j] = sqrtf(p[j] * p[j] + q[j] * q[j]);
y[j] = atan2(q[j], p[j]);
}
}
}
非极大抑制
这是canny的一大特色,为什么要算方向和幅值呢,在这里就要用到。
对于一个像素的八邻域而言,我们可以按圆的划分格式,将其平均分成八等分,那么像素点正右边就pi/8~-pi/8的范围内,以此类推。
然后对每一个点,看着沿梯度方向的两个点幅值是否比它大。
如果大的话,说明该点并不是强边缘,将该点幅值设0;否则保持
代码如下
void NMS(Mat& M, const Mat& Y)//M是幅值,Y是角度,此处进行非极大抑制
{
int nr = M.rows;
int nl = M.cols;
for (int i = 1; i < nr - 1; i++)
{
float* mu = M.ptr<float>(i - 1);
float* mm = M.ptr<float>(i);
float* md = M.ptr<float>(i + 1);
const float* y = Y.ptr<float>(i);
for (int j = 1; j < nl - 1; j++)
{
if (y[j]<M_PI / 8 && y[j] >-M_PI / 8)
{
if (mm[j] <= mm[j + 1] && mm[j] <= mm[j - 1])
{
mm[j] = 0;
}
}
else if (y[j] >= M_PI / 8 && y[j] < M_PI / 8 * 3)
{
if (mm[j] <= mu[j + 1] && mm[j] <= md[j - 1])
{
mm[j] = 0;
}
}
else if (y[j] >= M_PI / 8 * 3 || y[j] <= -M_PI / 8 * 3)
{
if (mm[j] <= mu[j] && mm[j] <= md[j])
{
mm[j] = 0;
}
}
else if (y[j]<-M_PI / 8 && y[j] >-M_PI / 8 * 3)
{
if (mm[j] <= mu[j - 1] && mm[j] <= md[j + 1])
{
mm[j] = 0;
}
}
}
}
}
然后阈值分割得到强边缘图,若边缘图
void proc(const Mat& M, Mat &M1, int div)//阈值分割,M原图像,M1是被处理之后的图像,div指的是阈值多少
{
int nr = M.rows;
int nl = M.cols;
for (int i = 0; i < nr; i++)
{
const float* m = M.ptr<float>(i);
float* m1 = M1.ptr<float>(i);
for (int j = 0; j < nl; j++)
{
if (m[j] < div)
{
m1[j] = 0;
}
else
{
m1[j] = 255;
}
}
}//二值化过程
}
当然为了防止越界,定义了一个bool函数用于检测是否越界
bool test(int i, int j, Mat& M)//要判断有没有越界,定义为bool
{
if (i < 0 || j < 0 || i >= M.rows || j >= M.cols)
return false;
else
return true;
}
八邻域查找
对于查找,可以先找到一个强边缘点,然后讲这个点入栈;
在这个点的八邻域搜索是否存在弱边缘;
如果有将该点入栈,并对该点打上标记;
对一个点的八邻域搜索完之后,从栈里面取出栈顶然后对这个点的八邻域进行搜索;
搜索到最后一定会将首次压栈的点的八邻域及其附属八邻域全部探索完。代码如下
void TwoE(const Mat& M1, const Mat& M2, Mat& MM)//M1指的是低阈值,M2指的是高阈值处理的图像,MM是最终要展示的图像
{
//M1指的是低阈值得到的图像
//M2指的是高阈值得到的图像
//MM指的是用于是不是已经将M1探索过了
//类型均为float CV_32FC型
int nr = M1.rows;
int nl = M1.cols;
stack<int>p;
for (int i = 1; i < nr - 1; i++)
{
const float* m2 = M2.ptr<float>(i);
for (int j = 1; j < nl - 1; j++)
{
if (m2[j] == 255)
{
p.push(i);//遍历,寻找高阈值点,并将这个点压入栈
//然后对这个点的八邻域寻找,看有没有符合低阈值图像上的点,有的话标记为1
p.push(j);//有的话入栈,寻找完一次八邻域,取出栈顶元素,对他搜寻八邻域
while (!p.empty())//以此往复进行,当栈空代表该点的所有可行八邻域的八邻域等等全部搜索过
{
int yy = p.top();
p.pop();
int xx = p.top();
p.pop();
for (int m = -1; m <= 1; m++)
{
if (xx + m >= 0 && xx + m < nr)
{
const float* m1 = M1.ptr<float>(xx + m);
float* mm = MM.ptr<float>(xx + m);
for (int n = -1; n <= 1; n++)
{
if (m == 0 && n == 0)
{
}
else if (test(xx + m, yy + n, MM) && (mm[yy + n] == 0) && (m1[yy + n] == 255))
{
p.push(xx + m);
p.push(yy + n);
mm[yy + n] = 1;
}
}
}
}
}
}
}
}
for (int i = 0; i < nr; i++)
{
const float* m2 = M2.ptr<float>(i);
float* mm = MM.ptr<float>(i);
for (int j = 0; j < nl; j++)
{
if ((mm[j] == 1) || (m2[j] == 255))
{
mm[j] = 255;
}
}
}//二值化
}
测试结果如下
全部代码如下
#include <iostream>
#include <opencv2/opencv.hpp>
#include <Eigen/Dense>
#include <math.h>
#include <stack>
#define _MATH_DEFINES_DEFINED
using namespace std;
using namespace cv;
using namespace Eigen;
void gauss(Mat& image)//高斯滤波器
{
int nr = image.rows;
int nl = image.cols;
for (int i = 0; i < nr; i++)//对图像横向高斯滤波,模板为1/4[1,2,1]
{
float* p = image.ptr<float>(i);
for (int j = 1; j < nl - 1; j++)
{
p[j] = (p[j - 1] + 2 * p[j] + p[j + 1]) / 4;
}
}
image = image.t();//转置之后再进行一次滤波,达到二维滤波的效果
for (int i = 0; i < nl; i++)
{
float* p = image.ptr<float>(i);
for (int j = 1; j < nr - 1; j++)
{
p[j] = (p[j - 1] + 2 * p[j] + p[j + 1]) / 4;
}
}
image = image.t();//再转置
}
void countPQMY(const Mat& image, Mat& P, Mat& Q, Mat& M, Mat& Y)//这一部分计算横向梯度P(x,y) 纵向梯度Q(x,y) 角度Y(x,y) 幅值M(x,y)
{
int nr = image.rows;
int nl = image.cols;
for (int i = 0; i < nr - 1; i++)
{
const float* up = image.ptr<float>(i);
const float* down = image.ptr<float>(i + 1);
float* p = P.ptr<float>(i);
float* q = Q.ptr<float>(i);
float* m = M.ptr<float>(i);
float* y = Y.ptr<float>(i);
for (int j = 0; j < nl - 1; j++)
{
p[j] = (-up[j] + up[j + 1] - down[j] + down[j + 1]) / 2;
q[j] = -(-up[j] - up[j + 1] + down[j] + down[j + 1]) / 2;
m[j] = sqrtf(p[j] * p[j] + q[j] * q[j]);
y[j] = atan2(q[j], p[j]);
}
}
}
void NMS(Mat& M, const Mat& Y)//M是幅值,Y是角度,此处进行非极大抑制
{
int nr = M.rows;
int nl = M.cols;
for (int i = 1; i < nr - 1; i++)
{
float* mu = M.ptr<float>(i - 1);
float* mm = M.ptr<float>(i);
float* md = M.ptr<float>(i + 1);
const float* y = Y.ptr<float>(i);
for (int j = 1; j < nl - 1; j++)
{
if (y[j]<M_PI / 8 && y[j] >-M_PI / 8)
{
if (mm[j] <= mm[j + 1] && mm[j] <= mm[j - 1])
{
mm[j] = 0;
}
}
else if (y[j] >= M_PI / 8 && y[j] < M_PI / 8 * 3)
{
if (mm[j] <= mu[j + 1] && mm[j] <= md[j - 1])
{
mm[j] = 0;
}
}
else if (y[j] >= M_PI / 8 * 3 || y[j] <= -M_PI / 8 * 3)
{
if (mm[j] <= mu[j] && mm[j] <= md[j])
{
mm[j] = 0;
}
}
else if (y[j]<-M_PI / 8 && y[j] >-M_PI / 8 * 3)
{
if (mm[j] <= mu[j - 1] && mm[j] <= md[j + 1])
{
mm[j] = 0;
}
}
}
}
}
void proc(const Mat& M, Mat &M1, int div)//阈值分割,M原图像,M1是被处理之后的图像,div指的是阈值多少
{
int nr = M.rows;
int nl = M.cols;
for (int i = 0; i < nr; i++)
{
const float* m = M.ptr<float>(i);
float* m1 = M1.ptr<float>(i);
for (int j = 0; j < nl; j++)
{
if (m[j] < div)
{
m1[j] = 0;
}
else
{
m1[j] = 255;
}
}
}//二值化过程
}
bool test(int i, int j, Mat& M)//要判断有没有越界,定义为bool
{
if (i < 0 || j < 0 || i >= M.rows || j >= M.cols)
return false;
else
return true;
}
void TwoE(const Mat& M1, const Mat& M2, Mat& MM)//M1指的是低阈值,M2指的是高阈值处理的图像,MM是最终要展示的图像
{
//M1指的是低阈值得到的图像
//M2指的是高阈值得到的图像
//MM指的是用于是不是已经将M1探索过了
//类型均为float CV_32FC型
int nr = M1.rows;
int nl = M1.cols;
stack<int>p;
for (int i = 1; i < nr - 1; i++)
{
const float* m2 = M2.ptr<float>(i);
for (int j = 1; j < nl - 1; j++)
{
if (m2[j] == 255)
{
p.push(i);//遍历,寻找高阈值点,并将这个点压入栈
//然后对这个点的八邻域寻找,看有没有符合低阈值图像上的点,有的话标记为1
p.push(j);//有的话入栈,寻找完一次八邻域,取出栈顶元素,对他搜寻八邻域
while (!p.empty())//以此往复进行,当栈空代表该点的所有可行八邻域的八邻域等等全部搜索过
{
int yy = p.top();
p.pop();
int xx = p.top();
p.pop();
for (int m = -1; m <= 1; m++)
{
if (xx + m >= 0 && xx + m < nr)
{
const float* m1 = M1.ptr<float>(xx + m);
float* mm = MM.ptr<float>(xx + m);
for (int n = -1; n <= 1; n++)
{
if (m == 0 && n == 0)
{
}
else if (test(xx + m, yy + n, MM) && (mm[yy + n] == 0) && (m1[yy + n] == 255))
{
p.push(xx + m);
p.push(yy + n);
mm[yy + n] = 1;
}
}
}
}
}
}
}
}
for (int i = 0; i < nr; i++)
{
const float* m2 = M2.ptr<float>(i);
float* mm = MM.ptr<float>(i);
for (int j = 0; j < nl; j++)
{
if ((mm[j] == 1) || (m2[j] == 255))
{
mm[j] = 255;
}
}
}//二值化
}
int main()
{
//Mat image = cv::imread("I://A.jpg", IMREAD_GRAYSCALE);
//cv::Mat_<uchar>im2 = image;
Mat Image = imread("I://C.jpg", IMREAD_GRAYSCALE);
Mat image;
Image.convertTo(image, CV_32FC1);
int flow = 15;
Mat F = image.clone();
gauss(F);
//cout << image << endl;
//cout << F << endl;
Mat P = Mat::zeros(image.rows, image.cols, CV_32FC1);//构建零矩阵
Mat Q = Mat::zeros(image.rows, image.cols, CV_32FC1);
Mat M = Mat::zeros(image.rows, image.cols, CV_32FC1);
Mat Y = Mat::zeros(image.rows, image.cols, CV_32FC1);
Mat M1 = Mat::zeros(image.rows, image.cols, CV_32FC1);
Mat M2 = Mat::zeros(image.rows, image.cols, CV_32FC1);
Mat MM = Mat::zeros(image.rows, image.cols, CV_32FC1);
countPQMY(image, P, Q, M, Y);
NMS(M, Y);
/*cout << image << endl;
cout << P << endl;
cout << Q << endl;
cout << M << endl;
cout << Y << endl;
cout << Z << endl;*/
proc(M, M1, flow);
proc(M, M2, 2 * flow);
TwoE(M1, M2, MM);
//cout << M1 << endl;
cv::imshow("原图", Image);
cv::imshow("低阈值", M1);
cv::imshow("高阈值", M2);
cv::imshow("canny", MM);
cv::waitKey(0);
}