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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章