diff options
Diffstat (limited to 'tflite/src/main/java/com/example/android/camerax')
| -rw-r--r-- | tflite/src/main/java/com/example/android/camerax/tflite/CameraActivity.kt | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/tflite/src/main/java/com/example/android/camerax/tflite/CameraActivity.kt b/tflite/src/main/java/com/example/android/camerax/tflite/CameraActivity.kt new file mode 100644 index 0000000..cde9778 --- /dev/null +++ b/tflite/src/main/java/com/example/android/camerax/tflite/CameraActivity.kt | |||
| @@ -0,0 +1,281 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 Google LLC | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camerax.tflite | ||
| 18 | |||
| 19 | import android.Manifest | ||
| 20 | import android.annotation.SuppressLint | ||
| 21 | import android.content.Context | ||
| 22 | import android.content.pm.PackageManager | ||
| 23 | import android.graphics.Bitmap | ||
| 24 | import android.graphics.Color | ||
| 25 | import android.graphics.Matrix | ||
| 26 | import android.graphics.RectF | ||
| 27 | import android.os.Bundle | ||
| 28 | import android.util.Log | ||
| 29 | import android.util.Size | ||
| 30 | import android.view.View | ||
| 31 | import android.view.ViewGroup | ||
| 32 | import androidx.appcompat.app.AppCompatActivity | ||
| 33 | import androidx.camera.core.AspectRatio | ||
| 34 | import androidx.camera.core.CameraSelector | ||
| 35 | import androidx.camera.core.ImageAnalysis | ||
| 36 | import androidx.camera.core.Preview | ||
| 37 | import androidx.camera.lifecycle.ProcessCameraProvider | ||
| 38 | import androidx.core.app.ActivityCompat | ||
| 39 | import androidx.core.content.ContextCompat | ||
| 40 | import androidx.lifecycle.LifecycleOwner | ||
| 41 | import com.android.example.camerax.tflite.databinding.ActivityCameraBinding | ||
| 42 | import java.net.DatagramPacket | ||
| 43 | import java.net.DatagramSocket | ||
| 44 | import java.net.InetSocketAddress | ||
| 45 | import java.util.concurrent.Executors | ||
| 46 | import java.util.concurrent.TimeUnit | ||
| 47 | import kotlin.random.Random | ||
| 48 | |||
| 49 | |||
| 50 | /** Activity that displays the camera and performs object detection on the incoming frames */ | ||
| 51 | class CameraActivity : AppCompatActivity() { | ||
| 52 | |||
| 53 | private lateinit var activityCameraBinding: ActivityCameraBinding | ||
| 54 | |||
| 55 | private lateinit var bitmapBuffer: Bitmap | ||
| 56 | |||
| 57 | private val executor = Executors.newSingleThreadExecutor() | ||
| 58 | private val permissions = listOf(Manifest.permission.CAMERA) | ||
| 59 | private val permissionsRequestCode = Random.nextInt(0, 10000) | ||
| 60 | |||
| 61 | private var lensFacing: Int = CameraSelector.LENS_FACING_BACK | ||
| 62 | private val isFrontFacing get() = lensFacing == CameraSelector.LENS_FACING_FRONT | ||
| 63 | |||
| 64 | private var pauseAnalysis = false | ||
| 65 | private var imageRotationDegrees: Int = 0 | ||
| 66 | private var socket: DatagramSocket = DatagramSocket() | ||
| 67 | |||
| 68 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 69 | super.onCreate(savedInstanceState) | ||
| 70 | activityCameraBinding = ActivityCameraBinding.inflate(layoutInflater) | ||
| 71 | setContentView(activityCameraBinding.root) | ||
| 72 | } | ||
| 73 | |||
| 74 | override fun onDestroy() { | ||
| 75 | |||
| 76 | // Terminate all outstanding analyzing jobs (if there is any). | ||
| 77 | executor.apply { | ||
| 78 | shutdown() | ||
| 79 | awaitTermination(1000, TimeUnit.MILLISECONDS) | ||
| 80 | } | ||
| 81 | super.onDestroy() | ||
| 82 | } | ||
| 83 | |||
| 84 | /** Declare and bind preview and analysis use cases */ | ||
| 85 | @SuppressLint("UnsafeExperimentalUsageError") | ||
| 86 | private fun bindCameraUseCases() = activityCameraBinding.viewFinder.post { | ||
| 87 | |||
| 88 | val cameraProviderFuture = ProcessCameraProvider.getInstance(this) | ||
| 89 | cameraProviderFuture.addListener ({ | ||
| 90 | |||
| 91 | // Camera provider is now guaranteed to be available | ||
| 92 | val cameraProvider = cameraProviderFuture.get() | ||
| 93 | |||
| 94 | // Set up the view finder use case to display camera preview | ||
| 95 | val preview = Preview.Builder() | ||
| 96 | .setTargetAspectRatio(AspectRatio.RATIO_4_3) | ||
| 97 | .setTargetRotation(activityCameraBinding.viewFinder.display.rotation) | ||
| 98 | .build() | ||
| 99 | |||
| 100 | // Set up the image analysis use case which will process frames in real time | ||
| 101 | val imageAnalysis = ImageAnalysis.Builder() | ||
| 102 | .setTargetResolution(Size(448, 236)) | ||
| 103 | .setTargetRotation(activityCameraBinding.viewFinder.display.rotation) | ||
| 104 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) | ||
| 105 | .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) | ||
| 106 | .build() | ||
| 107 | |||
| 108 | var frameCounter = 0 | ||
| 109 | var lastFpsTimestamp = System.currentTimeMillis() | ||
| 110 | |||
| 111 | imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { image -> | ||
| 112 | if (!::bitmapBuffer.isInitialized) { | ||
| 113 | // The image rotation and RGB image buffer are initialized only once | ||
| 114 | // the analyzer has started running | ||
| 115 | imageRotationDegrees = image.imageInfo.rotationDegrees | ||
| 116 | bitmapBuffer = Bitmap.createBitmap( | ||
| 117 | image.width, image.height, Bitmap.Config.ARGB_8888) | ||
| 118 | } | ||
| 119 | |||
| 120 | // Early exit: image analysis is in paused state | ||
| 121 | if (pauseAnalysis) { | ||
| 122 | image.close() | ||
| 123 | return@Analyzer | ||
| 124 | } | ||
| 125 | |||
| 126 | // Copy out RGB bits to our shared buffer | ||
| 127 | image.use { bitmapBuffer.copyPixelsFromBuffer(image.planes[0].buffer) } | ||
| 128 | |||
| 129 | // val CAM_WIDTH = 858 | ||
| 130 | // val CAM_HEIGHT = 480 | ||
| 131 | val CAM_WIDTH = 480 | ||
| 132 | val CAM_HEIGHT = 360 | ||
| 133 | |||
| 134 | val DISPLAY_WIDTH = 448 | ||
| 135 | val DISPLAY_HEIGHT = 160 | ||
| 136 | val DISPLAY_VHEIGHT = 236 | ||
| 137 | |||
| 138 | val scaledBitmap = Bitmap.createScaledBitmap(bitmapBuffer, DISPLAY_WIDTH, DISPLAY_VHEIGHT, true) | ||
| 139 | |||
| 140 | val scratch = IntArray((2 + DISPLAY_VHEIGHT) * DISPLAY_WIDTH) | ||
| 141 | val output = UByteArray(10 + DISPLAY_HEIGHT * DISPLAY_WIDTH / 8) | ||
| 142 | output[1] = 0x12u | ||
| 143 | output[4] = 0x23u | ||
| 144 | var offset = 10 | ||
| 145 | /* | ||
| 146 | for ( row in 0..DISPLAY_VHEIGHT - 1) { | ||
| 147 | val zeile = (row * bitmapBuffer.height ) / DISPLAY_VHEIGHT | ||
| 148 | for (column in 0..DISPLAY_WIDTH - 1) { | ||
| 149 | val spalte = (column * bitmapBuffer.width ) / DISPLAY_WIDTH | ||
| 150 | val pixel = bitmapBuffer.getPixel(spalte, zeile) | ||
| 151 | scratch[row * DISPLAY_WIDTH + column] = Color.red(pixel) * 19535 + Color.green(pixel) * 38470 + Color.blue(pixel) * 7448 | ||
| 152 | } | ||
| 153 | } | ||
| 154 | */ | ||
| 155 | scaledBitmap.getPixels(scratch, 0, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, DISPLAY_VHEIGHT) | ||
| 156 | for (off in 0..DISPLAY_VHEIGHT * DISPLAY_WIDTH) | ||
| 157 | scratch[off] = Color.red(scratch[off]) * 19535 + Color.green(scratch[off]) * 38470 + Color.blue(scratch[off]) * 7448 | ||
| 158 | |||
| 159 | var acc = 0 | ||
| 160 | var accv = 0 | ||
| 161 | |||
| 162 | for ( row in 0.. DISPLAY_VHEIGHT - 1) | ||
| 163 | for ( column in 0..DISPLAY_WIDTH - 1) { | ||
| 164 | val pixel = scratch[row * DISPLAY_WIDTH + column] | ||
| 165 | val bwpixel = if (pixel < 0x810000) 0 else 0xFFFFFF | ||
| 166 | |||
| 167 | if (row % 12 < 8) { | ||
| 168 | acc = (acc shl 1) + (bwpixel shr 23) | ||
| 169 | if (++accv == 8) { | ||
| 170 | output[offset++] = acc.toUByte() | ||
| 171 | acc = 0 | ||
| 172 | accv = 0 | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | val err = (pixel - bwpixel) / 42 | ||
| 177 | fun AddSatShift(xoff : Int, yoff: Int, shift : Int) { | ||
| 178 | val pixelold = scratch[(row + yoff) * DISPLAY_WIDTH + column + xoff ] | ||
| 179 | var r = pixelold + (err shl (16 - shift)) | ||
| 180 | if ( r < 0 ) r = 0 | ||
| 181 | if ( r > 0xFFFFFF) r = 0xFFFFFF | ||
| 182 | scratch[(row + yoff) * DISPLAY_WIDTH + column + xoff ] = r | ||
| 183 | } | ||
| 184 | |||
| 185 | AddSatShift(0, 1, 13) | ||
| 186 | AddSatShift(0, 2, 14) | ||
| 187 | if (column > 0) { | ||
| 188 | AddSatShift(-1, 1, 14) | ||
| 189 | AddSatShift(-1, 2, 15) | ||
| 190 | } | ||
| 191 | |||
| 192 | if (column > 1) { | ||
| 193 | AddSatShift(-2, 1, 15) | ||
| 194 | AddSatShift(-2, 2, 16) | ||
| 195 | } | ||
| 196 | |||
| 197 | if (column < DISPLAY_WIDTH - 1) { | ||
| 198 | AddSatShift( 1, 0, 13) | ||
| 199 | AddSatShift( 1, 1, 14) | ||
| 200 | AddSatShift( 1, 2, 15) | ||
| 201 | } | ||
| 202 | |||
| 203 | if (column < DISPLAY_WIDTH - 2) { | ||
| 204 | AddSatShift( 2, 0, 14) | ||
| 205 | AddSatShift( 2, 1, 15) | ||
| 206 | AddSatShift( 2, 2, 16) | ||
| 207 | } | ||
| 208 | } | ||
| 209 | val address = InetSocketAddress("172.23.42.29", 2342 ) | ||
| 210 | // val address = InetSocketAddress("192.168.178.69", 2342 ) | ||
| 211 | try { | ||
| 212 | socket.send(DatagramPacket(output.toByteArray(), offset, address)) | ||
| 213 | } catch (e: Exception) { | ||
| 214 | // Ignore network exceptions | ||
| 215 | } | ||
| 216 | |||
| 217 | // Compute the FPS of the entire pipeline | ||
| 218 | val frameCount = 10 | ||
| 219 | if (++frameCounter % frameCount == 0) { | ||
| 220 | frameCounter = 0 | ||
| 221 | val now = System.currentTimeMillis() | ||
| 222 | val delta = now - lastFpsTimestamp | ||
| 223 | val fps = 1000 * frameCount.toFloat() / delta | ||
| 224 | // Log.d(TAG, "FPS: ${"%.02f".format(fps)} " + bitmapBuffer.width + " x " + bitmapBuffer.height) | ||
| 225 | |||
| 226 | activityCameraBinding.viewFinder.post { | ||
| 227 | activityCameraBinding.textPrediction.text = "FPS: ${"%.02f".format(fps)}" | ||
| 228 | } | ||
| 229 | |||
| 230 | lastFpsTimestamp = now | ||
| 231 | } | ||
| 232 | }) | ||
| 233 | |||
| 234 | // Create a new camera selector each time, enforcing lens facing | ||
| 235 | val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build() | ||
| 236 | |||
| 237 | // Apply declared configs to CameraX using the same lifecycle owner | ||
| 238 | cameraProvider.unbindAll() | ||
| 239 | cameraProvider.bindToLifecycle( | ||
| 240 | this as LifecycleOwner, cameraSelector, preview, imageAnalysis) | ||
| 241 | |||
| 242 | // Use the camera object to link our preview use case with the view | ||
| 243 | preview.setSurfaceProvider(activityCameraBinding.viewFinder.surfaceProvider) | ||
| 244 | |||
| 245 | }, ContextCompat.getMainExecutor(this)) | ||
| 246 | } | ||
| 247 | |||
| 248 | override fun onResume() { | ||
| 249 | super.onResume() | ||
| 250 | |||
| 251 | // Request permissions each time the app resumes, since they can be revoked at any time | ||
| 252 | if (!hasPermissions(this)) { | ||
| 253 | ActivityCompat.requestPermissions( | ||
| 254 | this, permissions.toTypedArray(), permissionsRequestCode) | ||
| 255 | } else { | ||
| 256 | bindCameraUseCases() | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | override fun onRequestPermissionsResult( | ||
| 261 | requestCode: Int, | ||
| 262 | permissions: Array<out String>, | ||
| 263 | grantResults: IntArray | ||
| 264 | ) { | ||
| 265 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) | ||
| 266 | if (requestCode == permissionsRequestCode && hasPermissions(this)) { | ||
| 267 | bindCameraUseCases() | ||
| 268 | } else { | ||
| 269 | finish() // If we don't have the required permissions, we can't run | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | /** Convenience method used to check if all permissions required by this app are granted */ | ||
| 274 | private fun hasPermissions(context: Context) = permissions.all { | ||
| 275 | ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED | ||
| 276 | } | ||
| 277 | |||
| 278 | companion object { | ||
| 279 | private val TAG = CameraActivity::class.java.simpleName | ||
| 280 | } | ||
| 281 | } | ||
