Android OpenCV(二十八):图像距离

1,359 阅读4分钟

像素距离

对于像素p(x , y)q(s , t)z(v , w),用D(p , q)来表示像素p , q间的距离,像素间距离的D(x , y)应满足的如下条件:

  • D(p , q) ≥ 0
  • D(p , q) = D(q , p)
  • D(p , q) + D(q , z) ≥ D(p , z)

像素距离的分类及计算方法

  • 欧氏距离(Euclidean Distance)

    两个像素点之间的直线距离。与直角坐标系中两点之间的直线距离求取方式相同,分别计算两个像素在X方向和Y方向上的距离,之后利用勾股定理得到两个像素之间的距离。

    De=(x1x2)2+(y1y2)2D_e = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2 }

    根据欧式距离的定义,图像中两个像素之间的距离可以含有小数部分。在一个5×5的矩阵内,所有像素距离矩阵中心的欧式距离如下所示:

    欧氏距离

  • 街区距离(City-Block Distance)

    街区距离(也叫D4距离)只能走横竖两个方向,不能走斜向。两个点的距离就是横向加竖向的距离之和。欧式距离表示的是从一个像素点到另一个像素点的最短距离,然而有时我们并不能以两个点之间连线的方向前进,例如在一个城市内两点之间的连线可能存在障碍物的阻碍,因此从一个点到另一个点需要沿着街道行走,因此这种距离的度量方式被称为街区距离。

    D4=x1x2+y1y2D_4=|x_1-x_2|+|y_1-y_2|

    根据街区距离的定义,图像中两个像素之间的距离一定是整数。在一个5×5的矩阵内,所有像素距离矩阵中心的街区距离如下所示:

    街区距离

  • 棋盘距离(Chess Board Distance)

    棋盘距离(也叫D8距离)就是围着P点的所有元素都是相邻的,也就是它下一步可以走到包围它的八个点的任何一个,也就是横竖和斜向。对于可以横竖和斜向走的最短距离,竟然就是横向或竖向的距离的最大值。与街区距离相似,棋盘距离也是假定两个像素点之间不能够沿着连线方向靠近,像素点只能沿着X方向和Y方向移动,但是棋盘距离并不是表示由一个像素点移动到另一个像素点之间的距离,而是表示两个像素点移动到同一行或者同一列时需要移动的最大距离。

    D8=max(x1x2y1y2)D_8=max(|x_1-x_2|,|y_1-y_2|)

    根据棋盘距离的定义,图像中两个像素之间的距离一定是整数。在一个5×5的矩阵内,所有像素距离矩阵中心的棋盘距离如下所示:

    棋盘距离

API

public static void distanceTransform(Mat src, Mat dst, int distanceType, int maskSize, int dstType) 

参数一:src,输入图像,数据类型为CV_8U的单通道图像

参数二:dst,输出图像,与输入图像具有相同的尺寸,数据类型为CV_8U或者CV_32F的单通道图像

参数三:distanceType,选择计算两个像素之间距离方法的标志,参数可取如下值:

// C++: enum DistanceTypes
public static final int
        ……
        DIST_L1 = 1, //街区距离
        DIST_L2 = 2, //欧式距离
        DIST_C = 3,  //棋盘距离
        ……

参数四:maskSize,距离变换掩码矩阵的大小。如果上一个参数为DIST_L1或者DIST_C,这个参数会被强制设置为3。因为3X3的模板的计算结果和5X5模板的相同。参数可取如下值:

// C++: enum DistanceTransformMasks
public static final int
        DIST_MASK_3 = 3,
        DIST_MASK_5 = 5,
        DIST_MASK_PRECISE = 0;

参数五:dstType,输出图像的数据类型,可以是CV_8U或者CV_32F。CV_8U只针对distanceType参数为DIST_L1的场景。

操作

/**
 * 图像距离变换
 * author: yidong
 * 2020/6/13
 */
class DistanceTransformActivity : AppCompatActivity() {
    private lateinit var mBinding: ActivityDistanceTransformBinding
    private lateinit var mBinary: Mat
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_distance_transform)

        val bgr = Utils.loadResource(this, R.drawable.number)
        val gray = Mat()
        Imgproc.cvtColor(bgr, gray, Imgproc.COLOR_BGR2GRAY)
        mBinary = Mat()
        //区域内像素用零表示
        Imgproc.threshold(gray, mBinary, 50.0, 255.0, Imgproc.THRESH_BINARY_INV)
        showMat(mBinding.ivLena, mBinary)
        bgr.release()
        gray.release()
    }

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

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        title = item.title
        when (item.itemId) {
            R.id.dt_l1 -> {
                doDistanceTransform(Imgproc.DIST_L1)
            }
            R.id.dt_l2 -> {
                doDistanceTransform(Imgproc.DIST_L2)
            }
            R.id.dt_c -> {
                doDistanceTransform(Imgproc.DIST_C)
            }
        }
        return true
    }

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

    private fun doDistanceTransform(flag: Int) {
        val dst = Mat()
        // 计算每个二值图像像素到最近的零像素的近似或精确距离。对于零图像像素,距离显然是零。
        Imgproc.distanceTransform(mBinary, dst, flag, 5, CvType.CV_32S)
        dst.convertTo(dst, CvType.CV_8U)
        showMat(mBinding.ivResult, dst)
        dst.release()
    }
}

效果

街区距离

欧式距离 棋盘距离

源码

github.com/onlyloveyd/…

另外,大家也可以搜一搜我的公众号 【OpenCV or Android】,关注我的系列文章与我交流。