边缘检测之模板算子
对应GPUImage中的GPUImageSobelEdgeDetectionFilter
和GPUImagePrewittEdgeDetectionFilter
两个类。
模板算子
1-1.png边缘是图像中像素灰度值
突变
的结果,也就是不连续的像素,对于这些像素突变的地方,它的微积分运算中,一阶导数表现为极值点,二阶导数表现为过零点。因此,我们可以用微分算子来计算图像的边缘像素点,而这些微分算子,通常可以通过小区域的模板卷积来近似计算,这种小区域模板就是边缘检测的模板算子,这种模板卷积计算边缘像素的方法就叫做模板算子法。
Prewitt算子
Prewitt算子又叫交叉微分算子,通过计算中心像素周围邻域的差分来定位边缘像素。它的水平和垂直方向模板如下:
对于图1-1中的像素点 ,Prewitt算子在水平和垂直方向上的表示为:
写成公式则为:
即为点的梯度,表示图像在点处的最大变化率,亦是该点的边缘像素值。
Sobel算子
Sobel算子常用的是3 * 3
大小的模板算子,它在prewitt算子的基础上,考虑了中心像素与周围像素距离的关系,周围邻域像素距离中心像素越近,影响越大,因此,权重越大。Sobel算子常用水平和垂直方向模板分别如下:
对于图1-1中的像素点 ,Sobel算子在水平和垂直方向上的表示为:
写成公式:
有了公式我们就可以使用代码来实现了。下面是C代码实现的Sobel算子算法:
// stride = 4 * width
for (int j = 0; j < height; j ++) {
for (int k = 0; k < width; k ++) {
if (j < 1 || k < 1 || k > width -2 || j > height -2) {
ptr[0] = ptr[1] = ptr[2];
}else{
//H -p1 - 2 * p2 - p3 + p7 + 2 * p8 + p9
//V -p7 - 2 * p4 - p1 + p9 + 2 * p6 + p3
int pos = k * 4 + j * stride;
// tempData 是图片数据
int dx = tempData[pos - 4 + stride] + 2 * tempData[pos + stride] + tempData[pos + 4 + stride] - tempData[pos - 4 - stride] - 2 * tempData[pos - stride] - tempData[pos + 4 - stride];
int dy = tempData[pos + 4 - stride] + 2 * tempData[pos + 4] + tempData[pos + 4 + stride] - tempData[pos - 4 + stride] - 2 * tempData[pos - 4] - tempData[pos - 4 - stride];
ptr[0] = _CLIP3(sqrt(dx * dx + dy * dy), 0, 255);
pos++;
dx = tempData[pos - 4 + stride] + 2 * tempData[pos + stride] + tempData[pos + 4 + stride] - tempData[pos - 4 - stride] - 2 * tempData[pos - stride] - tempData[pos + 4 - stride];
dy = tempData[pos + 4 - stride] + 2 * tempData[pos + 4] + tempData[pos + 4 + stride] - tempData[pos - 4 + stride] - 2 * tempData[pos - 4] - tempData[pos - 4 - stride];
ptr[1] = _CLIP3(sqrt(dx * dx + dy * dy), 0, 255);
pos++;
dx = tempData[pos - 4 + stride] + 2 * tempData[pos + stride] + tempData[pos + 4 + stride] - tempData[pos - 4 - stride] - 2 * tempData[pos - stride] - tempData[pos + 4 - stride];
dy = tempData[pos + 4 - stride] + 2 * tempData[pos + 4] + tempData[pos + 4 + stride] - tempData[pos - 4 + stride] - 2 * tempData[pos - 4] - tempData[pos - 4 - stride];
ptr[2] = _CLIP3(sqrt(dx * dx + dy * dy), 0, 255);
}
ptr += 4;
}
}
static inline unsigned char _CLIP3(unsigned char min,unsigned char max,unsigned char v){
char ret = v;
if (ret < min) { ret = min; }
if (ret > max) { ret = max; }
return ret;
}
Shader
下面来看看在GPUImage中是如何使用Shader来实现算子算法的。其实用Shader来实现性能更好一些,因为顶点着色器和片元着色器本质就是一个循环结构,不需要显式的循环遍历。下面是GPUImage中使用Shader实现的Prewitt算子:
// 为什么下面都在获取纹理转像素后的红色通道的值?因为Prewitt或Sobel算子都是作用于灰度图的,
// 这里不光可以使用红色通道,使用绿色通道或是蓝色通道都可以。只要是单一的通道即可。
// 左下点 相当于P7
float bottomLeftIntensity = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
// 右上点 相当于P3
float topRightIntensity = texture2D(inputImageTexture, topRightTextureCoordinate).r;
// 左上点 相当于P1
float topLeftIntensity = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
// 右下点 相当于P9
float bottomRightIntensity = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
// 左点 相当于P4
float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
// 右点 相当于P6
float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
// 下点 相当于P8
float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
// 上点 相当于P2
float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;
// 水平方向 h = p7 + p8 + p9 - p1 - p2 - p3
float h = -topLeftIntensity - topIntensity - topRightIntensity + bottomLeftIntensity + bottomIntensity + bottomRightIntensity;
// 垂直方向 v = p3 + p6 + p9 - p1 - p4 - p7
float v = -bottomLeftIntensity - leftIntensity - topLeftIntensity + bottomRightIntensity + rightIntensity + topRightIntensity;
// 梯度
float mag = length(vec2(h, v)) * edgeStrength;
gl_FragColor = vec4(vec3(mag), 1.0);
要注意的是,在Shader中因为纹理坐标的范围为0.0 ~ 1.0
,需要对纹理的宽度和高度进行归一化
操作。
glUniform1f(texelWidthUniform, 1.0/(float)width);
glUniform1f(texelHeightUniform, 1.0/(float)height);
Sobel模板算子的Shader实现也大同小异。
效果如下:
原图.jpg | prewitt模板.png | Sobel模板.png |
---|
如果想进一步了解Shader与图像的关系
,可以看下《Graphics Shaders: Theory and Practice》这本书(GPUImage里面的有些filter也是参考的此书)。此外这本书的中文版我也上传到了网盘,需要的可以下载:传送门,提取码:u8tl。