1
1
package io.face.mask.detection
2
2
3
3
import android.Manifest
4
+ import android.annotation.SuppressLint
5
+ import android.content.Context
4
6
import android.content.pm.PackageManager
5
7
import android.content.res.Configuration
8
+ import android.graphics.Bitmap
9
+ import android.graphics.Matrix
10
+ import android.media.Image
6
11
import android.os.Bundle
7
12
import android.util.DisplayMetrics
8
13
import android.util.Log
@@ -13,18 +18,24 @@ import androidx.camera.core.*
13
18
import androidx.camera.lifecycle.ProcessCameraProvider
14
19
import androidx.core.app.ActivityCompat
15
20
import androidx.core.content.ContextCompat
21
+ import androidx.lifecycle.lifecycleScope
16
22
import com.google.common.util.concurrent.ListenableFuture
23
+ import io.face.mask.detection.ml.FackMaskDetection
17
24
import kotlinx.android.synthetic.main.activity_main.*
18
- import java.nio.ByteBuffer
25
+ import kotlinx.coroutines.Dispatchers
26
+ import kotlinx.coroutines.launch
27
+ import org.tensorflow.lite.support.image.TensorImage
28
+ import org.tensorflow.lite.support.label.Category
29
+ import org.tensorflow.lite.support.model.Model
19
30
import java.util.concurrent.ExecutorService
20
31
import java.util.concurrent.Executors
21
32
import kotlin.math.abs
22
33
import kotlin.math.max
23
34
import kotlin.math.min
24
35
25
- typealias AnalyzerListener = (pixel : Double ) -> Unit
36
+ typealias CameraBitmapOutputListener = (bitmap : Bitmap ) -> Unit
26
37
27
- class MainActivity : AppCompatActivity () {
38
+ class MainActivity : AppCompatActivity (R .layout.activity_main ) {
28
39
private var preview: Preview ? = null
29
40
private var imageAnalyzer: ImageAnalysis ? = null
30
41
private var cameraProvider: ProcessCameraProvider ? = null
@@ -37,7 +48,7 @@ class MainActivity : AppCompatActivity() {
37
48
38
49
override fun onCreate (savedInstanceState : Bundle ? ) {
39
50
super .onCreate(savedInstanceState)
40
- setContentView( R .layout.activity_main )
51
+ setupML( )
41
52
42
53
setupCameraThread()
43
54
setupCameraControllers()
@@ -49,7 +60,6 @@ class MainActivity : AppCompatActivity() {
49
60
}
50
61
}
51
62
52
-
53
63
private fun setupCameraThread () {
54
64
cameraExecutor = Executors .newSingleThreadExecutor()
55
65
}
@@ -72,7 +82,7 @@ class MainActivity : AppCompatActivity() {
72
82
CameraSelector .LENS_FACING_FRONT
73
83
}
74
84
setLensButtonIcon()
75
- bindCameraUseCases ()
85
+ setupCameraUseCases ()
76
86
}
77
87
78
88
try {
@@ -105,7 +115,7 @@ class MainActivity : AppCompatActivity() {
105
115
}
106
116
107
117
108
- private fun bindCameraUseCases () {
118
+ private fun setupCameraUseCases () {
109
119
// Lens-facing selector
110
120
val cameraSelector: CameraSelector =
111
121
CameraSelector .Builder ().requireLensFacing(lensFacing)
@@ -127,8 +137,8 @@ class MainActivity : AppCompatActivity() {
127
137
.setTargetRotation(rotation)
128
138
.build()
129
139
.also {
130
- it.setAnalyzer(cameraExecutor, AppImageAnalysis { luma ->
131
- Log .d( TAG , " Average luminosity: $luma " )
140
+ it.setAnalyzer(cameraExecutor, BitmapOutputAnalysis (applicationContext) { bitmap ->
141
+ setupMLOutput(bitmap )
132
142
})
133
143
}
134
144
@@ -146,6 +156,7 @@ class MainActivity : AppCompatActivity() {
146
156
}
147
157
}
148
158
159
+
149
160
private fun setupCamera () {
150
161
val cameraProviderFuture: ListenableFuture <ProcessCameraProvider > =
151
162
ProcessCameraProvider .getInstance(this )
@@ -154,13 +165,13 @@ class MainActivity : AppCompatActivity() {
154
165
cameraProvider = cameraProviderFuture.get()
155
166
156
167
lensFacing = when {
157
- hasBackCamera -> CameraSelector .LENS_FACING_BACK
158
168
hasFrontCamera -> CameraSelector .LENS_FACING_FRONT
169
+ hasBackCamera -> CameraSelector .LENS_FACING_BACK
159
170
else -> throw IllegalStateException (" No cameras on this devices" )
160
171
}
161
172
162
173
setupCameraControllers()
163
- bindCameraUseCases ()
174
+ setupCameraUseCases ()
164
175
}, ContextCompat .getMainExecutor(this ))
165
176
}
166
177
@@ -204,6 +215,45 @@ class MainActivity : AppCompatActivity() {
204
215
return AspectRatio .RATIO_16_9
205
216
}
206
217
218
+ private lateinit var faceMaskDetection: FackMaskDetection
219
+
220
+ private fun setupML () {
221
+ val options: Model .Options =
222
+ Model .Options .Builder ().setDevice(Model .Device .GPU ).setNumThreads(5 ).build()
223
+ faceMaskDetection = FackMaskDetection .newInstance(applicationContext, options)
224
+ }
225
+
226
+ private fun setupMLOutput (bitmap : Bitmap ) {
227
+ val tensorImage: TensorImage = TensorImage .fromBitmap(bitmap)
228
+ val result: FackMaskDetection .Outputs = faceMaskDetection.process(tensorImage)
229
+ val output: List <Category > =
230
+ result.probabilityAsCategoryList.apply {
231
+ sortByDescending { res -> res.score }
232
+ }
233
+ lifecycleScope.launch(Dispatchers .Main ) {
234
+ output.firstOrNull()?.let { category ->
235
+ tv_output.text = category.label
236
+ tv_output.setTextColor(
237
+ ContextCompat .getColor(
238
+ applicationContext,
239
+ if (category.label == " without_mask" ) R .color.red else R .color.green
240
+ )
241
+ )
242
+ overlay.background = getDrawable(
243
+ if (category.label == " without_mask" ) R .drawable.red_border else R .drawable.green_border
244
+ )
245
+
246
+ pb_output.progressTintList = AppCompatResources .getColorStateList(
247
+ applicationContext,
248
+ if (category.label == " without_mask" ) R .color.red else R .color.green
249
+ )
250
+ pb_output.progress = (category.score * 100 ).toInt()
251
+
252
+ Log .d(TAG , output.toString())
253
+ }
254
+ }
255
+ }
256
+
207
257
companion object {
208
258
private const val TAG = " Face-Mask-Detection"
209
259
private const val REQUEST_CODE_PERMISSIONS = 0x98
@@ -213,23 +263,48 @@ class MainActivity : AppCompatActivity() {
213
263
}
214
264
}
215
265
216
- private class AppImageAnalysis (private val listener : AnalyzerListener ) :
266
+ private class BitmapOutputAnalysis (
267
+ context : Context ,
268
+ private val listener : CameraBitmapOutputListener
269
+ ) :
217
270
ImageAnalysis .Analyzer {
218
271
219
- private fun ByteBuffer. toByteArray (): ByteArray {
220
- rewind() // Rewind the buffer to zero
221
- val data = ByteArray (remaining())
222
- get(data) // Copy the buffer into a byte array
223
- return data // Return the byte array
224
- }
272
+ private val yuvToRgbConverter = YuvToRgbConverter (context)
273
+ private lateinit var bitmapBuffer : Bitmap
274
+ private lateinit var rotationMatrix : Matrix
275
+
276
+ @SuppressLint( " UnsafeExperimentalUsageError " )
277
+ private fun ImageProxy. toBitmap (): Bitmap ? {
225
278
226
- override fun analyze (image : ImageProxy ) {
227
- val buffer: ByteBuffer = image.planes[0 ].buffer
228
- val data: ByteArray = buffer.toByteArray()
229
- val pixels: List <Int > = data.map { it.toInt() and 0xFF }
230
- val pixelsAverage: Double = pixels.average()
279
+ val image: Image = this .image ? : return null
231
280
232
- listener(pixelsAverage)
233
- image.close()
281
+ if (! ::bitmapBuffer.isInitialized) {
282
+ rotationMatrix = Matrix ()
283
+ rotationMatrix.postRotate(this .imageInfo.rotationDegrees.toFloat())
284
+ bitmapBuffer = Bitmap .createBitmap(
285
+ this .width, this .height, Bitmap .Config .ARGB_8888
286
+ )
287
+ }
288
+
289
+ // Pass image to an image analyser
290
+ yuvToRgbConverter.yuvToRgb(image, bitmapBuffer)
291
+
292
+ // Create the Bitmap in the correct orientation
293
+ return Bitmap .createBitmap(
294
+ bitmapBuffer,
295
+ 0 ,
296
+ 0 ,
297
+ bitmapBuffer.width,
298
+ bitmapBuffer.height,
299
+ rotationMatrix,
300
+ false
301
+ )
302
+ }
303
+
304
+ override fun analyze (imageProxy : ImageProxy ) {
305
+ imageProxy.toBitmap()?.let {
306
+ listener(it)
307
+ }
308
+ imageProxy.close()
234
309
}
235
310
}
0 commit comments