Android OpenCV(二十六):Canny算法边缘检测

Canny算法边缘检测

Canny边缘检测算法是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。截止2014年8月, Canny发表的该篇论文,已被引用19000余次。Canny 创立了边缘检测计算理论(Computational theory of edge detection)解释这项技术如何工作。
通常情况下边缘检测的目的是在保留原有图像属性的情况下,显著减少图像的数据规模。有多种算法可以进行边缘检测,虽然Canny算法年代久远,但可以说它是边缘检测的一种标准算法,而且仍在研究中广泛使用。

Canny 的目标是找到一个最优的边缘检测算法最优边缘检测的含义是:

  • 最优检测:算法能够尽可能多地标识出图像中的实际边缘,漏检真实边缘的概率和误检非边缘的概率都尽可能小;
  • 最优定位准则:检测到的边缘点的位置距离实际边缘点的位置最近,或者是由于噪声影响引起检测出的边缘偏离物体的真实边缘的程度最小;
  • 检测点与边缘点一一对应:算子检测的边缘点与实际边缘点应该是一一对应。

为了满足这些要求 Canny 使用了变分法(calculus of variations),这是一种寻找优化特定功能的函数的方法。最优检测使用四个指数函数项表示,但是它非常近似于高斯函数的一阶导数。

【引用自百度百科】

算法步骤

基于Canny算法的边缘检测主要有5个步骤,依次是高斯滤波像素梯度计算非极大值像素梯度抑制滞后阈值处理孤立弱边缘抑制

1. 高斯滤波

使用高斯滤波平滑图像,减少图像中噪声。一般情况下使用5×5的高斯滤波器。
1159[245424912945121512549129424542] \frac{1}{159}\left[ \begin{array}{ccc} 2 & 4 & 5 & 4 & 2\\\\ 4 & 9 & 12 & 9 & 4\\\\ 5 & 12 & 15 & 12 & 5\\\\ 4 & 9 & 12 & 9 & 4\\\\ 2 & 4 & 5 & 4 & 2\\\\ \end{array} \right]

2. 像素梯度计算

使用Sobel算子在水平和垂直方向上对平滑的图像检测边缘,以在水平方向(Gx)和垂直方向(Gy)上获得一阶导数。 从这两个图像中,我们可以找到每个像素的梯度幅度和方向,如下所示:
Edge_Gradient  (G)=Gx2+Gy2 Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2}

Angle  (θ)=tan1(GyGx) Angle \; (\theta) = \tan^{-1} \bigg(\frac{G_y}{G_x}\bigg)

梯度方向始终垂直于边缘。 为了简便,将其舍入为代表垂直,水平和两个对角线方向的四个角度之一。

3. 非极大值像素梯度抑制

应用非极大值抑制算法消除边缘检测带来的杂散响应。首先将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较, 如果当前像素的梯度强度与另外两个像素梯度强度相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。

非极大值像素梯度抑制

如上图点A在边缘(垂直方向)上。 梯度方向垂直于边缘。 点B和C在梯度方向上。 因此,将A点与B点和C点进行检查,看是否形成局部最大值。 如果是这样,则考虑将其用于下一阶段,否则将其抑制(置为零)。

4. 滞后阈值处理

应用双阈值法划分强边缘和弱边缘。将边缘处的梯度值与两个阈值进行比较,如果某像素的梯度幅值小于较小的阈值,则会被去除掉;如果某像素的梯度幅值大于较小阈值但小于较大阈值,则将该像素标记为弱边缘;如果某像素的梯度幅值大于较大阈值,则将该像素标记为强边缘。

5. 孤立弱边缘抑制

通常而言,由真实边缘引起的弱边缘像素点将连接到强边缘像素点,而噪声响应则未连接。通过查看弱边缘像素及其8个邻域像素,可根据其与强边缘的连接情况来进行判断。一般,可定义只要其中邻域像素其中一个为强边缘像素点,则该弱边缘就可以保留为强边缘,即真实边缘点。

磁滞阈值

如上图,边缘A在maxVal之上,因此被视为“强边缘”。 尽管边C低于maxVal,但它连接到边A,因此也被视为有效边,我们得到了完整的曲线。 但是边缘B尽管在minVal之上并且与边缘C处于同一区域,但是它没有连接到任何“强边缘”而被舍弃。 因此,非常重要的一点是我们必须使用合适的minVal和maxVal以获得正确的结果。

API

public static void Canny(Mat image, Mat edges, double threshold1, double threshold2, int apertureSize, boolean L2gradient)
  • c参数一:image,输入图像,必须是CV_8U的单通道或者三通道图像。

  • 参数二:edges,输出图像,与输入图像具有相同尺寸的单通道图像,且数据类型为CV_8U。

  • 参数三:threshold1,第一个滞后阈值。

  • 参数四:threshold2,第二个滞后阈值。

  • 参数五:apertureSize,Sobel算子的直径。

  • 参数六:L2gradient,计算图像梯度幅值方法的标志。默认为false,使用下方第一种计算方式
    Edge_Gradient  (G)=Gx+Gy Edge\_Gradient \; (G) = |G_x| + |G_y|

    Edge_Gradient  (G)=Gx2+Gy2 Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2}

第一个参数是需要提取边缘的输入图像,目前只支持数据类型为CV_8U的图像,输入图像可以是灰度图像或者彩色图像。第二个参数是边缘检测结果的输出图像,图像是数据类型为为CV_8U的单通道灰度图像。函数第三个和第四个参数是Canny算法中用于区分强边缘和弱边缘的两个阈值,两个参数不区分较大阈值和较小阈值,函数会自动比较区分两个阈值的大小,不过一般情况下,较大阈值与较小阈值的比值在2:1到3:1之间。函数最后一个参数是计算梯度幅值方法的选择标志,无特殊需求的情况下,使用默认值即可。

操作

/**
 * Canny边缘检测
 *
 * @author yidong
 * @date 2020-05-18
 */
class CannyEdgeDetectionActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityEdgeDetectionBinding
    private lateinit var mRgb: Mat

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_edge_detection)
        val bgr = Utils.loadResource(this, R.drawable.lena)
        mRgb = Mat()
        Imgproc.cvtColor(bgr, mRgb, Imgproc.COLOR_BGR2RGB)
        showMat(mBinding.ivLena, mRgb)
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_canny, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.low_canny_edge_detection -> {
                lowCannyEdgeDetection()
            }
            R.id.high_canny_edge_detection -> {
                highCannyEdgeDetection()
            }
            R.id.blur_canny_edge_detection -> {
                edgeDetectionAfterBlur()
            }
        }
        return true
    }

    private fun showMat(view: ImageView, source: Mat) {
        val bitmap = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888)
        bitmap.density = 360
        Utils.matToBitmap(source, bitmap)
        view.setImageBitmap(bitmap)
    }

    private fun lowCannyEdgeDetection() {
        title = "低阈值Canny边缘检测"
        val result = Mat()
        Imgproc.Canny(mRgb, result, 20.0, 40.0, 3)
        showMat(mBinding.ivResult, result)
    }


    private fun highCannyEdgeDetection() {
        title = "高阈值Canny边缘检测"
        val result = Mat()
        Imgproc.Canny(mRgb, result, 100.0, 200.0, 3)
        showMat(mBinding.ivResult, result)
    }

    private fun edgeDetectionAfterBlur() {
        title = "滤波后Canny边缘检测"
        val resultG = Mat()
        val result = Mat()
        Imgproc.GaussianBlur(mRgb, resultG, Size(3.0, 3.0), 5.0)
        Imgproc.Canny(resultG, result, 100.0, 200.0, 3)
        showMat(mBinding.ivResult, result)
    }
}

结果

低阈值
高阈值
高斯滤波后高阈值

较高的阈值会降低噪声信息对图像提取边缘结果的影响,但是同时也会减少结果中的边缘信息。所以,合适的阈值很关键。

源码

https://github.com/onlyloveyd/LearningAndroidOpenCV

扫码关注,持续更新

回复【计算机视觉】获取计算机视觉相关必备学习资料
回复【Android】获取Android,Kotlin必备学习资料

onlyloveyd CSDN认证博客专家 Android Kotlin OpenCV
个人公众号【Android or OpenCV】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注技术与研发。
©️2020 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值