Android OpenCV(二十二):边缘检测

边缘检测

什么是图像的边缘?

图像的边缘是图像最基本的特征之一。所谓边缘(或边沿)是指周围像素灰度有跳跃性变化或“屋顶”变化的那些像素的集合。边缘是图像局部强度变化最明显的地方,它主要存在于目标与目标、目标与背景、区域与区域之间,因此它是图像分割依赖的重要特征。从本质上说,图像边缘是图像局部特性不连续性(灰度突变、颜色突变、纹理结构突变等)的反应,它标志着一个区域的终结和另一个区域的开始。

检测出的边缘并不等同于实际目标的真实边缘。由于图像数据是二维的,而实际物体是三维的,从三维到二维的投影必然会造成信息的丢失,再加上成像过程中的光照不均和噪声等因素的影响,使得有边缘的地方不一定能被检测出来,而检测出的边缘也不一定代表实际边缘。

图像的边缘有方向和幅度两个属性,沿边缘方向像素变化平缓,垂直于边缘方向像素变化剧烈。边缘上的这种变化可以用微分算子检测出来,通常用一阶或两阶导数来检测边缘。一阶导数认为最大值对应边缘位置,而二阶导数则以过零点对应边缘位置。

检测原理

由于图像是离散的信号,我们可以用临近的两个像素差值来表示像素灰度值函数的导数,如下:

一阶求导

这种X轴方向求导方式对应于滤波器为[1, -1],对于Y轴方向则是[1, -1]的转置矩阵。

但是这种求导方式的计算结果最接近于两个像素中间位置的梯度,而两个像素之间是不存在像素的,因此,我们应该选取像素点的前后像素的差值来进行计算,如下:

优化后的计算方式此时,对应的X轴方向的滤波器为[0.5, 0, -0.5],对应的Y轴方向的滤波器则是其转置矩阵。根据这种方式,我们也可以得到计算45度方向的梯度对应的滤波器。
0.500000000.5 \begin{matrix} 0.5 & 0 & 0 \\ 0 & 0 & 0 \\ 0 & 0 & -0.5 \end{matrix}

000.50000.500 \begin{matrix} 0 & 0 & 0.5 \\ 0 & 0 & 0 \\ -0.5 & 0 & 0 \end{matrix}

API

图像卷积

public static void filter2D(Mat src, Mat dst, int ddepth, Mat kernel, Point anchor, double delta, int borderType) 
  • 参数一:src,输入图像。
  • 参数二:dst,输出图像,与输入图像具有相同的尺寸和通道数。
  • 参数三:ddepth,输出图像的数据类型(深度),根据输入图像的数据类型不同拥有不同的取值范围。当赋值为-1时,输出图像的数据类型自动选择。
  • 参数四:kernel,卷积核,CV_32FC1类型的矩阵。
  • 参数五:anchor,内核的基准点(锚点),默认值(-1,-1)代表内核基准点位于kernel的中心位置。基准点即卷积核中与进行处理的像素点重合的点,其位置必须在卷积核的内部。
  • 参数六:delta,偏值,在计算结果中加上偏值。
  • 参数七:borderType,像素外推法选择标志。默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

绝对值

图像的边缘有可能是由高像素值变为低像素值,也有可能是由低像素值变成高像素值,通过卷积计算得到的正数值表示需要像素值突然由低变高,得到的负数值表示像素值由高到低,这两种都是图像的边缘,因此为了在图像中同时表示出这两种边缘信息,需要将计算的结果求取绝对值。

public static void convertScaleAbs(Mat src, Mat dst, double alpha, double beta) 
  • 参数一:src,输入图像。
  • 参数二:dst,计算绝对值后输入矩阵。
  • 参数三:缩放因子,默认参数为只求取绝对值不进行缩放。
  • 参数四:在原始数据上添加的偏值,默认参数表示不增加偏值。

该函数可以求取矩阵中所有数据的绝对值。函数前两个参数分别为输入、输出矩阵,两个参数可以是相同的变量。函数第三个和第四个参数为对绝对值的缩放和原始数据上的偏移。函数的计算原理如式:
dst(I)=saturate_cast<uchar>(src(I)alpha+beta) \texttt{dst} (I)= \texttt{saturate\_cast<uchar>} (| \texttt{src} (I)* \texttt{alpha} + \texttt{beta} |)

操作

/**
 * 边缘检测
 * author: yidong
 * 2020/5/2
 */
class EdgeDetectionActivity : 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)
    }

    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)
    }

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

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.edge_detection_x -> {
                edgeDetectionX()
            }
            R.id.edge_detection_y -> {
                edgeDetectionY()
            }
            R.id.edge_detection_x_y -> {
                edgeDetectionXAndY()
            }
            R.id.edge_detection_xy -> {
                edgeDetectionXY()
            }
            R.id.edge_detection_yx -> {
                edgeDetectionYX()
            }
        }
        return true
    }

    private fun edgeDetectionX() {
        title = "X轴方向边缘检测"
        // X轴方向边缘检测
        val kernelX = Mat(1, 3, CvType.CV_16S)
        val arrayX = shortArrayOf(-1, 0, 1)
        kernelX.put(0, 0, arrayX)
        val resultKernelX = Mat()
        Imgproc.filter2D(mRgb, resultKernelX, CvType.CV_16S, kernelX)
        Core.convertScaleAbs(resultKernelX, resultKernelX)
        showMat(mBinding.ivResult, resultKernelX)
        kernelX.release()
        resultKernelX.release()
    }

    private fun edgeDetectionY() {
        title = "Y轴方向边缘检测"
        // Y轴方向边缘检测
        val kernelY = Mat(3, 1, CvType.CV_16S)
        val arrayY = shortArrayOf(-1, 0, 1)
        kernelY.put(0, 0, arrayY)
        val resultKernelY = Mat()
        Imgproc.filter2D(mRgb, resultKernelY, CvType.CV_16S, kernelY)
        Core.convertScaleAbs(resultKernelY, resultKernelY)
        showMat(mBinding.ivResult, resultKernelY)
        kernelY.release()
        resultKernelY.release()
    }

    private fun edgeDetectionXAndY() {
        title = "X和Y轴方向边缘检测"
        // X轴方向边缘检测
        val kernelX = Mat(1, 3, CvType.CV_16S)
        val arrayX = shortArrayOf(-1, 0, 1)
        kernelX.put(0, 0, arrayX)
        val resultKernelX = Mat()
        Imgproc.filter2D(mRgb, resultKernelX, CvType.CV_16S, kernelX)
        Core.convertScaleAbs(resultKernelX, resultKernelX)
        // Y轴方向边缘检测
        val kernelY = Mat(3, 1, CvType.CV_16S)
        val arrayY = shortArrayOf(-1, 0, 1)
        kernelY.put(0, 0, arrayY)
        val resultKernelY = Mat()
        Imgproc.filter2D(mRgb, resultKernelY, CvType.CV_16S, kernelY)
        Core.convertScaleAbs(resultKernelY, resultKernelY)

        // X,Y轴方向合并
        val resultXY = Mat()
        Core.add(resultKernelX, resultKernelY, resultXY)
        showMat(mBinding.ivResult, resultXY)

        kernelX.release()
        resultKernelX.release()
        kernelY.release()
        resultKernelY.release()
        resultXY.release()
    }

    private fun edgeDetectionXY() {
        title = "由左上到右下方向边缘检测"
        //由左上到右下方向边缘检测
        val kernelXY = Mat(2, 2, CvType.CV_16S)
        val arrayXY = shortArrayOf(1, 0, 0, -1)
        kernelXY.put(0, 0, arrayXY)
        val resultKernelXY = Mat()
        Imgproc.filter2D(mRgb, resultKernelXY, CvType.CV_16S, kernelXY)
        Core.convertScaleAbs(resultKernelXY, resultKernelXY)
        showMat(mBinding.ivResult, resultKernelXY)
        kernelXY.release()
        resultKernelXY.release()
    }

    private fun edgeDetectionYX() {
        title = "由右上到左下方向边缘检测"
        //由右上到左下方向边缘检测
        val kernelYX = Mat(2, 2, CvType.CV_16S)
        val arrayYX = shortArrayOf(0, -1, 1, 0)
        kernelYX.put(0, 0, arrayYX)
        val resultKernelYX = Mat()
        Imgproc.filter2D(mRgb, resultKernelYX, CvType.CV_16S, kernelYX)
        Core.convertScaleAbs(resultKernelYX, resultKernelYX)
        showMat(mBinding.ivResult, resultKernelYX)
        kernelYX.release()
        resultKernelYX.release()
    }
}

效果

X轴方向边缘检测
Y轴方向边缘检测
X轴Y轴方向边缘检测
从左上到右下边缘检测
从右上往左下边缘检测

源码

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币套餐、付费专栏及课程。

余额充值