这是我参与更文挑战的第 20 天,活动详情查看: 更文挑战
图像积分图
积分图像是Crow在1984年首次提出,是为了在多尺度透视投影中提高渲染速度,是一种快速计算图像区域和与平方和的算法。其核心思想是对每个图像建立自己的积分图查找表,在图像积分处理计算阶段根据预先建立的积分图查找表,直接查找从而实现对均值卷积线性时间计算,做到了卷积执行的时间与半径窗口大小的无关联。图像积分图在图像特征提取HAAR/SURF、二值图像分析、图像相似相关性NCC计算、图像卷积快速计算等方面均有应用,是图像处理中的经典算法之一。
原理
积分图像采用不同的计算规则获取不同的积分图像:
使用这些积分图像,我们可以计算出图像中水平矩形或旋转矩形区域上的总和,均值和标准差。
通过积分图像,我们可以快速模糊图像。在多通道图像的情况下,每个通道的总和是独立累加的。
如下图,显示一个垂直矩形Rect(3,3,3,2)和倾斜矩形Rect(5,1,2,3)的积分计算过程。显示原始图像中的选定像素,以及相关像素在积分图像中的和。
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);
}
}