图像处理经常会用到这张赫赫有名的图片,这位lena女士的照片原本用在“花花公子”的杂志封面上,机缘巧合被当做测试素材,广泛用在图形处理领域。
原图是一张半裸的全身照,截取了头像部分,有兴趣可以去网上找找
参考:Lena.jpg
IEEE图像处理汇刊的主编David C.Munson总结了两点原因: 1.该图适度的混合了细节、平滑区域、阴影和纹理,从而能很好的测试各种图像处理算法。 2.Lenna是个美女,对于图象处理界的研究者来说,美女图可以有效的吸引他们来做研究
图像边缘检测的算法有很多,包括传统的模板算⼦(Sobel、Roberts、Prewitt、Laplace)、形态学边缘检测、经典的 Canny 边缘检测及基于深度学习的边缘检测算法等。这篇文章讲两个有代表性的算子:sobel边缘检测和canny边缘检测
基于梯度是最基本的边缘检测算法,存在较大误差和不稳定性。
Sobel 模板算⼦是 Irwin Sobel 在 1968 年发表的论⽂ An Isotropic 3×3 Image Gradient Operator中提出的⼀种⼀阶导数模板算⼦,⽤来计算图像灰度函数的近似梯度。
其中,Gx表⽰⽔平⽅向的卷积模板,Gy表⽰垂直⽅向的卷积模板
不同阈值
实现代码见文末
Canny边缘检测是 John Canny在 1986年⾸次提出的⼀种改进的边缘检测⽅法。该⽅法主要通过图像信号函数的极⼤值来判断图像的边缘像素点,与基本的 Sobel 模板算⼦等相⽐,其具有低错误率、⾼定位性等优点,因⽽被⼴泛应⽤。 算法实现步骤:
使⽤⼀阶导数算⼦(⼀般⽤ Sobel 模板算⼦)计算灰度图像中每个像素点在⽔平和垂直⽅向上的导数GX、GY,得出梯度向量(GX,GY),最后得到该像素点的梯度幅度G和相位⾓D 相位角后面用来根据梯度方向取临近点
将当前像素的梯度值与其在梯度⽅向上的邻域像素的梯度值做对⽐,如果当前像素的梯度值为最⼤值,则保留该点的梯度信息,否则将该点删除或将像素值置为0
设置(min, max)范围,抑高于max视为边缘,低于min过滤掉。居于中间,进入第4步过滤,取临近点对比
范围(5, 50)
完整代码见文末
#include"f_Sobel.h"
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<stdio.h>
#include"Commen.h"
int f_Sobel(unsigned char *srcData, int width, int height,int stride, int threshold)
{
int ret = 0;
unsigned char *dstData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
memset(dstData, 255, sizeof(unsigned char) * height * stride);
int x, y, i, k, pos;
int hValue, vValue, value;
unsigned char *pSrcL0;
unsigned char *pSrcL1;
unsigned char *pSrcL2;
unsigned char *pDstL;
unsigned char SqrtValue[65026];
pSrcL0 = srcData;
pSrcL1 = srcData + stride;
pSrcL2 = srcData + stride * 2;
pDstL = dstData + stride;
for (i = 0; i < 65026; i++)
{
SqrtValue[i] = (unsigned char)(sqrt((float)i) < threshold ? 0 : 255);
// ps中这一行改成
// SqrtValue[i] = (unsigned char)(255 - (int)sqrt((float)i));
// 如果颜色相近sqrt算出来接近0,255-0得255,接近白色
// sqrt算出来很大,255 - 255接近0, 显示成黑色,识别为边缘
// 猜测人眼对白底黑字比较敏感,所以用255 - ?取反计算显示
}
for (y = 1; y < height - 1; y++)
{
for (x = 1; x < width - 1; x++)
{
pos = x * 4;
hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
k = hValue * hValue + vValue * vValue;
k = MIN2(k, 65025);
pDstL[pos] = SqrtValue[k];
pos++;
hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
k = hValue * hValue + vValue * vValue;
k = MIN2(k, 65025);
pDstL[pos] = SqrtValue[k];
pos++;
hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
k = hValue * hValue + vValue * vValue;
k = MIN2(k, 65025);
pDstL[pos] = SqrtValue[k];
}
pSrcL0 += stride;
pSrcL1 += stride;
pSrcL2 += stride;
pDstL += stride;
}
memcpy(srcData, dstData, sizeof(unsigned char) * height * stride);
free(dstData);
return ret;
}
#include"f_Canny.h"
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<stdio.h>
#include"Commen.h"
//单通道灰度化
static int f_GrayOneChannel(unsigned char* srcData, unsigned char* grayData, int width, int height, int stride)
{
int ret = 0;
int i, j, gray, offset;
offset = stride - (width * 4);
unsigned char* pSrc = srcData;
unsigned char* pGray = grayData;
for(j = 0; j < height; j++)
{
for(i = 0; i < width; i++)
{
gray = (pSrc[2] + pSrc[1] + pSrc[0]) / 3;
*pGray = gray;
pSrc += 4;
pGray ++;
}
pSrc += offset;
}
return ret;
};
//梯度相位角获取
static void GetGradientDegree(unsigned char* srcBytes, int width, int height, float gradient[], unsigned char degree[], float* GradientMax)
{
float gx, gy;
int temp, pos;
float div;
float PI = 3.1415926f;
float t = 180.0f/PI;
for (int j = 1; j < height - 1; j++)
{
for (int i = 1; i < width - 1; i++)
{
pos = i + j * width;
gx = srcBytes[pos + 1 - width] + srcBytes[pos + 1] + srcBytes[pos + 1] + srcBytes[pos + 1 + width] - srcBytes[pos - 1 - width] - (srcBytes[pos - 1] + srcBytes[pos - 1]) - srcBytes[pos - 1 + width];
gy = srcBytes[pos - 1 - width] + srcBytes[pos - width] + srcBytes[pos - width] + srcBytes[pos + 1 - width] - srcBytes[pos - 1 + width] - (srcBytes[pos + width] + srcBytes[pos + width]) - srcBytes[pos + 1 + width];
// 求出每个点的梯度值幅度
gradient[pos] = (float)sqrt((float)(gx * gx + gy * gy));
if (*GradientMax < gradient[pos])
{
*GradientMax = gradient[pos];
}
if (gx == 0)
{
temp = (gy == 0) ? 0 : 90;
}
else
{
div = gy / gx;
if (div < 0)
{
temp = (int)(180 - atan(-div) * t);
}
else
{
temp = (int)(atan(div) * t);
}
if (temp < 22.5f)
{
temp = 0;
}
else if (temp < 67.5f)
{
temp = 45;
}
else if (temp < 112.5f)
{
temp = 90;
}
else if (temp < 157.5f)
{
temp = 135;
}
else
temp = 0;
}
degree[pos] = temp;
}
}
} ;
//非极大值抑制
static void NonMaxMini(unsigned char* srcBytes, int width, int height, float gradient[], float GradientMax, unsigned char degree[])
{
float leftPixel = 0, rightPixel = 0;
int pos;
for (int j = 1; j < height - 1; j++)
{
for (int i = 1; i < width - 1; i++)
{
pos = i + j * width;
switch (degree[pos])
{
case 0:
leftPixel = gradient[pos - 1];
rightPixel = gradient[pos + 1];
break;
case 45:
leftPixel = gradient[pos - 1 + width];
rightPixel = gradient[pos + 1 - width];
break;
case 90:
leftPixel = gradient[pos + width];
rightPixel = gradient[pos - width];
break;
case 135:
leftPixel = gradient[pos + 1 + width];
rightPixel = gradient[pos - 1 - width];
break;
default:
break;
}
if ((gradient[pos] < leftPixel) || (gradient[pos] < rightPixel))
{
srcBytes[pos] = 0;
}
else
{
srcBytes[pos] = (int)(255.0f * gradient[pos] / GradientMax);
}
}
}
};
//双阈值边缘判断
static void TwoThreshouldJudge(unsigned char* srcBytes, int width, int height, int highThreshold, int lowThreshould)
{
int pos = 0;
for (int j = 1; j < height - 1; j++)
{
for (int i = 1; i < width - 1; i++)
{
pos = i + j * width;
if (srcBytes[pos] > highThreshold)
{
srcBytes[pos] = 255;
}
else if (srcBytes[pos] < lowThreshould)
{
srcBytes[pos] = 0;
}
else
{
// 8领域都小于高阈值,视为噪点,剔除掉
if (srcBytes[pos - 1 - width] < highThreshold && srcBytes[pos - width] < highThreshold && srcBytes[pos + 1 - width] < highThreshold && srcBytes[pos - 1] < highThreshold
&& srcBytes[pos + 1] < highThreshold && srcBytes[pos - 1 + width] < highThreshold && srcBytes[pos + width] < highThreshold && srcBytes[pos + 1 + width] < highThreshold)
{
srcBytes[pos] = 0;
}
else
srcBytes[pos] = 255;
}
}
}
};
/************************************************************
*Function: Canny edge detection
*Description: Canny edge detection
*Params:
*srcData: image bgr data
*width :image width
*height :image height
*stride :image stride
*highThreshold:[0,255]
*lowThreshold: [0,255],default 0.4 * highThreshold
*Return :0-OK,or failed
************************************************************/
int f_CannyEdgedetection(unsigned char* srcData, int width ,int height, int stride, int highThreshold,int lowThreshold)
{
int ret = 0;
int i, j, offset, pos, temp, size;
unsigned char* pSrc = srcData;
size = width * height;
unsigned char* grayData = (unsigned char*)malloc(sizeof(unsigned char) * size);
memset(grayData, 0, sizeof(unsigned char) * size);
offset = stride - width * 4;
//gray
f_GrayOneChannel(srcData, grayData, width, height, stride);
//gauss fiter
for(j = 0; j < height; j++)
{
for(i = 0; i < width; i++)
{
pos = i + j * width;
if(i == 0 || j == 0 || i == width - 1 || j == height - 1)
{
grayData[pos] = 0;
}
else
{
temp = ((grayData[pos] << 2) + grayData[pos - width - 1] + grayData[pos + 1 - width] + grayData[pos - 1 + width] + grayData[pos + 1 + width] + grayData[pos - width] + grayData[pos - width] + grayData[pos - 1] + grayData[pos - 1] + grayData[pos + width] + grayData[pos + width] + grayData[pos + 1] + grayData[pos + 1]) >> 4;
grayData[pos] = temp;
}
}
}
//gradient
float* gradient = (float*)malloc(sizeof(float) * size);
memset(gradient, 0, sizeof(float) * size);
unsigned char* degree = (unsigned char*)malloc(sizeof(unsigned char) * size);
memset(degree, 0, sizeof(unsigned char) * size);
float GradientMax = 0;
GetGradientDegree(grayData, width, height, gradient, degree, &GradientMax);
//none max value
NonMaxMini(grayData, width, height, gradient,GradientMax,degree);
//two threshold judgement
TwoThreshouldJudge(grayData,width, height,highThreshold,lowThreshold);
//recovery
for(j = 0; j < height; j++)
{
for(i = 0; i < width; i++)
{
pSrc[0] = pSrc[1] = pSrc[2] = grayData[i + j * width];
pSrc += 4;
}
pSrc += offset;
}
free(grayData);
free(gradient);
free(degree);
return ret;
};