C/C++实现Canny边缘检测与OpenCV实现对比

目录

1.代码

2.原理

 2.1“边缘”

2.2高斯滤波

 2.3 计算梯度

2.4 非极大值抑制

 2.5 滞后阈值化


1.代码

        这是一个很老很常用的方法。博主写了一下,与OpenCV460做对比。在之前的版本(Opencv3.x)个人感觉其实现效果不理想,于是自己写了一遍,效果比OpenCV好。今天与OpenCV460比发现效果却比不过460的实现。这里直接附上代码。分别是mcanny.h和main函数文件。

#pragma once
#include
#include 
#include 
	class MCannyEdgeDetector
	{
		struct BITMAP
		{
			int nW;
			int nH;
		};
	public:

		MCannyEdgeDetector(const float& cannyL, const float& cannyH,const double& sig, unsigned char* pixelAddr,const int& imgW,const int& imgH);
		~MCannyEdgeDetector();
		bool In(unsigned char* pixelAddr,int imgW, int imgH);
		bool Out(unsigned char*outMatData);
		void Do();
		
	private:
		long int						m_nPixLength;
		BITMAP							m_bitImgWH;
		int								m_nImgW;
		int								m_nImgH;
		unsigned char*					m_pImgPixAddr;
		unsigned char*					m_pBinaryImg;
		double*							m_pDoubleImg;
		unsigned char*					m_pEdgeMap;
		float							m_fMaxRadius;		 
		float							m_fLowThresh;		 
		float							m_fHighThresh;		 
		double							m_dSigma;    		
	};

	MCannyEdgeDetector::MCannyEdgeDetector(const float& cannyL, const float& cannyH,const double& sig, unsigned char* pixelAddr,const int& imgW,const int& imgH)
	{
		m_fHighThresh = cannyH;
		m_fLowThresh = cannyL;
		m_pImgPixAddr = NULL;
		m_pEdgeMap = NULL;
		memset(&m_bitImgWH, 0, sizeof(BITMAP));
		m_dSigma = sqrt(sig);
		In(pixelAddr, imgW, imgH);
	}

	MCannyEdgeDetector::~MCannyEdgeDetector()
	{

	}

	bool MCannyEdgeDetector::In
	(unsigned char* pixelAddr, int imgW, int imgH)
	{

		if (m_pImgPixAddr != NULL)
		{
			delete m_pImgPixAddr;
			m_pImgPixAddr = NULL;
		}
		int pixelNum = imgW * imgH;
		m_nPixLength = pixelNum;
		m_bitImgWH.nH = imgH;
		m_bitImgWH.nW= imgW;
		m_nImgH = imgH;
		m_nImgW = imgW;
		
		m_pImgPixAddr = new unsigned char[pixelNum];
		memset(m_pImgPixAddr, 0, pixelNum);
		memcpy(m_pImgPixAddr, pixelAddr, pixelNum);

		
		if (m_pDoubleImg != NULL)
		{
			delete m_pDoubleImg;
			m_pDoubleImg = NULL;
		}
		m_pDoubleImg = new double[pixelNum];
		memset(m_pDoubleImg, 0, pixelNum * sizeof(double));
		for (int i = 0; i < pixelNum; i++)
		{
			m_pDoubleImg[i] = double(m_pImgPixAddr[i] / 255.0);
		}

		return true;

	}

	void MCannyEdgeDetector::Do()
	{
		double PI = 3.1415926535;
		int halfGaussLength = ceil(m_dSigma * 4);
		int gaussLength = 2 * halfGaussLength + 1;
		double coeffC = 1.0 / (sqrt(2 * PI) * m_dSigma);
		double sumOfKernel = 0.0;

		double* pGaussKernel = new double[gaussLength];
		double* pDeltaGaussKernel = new double[gaussLength];

		
		for (int i = 0; i < gaussLength; i++)
		{
			double value;
			value = coeffC * exp(-pow(-halfGaussLength + i, 2.0) / (2.0 * pow(m_dSigma, 2.0)));
			pGaussKernel[i] = value;
			sumOfKernel = sumOfKernel + value;
		}
		for (int i = 0; i < gaussLength; i++)
		{
			pGaussKernel[i] = pGaussKernel[i] / sumOfKernel;
		}

		double sumOfPos = 0.0;
		double sumofNeg = 0.0;
		for (int i = 0; i < gaussLength; i++)
		{
			if (i == 0)
			{
				pDeltaGaussKernel[i] = pGaussKernel[i + 1] - pGaussKernel[i];
			}
			else if (i == gaussLength - 1)
			{
				pDeltaGaussKernel[i] = pGaussKernel[i] - pGaussKernel[i - 1];
			}
			else
			{
				pDeltaGaussKernel[i] = (pGaussKernel[i + 1] - pGaussKernel[i - 1]) / 2.0;
			}
			if (pDeltaGaussKernel[i] > 0)
			{
				sumOfPos += pDeltaGaussKernel[i];
			}
			if (pDeltaGaussKernel[i] < 0)
			{
				sumofNeg += pDeltaGaussKernel[i];
			}
		}
		for (int i = 0; i < gaussLength; i++)
		{
			if (pDeltaGaussKernel[i] > 0)
			{
				pDeltaGaussKernel[i] /= sumOfPos;
			}
			if (pDeltaGaussKernel[i] < 0)
			{
				pDeltaGaussKernel[i] /= -sumofNeg;
			}

		}
		if (m_pDoubleImg == NULL)
		{
			return;
		}


		double* xBlurImg = new double[m_nPixLength];
		double* dxBlurImg = new double[m_nPixLength];
		double* yBlurImg = new double[m_nPixLength];
		double* dyBlurImg = new double[m_nPixLength];
		memset(dyBlurImg, 0, sizeof(double) * m_nPixLength);
		memset(dxBlurImg, 0, sizeof(double) * m_nPixLength);
		memset(yBlurImg, 0, sizeof(double) * m_nPixLength);
		memset(xBlurImg, 0, sizeof(double) * m_nPixLength);
	
		for (int iCol = halfGaussLength; iCol < m_nImgW - halfGaussLength; iCol++)
		{
			for (int iRow = halfGaussLength; iRow < m_nImgH - halfGaussLength; iRow++)
			{
				double convValue = 0.0;
				int kIndex = gaussLength - 1;
				for (int iKernel = 0; iKernel < gaussLength; iKernel++)
				{
					convValue = convValue + pGaussKernel[kIndex - iKernel]
						* (m_pDoubleImg[(iRow + iKernel - halfGaussLength) * m_nImgW
							+ iCol]);
				}
				xBlurImg[iRow * m_nImgW + iCol] = convValue;
			}
		}
		for (int iRow = halfGaussLength; iRow < m_nImgH - halfGaussLength; iRow++)
		{
			for (int iCol = halfGaussLength; iCol < m_nImgW - halfGaussLength; iCol++)
			{
				double convValue = 0.0;
				int	kIndex = gaussLength - 1;
				for (int iKernel = 0; iKernel < gaussLength; iKernel++)
				{
					convValue = convValue + pDeltaGaussKernel[kIndex - iKernel]
						* xBlurImg[iRow * m_nImgW
						+ iCol + iKernel - halfGaussLength];
				}
				dxBlurImg[iRow * m_nImgW + iCol] = convValue;
			}
		}
		
		for (int iRow = halfGaussLength; iRow < m_nImgH - halfGaussLength; iRow++)
		{
			for (int iCol = halfGaussLength; iCol < m_nImgW - halfGaussLength; iCol++)
			{
				double convValue = 0.0;
				int	kIndex = gaussLength - 1;
				for (int iKernel = 0; iKernel < gaussLength; iKernel++)
				{
					convValue = convValue + pGaussKernel[kIndex - iKernel] *
						m_pDoubleImg[iRow * m_nImgW
						- halfGaussLength + iCol + iKernel];
				}
				yBlurImg[iRow * m_nImgW + iCol] = convValue;
			}
		}
		for (int iCol = halfGaussLength; iCol < m_nImgW - halfGaussLength; iCol++)
		{
			for (int iRow = halfGaussLength; iRow < m_nImgH - halfGaussLength; iRow++)
			{
				double convValue = 0.0;
				int	kIndex = gaussLength - 1;
				for (int iKernel = 0; iKernel < gaussLength; iKernel++)
				{
					convValue = convValue + pDeltaGaussKernel[kIndex - iKernel] *
						yBlurImg[(iRow - halfGaussLength + iKernel)
						* m_nImgW + iCol];
				}
				dyBlurImg[iRow * m_nImgW + iCol] = convValue;
			}
		}
		delete xBlurImg;
		delete yBlurImg;
		xBlurImg = NULL;
		yBlurImg = NULL;
		
		double* magBlurGrad = new double[m_nPixLength];
		memset(magBlurGrad, 0, m_nPixLength * sizeof(double));
		double maxG = 0.0;
		for (int iRow = 0; iRow < m_nImgH; iRow++)
		{
			for (int iCol = 0; iCol < m_nImgW; iCol++)
			{
				double mValue = sqrt(
					dxBlurImg[iRow * m_nImgW + iCol] *
					dxBlurImg[iRow * m_nImgW + iCol] +
					dyBlurImg[iRow * m_nImgW + iCol] *
					dyBlurImg[iRow * m_nImgW + iCol]);
				magBlurGrad[iRow * m_nImgW + iCol] = mValue;
				if (mValue >= maxG)
				{
					maxG = mValue;
				}
			}
		}
		if (maxG > 0)
		{
			for (int iRow = 0; iRow < m_nImgH; iRow++)
			{
				for (int iCol = 0; iCol < m_nImgW; iCol++)
				{
					magBlurGrad[iRow * m_nImgW + iCol] =
						magBlurGrad[iRow * m_nImgW + iCol] / maxG;

				}
			}
		}

		int edgePixNum = 0;
		int* edgeX = new int[m_nPixLength];
		int* edgeY = new int[m_nPixLength];
		int* changed = new int[m_nPixLength];
		unsigned char* edgeMap = new unsigned char[m_nPixLength];
		memset(edgeMap, 0, m_nPixLength);
		for (int r = 0; r < m_nImgH - 1; r++)
		{
			for (int c = 0; c < m_nImgW - 1; c++)
			{
				double angleC;
				double dx = dxBlurImg[r * m_nImgW + c];
				double dy = dyBlurImg[r * m_nImgW + c];
				double magDirA, magDirB;
				double magThe = magBlurGrad[r * m_nImgW + c];
				if ((dy <= 0 && dx > -dy) || (dy >= 0 && dx < -dy))
				{
					angleC = abs(dy / dx);
					magDirA = magBlurGrad[(r - 1) * m_nImgW + c + 1] * angleC +
						magBlurGrad[r * m_nImgW + c + 1] * (1 - angleC);
					magDirB = magBlurGrad[r * m_nImgW + c - 1] * (1 - angleC) +
						magBlurGrad[(r + 1) * m_nImgW + c - 1] * angleC;
				}
				if ((dx > 0 && -dy >= dx) || (dx < 0 && -dy <= dx))
				{
					angleC = abs(dx / dy);
					magDirA = magBlurGrad[(r - 1) * m_nImgW + c] * (1 - angleC)
						+ magBlurGrad[(r - 1) * m_nImgW + c + 1] * angleC;
					magDirB = magBlurGrad[(r + 1) * m_nImgW + c] * (1 - angleC)
						+ magBlurGrad[(r + 1) * m_nImgW + c - 1] * angleC;

				}
				if ((dx <= 0 && dx > dy) || (dx >= 0 && dx < dy))
				{
					angleC = abs(dx / dy);
					magDirA = magBlurGrad[(r - 1) * m_nImgW + c] * (1 - angleC)
						+ magBlurGrad[(r - 1) * m_nImgW + c - 1] * angleC;
					magDirB = magBlurGrad[(r + 1) * m_nImgW + c] * (1 - angleC)
						+ magBlurGrad[(r + 1) * m_nImgW + c + 1] * angleC;

				}
				if ((dy < 0 && dx <= dy) || (dy > 0 && dx >= dy))
				{
					angleC = abs(dy / dx);
					magDirA = magBlurGrad[r * m_nImgW + c - 1] * (1 - angleC) +
						magBlurGrad[(r - 1) * m_nImgW + c - 1] * angleC;
					magDirB = magBlurGrad[r * m_nImgW + c + 1] * (1 - angleC) +
						magBlurGrad[(r + 1) * m_nImgW + c + 1] * angleC;
				}
				if (r <= halfGaussLength || c <= halfGaussLength  || r >= m_bitImgWH.nH - halfGaussLength-1 || c >= m_bitImgWH.nW - halfGaussLength-1) continue;
				if (magThe > magDirA && magThe >= magDirB)
				{
					if (magThe >= m_fLowThresh)
					{
						edgeX[edgePixNum] = c;
						edgeY[edgePixNum] = r;
						changed[edgePixNum] = 0;
						edgePixNum++;
						if (magThe >= m_fHighThresh)
						{
							edgePixNum--;
							edgeMap[r * m_nImgW + c] = 255;
						}
					}
				}
			}
		}
		int deltaWeak = 1;

		while (deltaWeak != 0)
		{
			deltaWeak = 0;
			for (int iPt = 0; iPt < edgePixNum; iPt++)
			{
				if (changed[iPt] == 1)
				{
					continue;
				}
				for (int i = -1; i < 2; i++)
				{
					bool upLevel = 0;
					for (int j = -1; j < 2; j++)
					{
						int c = edgeX[iPt] + j;
						int r = edgeY[iPt] + i;
						int r0 = edgeY[iPt];
						int c0 = edgeX[iPt];
						if (edgeMap[r * m_nImgW + c] == 1)
						{
							edgeMap[r0 * m_nImgW + c0] = 255;
							changed[iPt] = 1;
							deltaWeak++;
							upLevel = 1;
							break;
						}
					}
					if (upLevel)
					{
						break;
					}
				}
			}
		}


		delete[]pDeltaGaussKernel;
		delete[]pGaussKernel;
		delete[]dxBlurImg;
		delete[]dyBlurImg;
		delete[]edgeX;
		delete[]edgeY;
		delete[]changed;
		delete[]magBlurGrad;
		
		if (m_pEdgeMap != NULL)
		{
			delete[] m_pEdgeMap;
			m_pEdgeMap = NULL;
		}
		if (m_pEdgeMap == NULL)
		{
			m_pEdgeMap = new unsigned char[m_nPixLength];
		}
		for (int i = 0; i < m_nImgW; i++)
		{
			for (int j = 0; j < m_nImgH; j++)
			{
				m_pEdgeMap[j * m_nImgW + i] = edgeMap[j * m_nImgW + i];
			}
		}
		delete[]edgeMap;
	}

	bool MCannyEdgeDetector::Out(unsigned char* outMatData)
	{
		if (NULL == m_pEdgeMap)
		{
			return false;
		}
		for (int i = 0; i < m_nImgW; i++)
		{
			for (int j = 0; j < m_nImgH; j++)
			{
				if (m_pEdgeMap[j * m_nImgW + i] != 0)
				{
					outMatData[j * m_nImgW + i] = 255;
				}
			}
		}
		return true;
	}
#include
#include "mcanny.h"

int main()
{
	//导入灰度图显示
	cv::Mat1b src = cv::imread("gta5.jpeg",cv::IMREAD_GRAYSCALE);
	cv::namedWindow("src", cv::WINDOW_NORMAL);
	cv::imshow("src", src);
	cv::waitKey(0);

	//调用opencv canny 
	cv::Mat cvEdgeMap,src_blur;
	cv::GaussianBlur(src, src_blur,cv::Size(3,3),2);
	cv::Canny(src_blur, cvEdgeMap,50,150);
	cv::namedWindow("cvEdgeMap", cv::WINDOW_NORMAL);
	cv::imshow("cvEdgeMap", cvEdgeMap);
	cv::waitKey(0);

	//调用博主实现canny
	cv::Mat myEdgeMap=cv::Mat1b::zeros(src.rows,src.cols);
	MCannyEdgeDetector* mCanny = new MCannyEdgeDetector(0.03,0.1,2,src.data,src.cols,src.rows);
	mCanny->Do();
	mCanny->Out(myEdgeMap.data);
	cv::namedWindow("myEdgeMap", cv::WINDOW_NORMAL);
	cv::imshow("myEdgeMap", myEdgeMap);
	cv::waitKey(0);
	
}

下面是测试结果:

C/C++实现Canny边缘检测与OpenCV实现对比_第1张图片 原图

C/C++实现Canny边缘检测与OpenCV实现对比_第2张图片 博主实现

C/C++实现Canny边缘检测与OpenCV实现对比_第3张图片 OpenCV实现

C/C++实现Canny边缘检测与OpenCV实现对比_第4张图片 博主实现细节 C/C++实现Canny边缘检测与OpenCV实现对比_第5张图片 cv实现细节

         结果显示,Opencv460边缘检测结果比博主实现的更好,获取的边缘图更整洁连续,含噪声更少。

2.原理

 2.1“边缘”

        在3D世界,场景表面法向不连续或者深度不连续,阴影照明不连续,或者高亮和表面颜色纹理不连续,这些位置在图像上会表现为边缘。图像上,边缘处灰度变化十分剧烈,灰度梯度值十分大。更准确的说,只有灰度梯度值是极值的地方才有可能是边缘。这就对应了Canny边缘检测算法中计算梯度和非极大值抑制两步;非极大值抑制可以有效抑制噪声带来的误检测,并且能准确定位边缘像素点。为了抑制噪声,Canny边缘检测在计算梯度前附加了高斯平滑以降噪;在计算完成后加上进行强弱边缘连接(滞后阈值化/双阈值处理)以减少漏检测。因此Canny边缘检测是针对阶跃边缘的很好的方法,能有效减少误检测和漏检测,并且获取准确的边缘位置,是整像素精度的获取单像素宽度边缘的方法。

2.2高斯滤波

        用高斯核与图像作卷积,生成平滑图像。这里博主按照分离滤波器的方式实现,利用卷积算法的性质减少计算次数,加快运算速度。即先计算行方向,再计算列方向。

C/C++实现Canny边缘检测与OpenCV实现对比_第6张图片

 2.3 计算梯度

        可以用任意的梯度算子与上一步平滑后的图像作卷积,生成梯度图像。这里作者选用的是soble算子,分别计算x方向梯度核y方向梯度,这样可以得到梯度幅值图和梯度方向图。除了soble算在,常用的边缘检测算子还有Roberts,Laplace,prewitt,robinson,kirsch等。

2.4 非极大值抑制

        有了梯度方向图和梯度幅值图,就要对每个像素,沿着其梯度方向,判断该像素点的梯度幅值是否比其梯度方向上(和梯度负方向上)的两个像素的梯度幅值大。这是因为边缘方向与梯度方向垂直,而边缘处一定是梯度极大值点。因此只有像素点,在梯度方向上是极大值,才可能是边缘点。

        经过这步操作,可以得到边缘候选点。

C/C++实现Canny边缘检测与OpenCV实现对比_第7张图片C/C++实现Canny边缘检测与OpenCV实现对比_第8张图片

 2.5 滞后阈值化

       对于上述候选点。梯度幅值超过高阈值H的一定是强边缘点;对于梯度幅值高于低阈值h的点(弱边缘点),判断其周围是否有强边缘点,若有则也认为是强边缘点。因此这一步需要迭代。不断遍历弱边缘点,判断决定是否转换为强边缘点。在迭代至没有弱边缘点转换为强边缘点的时候,整个算法就进行完成了,强边缘点就是要输出的边缘点,剩下的弱边缘点被抛弃。

判断迭代完成的条件:Δ(num_of_weakPoints)=0.

你可能感兴趣的:(c++,c语言,opencv,视觉检测)