目录
1.代码
2.原理
2.1“边缘”
2.2高斯滤波
2.3 计算梯度
2.4 非极大值抑制
2.5 滞后阈值化
这是一个很老很常用的方法。博主写了一下,与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);
}
下面是测试结果:
结果显示,Opencv460边缘检测结果比博主实现的更好,获取的边缘图更整洁连续,含噪声更少。
在3D世界,场景表面法向不连续或者深度不连续,阴影照明不连续,或者高亮和表面颜色纹理不连续,这些位置在图像上会表现为边缘。图像上,边缘处灰度变化十分剧烈,灰度梯度值十分大。更准确的说,只有灰度梯度值是极值的地方才有可能是边缘。这就对应了Canny边缘检测算法中计算梯度和非极大值抑制两步;非极大值抑制可以有效抑制噪声带来的误检测,并且能准确定位边缘像素点。为了抑制噪声,Canny边缘检测在计算梯度前附加了高斯平滑以降噪;在计算完成后加上进行强弱边缘连接(滞后阈值化/双阈值处理)以减少漏检测。因此Canny边缘检测是针对阶跃边缘的很好的方法,能有效减少误检测和漏检测,并且获取准确的边缘位置,是整像素精度的获取单像素宽度边缘的方法。
用高斯核与图像作卷积,生成平滑图像。这里博主按照分离滤波器的方式实现,利用卷积算法的性质减少计算次数,加快运算速度。即先计算行方向,再计算列方向。
可以用任意的梯度算子与上一步平滑后的图像作卷积,生成梯度图像。这里作者选用的是soble算子,分别计算x方向梯度核y方向梯度,这样可以得到梯度幅值图和梯度方向图。除了soble算在,常用的边缘检测算子还有Roberts,Laplace,prewitt,robinson,kirsch等。
有了梯度方向图和梯度幅值图,就要对每个像素,沿着其梯度方向,判断该像素点的梯度幅值是否比其梯度方向上(和梯度负方向上)的两个像素的梯度幅值大。这是因为边缘方向与梯度方向垂直,而边缘处一定是梯度极大值点。因此只有像素点,在梯度方向上是极大值,才可能是边缘点。
经过这步操作,可以得到边缘候选点。
对于上述候选点。梯度幅值超过高阈值H的一定是强边缘点;对于梯度幅值高于低阈值h的点(弱边缘点),判断其周围是否有强边缘点,若有则也认为是强边缘点。因此这一步需要迭代。不断遍历弱边缘点,判断决定是否转换为强边缘点。在迭代至没有弱边缘点转换为强边缘点的时候,整个算法就进行完成了,强边缘点就是要输出的边缘点,剩下的弱边缘点被抛弃。
判断迭代完成的条件:Δ(num_of_weakPoints)=0.