Android CameraX | 颜色检测

huangapple go评论187阅读模式
英文:

Android CameraX | Color detection

问题

I'm working with the new CameraX on Android.

我正在使用Android上的新CameraX。

I did a basic application (similar to the "Get Started") in which I have a camera preview and a luminosity analyzer. Every second I display my luminosity in a TextView.

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

Now, following the CameraX guidelines, I would like to do color detection. Every second or so, I want to have the color from the pixel in the center of my screen.

现在,根据CameraX的指南,我想进行颜色检测。大约每秒钟,我想获取屏幕中心像素的颜色。

The fact is that I don't know how to do color detection following the same structure as the luminosity analyzer.

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

Luminosity Analyzer Class :

亮度分析器类:

  1. class LuminosityAnalyzer : ImageAnalysis.Analyzer {
  2. private var lastTimeStamp = 0L
  3. private val TAG = this.javaClass.simpleName
  4. var luma = BehaviorSubject.create<Double>()
  5. override fun analyze(image: ImageProxy, rotationDegrees: Int) {
  6. val currentTimeStamp = System.currentTimeMillis()
  7. val intervalInSeconds = TimeUnit.SECONDS.toMillis(1)
  8. val deltaTime = currentTimeStamp - lastTimeStamp
  9. if(deltaTime >= intervalInSeconds) {
  10. val buffer = image.planes[0].buffer
  11. val data = buffer.toByteArray()
  12. val pixels = data.map { it.toInt() and 0xFF }
  13. luma.onNext(pixels.average())
  14. lastTimeStamp = currentTimeStamp
  15. Log.d(TAG, "Average luminosity: ${luma.value}")
  16. }
  17. }
  18. private fun ByteBuffer.toByteArray(): ByteArray {
  19. rewind()
  20. val data = ByteArray(remaining())
  21. get(data)
  22. return data
  23. }
  24. }

Main Activity :

主要活动:

  1. /* display the luminosity */
  2. private fun createLuminosityAnalyzer(): ImageAnalysis{
  3. val analyzerConfig = ImageAnalysisConfig.Builder().apply {
  4. setLensFacing(lensFacing)
  5. setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
  6. }.build()
  7. val analyzer = ImageAnalysis(analyzerConfig).apply {
  8. val luminosityAnalyzer = LuminosityAnalyzer()
  9. luminosityAnalyzer.luma
  10. .observeOn(AndroidSchedulers.mainThread())
  11. .subscribe({
  12. // success
  13. luminosity.text = it.toString()
  14. },{
  15. // error
  16. Log.d(TAG, "Can not get luminosity :(")
  17. })
  18. setAnalyzer(executor, luminosityAnalyzer)
  19. }
  20. return analyzer
  21. }

How can I do something equivalent but being a Color Analyzer?

我如何做一个类似的东西,但作为颜色分析器?

英文:

I'm working with the new CameraX on Android.

I did a basic application (similar to the "Get Started") in which I have a camera preview and a luminosity analyzer. Every second I display my lumonisity in a TextView.

Now, following the CameraX guidelines, I would like to do color detection. Every second or so, I want to have the color from the pixel in the center of my screen.

The fact is that I don't know how to do color detection following the same sructure as luminosity analyzer.

Luminosity Analyzer Class :

  1. class LuminosityAnalyzer : ImageAnalysis.Analyzer {
  2. private var lastTimeStamp = 0L
  3. private val TAG = this.javaClass.simpleName
  4. var luma = BehaviorSubject.create&lt;Double&gt;()
  5. override fun analyze(image: ImageProxy, rotationDegrees: Int) {
  6. val currentTimeStamp = System.currentTimeMillis()
  7. val intervalInSeconds = TimeUnit.SECONDS.toMillis(1)
  8. val deltaTime = currentTimeStamp - lastTimeStamp
  9. if(deltaTime &gt;= intervalInSeconds) {
  10. val buffer = image.planes[0].buffer
  11. val data = buffer.toByteArray()
  12. val pixels = data.map { it.toInt() and 0xFF }
  13. luma.onNext(pixels.average())
  14. lastTimeStamp = currentTimeStamp
  15. Log.d(TAG, &quot;Average luminosity: ${luma.value}&quot;)
  16. }
  17. private fun ByteBuffer.toByteArray(): ByteArray {
  18. rewind()
  19. val data = ByteArray(remaining())
  20. get(data)
  21. return data
  22. }
  23. }

Main Activity :

  1. /* display the luminosity */
  2. private fun createLuminosityAnalyzer(): ImageAnalysis{
  3. val analyzerConfig = ImageAnalysisConfig.Builder().apply {
  4. setLensFacing(lensFacing)
  5. setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
  6. }.build()
  7. val analyzer = ImageAnalysis(analyzerConfig).apply {
  8. val luminosityAnalyzer = LuminosityAnalyzer()
  9. luminosityAnalyzer.luma
  10. .observeOn(AndroidSchedulers.mainThread())
  11. .subscribe({
  12. // success
  13. luminosity.text = it.toString()
  14. },{
  15. // error
  16. Log.d(TAG, &quot;Can not get luminosity :(&quot;)
  17. })
  18. setAnalyzer(executor, luminosityAnalyzer)
  19. }
  20. return analyzer
  21. }

How can I do something equivalent but being a Color Analyzer ?

答案1

得分: 5

Sure, here's the translated code:

  1. So I figured out how to do it by myself
  2. **Color Analyzer Class:**
  3. class ColorAnalyzer : ImageAnalysis.Analyzer {
  4. private var lastTimeStamp = 0L
  5. private val TAG = this.javaClass.simpleName
  6. var hexColor = BehaviorSubject.create<Any>()
  7. /* every 100ms, analyze the image we receive from the camera */
  8. override fun analyze(image: ImageProxy, rotationDegrees: Int) {
  9. val currentTimeStamp = System.currentTimeMillis()
  10. val intervalInMilliSeconds = TimeUnit.MILLISECONDS.toMillis(100)
  11. val deltaTime = currentTimeStamp - lastTimeStamp
  12. if (deltaTime >= intervalInMilliSeconds) {
  13. val imageBitmap = image.image?.toBitmap()
  14. val pixel = imageBitmap!!.getPixel((imageBitmap.width / 2), (imageBitmap.height / 2))
  15. val red = Color.red(pixel)
  16. val blue = Color.blue(pixel)
  17. val green = Color.green(pixel)
  18. hexColor.onNext(String.format("#%02x%02x%02x", red, green, blue))
  19. Log.d(TAG, "Color: ${hexColor.value}")
  20. lastTimeStamp = currentTimeStamp
  21. }
  22. }
  23. // convert the image into a bitmap
  24. private fun Image.toBitmap(): Bitmap {
  25. val yBuffer = planes[0].buffer // Y
  26. val uBuffer = planes[1].buffer // U
  27. val vBuffer = planes[2].buffer // V
  28. val ySize = yBuffer.remaining()
  29. val uSize = uBuffer.remaining()
  30. val vSize = vBuffer.remaining()
  31. val nv21 = ByteArray(ySize + uSize + vSize)
  32. yBuffer.get(nv21, 0, ySize)
  33. vBuffer.get(nv21, ySize, vSize)
  34. uBuffer.get(nv21, ySize + vSize, uSize)
  35. val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
  36. val out = ByteArrayOutputStream()
  37. yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
  38. val imageBytes = out.toByteArray()
  39. return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
  40. }
  41. }
  42. **Main Activity:**
  43. /* Get the color from Color Analyzer Class */
  44. private fun createColorAnalyzer(): ImageAnalysis {
  45. val analyzerConfig = ImageAnalysisConfig.Builder().apply {
  46. setLensFacing(lensFacing)
  47. setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
  48. }.build()
  49. val analyzer = ImageAnalysis(analyzerConfig).apply {
  50. val colorAnalyzer = ColorAnalyzer()
  51. colorAnalyzer.hexColor
  52. .observeOn(AndroidSchedulers.mainThread())
  53. .subscribe({
  54. // success
  55. colorName.text = it.toString() // hexa code in the textView
  56. colorName.setBackgroundColor(Color.parseColor(it.toString())) // background color of the textView
  57. (sight.drawable as GradientDrawable).setStroke(10, Color.parseColor(it.toString())) // border color of the sight in the middle of the screen
  58. }, {
  59. // error
  60. Log.d(TAG, "Can not get color :(")
  61. })
  62. setAnalyzer(executor, colorAnalyzer)
  63. }
  64. return analyzer
  65. }
  66. Hope it will be useful for someone ;)
  67. **EDIT:**
  68. If you read the @Minhaz answer getting the color by doing image -> bitmap -> getPixel() is not very efficient. The most effective is to do image -> RGB.
  69. So here's the Minhaz answer working with Kotlin.
  70. **Color Analyzer Class:**
  71. class ColorAnalyzer : ImageAnalysis.Analyzer {
  72. private var lastAnalyzedTimestamp = 0L
  73. private fun ByteBuffer.toByteArray(): ByteArray {
  74. rewind() // Rewind the buffer to zero
  75. val data = ByteArray(remaining())
  76. get(data) // Copy the buffer into a byte array
  77. return data // Return the byte array
  78. }
  79. private fun getRGBfromYUV(image: ImageProxy): Triple<Double, Double, Double> {
  80. val planes = image.planes
  81. val height = image.height
  82. val width = image.width
  83. // Y
  84. val yArr = planes[0].buffer
  85. val yArrByteArray = yArr.toByteArray()
  86. val yPixelStride = planes[0].pixelStride
  87. val yRowStride = planes[0].rowStride
  88. // U
  89. val uArr = planes[1].buffer
  90. val uArrByteArray = uArr.toByteArray()
  91. val uPixelStride = planes[1].pixelStride
  92. val uRowStride = planes[1].rowStride
  93. // V
  94. val vArr = planes[2].buffer
  95. val vArrByteArray = vArr.toByteArray()
  96. val vPixelStride = planes[2].pixelStride
  97. val vRowStride = planes[2].rowStride
  98. val y = yArrByteArray[(height * yRowStride + width * yPixelStride) / 2].toInt() and 255
  99. val u = (uArrByteArray[(height * uRowStride + width * uPixelStride) / 4].toInt() and 255) - 128
  100. val v = (vArrByteArray[(height * vRowStride + width * vPixelStride) / 4].toInt() and 255) - 128
  101. val r = y + (1.370705 * v)
  102. val g = y - (0.698001 * v) - (0.337633 * u)
  103. val b = y + (1.732446 * u)
  104. return Triple(r, g, b)
  105. }
  106. // analyze the color
  107. override fun analyze(image: ImageProxy, rotationDegrees: Int) {
  108. val currentTimestamp = System.currentTimeMillis()
  109. if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.MILLISECONDS.toMillis(100)) {
  110. val colors = getRGBfromYUV(image)
  111. var hexColor = String.format("#%02x%02x%02x", colors.first.toInt(), colors.second.toInt(), colors.third.toInt())
  112. Log.d("test", "hexColor: $hexColor")
  113. lastAnalyzedTimestamp = currentTimestamp
  114. }
  115. }
  116. }
英文:

So I figured out how to do it by myself

Color Analyzer Class :

  1. class ColorAnalyzer : ImageAnalysis.Analyzer {
  2. private var lastTimeStamp = 0L
  3. private val TAG = this.javaClass.simpleName
  4. var hexColor = BehaviorSubject.create&lt;Any&gt;()
  5. /* every 100ms, analyze the image we receive from camera */
  6. override fun analyze(image: ImageProxy, rotationDegrees: Int) {
  7. val currentTimeStamp = System.currentTimeMillis()
  8. val intervalInMilliSeconds = TimeUnit.MILLISECONDS.toMillis(100)
  9. val deltaTime = currentTimeStamp - lastTimeStamp
  10. if(deltaTime &gt;= intervalInMilliSeconds) {
  11. val imageBitmap = image.image?.toBitmap()
  12. val pixel = imageBitmap!!.getPixel((imageBitmap.width/2), (imageBitmap.height/2))
  13. val red = Color.red(pixel)
  14. val blue = Color.blue(pixel)
  15. val green = Color.green(pixel)
  16. hexColor.onNext(String.format(&quot;#%02x%02x%02x&quot;, red, green, blue))
  17. Log.d(TAG, &quot;Color: ${hexColor.value}&quot;)
  18. lastTimeStamp = currentTimeStamp
  19. }
  20. }
  21. // convert the image into a bitmap
  22. private fun Image.toBitmap(): Bitmap {
  23. val yBuffer = planes[0].buffer // Y
  24. val uBuffer = planes[1].buffer // U
  25. val vBuffer = planes[2].buffer // V
  26. val ySize = yBuffer.remaining()
  27. val uSize = uBuffer.remaining()
  28. val vSize = vBuffer.remaining()
  29. val nv21 = ByteArray(ySize + uSize + vSize)
  30. yBuffer.get(nv21, 0, ySize)
  31. vBuffer.get(nv21, ySize, vSize)
  32. uBuffer.get(nv21, ySize + vSize, uSize)
  33. val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
  34. val out = ByteArrayOutputStream()
  35. yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
  36. val imageBytes = out.toByteArray()
  37. return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
  38. }
  39. }

Main Activity :

  1. /* Get the color from Color Analyzer Class */
  2. private fun createColorAnalyzer(): ImageAnalysis{
  3. val analyzerConfig = ImageAnalysisConfig.Builder().apply {
  4. setLensFacing(lensFacing)
  5. setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
  6. }.build()
  7. val analyzer = ImageAnalysis(analyzerConfig).apply {
  8. val colorAnalyzer = ColorAnalyzer()
  9. colorAnalyzer.hexColor
  10. .observeOn(AndroidSchedulers.mainThread())
  11. .subscribe({
  12. // success
  13. colorName.text = it.toString() //hexa code in the textView
  14. colorName.setBackgroundColor(Color.parseColor(it.toString())) //background color of the textView
  15. (sight.drawable as GradientDrawable).setStroke(10, Color.parseColor(it.toString())) //border color of the sight in the middle of the screen
  16. },{
  17. // error
  18. Log.d(TAG, &quot;Can not get color :(&quot;)
  19. })
  20. setAnalyzer(executor, colorAnalyzer)
  21. }
  22. return analyzer
  23. }

Hope it will be useful for someone Android CameraX | 颜色检测

EDIT :

If you read the @Minhaz answer getting the color by doing image -> bitmap -> getPixel() is not very efficient. The most effective is to do image -> RGB.

So here's the Minhaz answer working with Kotlin.

Color Analyzer Class :

  1. class ColorAnalyzer : ImageAnalysis.Analyzer {
  2. private var lastAnalyzedTimestamp = 0L
  3. private fun ByteBuffer.toByteArray(): ByteArray {
  4. rewind() // Rewind the buffer to zero
  5. val data = ByteArray(remaining())
  6. get(data) // Copy the buffer into a byte array
  7. return data // Return the byte array
  8. }
  9. private fun getRGBfromYUV(image: ImageProxy): Triple&lt;Double, Double, Double&gt; {
  10. val planes = image.planes
  11. val height = image.height
  12. val width = image.width
  13. // Y
  14. val yArr = planes[0].buffer
  15. val yArrByteArray = yArr.toByteArray()
  16. val yPixelStride = planes[0].pixelStride
  17. val yRowStride = planes[0].rowStride
  18. // U
  19. val uArr = planes[1].buffer
  20. val uArrByteArray =uArr.toByteArray()
  21. val uPixelStride = planes[1].pixelStride
  22. val uRowStride = planes[1].rowStride
  23. // V
  24. val vArr = planes[2].buffer
  25. val vArrByteArray = vArr.toByteArray()
  26. val vPixelStride = planes[2].pixelStride
  27. val vRowStride = planes[2].rowStride
  28. val y = yArrByteArray[(height * yRowStride + width * yPixelStride) / 2].toInt() and 255
  29. val u = (uArrByteArray[(height * uRowStride + width * uPixelStride) / 4].toInt() and 255) - 128
  30. val v = (vArrByteArray[(height * vRowStride + width * vPixelStride) / 4].toInt() and 255) - 128
  31. val r = y + (1.370705 * v)
  32. val g = y - (0.698001 * v) - (0.337633 * u)
  33. val b = y + (1.732446 * u)
  34. return Triple(r,g,b)
  35. }
  36. // analyze the color
  37. override fun analyze(image: ImageProxy, rotationDegrees: Int) {
  38. val currentTimestamp = System.currentTimeMillis()
  39. if (currentTimestamp - lastAnalyzedTimestamp &gt;= TimeUnit.MILLISECONDS.toMillis(100)) {
  40. val colors = getRGBfromYUV(image)
  41. var hexColor = String.format(&quot;#%02x%02x%02x&quot;, colors.first.toInt(), colors.second.toInt(), colors.third.toInt())
  42. Log.d(&quot;test&quot;, &quot;hexColor: $hexColor&quot;)
  43. lastAnalyzedTimestamp = currentTimestamp
  44. }
  45. }
  46. }

答案2

得分: 5

正如评论中提到的,如果您的目标仅是获取中心像素的颜色,将整个YUV图像转换为位图然后分析中心值的逻辑可能非常低效。您可以直接查看YUV图像中的颜色,定位到正确的像素。在YUV图像中,有三个平面,一个用于Y(每像素1字节),另一个用于U和V平面(每像素0.5字节,交织)。暂时忽略旋转,因为中心像素应该与旋转无关(不考虑高度或宽度的奇数值的可能性)。获取中心像素RGB值的高效逻辑如下:

  1. planes = imageProxy.getPlanes()
  2. val height = imageProxy.getHeight()
  3. val width = imageProxy.getWidth()
  4. // You may have to find the logic to get array from ByteBuffer
  5. // Y
  6. val yArr = planes[0].buffer.array()
  7. val yPixelStride = planes[0].getPixelStride()
  8. val yRowStride = planes[0].getRowStride()
  9. // U
  10. val uArr = planes[1].buffer.array()
  11. val uPixelStride = planes[1].getPixelStride()
  12. val uRowStride = planes[1].getRowStride()
  13. // V
  14. val vArr = planes[2].buffer.array()
  15. val vPixelStride = planes[2].getPixelStride()
  16. val vRowStride = planes[2].getRowStride()
  17. val y = yArr[(height * yRowStride + width * yPixelStride) / 2] & 255
  18. val u = (uArr[(height * uRowStride + width * uPixelStride) / 4] & 255) - 128
  19. val v = (vArr[(height * vRowStride + width * vPixelStride) / 4] & 255) - 128
  20. val r = y + (1.370705 * v);
  21. val g = y - (0.698001 * v) - (0.337633 * u);
  22. 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)的操作复杂度降低到常数时间复杂度。

英文:

As mentioned in the comment, if your target is to only get center pixel color the logic of converting the whole YUV image to Bitmap and then analysing the center value may be very inefficient. You can directly look into the color in YUV image by targeting the right pixel. In a YUV image you have three planes one for Y (1 byte per pixel) and U & V plane (.5 byte per pixel, interleaved). Ignoring the rotation at the moment as center pixel should be same regardless of rotation (discarding possibility of odd value of height or width). The efficient logic for getting center pixel rgb values would look like:

  1. planes = imageProxy.getPlanes()
  2. val height = imageProxy.getHeight()
  3. val width = imageProxy.getWidth()
  4. // You may have to find the logic to get array from ByteBuffer
  5. // Y
  6. val yArr = planes[0].buffer.array()
  7. val yPixelStride = planes[0].getPixelStride()
  8. val yRowStride = planes[0].getRowStride()
  9. // U
  10. val uArr = planes[1].buffer.array()
  11. val uPixelStride = planes[1].getPixelStride()
  12. val uRowStride = planes[1].getRowStride()
  13. // V
  14. val vArr = planes[2].buffer.array()
  15. val vPixelStride = planes[2].getPixelStride()
  16. val vRowStride = planes[2].getRowStride()
  17. val y = yArr[(height * yRowStride + width * yPixelStride) / 2] &amp; 255
  18. val u = (uArr[(height * uRowStride + width * uPixelStride) / 4] &amp; 255) - 128
  19. val v = (vArr[(height * vRowStride + width * vPixelStride) / 4] &amp; 255) - 128
  20. val r = y + (1.370705 * v);
  21. val g = y - (0.698001 * v) - (0.337633 * u);
  22. val b = y + (1.732446 * u);

Reference to magic values: https://en.wikipedia.org/wiki/YUV#Y%E2%80%B2UV420sp_(NV21)_to_RGB_conversion_(Android)

Try using this logic in your Kotlin code to see if it's working and is fast for realtime operations. This should definitely reduce a O(height * width) operation to constant time complexity.

huangapple
  • 本文由 发表于 2020年1月6日 22:32:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/59613886.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定