阅读 203

Android OpenCV(十五):图像卷积

图像卷积

在信号处理中卷积操作需要给出一个卷积函数与信号进行计算,图像的卷积形式与其相同,需要给出一个卷积模板与原图像进行卷积计算。整个过程可以看成是一个卷积模板在另外一个大的图像上移动,对每个卷积模板覆盖的区域进行点乘,得到的值作为中心像素点的输出值。

卷积首先需要将卷积模板旋转180°,之后从图像的左上角开始移动旋转后的卷积模板,从左到右,从上到下依次进行卷积计算,最终得到卷积后的图像。卷积模板又被称为卷积核或者内核,是一个固定大小的二维矩阵,矩阵中存放着预先设定的数值。

卷积计算

单个点
单个点
过程动画
过程动画

上图均来自于:https://mlnotebook.github.io/post/CNN1/

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,表示不包含边界值倒序填充。

边界填充 作用
BORDER_CONSTANT 0 用特定值填充,如iiiiii|abcdefgh|iiiiiii
BORDER_REPLICATE 1 两端复制填充,如aaaaaa|abcdefgh|hhhhhhh
BORDER_REFLECT 2 倒叙填充,如fedcba|abcdefgh|hgfedcb
BORDER_WRAP 3 正序填充,如cdefgh|abcdefgh|abcdefg
BORDER_REFLECT_101 4 不包含边界值倒叙填充,gfedcb|abcdefgh|gfedcba
BORDER_TRANSPARENT 5 随机填充,uvwxyz|abcdefgh|ijklmno
BORDER_REFLECT101 4 与BORDER_REFLECT_101相同
BORDER_DEFAULT 4 与BORDER_REFLECT_101相同
BORDER_ISOLATED 16 不关心感兴趣区域之外的部分

该函数不会将卷积模板进行旋转,如果卷积模板不对称,需要首先将卷积模板旋转180°后再输入给函数的第四个参数。

操作

代码中常用Image kernel数据来源于:https://setosa.io/ev/image-kernels/。该网站也支持在线查看卷积运算后的图片效果。

/**
* 图像卷积
* author: yidong
* 2020/3/28
*/

class Filter2DActivity : AppCompatActivity() {
private lateinit var mGray: Mat
private lateinit var mBinding: ActivityFilter2dBinding
private lateinit var mAdapter: FilterAdapter
private var values = ArrayList<Float>()

companion object {
val FILTER_DEFAULT = arrayOf(-1F, -1F, -1F, -1F, 8F, -1F, -1F, 1F, -1F)
val FILTER_BLUR =
arrayOf(0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F)
val FILTER_EMBOSS = arrayOf(-2F, -1F, 0F, -1F, 1F, 1F, 0F, 1F, 2F)
val FILTER_IDENTITY = arrayOf(0F, 0F, 0F, 0F, 1F, 0F, 0F, 0F, 0F)
val FILTER_OUTLINE = arrayOf(-1F, -1F, -1F, -1F, 8F, -1F, -1F, -1F, -1F)
val FILTER_SHARPEN =
arrayOf(0F, -1F, 0F, -1F, 5F, -1F, 0F, -1F, 0F)
val FILTER_LEFT_SOBEL =
arrayOf(1F, 0F, -1F, 2F, 0F, -2F, 1F, 0F, -1F)
val FILTER_RIGHT_SOBEL =
arrayOf(-1F, 0F, 1F, -2F, 0F, 2F, -1F, 0F, 1F)
val FILTER_TOP_SOBEL =
arrayOf(1F, 2F, 1F, 0F, 0F, 0F, -1F, -2F, -1F)
val FILTER_BOTTOM_SOBEL =
arrayOf(-1F, -2F, -1F, 0F, 0F, 0F, 1F, 2F, 1F)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_filter2d)
mBinding.presenter = this
values.clear()
values.addAll(FILTER_DEFAULT)
mAdapter = FilterAdapter(this, values)
mBinding.kernel.adapter = mAdapter

val bgr = Utils.loadResource(this, R.drawable.lena)
mGray = Mat()
Imgproc.cvtColor(bgr, mGray, Imgproc.COLOR_BGR2GRAY)
showMat(mBinding.ivLena, mGray)
}

override fun onDestroy() {
mGray.release()
super.onDestroy()
}

fun doFilter() {
hideKeyboard()
val kernelArray = FloatArray(9) {
values[it]
}
val kernel = Mat(3, 3, CvType.CV_32FC1)
kernel.put(0, 0, kernelArray)

val result = Mat()
Imgproc.filter2D(
mGray,
result,
-1,
kernel,
Point(-1.0, -1.0),
2.0,
Core.BORDER_CONSTANT
)
showMat(mBinding.ivResult, result)
}

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 hideKeyboard() {
val inputMethodManager =
this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(
mBinding.ivLena.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}

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

override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.filter_blur -> {
mAdapter.setData(FILTER_BLUR)
doFilter()
}
R.id.filter_emboss -> {
mAdapter.setData(FILTER_EMBOSS)
doFilter()
}
R.id.filter_identity -> {
mAdapter.setData(FILTER_IDENTITY)
doFilter()
}
R.id.filter_sharpen -> {
mAdapter.setData(FILTER_SHARPEN)
doFilter()
}
R.id.filter_outline -> {
mAdapter.setData(FILTER_OUTLINE)
doFilter()
}
R.id.filter_left_sobel -> {
mAdapter.setData(FILTER_LEFT_SOBEL)
doFilter()
}
R.id.filter_right_sobel -> {
mAdapter.setData(FILTER_RIGHT_SOBEL)
doFilter()
}

R.id.filter_top_sobel -> {
mAdapter.setData(FILTER_TOP_SOBEL)
doFilter()
}
R.id.filter_bottom_sobel -> {
mAdapter.setData(FILTER_BOTTOM_SOBEL)
doFilter()
}

}
return true
}
}
复制代码

效果

卷积运算
卷积运算

源码