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

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

参数三:

)

参数七:

边界填充 作用
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 不关心感兴趣区域之外的部分

操作

代码中常用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
    }
}

效果

卷积运算

源码

https://github.com/onlyloveyd/LearningAndroidOpenCV

onlyloveyd CSDN认证博客专家 Android Kotlin OpenCV
个人公众号【Android or OpenCV】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注技术与研发。
©️2020 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值