圖像卷積
在信號處理中卷積操作需要給出一個卷積函數與信號進行計算,圖像的卷積形式與其相同,需要給出一個卷積模板與原圖像進行卷積計算。整個過程可以看成是一個卷積模板在另外一個大的圖像上移動,對每個卷積模板覆蓋的區域進行點乘,得到的值作爲中心像素點的輸出值。
卷積首先需要將卷積模板旋轉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
}
}