Android OpenCV(四十九):图像积分图

1,810 阅读3分钟

这是我参与更文挑战的第 20 天,活动详情查看: 更文挑战

图像积分图

积分图像是Crow在1984年首次提出,是为了在多尺度透视投影中提高渲染速度,是一种快速计算图像区域和与平方和的算法。其核心思想是对每个图像建立自己的积分图查找表,在图像积分处理计算阶段根据预先建立的积分图查找表,直接查找从而实现对均值卷积线性时间计算,做到了卷积执行的时间与半径窗口大小的无关联。图像积分图在图像特征提取HAAR/SURF、二值图像分析、图像相似相关性NCC计算、图像卷积快速计算等方面均有应用,是图像处理中的经典算法之一。

原理

积分图像采用不同的计算规则获取不同的积分图像:

sum(X,Y)=x<X,y<Yimage(x,y){sum} (X,Y) = \sum _{x<X,y<Y} \texttt{image} (x,y)
sqsum(X,Y)=x<X,y<Yimage(x,y)2{sqsum} (X,Y) = \sum _{x<X,y<Y} \texttt{image} (x,y)^2
tilted(X,Y)=y<Y,abs(xX+1)Yy1image(x,y){tilted} (X,Y) = \sum _{y<Y,abs(x-X+1) \leq Y-y-1} {image} (x,y)
简要说明

使用这些积分图像,我们可以计算出图像中水平矩形或旋转矩形区域上的总和,均值和标准差。

x1x<x2,y1y<y2image(x,y)=sum(x2,y2)sum(x1,y2)sum(x2,y1)+sum(x1,y1)\sum _{x_1 \leq x < x_2, \, y_1 \leq y < y_2} \texttt{image} (x,y) = \texttt{sum} (x_2,y_2)- \texttt{sum} (x_1,y_2)- \texttt{sum} (x_2,y_1)+ \texttt{sum} (x_1,y_1)
利用和表计算区域和

通过积分图像,我们可以快速模糊图像。在多通道图像的情况下,每个通道的总和是独立累加的。

如下图,显示一个垂直矩形Rect(3,3,3,2)和倾斜矩形Rect(5,1,2,3)的积分计算过程。显示原始图像中的选定像素,以及相关像素在积分图像中的和。

integral

API

public static void integral3(Mat src, Mat sum, Mat sqsum, Mat tilted, int sdepth, int sqdepth)
  • 参数一:src,输入图像为( W * H ),8位或浮点数(32f或64f)。

  • 参数二:sum,积分图像(和表),必须为( W + 1 ) * ( H + 1),类型为32位或64位浮点数。

  • 参数三:sqsum,像素值平方的积分图像(平方和表),它是 ( W + 1 ) * ( H + 1 )双精度64位浮点数数组;。

  • 参数四:tilted,旋转45度的积分图像,它是 ( W + 1 ) * ( H + 1 )数组,类型与sum相同。

  • 参数五:sdepth,期望的积分图像和旋转45度的积分图像的深度,CV_32S,CV_32F或CV_64F。

  • 参数六:sqdepth,期望的像素值平方的积分图像的深度,CV_32F或CV_64F。

简化方法

public static void integral2(Mat src, Mat sum, Mat sqsum, int sdepth, int sqdepth)
public static void integral(Mat src, Mat sum, int sdepth)

操作

/**
 * 图像积分图
 * author: yidong
 * 2020/12/16
 */
class IntegralActivity : AppCompatActivity() {
    private val mBinding: ActivityIntegralBinding by lazy {
        ActivityIntegralBinding.inflate(layoutInflater)
    }

    private lateinit var rgb: Mat
    private lateinit var sum: Mat
    private lateinit var sqrSum: Mat

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        val bgr = getBgrFromResId(R.drawable.lena)
        rgb = Mat()
        rgb = bgr.toRgb()
        mBinding.ivLena.showMat(rgb)

        sum = Mat()
        sqrSum = Mat()
        Imgproc.integral2(rgb, sum, sqrSum, CvType.CV_32S, CvType.CV_32F);
    }

    override fun onDestroy() {
        rgb.release()
        sum.release()
        sqrSum.release()
        super.onDestroy()
    }

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

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.menu_integral_sum -> {
                doSumNormalize()
            }
            R.id.menu_integral_blur -> {
                doBlur()
            }
        }
        return true
    }

    private fun doSumNormalize() {
        val result = Mat()
        GlobalScope.launch(Dispatchers.Main) {
            mBinding.isLoading = true
            withContext(Dispatchers.IO) {
                Core.normalize(sum, result, 0.0, 255.0, NORM_MINMAX, CV_8UC1)
            }
            mBinding.isLoading = false
            mBinding.ivResult.showMat(result)
        }
    }

    private fun doBlur() {
        GlobalScope.launch(Dispatchers.Main) {
            mBinding.isLoading = true
            val result = Mat.zeros(rgb.size(), rgb.type())
            withContext(Dispatchers.IO) {
                val w = rgb.cols()
                val h = rgb.rows()
                var x2 = 0
                var y2 = 0
                var x1 = 0
                var y1 = 0
                val ksize = 5
                val radius = ksize / 2
                val ch = rgb.channels()
                var cx = 0
                var cy = 0

                for (row in 0 until h + radius) {
                    y2 = if ((row + 1) > h) {
                        h
                    } else {
                        row + 1
                    }
                    y1 = if ((row - ksize) < 0) {
                        0
                    } else {
                        row - ksize
                    }
                    for (col in 0 until w + radius) {
                        x2 = if ((col + 1) > w) w else (col + 1);
                        x1 = if ((col - ksize) < 0) 0 else (col - ksize);
                        cx = if ((col - radius) < 0) 0 else col - radius;
                        cy = if ((row - radius) < 0) 0 else row - radius;
                        val num = (x2 - x1) * (y2 - y1)
                        val values = ByteArray(ch) { 0 }
                        for (i in 0 until ch) {
                            // 积分图查找和表,计算卷积
                            val s = getBlockSum(sum, x1, y1, x2, y2, i)
                            values[i] = (s / num).toByte()
                        }
                        result.put(cy, cx, values);
                    }
                }
            }
            mBinding.isLoading = false
            mBinding.ivResult.showMat(result)
        }
    }

    private fun getBlockSum(sum: Mat, x1: Int, y1: Int, x2: Int, y2: Int, i: Int): Int {
        // top left
        val tl = sum.get(y1, x1)[i].toInt()
        // top right
        val tr = sum.get(y2, x1)[i].toInt()
        // bottom left
        val bl = sum.get(y1, x2)[i].toInt()
        // bottom right
        val br = sum.get(y2, x2)[i].toInt()

        return (br - bl - tr + tl);
    }
}

效果

和表 图像模糊

源码

github.com/onlyloveyd/…