C C++实现Canny 边缘检测 - 图像处理

C C++实现Canny 边缘检测 - 图像处理

Canny 边缘检测的实现步骤

1 灰度化

在这里插入图片描述

2 高斯滤波

在这里插入图片描述
在这里插入图片描述

3 Sobel 卷积核计算 X、Y 方向梯度和梯度角

在这里插入图片描述

4 极大值抑制

在这里插入图片描述

5 双阈值法

在这里插入图片描述

Canny 边缘检测的代码

##########################ImgProcess.h##########################

#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
using namespace cv;
using namespace std;

class MyImg{																	//自定义类

private:
	Mat OriImg;																	//原始图像
	string filename;														    //文件名

public: MyImg();																//构造函数

		~MyImg();																//析构函数

		void ImgShow(string namewindow, Mat img);						        //显示图像

		void ImgRead();															//读取图片

		Mat GetOriImg();														//获取原始图片

		string Getfilename();													//获取图片路径

		void Setfilename(string s);												//设置图片路径

		Mat RGBToGray(Mat img);													//灰度化

		Mat GaussFilter(Mat img, double KernalSigma, int KernalSize);			//高斯滤波

		void GetGaussianKernel(double **KernalModel,int size, double sigma);	//高斯模板

		void SobelGrade(Mat img, Mat Gx, Mat Gy, Mat Arg, Mat Grad);			//高斯滤波
			
		int GetDir(double num);													//获取梯度方向

		Mat GetNMS(Mat Arg, Mat Grad,int diff);                                 //非极大值抑制

		Mat GetDouThre(Mat img, Mat LThImg, Mat HThImg, double lowThreshold, double highThreshold,int size);     //双阈值法


};

##########################ImgProcess.c##########################

#include"ImgProcess.h"
#include "math.h"
#include <iostream>  

MyImg::MyImg() {                              // 构造函数
	printf("Creat Class MyImg......\n");
}

MyImg::~MyImg() {                             // 析构函数

	printf("Delete Class MyImg......\n");
}

void MyImg::ImgRead() {                       // 读取图片
	printf("Img Reading......\n");
	OriImg = imread(filename);
}

Mat MyImg::GetOriImg()                        // 获取原始图片
{
	return(OriImg);
}

string MyImg::Getfilename()                  // 获取路径名字
{
	return(filename);
}

void MyImg::Setfilename(string s)            // 设置路径名
{
	/******************************
	// 参数 s: 图片路径名字
	********************************/
	filename = s;
}

void MyImg::ImgShow(string namewindow,Mat img) {         // 显示图像
	/******************************
	// 参数 namewindow: 窗口名字
	//      img: 显示的图像
	********************************/
	printf("Img Showing......\n");
	namedWindow(namewindow);
	imshow(namewindow, img);
}

Mat MyImg::RGBToGray(Mat img)                           // 灰度化
{
	/******************************
	// 参数img: 输入彩色图
	********************************/
	Mat image_gray = Mat::zeros(img.size(), CV_8UC1);  // 初始化图像  单通道
	for (int i = 0; i < img.rows; i++)                 // 循环每个像素点 灰度化
	{
		for (int j = 0; j < img.cols; j++)
		{
			image_gray.at<uchar>(i, j) = 0.114 * img.at<Vec3b>(i, j)[0] + 0.587 * img.at<Vec3b>(i, j)[1] + 0.299 * img.at<Vec3b>(i, j)[2];
		}
	}
	//cout << img.at<Vec3b>(5, 5)[0] << endl;
	return(image_gray);

}

Mat MyImg::GaussFilter(Mat img,double KernalSigma, int KernalSize)   // 高斯滤波 单通道 灰度图
{
	/******************************
	// 参数img: 输入灰度
	//KernalSigma:高斯核方差
	//KernalSize:高斯核大小(奇数)
	********************************/
	Mat image_gray = img.clone();									 // 初始化图像  单通道
	int height = img.rows;											 // 图像高度
	int width = img.cols;											 // 图像的宽度

	// 高斯核

	//int KernalSize = 5;											 //定义卷积核大小
	double **KernalModel = new double *[KernalSize];				 //卷积核数组
	for (int i = 0; i<KernalSize; i++)
	{ 
		KernalModel[i] = new double[KernalSize];					 //动态生成二维数组
	}
	//double KernalSigma = 1;										 // 高斯模板的方差
	GetGaussianKernel(KernalModel, KernalSize, KernalSigma);		 //获取高斯核

	int diff = KernalSize / 2;										 // 边界
	double sum;


	for (int i = diff; i < height-diff; i++)                         //高斯滤波   边界直接用原来的值
	{
		for (int j = diff; j < width-diff; j++)
		{
			sum = 0;
			for (int m = 0; m < KernalSize; m++) {
				for (int n = 0; n < KernalSize; n++) {
					sum = sum + KernalModel[m][n] * img.at<uchar>(i-diff+m, j-diff+n);
				}
			}
			image_gray.at<uchar>(i, j) = sum;
		}
	}




	return image_gray;
}

void MyImg::GetGaussianKernel(double **KernalModel, int size, double sigma)  // 获取高斯核
{
	/******************************
	// 参数KernalModel: 初始化高斯核
	//sigma:高斯核方差
	//size:高斯核大小(奇数)
	********************************/
	
	const double PI = 3.141592653;				//Pi
	int center = size / 2;						//中心
	double sum = 0;                             //高斯模板总和
	for (int i = 0; i < size; i++)              //高斯模板计算
	{
		for (int j = 0; j < size; j++)
		{
			KernalModel[i][j] = (1 / (2 * PI*sigma*sigma))*exp(-((i - center)*(i - center) + (j - center)*(j - center)) / (2 * sigma*sigma));
			sum += KernalModel[i][j];
		}
	}
	cout <<endl<< "归一化高斯模板:" << endl << endl;
	for (int m = 0; m < size; m++)				// 归一化
	{
		for (int n = 0; n < size; n++)
		{
			KernalModel[m][n] /= sum;
			cout << KernalModel[m][n] << ",";
		}
		cout <<endl<<endl;
	}
}

void MyImg::SobelGrade(Mat img, Mat Gx, Mat Gy, Mat Arg, Mat Grad)
{
	/******************************
	// 参数img: 输入图像
	//Gx:x方向梯度图
	//Gy:y方向梯度图
	//Arg:梯度角度图
	//Grad:梯度模值图
	********************************/
	double KernalModel1[3][3] = { {-1,0,1},{-2,0,2},{-1,0,1} };
	double KernalModel2[3][3] = { { 1,2,1 },{ 0,0,0 },{ -1,-2,-1 } };

	int height = img.rows;								 // 图像高度
	int width = img.cols;								 // 图像的宽度

	//cout << height << endl << width;

	int KernalSize = 3;										//定义卷积核大小
	int diff = KernalSize / 2;   // 边界
	double sum1;
	double sum2;

	for (int i = diff; i < height - diff; i++)              //高斯滤波   边界直接用原来的值
	{
		for (int j = diff; j < width - diff; j++)
		{
			sum1 = 0;
			sum2 = 0;

			for (int m = 0; m < KernalSize; m++) {          // 模板卷积操作
				for (int n = 0; n < KernalSize; n++) {
					sum1 = sum1 + KernalModel1[m][n] * img.at<uchar>(i - diff + m, j - diff + n);
					sum2 = sum2 + KernalModel2[m][n] * img.at<uchar>(i - diff + m, j - diff + n);
				}
			}
			//cout<<sum1<<endl;
			//cout << sum2 << endl << endl;
			Gx.at<uchar>(i, j) = ((abs(sum1))!=0)? abs(sum1):0.0000001;   //x 方向    防止分母为0
			Gy.at<uchar>(i, j) = abs(sum2);                               //y方向
			Grad.at<uchar>(i, j) = sqrt(sum1*sum1+sum2*sum2);            //梯度模值
			Arg.at<uchar>(i, j) = atan(sum1 / sum2)*57.3;                 // 梯度角度   
			//cout << atan(sum1 / sum2)*57.3 <<" , ";

		}
	}

}

int MyImg::GetDir(double num)
{	
	/******************************
	// 获取方向 num表示梯度角度角度
	//1:-90度  90度
	//2:-45度
	//3:0度
	//4:45度
	********************************/
	if (num < -67.5)
		return(1);                              //-90度
	else if(num>= -67.5&& num < -22.5)
	{
		return(2);                             //-45度
	}
	else if (num >= -22.5 && num < 22.5)
	{
		return(3);                             //0度
	}
	else if (num >= 22.5 && num < 67.5)
	{
		return(4);                             //45度
	}
	else {
		return(1);                             //90度
	}
}

Mat MyImg::GetNMS(Mat Arg, Mat Grad,int diff)
{
	//非极大值抑制
	// 沿着梯度方向 判断该点的梯度是否为最大
	/******************************
	// 获取方向 num表示梯度角度角度
	//Arg:梯度角度图
	//Grad:梯度模值
	//diff:沿着梯度方向判断的个数
	********************************/
	Mat NMS_Img = Grad.clone();								   // 初始化图像  单通道

	int height = Grad.rows;								     // 图像高度
	int width = Grad.cols;								     // 图像的宽度

	int dir;

	for (int i = diff; i < height - diff; i++)                 //高斯滤波   边界直接用原来的值
	{
		for (int j = diff; j < width - diff; j++)
		{
			dir = GetDir(Arg.at<uchar>(i, j));
			if (dir == 1) {        //-90 90
			
				for (int index = 0; index <diff; index++) {   //判断是否为极大值
					
					if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i - diff + index, j)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i + diff - index, j))) {

						NMS_Img.at<uchar>(i, j) = 0;
						break;
					}
				}
			}
			else if (dir == 2) {   //-45
			
				for (int index = 0; index <diff; index++) {   //判断是否为极大值

					if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i - diff + index, j - diff + index)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i + diff - index, j + diff - index))) {

						NMS_Img.at<uchar>(i, j) = 0;
						break;
					}
				}
			}
			else if (dir == 3) {   //0

				for (int index = 0; index <diff; index++) {   //判断是否为极大值

					if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i, j - diff + index)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i, j + diff - index))) {

						NMS_Img.at<uchar>(i, j) = 0;
						break;
					}
				}
			}
			else{                  //45

				for (int index = 0; index <diff; index++) {   //判断是否为极大值

					if ((Grad.at<uchar>(i, j) < Grad.at<uchar>(i - diff + index, j + diff - index)) || (Grad.at<uchar>(i, j) < Grad.at<uchar>(i + diff - index, j - diff + index))) {

						NMS_Img.at<uchar>(i, j) = 0;
						break;
					}
				}

			}
		}
	}
	return NMS_Img;
}

Mat MyImg::GetDouThre(Mat img, Mat LThImg, Mat HThImg, double lowThreshold, double highThreshold,int size)
{
	// 双阈值法
	/******************************
	// img: 输入图像
	//LThImg:低阈值图
	//HThImg:高阈值图
	//lowThreshold:低阈值
	//highThreshold:高阈值
	//size;尺寸
	********************************/
	int height = img.rows;								     // 图像高度
	int width = img.cols;								     // 图像的宽度

	for (int i = 0; i < height; i++)					     // 高低阈值处理
	{
		for (int j = 0; j < width; j++)
		{
			if(img.at<uchar>(i, j)>lowThreshold){			//低阈值处理
				LThImg.at<uchar>(i, j) = 255;
			}
			else
			{
				LThImg.at<uchar>(i, j) = 0;
			}
			if (img.at<uchar>(i, j)>highThreshold) {		//高阈值处理
				HThImg.at<uchar>(i, j) = 255;
			}
			else
			{
				HThImg.at<uchar>(i, j) = 0;
			}
		}
	}
	Mat DouImg = HThImg.clone();                             //初始化最终Canny检测图像
	for (int i = 0; i < height; i++)					     //双阈值检测边缘
	{
		for (int j = 0; j < width; j++)
		{
			if ((HThImg.at<uchar>(i, j) == 0) && (LThImg.at<uchar>(i, j) == 255)) {
				
				/*if (DouImg.at<uchar>(i - 1, j - 1) == 255 || DouImg.at<uchar>(i, j - 1) == 255 || DouImg.at<uchar>(i + 1, j - 1) == 255 || DouImg.at<uchar>(i - 1, j) == 255 ||
					DouImg.at<uchar>(i+1, j) == 255 || DouImg.at<uchar>(i - 1, j + 1) == 255 || DouImg.at<uchar>(i, j + 1) == 255 || DouImg.at<uchar>(i + 1, j + 1) == 255) 
				{
					DouImg.at<uchar>(i, j) = 255;
				}*/
				for (int m = i-size; m < i+size; m++) {   // 判断size * size 是否存在高阈值点  如果当前点也是边缘
					for (int n = j - size; n < j + size; n++) {

						if (DouImg.at<uchar>(m, n) == 255) {
							DouImg.at<uchar>(i, j) = 255;
							break;
						}
					}
				}
			}

		}
	}

	return(DouImg);

}


##########################main.c##########################

#include <iostream> 

#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp>

#include"ImgProcess.h"

using namespace cv;
using namespace std;

int main()
{
	MyImg myImg;												  //定义自定义对象

	myImg.Setfilename("lena.tiff");							      //设置路径
	myImg.ImgRead();										      //读取图片

	Mat grayimg = myImg.RGBToGray(myImg.GetOriImg());		      //灰度化

	//Mat grayimg2;
	//GaussianBlur(grayimg, grayimg2, Size(3, 3), 1, 1);	      //高斯滤波
	//myImg.ImgShow("高斯滤波图像2", grayimg2);                   //显示灰度图

	myImg.ImgShow("原始图像", myImg.GetOriImg());                 //显示原图
	myImg.ImgShow("灰度图像", grayimg);                           //显示灰度图

	Mat GaussFiltergrayimg = myImg.GaussFilter(grayimg, 1, 3);    //自定义高斯滤波
	myImg.ImgShow("高斯滤波图像", GaussFiltergrayimg);            //显示灰度图

	Mat Gx = GaussFiltergrayimg.clone();                          //初始化x梯度图像
	Mat Gy = GaussFiltergrayimg.clone();                          //初始化y梯度图像
	Mat Arg = GaussFiltergrayimg.clone();						  //初始化梯度角度图像
	Mat Grad = GaussFiltergrayimg.clone();						  //初始化梯度模值图像
	myImg.SobelGrade(GaussFiltergrayimg,Gx,Gy,Arg, Grad);		  //高斯滤波

	myImg.ImgShow("Gx", Gx);                                      //显示x梯度图像
	myImg.ImgShow("Gy", Gy);                                      //显示y梯度图像
	//myImg.ImgShow("梯度角度图", Arg);                           //显示梯度角度图像
	myImg.ImgShow("梯度模值图", Grad);                            //显示初始化梯度模值图像
	Mat NMSImg = myImg.GetNMS(Arg, Grad, 3);                      //极大值抑制
	myImg.ImgShow("极大值抑制", NMSImg);                          //显示初始化梯度模值图像

	Mat LThImg = NMSImg.clone();								  //初始化低阈值图像
	Mat HThImg = NMSImg.clone();								  //初始化高阈值图像
	
	double lowThreshold = 50;									  //低阈值
	double highThreshold = 180;									  //高阈值
	int Dousize = 7;                                              //领域尺寸
	Mat DouImg = myImg.GetDouThre(NMSImg, LThImg, HThImg,lowThreshold, highThreshold, Dousize); //双阈值处理
	myImg.ImgShow("低阈值图像", LThImg);                           //显示低阈值图像
	myImg.ImgShow("高阈值图像", HThImg);                           //显示高阈值图像
	myImg.ImgShow("Canny边缘检测", DouImg);                        //显示Canny图像
	waitKey(0);													   //暂停
	system("pause");
	cin.get();
	return(0);
}

Canny 边缘检测结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  结果说明:根据 Canny 检测的步骤,依次得到以上的结果,图像高斯滤波 后更为平滑,模糊(图 3.3),接着得到 x 和 y 方向的梯度图,以及模值图像。 经过极大值抑制,图像的粗略边缘已经得到,如图 3.7,但仍然有一些非边缘点。 在极大值抑制的结果上,经过高阈值(图 3.9)和低阈值(图 3.8)处理,得到两 幅边缘图像;高阈值图像的边缘点少于低阈值图像的边缘点,通过双阈值法,得 到最后的 Canny 检测轮廓的结果,如图 3.10,边缘检测效果很好。

可执行文件与全部源代码下载

  主页可以下载,或者点这里这里!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cuntou0906

玛莎拉蒂是我的目标!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值