前言
平时在使用『Photoshop』做图时,我们常会对图片做去色处理,以实现各种丰富的细节。灰度处理还有如下作用:
- 降低数据维度:彩色图像通常包含了大量的冗余信息,而灰度图像只保留了图像的亮度信息。通过灰度处理,可以将图像的数据维度从三维(RGB 通道)降低为二维(灰度值)。
- 简化图像处理:在许多图像处理任务中,只需要关注图像的亮度信息而不需要颜色信息。通过将图像转换为灰度图像,可以简化后续的图像处理操作,减少计算量和复杂度。
- 提高图像分析效果:在某些情况下,灰度图像可以更好地突出图像中的细节和特征。例如,在边缘检测、形状识别、人脸识别等任务中,使用灰度图像可以更容易地提取和分析图像的特征。
那么在 Android 开发中我们能否对图片做类似的灰度处理呢?
原理
色饱和度(Saturation)是指是播放的光的彩色深浅度或鲜艳度。色饱和度表示播放的光的彩色鲜艳度,取决于彩色中的灰度,灰度越高,色彩饱和度即越低,反之亦然。
图像的灰度处理中有很多种方法,其中比较有名的算法是通过对彩色图像的 RGB 通道进行加权平均来计算灰度值。通常使用以下公式计算每个像素的灰度值, 其中,R、G、B 分别表示红色、绿色和蓝色通道的像素值。
由于人眼对绿色的敏感度最高,对蓝色的敏感度最低,该方法基于人眼对不同颜色通道的敏感度不同,对 RGB 三分量进行加权平均能相对合理地将彩色信息转换为灰度信息。
浮点运算效率不高,因此该公式也有很多变种,本文暂不讨论。
实现
我们可以通过遍历图片中的每个像素,使用上述公式计算出其灰度值,再生成图像:
fun Bitmap.toGray(): Bitmap {
val ret = Bitmap.createBitmap(width, height, config)
for (i in 0 until width) {
for (j in 0 until height) {
val pixel = getPixel(i, j)
val gray = Color.red(pixel) * 0.299 + Color.green(pixel) * 0.587 + Color.blue(pixel) * 0.114
val color = Color.rgb(gray.toInt(), gray.toInt(), gray.toInt())
ret.setPixel(i, j, color)
}
}
return ret
}
setPixel()
内部是调用 Native 方法实现的,在主线程中循环调用很容易造成 ANR,因此应当放在子线程中处理。
当然,我们可以先把总的灰度值算出来,再一次性对 Bitmap
设置:
fun Bitmap.toGray(): Bitmap {
val ret = Bitmap.createBitmap(width, height, config)
val pixels = IntArray(width * height)
this.getPixels(pixels, 0, width, 0, 0, width, height)
val alpha = 0xFF shl 24
for (i in 0 until height) {
for (j in 0 until width) {
val pixel = pixels[width * i + j]
val gray = (Color.red(pixel) * 0.299 + Color.green(pixel) * 0.587 + Color.blue(pixel) * 0.114).toInt()
val color = alpha or (gray shl 16) or (gray shl 8) or gray
pixels[width * i + j] = color
}
}
ret.setPixels(pixels, 0, width, 0, 0, width, height)
return ret
}
这里使用一个数组来存储每个像素的计算结果,最后再通过 setPixels()
方法一次性设置到 Bitmap
中。
其实 Android 本身就提供了对饱和度的处理方法,我们可以直接调用实现,无需手动计算:
fun Bitmap.toGray(): Bitmap {
val ret = Bitmap.createBitmap(this.width, this.height, this.config)
val canvas = Canvas(ret)
val cm = ColorMatrix().apply { setSaturation(0f) }
val paint = Paint().apply { colorFilter = ColorMatrixColorFilter(cm) }
canvas.drawBitmap(this, 0f, 0f, paint)
return ret
}
如果项目有使用 OpenCV,那么也可以:
fun Bitmap.toGray(): Bitmap {
val src = Mat()
val dst = Mat()
Utils.bitmapToMat(this, src)
Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY)
Utils.matToBitmap(dst, this)
src.release()
dst.release()
return this
}