完整代码位置:AndroidShaderDemo
下面两篇文章对Canny边界探测算法描述的很好,可以查看了解下原理。这里重点讲的是在Android里通过Shader实现。
Canny边界探测算法
边缘检测之Canny
android-gpuimage里没有实现Canny边缘检测,在网上查了很多,大都是OpenCV,C++等版本实现,没有看到在Android里通过Shader实现,这里研究了下ios版的gpuimage,写了个Android版的,为了清楚表示各个步骤,代码没有封装,最后效果和OpenCV的比有一定差距,有哪位提出改进更好。
主要实现在CannyRender.java
Canny边缘检测主要有以下几个步骤:
1、使用高斯滤波器,以平滑图像,滤除噪声,生成灰度图。
2、使用Sobel滤波器,计算每个像素点的梯度强度和方向。
3、用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
4、应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
5、通过抑制孤立的弱边缘最终完成边缘检测。
在CannyRender的onDrawFrame方法里,分别用gaussianProgram,sobelProgram,nonmaximumSuppressionProgram,weakPixelInclusionProgram这4个Program完成以上工作。因为需要4次渲染,前三次分别渲染到3个帧缓存,最后一次渲染到屏幕。onDrawFrame方法如下:
@Override
public void onDrawFrame(GL10 gl) {
glClear(GL_COLOR_BUFFER_BIT);
//render to framebuffer0,gaussian and gray
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
GLES20.glClearColor(0, 0, 0, 0);
gaussianProgram.useProgram();
gaussianProgram.setUniforms(textureIDs[0], 1.0f / width, 1.0f / height);
vertexArray.setVertexAttribPointer(
0,
gaussianProgram.getPositionAttributeLocation(),
POSITION_COMPONENT_COUNT,
STRIDE);
vertexArray.setVertexAttribPointer(
POSITION_COMPONENT_COUNT,
gaussianProgram.getTextureCoordinatesAttributeLocation(),
TEXTURE_COORDINATES_COMPONENT_COUNT,
STRIDE);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
//render to framebuffer1, sobel
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[1]);
GLES20.glClearColor(0, 0, 0, 0);
sobelProgram.useProgram();
sobelProgram.setUniforms(mFrameBufferTextures[0], 1.0f / width, 1.0f / height);
vertexArray.setVertexAttribPointer(
0,
sobelProgram.getPositionAttributeLocation(),
POSITION_COMPONENT_COUNT,
STRIDE);
vertexArray.setVertexAttribPointer(
POSITION_COMPONENT_COUNT,
sobelProgram.getTextureCoordinatesAttributeLocation(),
TEXTURE_COORDINATES_COMPONENT_COUNT,
STRIDE);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
//render to framebuffer2, NonmaximumSuppression
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[2]);
GLES20.glClearColor(0, 0, 0, 0);
nonmaximumSuppressionProgram.useProgram();
nonmaximumSuppressionProgram.setUniforms(mFrameBufferTextures[1], 1.0f / width, 1.0f / height);
vertexArray.setVertexAttribPointer(
0,
nonmaximumSuppressionProgram.getPositionAttributeLocation(),
POSITION_COMPONENT_COUNT,
STRIDE);
vertexArray.setVertexAttribPointer(
POSITION_COMPONENT_COUNT,
nonmaximumSuppressionProgram.getTextureCoordinatesAttributeLocation(),
TEXTURE_COORDINATES_COMPONENT_COUNT,
STRIDE);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
//render to screen,weakPixelInclusion
weakPixelInclusionProgram.useProgram();
weakPixelInclusionProgram.setUniforms(mFrameBufferTextures[2], 1.0f / width, 1.0f / height);
flipVertexArray.setVertexAttribPointer(
0,
weakPixelInclusionProgram.getPositionAttributeLocation(),
POSITION_COMPONENT_COUNT,
STRIDE);
flipVertexArray.setVertexAttribPointer(
POSITION_COMPONENT_COUNT,
weakPixelInclusionProgram.getTextureCoordinatesAttributeLocation(),
TEXTURE_COORDINATES_COMPONENT_COUNT,
STRIDE);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
最终渲染结果如下,图片压缩后效果不好: