阅读 422

Android OpenCV(十三):图像金字塔

图像金字塔

图像金字塔是图像中多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。

图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。

金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。

我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。

高斯金字塔

高斯金字塔的底层为原始图像,每向上一层则是通过高斯滤波和1/2采样得到(去掉偶数行和列)。通过下采样不断的将图像的尺寸缩小,进而在金字塔中包含多个尺度的图像,一般情况下,高斯金字塔的最底层为图像的原图,每上一层就会通过下采样缩小一次图像的尺寸,通常情况尺寸会缩小为原来的一半,但是如果有特殊需求,缩小的尺寸也可以根据实际情况进行调整。由于每次图像的尺寸都缩小为原来的一半,图像尺缩小的速度非常快,因此常见高斯金字塔的层数为3到6层。

高斯滤波器可以看做一个低通滤波器,那么每经过一次的高斯滤波,图像中仅能够保留某个频率值以下的频率部分,所以高斯金字塔也可以看做一个低通金字塔(每一级只保留某个频率以下的成分)。

高斯金字塔
高斯金字塔

拉普拉斯金字塔

拉普拉斯金字塔与高斯金字塔正好相反,高斯金字塔通过底层图像构建上层图像,而拉普拉斯是通过上层小尺寸的图像构建下层大尺寸的图像。拉普拉斯金字塔具有预测残差的作用,需要与高斯金字塔联合一起使用,假设我们已经有一个高斯图像金字塔,对于其中的第i层图像(高斯金字塔最下面为第0层),首先通过下采样得到一尺寸缩小一半的图像,即高斯金字塔中的第i+1层或者不在高斯金字塔中,之后对这张图像再进行上采样,将图像尺寸恢复到第i层图像的大小,最后求取高斯金字塔第i层图像与经过上采样后得到的图像的差值图像,这个差值图像就是拉普拉斯金字塔的第i层图像。

拉普拉斯金字塔生成流程
拉普拉斯金字塔生成流程

API

下采样

public static void pyrDown(Mat src, Mat dst, Size dstsize, int borderType) 
复制代码
  • 参数一:src,输入待下采样的图像。

  • 参数二:dst,输出下采样后的图像,图像尺寸可以指定,但是数据类型和通道数与src相同,

  • 参数三:dstsize,输出图像尺寸,可以缺省。

  • 参数四:borderType,像素边界外推方法的标志

    // C++: enum BorderTypes
    public static final int
    BORDER_CONSTANT = 0,
    BORDER_REPLICATE = 1,
    BORDER_REFLECT = 2,
    BORDER_WRAP = 3,
    BORDER_REFLECT_101 = 4,
    BORDER_TRANSPARENT = 5,
    BORDER_REFLECT101 = BORDER_REFLECT_101,
    BORDER_DEFAULT = BORDER_REFLECT_101,
    BORDER_ISOLATED = 16;
    复制代码

默认状态下函数输出的图像的尺寸为输入图像尺寸的一半,但是也可以通过dstsize参数来设置输出图像的大小,需要注意的是无论输出尺寸为多少都应满足下面式中条件。该函数首先将原图像与内核矩阵进行卷积,内核矩阵如下所示,之后通过不使用偶数行和列的方式对图像进行下采样,最终实现尺寸缩小的下采样图像。

上采样

public static void pyrUp(Mat src, Mat dst, Size dstsize, int borderType) 
复制代码
  • 参数一:src,输入待上采样的图像。
  • 参数二:dst,输出上采样后的图像,图像尺寸可以指定,但是数据类型和通道数与src相同,
  • 参数三:dstsize,输出图像尺寸,可以缺省。
  • 参数四:borderType,像素边界外推方法的标志

操作

class GLPyramidActivity : CardGalleryActivity() {

override fun buildCards() {
val bgr = Utils.loadResource(this, R.drawable.lena)
val rgb = Mat()
Imgproc.cvtColor(bgr, rgb, Imgproc.COLOR_BGR2RGB)
bgr.release()
buildGauss(rgb)
rgb.release()
}

private fun buildGauss(source: Mat) {
val gaussList = arrayListOf<Mat>()
gaussList.add(source)
for (i in 0..2) {
val gauss = Mat()
Imgproc.pyrDown(gaussList[i], gauss)
gaussList.add(gauss)
}

for (i in gaussList.indices) {
val bitmap = Bitmap.createBitmap(
gaussList[i].width(),
gaussList[i].height(),
Bitmap.Config.ARGB_8888
)
Utils.matToBitmap(gaussList[i], bitmap)
cards.add(Card("Gauss${i}", bitmap))
}
buildLaplace(gaussList)
}

private fun buildLaplace(gaussList: List<Mat>) {
val laplaceList = arrayListOf<Mat>()
for (i in gaussList.size - 1 downTo 1) {
val lap = Mat()
val upGauss = Mat()
if (i == gaussList.size - 1) {
val down = Mat()
Imgproc.pyrDown(gaussList[i], down)
Imgproc.pyrUp(down, upGauss)
Core.subtract(gaussList[i], upGauss, lap)
laplaceList.add(lap.clone())
}
Imgproc.pyrUp(gaussList[i], upGauss)
Core.subtract(gaussList[i - 1], upGauss, lap)
laplaceList.add(lap.clone())
}

for (i in laplaceList.indices) {
val bitmap = Bitmap.createBitmap(
laplaceList[i].width(),
laplaceList[i].height(),
Bitmap.Config.ARGB_8888
)
Utils.matToBitmap(laplaceList[i], bitmap)
cards.add(Card("Laplace${i}", bitmap))
}

for (gauss in gaussList) {
gauss.release()
}
for (lap in laplaceList) {
lap.release()
}
}
}
复制代码

效果

高斯金字塔和拉普拉斯金字塔
高斯金字塔和拉普拉斯金字塔

上面这个拉普拉斯可能看不太清,但是细看是有图像的。让图片宽度充满看看。

为了看清宽度充满
为了看清宽度充满

为了看清拉普拉斯金字塔的图像,图片大小本身并非如此

源码

github.com/onlyloveyd/…

本文使用 mdnice 排版