安卓相机X |颜色检测

Posted

技术标签:

【中文标题】安卓相机X |颜色检测【英文标题】:Android CameraX | Color detection 【发布时间】:2020-04-24 02:27:06 【问题描述】:

我正在使用 android 上的新 CameraX。

我做了一个基本的应用程序(类似于“入门”),其中有一个相机预览和一个光度分析器。每一秒我都会在 TextView 中显示我的亮度。

现在,按照 CameraX 指南,我想做颜色检测。每隔一秒左右,我就想从屏幕中心的像素中获取颜色。

事实是我不知道如何按照与光度分析仪相同的结构进行颜色检测。

亮度分析器类:

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


主要活动:

/* 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

作为色彩分析仪,我怎样才能做同样的事情?

【问题讨论】:

【参考方案1】:

所以我自己想出了办法

色彩分析器类:

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)


主要活动:

 /* 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 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 数据本身具有三个平面,一个带有 Y 缓冲区(亮度),另外两个缓冲区指示色度值 U 和 V 缓冲区。虽然每个像素都有一个 Y 字节,但 U 和 V 可用作交错值。使用 YUV 数据本身和 Pixel Stride 和 Row Stride 可以获得任何像素的颜色值,而不是将整个图像转换为位图(顺便说一下,它比内存中的 YUV 大 8 / 3 倍) 如果您正在寻找代码示例以获得有效的解决方案,请告诉我 - 我会尝试在这里提供一个。 非常欢迎您的解决方案。另一方面,我将总共有 16 个像素区域来分析颜色,因此我想将整个图像转换为位图效率会降低 嘿,又是我,我真的需要您所说的高效解决方案。我正在尝试这样做,但我很难直接从 YUV 飞机上获取颜色。谢谢 ! @Minhaz 将逻辑添加为注释。【参考方案2】:

如评论中所述,如果您的目标是仅获取中心像素颜色,则将整个 YUV 图像转换为位图然后分析中心值的逻辑可能非常低效。您可以通过定位正确的像素直接查看 YUV 图像中的颜色。在 YUV 图像中,您有三个平面,一个用于 Y(每个像素 1 个字节)和 U 和 V 平面(每个像素 0.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) 操作降低到恒定的时间复杂度。

【讨论】:

以上是关于安卓相机X |颜色检测的主要内容,如果未能解决你的问题,请参考以下文章

在Android中将任何颜色代码转换为最接近的人类颜色[重复]

Matlab 运动检测使用 IP 网络摄像头给出奇怪的颜色

从相机意图中获取像素的颜色

我可以使用哪个算法或库来检测和测量一个或多个盒子尺寸

Android自动裁剪相机捕获的图像

从实时相机预览中获取特定像素的颜色 - Ionic