Android 相机 X |颜色检测
Android CameraX | Color detection
我正在 Android 上使用新的 CameraX。
我做了一个基本应用程序(类似于 "Get Started"),其中有一个相机预览和一个光度分析器。每一秒我都会在 TextView 中显示我的亮度。
现在,按照 CameraX 指南,我想进行颜色检测。每隔一秒左右,我就想从屏幕中央的像素中获取颜色。
事实是我不知道如何按照与亮度分析仪相同的结构进行颜色检测。
亮度分析仪 Class :
class LuminosityAnalyzer : ImageAnalysis.Analyzer {
private var lastTimeStamp = 0L
private val TAG = this.javaClass.simpleName
var luma = BehaviorSubject.create<Double>()
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimeStamp = System.currentTimeMillis()
val intervalInSeconds = TimeUnit.SECONDS.toMillis(1)
val deltaTime = currentTimeStamp - lastTimeStamp
if(deltaTime >= intervalInSeconds) {
val buffer = image.planes[0].buffer
val data = buffer.toByteArray()
val pixels = data.map { it.toInt() and 0xFF }
luma.onNext(pixels.average())
lastTimeStamp = currentTimeStamp
Log.d(TAG, "Average luminosity: ${luma.value}")
}
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
}
主要 Activity :
/* display the luminosity */
private fun createLuminosityAnalyzer(): ImageAnalysis{
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setLensFacing(lensFacing)
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
val analyzer = ImageAnalysis(analyzerConfig).apply {
val luminosityAnalyzer = LuminosityAnalyzer()
luminosityAnalyzer.luma
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// success
luminosity.text = it.toString()
},{
// error
Log.d(TAG, "Can not get luminosity :(")
})
setAnalyzer(executor, luminosityAnalyzer)
}
return analyzer
}
我怎样才能做一些与色彩分析仪相当的事情?
所以我自己想办法
颜色分析仪 Class :
class ColorAnalyzer : ImageAnalysis.Analyzer {
private var lastTimeStamp = 0L
private val TAG = this.javaClass.simpleName
var hexColor = BehaviorSubject.create<Any>()
/* every 100ms, analyze the image we receive from camera */
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimeStamp = System.currentTimeMillis()
val intervalInMilliSeconds = TimeUnit.MILLISECONDS.toMillis(100)
val deltaTime = currentTimeStamp - lastTimeStamp
if(deltaTime >= intervalInMilliSeconds) {
val imageBitmap = image.image?.toBitmap()
val pixel = imageBitmap!!.getPixel((imageBitmap.width/2), (imageBitmap.height/2))
val red = Color.red(pixel)
val blue = Color.blue(pixel)
val green = Color.green(pixel)
hexColor.onNext(String.format("#%02x%02x%02x", red, green, blue))
Log.d(TAG, "Color: ${hexColor.value}")
lastTimeStamp = currentTimeStamp
}
}
// convert the image into a bitmap
private fun Image.toBitmap(): Bitmap {
val yBuffer = planes[0].buffer // Y
val uBuffer = planes[1].buffer // U
val vBuffer = planes[2].buffer // V
val ySize = yBuffer.remaining()
val uSize = uBuffer.remaining()
val vSize = vBuffer.remaining()
val nv21 = ByteArray(ySize + uSize + vSize)
yBuffer.get(nv21, 0, ySize)
vBuffer.get(nv21, ySize, vSize)
uBuffer.get(nv21, ySize + vSize, uSize)
val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
val out = ByteArrayOutputStream()
yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
val imageBytes = out.toByteArray()
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
}
主要 Activity :
/* Get the color from Color Analyzer Class */
private fun createColorAnalyzer(): ImageAnalysis{
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setLensFacing(lensFacing)
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
val analyzer = ImageAnalysis(analyzerConfig).apply {
val colorAnalyzer = ColorAnalyzer()
colorAnalyzer.hexColor
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// success
colorName.text = it.toString() //hexa code in the textView
colorName.setBackgroundColor(Color.parseColor(it.toString())) //background color of the textView
(sight.drawable as GradientDrawable).setStroke(10, Color.parseColor(it.toString())) //border color of the sight in the middle of the screen
},{
// error
Log.d(TAG, "Can not get color :(")
})
setAnalyzer(executor, colorAnalyzer)
}
return analyzer
}
希望它对某人有用;)
编辑:
如果您阅读@Minhaz 的回答,通过 image -> bitmap -> getPixel() 获取颜色的效率不是很高。最有效的是做image -> RGB。
下面是使用 Kotlin 的 Minhaz 答案。
颜色分析仪 Class :
class ColorAnalyzer : ImageAnalysis.Analyzer {
private var lastAnalyzedTimestamp = 0L
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
private fun getRGBfromYUV(image: ImageProxy): Triple<Double, Double, Double> {
val planes = image.planes
val height = image.height
val width = image.width
// Y
val yArr = planes[0].buffer
val yArrByteArray = yArr.toByteArray()
val yPixelStride = planes[0].pixelStride
val yRowStride = planes[0].rowStride
// U
val uArr = planes[1].buffer
val uArrByteArray =uArr.toByteArray()
val uPixelStride = planes[1].pixelStride
val uRowStride = planes[1].rowStride
// V
val vArr = planes[2].buffer
val vArrByteArray = vArr.toByteArray()
val vPixelStride = planes[2].pixelStride
val vRowStride = planes[2].rowStride
val y = yArrByteArray[(height * yRowStride + width * yPixelStride) / 2].toInt() and 255
val u = (uArrByteArray[(height * uRowStride + width * uPixelStride) / 4].toInt() and 255) - 128
val v = (vArrByteArray[(height * vRowStride + width * vPixelStride) / 4].toInt() and 255) - 128
val r = y + (1.370705 * v)
val g = y - (0.698001 * v) - (0.337633 * u)
val b = y + (1.732446 * u)
return Triple(r,g,b)
}
// analyze the color
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimestamp = System.currentTimeMillis()
if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.MILLISECONDS.toMillis(100)) {
val colors = getRGBfromYUV(image)
var hexColor = String.format("#%02x%02x%02x", colors.first.toInt(), colors.second.toInt(), colors.third.toInt())
Log.d("test", "hexColor: $hexColor")
lastAnalyzedTimestamp = currentTimestamp
}
}
}
如评论中所述,如果您的目标是仅获取中心像素颜色,则将整个 YUV 图像转换为 Bitmap 然后分析中心值的逻辑可能非常低效。您可以通过瞄准正确的像素直接查看 YUV 图像中的颜色。在 YUV 图像中,您有三个平面,一个用于 Y(每像素 1 字节)和 U 和 V 平面(每像素 .5 字节,交错)。无论旋转如何,忽略此刻作为中心像素的旋转应该是相同的(丢弃高度或宽度奇值的可能性)。获取中心像素 rgb 值的有效逻辑如下所示:
planes = imageProxy.getPlanes()
val height = imageProxy.getHeight()
val width = imageProxy.getWidth()
// You may have to find the logic to get array from ByteBuffer
// Y
val yArr = planes[0].buffer.array()
val yPixelStride = planes[0].getPixelStride()
val yRowStride = planes[0].getRowStride()
// U
val uArr = planes[1].buffer.array()
val uPixelStride = planes[1].getPixelStride()
val uRowStride = planes[1].getRowStride()
// V
val vArr = planes[2].buffer.array()
val vPixelStride = planes[2].getPixelStride()
val vRowStride = planes[2].getRowStride()
val y = yArr[(height * yRowStride + width * yPixelStride) / 2] & 255
val u = (uArr[(height * uRowStride + width * uPixelStride) / 4] & 255) - 128
val v = (vArr[(height * vRowStride + width * vPixelStride) / 4] & 255) - 128
val r = y + (1.370705 * v);
val g = y - (0.698001 * v) - (0.337633 * u);
val b = y + (1.732446 * u);
参考魔法值:https://en.wikipedia.org/wiki/YUV#Y%E2%80%B2UV420sp_(NV21)_to_RGB_conversion_(Android)
尝试在您的 Kotlin 代码中使用此逻辑,看看它是否正常工作以及是否可以快速进行实时操作。这绝对应该将 O(height * width)
操作减少到常数时间复杂度。
我正在 Android 上使用新的 CameraX。
我做了一个基本应用程序(类似于 "Get Started"),其中有一个相机预览和一个光度分析器。每一秒我都会在 TextView 中显示我的亮度。
现在,按照 CameraX 指南,我想进行颜色检测。每隔一秒左右,我就想从屏幕中央的像素中获取颜色。
事实是我不知道如何按照与亮度分析仪相同的结构进行颜色检测。
亮度分析仪 Class :
class LuminosityAnalyzer : ImageAnalysis.Analyzer {
private var lastTimeStamp = 0L
private val TAG = this.javaClass.simpleName
var luma = BehaviorSubject.create<Double>()
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimeStamp = System.currentTimeMillis()
val intervalInSeconds = TimeUnit.SECONDS.toMillis(1)
val deltaTime = currentTimeStamp - lastTimeStamp
if(deltaTime >= intervalInSeconds) {
val buffer = image.planes[0].buffer
val data = buffer.toByteArray()
val pixels = data.map { it.toInt() and 0xFF }
luma.onNext(pixels.average())
lastTimeStamp = currentTimeStamp
Log.d(TAG, "Average luminosity: ${luma.value}")
}
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
}
主要 Activity :
/* display the luminosity */
private fun createLuminosityAnalyzer(): ImageAnalysis{
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setLensFacing(lensFacing)
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
val analyzer = ImageAnalysis(analyzerConfig).apply {
val luminosityAnalyzer = LuminosityAnalyzer()
luminosityAnalyzer.luma
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// success
luminosity.text = it.toString()
},{
// error
Log.d(TAG, "Can not get luminosity :(")
})
setAnalyzer(executor, luminosityAnalyzer)
}
return analyzer
}
我怎样才能做一些与色彩分析仪相当的事情?
所以我自己想办法
颜色分析仪 Class :
class ColorAnalyzer : ImageAnalysis.Analyzer {
private var lastTimeStamp = 0L
private val TAG = this.javaClass.simpleName
var hexColor = BehaviorSubject.create<Any>()
/* every 100ms, analyze the image we receive from camera */
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimeStamp = System.currentTimeMillis()
val intervalInMilliSeconds = TimeUnit.MILLISECONDS.toMillis(100)
val deltaTime = currentTimeStamp - lastTimeStamp
if(deltaTime >= intervalInMilliSeconds) {
val imageBitmap = image.image?.toBitmap()
val pixel = imageBitmap!!.getPixel((imageBitmap.width/2), (imageBitmap.height/2))
val red = Color.red(pixel)
val blue = Color.blue(pixel)
val green = Color.green(pixel)
hexColor.onNext(String.format("#%02x%02x%02x", red, green, blue))
Log.d(TAG, "Color: ${hexColor.value}")
lastTimeStamp = currentTimeStamp
}
}
// convert the image into a bitmap
private fun Image.toBitmap(): Bitmap {
val yBuffer = planes[0].buffer // Y
val uBuffer = planes[1].buffer // U
val vBuffer = planes[2].buffer // V
val ySize = yBuffer.remaining()
val uSize = uBuffer.remaining()
val vSize = vBuffer.remaining()
val nv21 = ByteArray(ySize + uSize + vSize)
yBuffer.get(nv21, 0, ySize)
vBuffer.get(nv21, ySize, vSize)
uBuffer.get(nv21, ySize + vSize, uSize)
val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
val out = ByteArrayOutputStream()
yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
val imageBytes = out.toByteArray()
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
}
主要 Activity :
/* Get the color from Color Analyzer Class */
private fun createColorAnalyzer(): ImageAnalysis{
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setLensFacing(lensFacing)
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
val analyzer = ImageAnalysis(analyzerConfig).apply {
val colorAnalyzer = ColorAnalyzer()
colorAnalyzer.hexColor
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// success
colorName.text = it.toString() //hexa code in the textView
colorName.setBackgroundColor(Color.parseColor(it.toString())) //background color of the textView
(sight.drawable as GradientDrawable).setStroke(10, Color.parseColor(it.toString())) //border color of the sight in the middle of the screen
},{
// error
Log.d(TAG, "Can not get color :(")
})
setAnalyzer(executor, colorAnalyzer)
}
return analyzer
}
希望它对某人有用;)
编辑:
如果您阅读@Minhaz 的回答,通过 image -> bitmap -> getPixel() 获取颜色的效率不是很高。最有效的是做image -> RGB。
下面是使用 Kotlin 的 Minhaz 答案。
颜色分析仪 Class :
class ColorAnalyzer : ImageAnalysis.Analyzer {
private var lastAnalyzedTimestamp = 0L
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
private fun getRGBfromYUV(image: ImageProxy): Triple<Double, Double, Double> {
val planes = image.planes
val height = image.height
val width = image.width
// Y
val yArr = planes[0].buffer
val yArrByteArray = yArr.toByteArray()
val yPixelStride = planes[0].pixelStride
val yRowStride = planes[0].rowStride
// U
val uArr = planes[1].buffer
val uArrByteArray =uArr.toByteArray()
val uPixelStride = planes[1].pixelStride
val uRowStride = planes[1].rowStride
// V
val vArr = planes[2].buffer
val vArrByteArray = vArr.toByteArray()
val vPixelStride = planes[2].pixelStride
val vRowStride = planes[2].rowStride
val y = yArrByteArray[(height * yRowStride + width * yPixelStride) / 2].toInt() and 255
val u = (uArrByteArray[(height * uRowStride + width * uPixelStride) / 4].toInt() and 255) - 128
val v = (vArrByteArray[(height * vRowStride + width * vPixelStride) / 4].toInt() and 255) - 128
val r = y + (1.370705 * v)
val g = y - (0.698001 * v) - (0.337633 * u)
val b = y + (1.732446 * u)
return Triple(r,g,b)
}
// analyze the color
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimestamp = System.currentTimeMillis()
if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.MILLISECONDS.toMillis(100)) {
val colors = getRGBfromYUV(image)
var hexColor = String.format("#%02x%02x%02x", colors.first.toInt(), colors.second.toInt(), colors.third.toInt())
Log.d("test", "hexColor: $hexColor")
lastAnalyzedTimestamp = currentTimestamp
}
}
}
如评论中所述,如果您的目标是仅获取中心像素颜色,则将整个 YUV 图像转换为 Bitmap 然后分析中心值的逻辑可能非常低效。您可以通过瞄准正确的像素直接查看 YUV 图像中的颜色。在 YUV 图像中,您有三个平面,一个用于 Y(每像素 1 字节)和 U 和 V 平面(每像素 .5 字节,交错)。无论旋转如何,忽略此刻作为中心像素的旋转应该是相同的(丢弃高度或宽度奇值的可能性)。获取中心像素 rgb 值的有效逻辑如下所示:
planes = imageProxy.getPlanes()
val height = imageProxy.getHeight()
val width = imageProxy.getWidth()
// You may have to find the logic to get array from ByteBuffer
// Y
val yArr = planes[0].buffer.array()
val yPixelStride = planes[0].getPixelStride()
val yRowStride = planes[0].getRowStride()
// U
val uArr = planes[1].buffer.array()
val uPixelStride = planes[1].getPixelStride()
val uRowStride = planes[1].getRowStride()
// V
val vArr = planes[2].buffer.array()
val vPixelStride = planes[2].getPixelStride()
val vRowStride = planes[2].getRowStride()
val y = yArr[(height * yRowStride + width * yPixelStride) / 2] & 255
val u = (uArr[(height * uRowStride + width * uPixelStride) / 4] & 255) - 128
val v = (vArr[(height * vRowStride + width * vPixelStride) / 4] & 255) - 128
val r = y + (1.370705 * v);
val g = y - (0.698001 * v) - (0.337633 * u);
val b = y + (1.732446 * u);
参考魔法值:https://en.wikipedia.org/wiki/YUV#Y%E2%80%B2UV420sp_(NV21)_to_RGB_conversion_(Android)
尝试在您的 Kotlin 代码中使用此逻辑,看看它是否正常工作以及是否可以快速进行实时操作。这绝对应该将 O(height * width)
操作减少到常数时间复杂度。