上篇我们成功搭建了OpenCV的Android开发环境,并简单介绍了一下OpenCV的相关应用:
接下来我们继续探索OpenCV在Android上对应用。
在进行图像处理的一些高级操作时候,我们需要对图片进行预处理,以过滤一些不必要对信息,缩短计算时间,接下来我们就来介绍一些基本的图片处理操作
开始之前,我们先来了解一些OpenCV中是如何保存操作图片对象对。对于Android系统API相信大家都知道系统有提供Bitmap,Drawable来临时保存图片对象供我们使用操作,相对都,OpenCV也有提供相关对工具类:Mat
Mat对象保存来图片对行数(高度)、列数(宽度)、通道(颜色通道)、图片数据等相关信息,并封装来一些图片等操作方法(后续会有用到再做介绍)。
在进行许多复杂的图像处理之前,我们都需要将图像转换成灰度单通道。
在没有使用OpenCV之前,我们在Android上操作Bitmap是如何得到一张RGBA图片的灰度图像的:
Android转化为bitmap类得到灰度图像,并对其数据做如下操作:
A通道保持不变,然后逐像素计算:X = 0.3×R+0.59×G+0.11×B,并使这个像素的值新R,G,B值为X,即:
new_R = X, new_G = X, new_B = X
例如:原来一个像素是4个byte,分别为ARGB,现在这个像素应该为AXXX
没错,我们通过重新计算每个像素点的颜色值,然后重新绘图得到,得到的图片的通道仍然与原图保持一致,归根揭底还是一个RGBA的图片,只是颜色值为gray而已。
OpenCV给我们提供来更为彻底的处理函数:
Imgproc.cvtColor(Mat src, Mat dst, ind code)
Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2GRAY)
得到的是一个单通道的GRAY图像,效果如下:
线性滤波最常见对一种用途是降噪,噪声是图像中亮度或色彩信息对随机变化,我们用模糊操作来减少图像中对噪声。
高斯模糊是比较常用的模糊算法,在许多图片美化工具中都用经常用到。具体的高斯模糊函数以及原理我们这里不做介绍(高数实在是全部换回去了,无力研究),感兴趣的可以自己去找资料研究一下。
OpenCV为我们提供了内置函数用来在应用中执行高斯模糊:GaussianBlur
Imgproc.GaussianBlur(
Mat src, // 要处理的图像
Mat dst, // 输出图像
Size kSize, // 高斯内核大小
double sigmax, // 高斯函数在x方向上的标准偏差
double sigmay // 高斯函数在y方向上的标准偏差
)
例如我们对图片进行一次高斯模糊处理:
// 高斯模糊
Imgproc.GaussianBlur(src, src, Size(3.0, 3.0), 0.0,0.0)
效果图:
噪声在图片是是一种比较常见对现象,尤其是椒盐噪声,该噪声是疏密分布与图片中对黑色白色像素点。我们可以利用中值滤波去除这一类噪声。
OpenCV给我们提供了medianBlur内置函数来进行中值滤波
medianBlur(Mat src, Mat dst, int ksize)
例:
// 中值滤波
Imgproc.medianBlur(src, src, 3)
效果图:
均值模糊是最简单一中模糊处理方式
在OpenCV中,我们使用内置函数:blur来进行处理
blur(Mat src, Mat dst, Size ksize)
例:
// 均值模糊
Imgproc.blur(src, src, Size(8.0, 8.0))
效果图:
锐化可以看作是一种线性滤波操作,具体原理性质的概念不做赘述。通常我们在处理沙滩、毛发之类的图片时会经常用到锐化的操作,可以给人一中更有质感的感觉。
话不多说,OpenCV 中的内置函数:filter2D 可以帮助我们实现这一效果
filter2D(Mat src, Mat dst, int ddepth, Mat kernel)
例:
//锐化处理,做卷积
val kernel = Mat(3, 3, CvType.CV_16SC1)
kernel.put(
0, 0,
0.0, -1.0, 0.0,
-1.0, 5.0, -1.0,
0.0, -1.0, 0.0
)
Imgproc.filter2D(src, src, src.depth(), kernel)
效果图:
阈值化是一种将我们想要在图像中分析的区域分割出来的方法,基本原理是把每个像素值跟我们预设的值进行比较,再根据比较结果进行像素调整。
在OpenCV中,提供来5中阈值化操作:
通过内置函数:threshold 来处理
threshold(Mat src, Mat dst, double thresh, double maxval, int type)
当然,图像受关照条件等的影响,我们只定义一个全局的阈值比不是好的选择,为了克服这个限制,我们要试图根据邻像素为任意像素计算阈值:自适应阈值
OpenCV提供给我们自适应阈值的操作:adaptiveThreshold
adaptiveThreshold(
Mat src,
Mat dst,
double maxValue,
int adaptiveMethod,
int thresholdType,
int blockSize,
double C
)
例:
// 自适应阈值
Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2GRAY)
Imgproc.adaptiveThreshold(
src,
src,
255.0,
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
Imgproc.THRESH_BINARY,
3,
0.0
)
Imgproc.cvtColor(src, src, Imgproc.COLOR_GRAY2RGBA)
效果:
形态学运算是一类根据图像特征和结构元素进行图像处理的操作,大多针对二值或灰度图像。
膨胀是一种将图像中亮区域扩张的方法,相反的,腐蚀是一种将图像中暗区域扩张的方法。
我们利用OpenCV 通过的 erode方法进行腐蚀处理,用dilate进行膨胀处理
dilate(Mat src, Mat dst, Mat kernel)
erode(Mat src, Mat dst, Mat kernel)
例:
// 膨胀
val dilateKernel = Imgproc.getStructuringElement(
Imgproc.MORPH_RECT,
Size(3.0, 3.0)
)
Imgproc.dilate(src, src, dilateKernel)
// 腐蚀
val erodeKernel = Imgproc.getStructuringElement(
Imgproc.MORPH_ELLIPSE,
Size(5.0, 5.0)
)
Imgproc.erode(src, src, erodeKernel)
效果图(膨胀左,腐蚀右):
直方图均衡化是图像处理领域中利用图像直方图对对比度进行调整的方法。通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度。
我利用:equalizeHist函数来处理(注:该函数只能处理单通道图像)
equalizeHist(Mat src, Mat dst)
如我们将一个图片均衡处理:
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY)
Imgproc.equalizeHist(src, src)
效果图:
那么进行直方图均衡处理得到一张彩色但图片呢?
val mats = ArrayList<Mat>()
// 通道分解
Core.split(src, mats)
mats.forEach {
// 直方图均衡每个通道
Imgproc.equalizeHist(it, it)
}
// 通道合并
Core.merge(mats, src)
效果图:
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import com.hankang.imagepick.PictureSelector
import com.hankang.opencv.R
import org.opencv.android.Utils
import org.opencv.core.Core
import org.opencv.core.CvType
import org.opencv.core.Mat
import org.opencv.core.Size
import org.opencv.imgproc.Imgproc
import kotlin.collections.ArrayList
class ProcessActivity : AppCompatActivity() {
private val PICTURE_REQUEST_CODE = 0x01
lateinit var imageSource: ImageView
lateinit var imageResult: ImageView
lateinit var imageBitmap: Bitmap
private val BLUR = 0x01
private val GUSSIAN_BlUR = 0x02
private val MEDIAN_BlUR = 0x03
private val SHARPEN = 0x04
private val DILATE = 0x05
private val ERODE = 0x06
private val CVT = 0x07
private val GRAY = 0x08
private val GRAYJH = 0x09
private val COLORJH = 0x10
init {
System.loadLibrary("opencv_java3")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_process)
imageSource = findViewById(R.id.image_1)
imageResult = findViewById(R.id.image_2)
findViewById<Button>(R.id.main_button).setOnClickListener {
PictureSelector
.create(this, PICTURE_REQUEST_CODE)
.selectPicture(true, 200, 200, 1, 1)
}
findViewById<Button>(R.id.main_button1).setOnClickListener {
processImage(BLUR)
}
findViewById<Button>(R.id.main_button2).setOnClickListener {
processImage(GUSSIAN_BlUR)
}
findViewById<Button>(R.id.main_button3).setOnClickListener {
processImage(MEDIAN_BlUR)
}
findViewById<Button>(R.id.main_button4).setOnClickListener {
processImage(SHARPEN)
}
findViewById<Button>(R.id.main_button5).setOnClickListener {
processImage(DILATE)
}
findViewById<Button>(R.id.main_button6).setOnClickListener {
processImage(ERODE)
}
findViewById<Button>(R.id.main_button7).setOnClickListener {
processImage(CVT)
}
findViewById<Button>(R.id.main_button8).setOnClickListener {
processImage(GRAY)
}
findViewById<Button>(R.id.main_button9).setOnClickListener {
processImage(GRAYJH)
}
findViewById<Button>(R.id.main_button10).setOnClickListener {
processImage(COLORJH)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != Activity.RESULT_OK) {
return
}
when (requestCode) {
PICTURE_REQUEST_CODE -> {
data?.apply {
val path = getStringExtra(PictureSelector.PICTURE_PATH)
imageBitmap = BitmapFactory.decodeFile(path)
imageSource.setImageBitmap(imageBitmap)
}
}
}
}
private fun processImage(model: Int) {
val src = Mat(imageBitmap.height, imageBitmap.width, CvType.CV_8UC4)
Utils.bitmapToMat(imageBitmap, src)
when (model) {
BLUR -> {
// 均值模糊
Imgproc.blur(src, src, Size(8.0, 8.0))
}
GUSSIAN_BlUR -> {
// 高斯模糊
Imgproc.GaussianBlur(src, src, Size(11.0, 11.0), 0.0,0.0)
}
MEDIAN_BlUR -> {
// 中值滤波
Imgproc.medianBlur(src, src, 9)
}
SHARPEN -> {
//做卷积
val kernel = Mat(3, 3, CvType.CV_16SC1)
kernel.put(
0, 0,
0.0, -1.0, 0.0,
-1.0, 5.0, -1.0,
0.0, -1.0, 0.0
)
Imgproc.filter2D(src, src, src.depth(), kernel)
}
DILATE -> {
// 膨胀
val dilateKernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(3.0, 3.0))
Imgproc.dilate(src, src, dilateKernel)
}
ERODE -> {
// 腐蚀
val erodeKernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, Size(5.0, 5.0))
Imgproc.erode(src, src, erodeKernel)
}
CVT -> {
// 自适应阈值
Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2GRAY)
Imgproc.adaptiveThreshold(
src,
src,
255.0,
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
Imgproc.THRESH_BINARY,
3,
0.0
)
Imgproc.cvtColor(src, src, Imgproc.COLOR_GRAY2RGBA)
}
GRAY -> {
Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2GRAY)
}
GRAYJH -> {
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY)
Imgproc.equalizeHist(src, src)
}
COLORJH -> {
val mats = ArrayList<Mat>()
// 通道分解
Core.split(src, mats)
mats.forEach {
// 直方图均衡每个通道
Imgproc.equalizeHist(it, it)
}
// 通道合并
Core.merge(mats, src)
}
}
val processBitmap = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888)
Utils.matToBitmap(src, processBitmap)
imageResult.setImageBitmap(processBitmap)
}
}