Android OpenCV(十八):均值滤波

574 阅读3分钟

均值滤波

均值滤波也称为线性滤波,其采用的主要方法为邻域平均法。线性滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点(x,y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度g(x,y),即g(x,y)=∑f(x,y)/m m为该模板中包含当前像素在内的像素总个数。从频率域观点来看均值滤波是一种低通滤波器,高频信号将会去掉,因此可以帮助消除图像尖锐噪声,实现图像平滑,模糊等功能。

从均值滤波的定义上看,滤波操作也是进行图像卷积运算。均值滤波使用到的卷积核如下

K=1ksize.width*ksize.height[111111111111111]\texttt{K} = \frac{1}{\texttt{ksize.width*ksize.height}} \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \cdots \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \end{bmatrix}

API

public static void blur(Mat src, Mat dst, Size ksize, Point anchor, int borderType)
  • 参数一:src,待均值滤波的图像,图像的数据类型必须是CV_8U、CV_16U、CV_16S、CV_32F和CV_64F这五种数据类型之一。

  • 参数二:dst,均值滤波后的图像,与输入图像具有相同的尺寸和数据类型。

  • 参数三:ksize,卷积核尺寸。

  • 参数四:anchor,内核的基准点(锚点),其默认值为(-1,-1)代表内核基准点位于kernel的中心位置。基准点即卷积核中与进行处理的像素点重合的点,其位置必须在卷积核的内部。

  • 参数五:borderType,像素外推法选择标志。默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。

  • 边界填充作用
    BORDER_CONSTANT0用特定值填充,如iiiiii|abcdefgh|iiiiiii
    BORDER_REPLICATE1两端复制填充,如aaaaaa|abcdefgh|hhhhhhh
    BORDER_REFLECT2倒叙填充,如fedcba|abcdefgh|hgfedcb
    BORDER_WRAP3正序填充,如cdefgh|abcdefgh|abcdefg
    BORDER_REFLECT_1014不包含边界值倒叙填充,gfedcb|abcdefgh|gfedcba
    BORDER_TRANSPARENT5随机填充,uvwxyz|abcdefgh|ijklmno
    BORDER_REFLECT1014与BORDER_REFLECT_101相同
    BORDER_DEFAULT4与BORDER_REFLECT_101相同
    BORDER_ISOLATED16不关心感兴趣区域之外的部分

操作

/**
 * 均值滤波
 * author: yidong
 * 2020/4/11
 */
class MeanFilterActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityMeanFilterBinding
    private lateinit var mRgb: Mat

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_mean_filter)
        val bgr = Utils.loadResource(this, R.drawable.lena)
        mRgb = Mat()
        Imgproc.cvtColor(bgr, mRgb, Imgproc.COLOR_BGR2RGB)
        bgr.release()
        showMat(mBinding.ivLena, mRgb)
    }

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

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.menu_3_3 -> {
                showMat(mBinding.ivLena, mRgb)

                val result = Mat()
                Imgproc.blur(mRgb, result, Size(3.0, 3.0))
                showMat(mBinding.ivResult, result)
                result.release()
            }
            R.id.menu_9_9 -> {
                showMat(mBinding.ivLena, mRgb)

                val result = Mat()
                Imgproc.blur(mRgb, result, Size(9.0, 9.0))
                showMat(mBinding.ivResult, result)
                result.release()
            }
            R.id.menu_12_12 -> {
                showMat(mBinding.ivLena, mRgb)

                val result = Mat()
                Imgproc.blur(mRgb, result, Size(12.0, 12.0))
                showMat(mBinding.ivResult, result)
                result.release()
            }
            R.id.menu_salt_pepper_noise -> {
                saltPepperNoiseAndMeanFilter()
            }
            R.id.menu_gaussian_noise -> {
                gaussianNoiseAndMeanFilter()
            }
        }
        return true
    }

    private fun saltPepperNoiseAndMeanFilter() {
        val source = mRgb.clone()
        val number = 10000
        for (k in 0..number) {
            val i = (0..1000).random() % source.cols()
            val j = (0..1000).random() % source.rows()
            when ((0..100).random() % 2) {
                0 -> {
                    when (source.channels()) {
                        1 -> {
                            source.put(j, i, 255.0)
                        }
                        2 -> {
                            source.put(j, i, 255.0, 255.0)
                        }
                        3 -> {
                            source.put(j, i, 255.0, 255.0, 255.0)
                        }
                        else -> {
                            source.put(j, i, 255.0, 255.0, 255.0, 255.0)
                        }
                    }
                }
                1 -> {
                    when (source.channels()) {
                        1 -> {
                            source.put(j, i, 0.0)
                        }
                        2 -> {
                            source.put(j, i, 0.0, 0.0)
                        }
                        3 -> {
                            source.put(j, i, 0.0, 0.0, 0.0)
                        }
                        else -> {
                            source.put(j, i, 0.0, 0.0, 0.0, 0.0)
                        }
                    }
                }
            }
        }
        showMat(mBinding.ivLena, source)

        val result = Mat()
        Imgproc.blur(source, result, Size(9.0, 9.0))
        showMat(mBinding.ivResult, result)

        result.release()
        source.release()
    }

    private fun gaussianNoiseAndMeanFilter() {
        val source = mRgb.clone()
        val noise = Mat(source.size(), source.type())
        val gaussian = Mat()
        Core.randn(noise, 20.0, 50.0)
        Core.add(source, noise, gaussian)
        showMat(mBinding.ivLena, gaussian)

        val result = Mat()
        Imgproc.blur(gaussian, result, Size(9.0, 9.0))
        showMat(mBinding.ivResult, result)

        source.release()
        noise.release()
        gaussian.release()
        result.release()
    }

    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)
    }

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

效果

图片效果

均值滤波3X3均值滤波9X9均值滤波12X12椒盐噪声和均值滤波9X9高斯噪声和均值滤波9X9

GIF

均值滤波

源码

github.com/onlyloveyd/…