65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Android 上使用实时摄像头进行 AI 危险检测

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2021年1月12日

CPOL

4分钟阅读

viewsIcon

8787

在安卓系统上 AI 危害检测系列的下一部分中,我们将让应用程序处理实时数据。

这是关于如何使用 Android 和 TensorFlow Lite 构建实时危害检测系统的系列文章的第六篇。在之前的文章中,我们准备了一个经过训练的网络模型,以便在 Android 中使用,创建了一个使用 TensorFlow Lite 的项目,并致力于该解决方案的其他组件。但到目前为止,开发一直使用的是静态图像。

在此版本中,我们将从使用静态图像切换到使用来自摄像头的实时馈送。我们编写的大部分代码都可以无需修改即可工作。如果您按照之前的文章进行了操作,那么您应该已经设置了应用程序的权限,以允许访问摄像头。

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />

设置用户界面

该项目这部分的用户界面将在 Android Studio 创建的全屏活动中构建。界面的内容将包含一些项目。布局上的 TextureView 显示来自摄像头的视频。InfoOverlayView 是本系列之前文章中创建的一个视图,用于在视频馈送之上渲染高光。

<androidx.constraintlayout.widget.ConstraintLayout
   android:id="@+id/fullscreen_content"
   android:keepScreenOn="true">
   <TextureView android:id="@+id/camera_preview" />
   <net.j2i.drivinghazards.InfoOverlayView />
</androidx.constraintlayout.widget.ConstraintLayout>

TextureView 不会自动显示视频馈送。相反,我们必须编写代码来使用来自摄像头的图像更新它。当 TextureView 更新时,我们可以检索正在显示的帧的 Bitmap 并将其传递给 Detector。当 TextureView 准备好显示内容时,它会通知 SurfaceTextureListenerSurfaceTextureListener 的界面如下所示。

SurfaceTextureListener {
   fun onSurfaceTextureAvailable(
       surface: SurfaceTexture, width: Int, height: Int
   )

   fun onSurfaceTextureSizeChanged(
       surface: SurfaceTexture, width: Int, height: Int
   )

   override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean

   override fun onSurfaceTextureUpdated(surface: SurfaceTexture)
}

函数 onSurfaceTextureAvailableonSurfaceTextureUpdated 是最感兴趣的函数。我们在 onSurfaceTextureAvailable 中打开摄像头,并在 onSurfaceTextureUpdated 中接收更新。

选择正确的摄像头

在这个简化的 onSurfaceTextureAvailable 实现中,应用程序获取摄像头 ID 列表并逐个检查它们,直到找到一个不是前置摄像头的摄像头。对于大多数设备,将只有两个摄像头。有些设备支持将第三个摄像头通过 USB 连接到手机。如果我们想在支持外部摄像头的设备上使用外部摄像头并忽略内置摄像头,则可以选择逻辑更改为过滤 LENS_FACING 属性值为 LENS_FACING_FRONT 的摄像头。函数 openCamera 是我们在代码中定义的函数,稍后将详细介绍。

override fun onSurfaceTextureAvailable(
   surface: SurfaceTexture,
   width: Int,
   height: Int
) {
   val cm = getSystemService(CAMERA_SERVICE) as CameraManager
   for (cameraID in cm.cameraIdList) {
      val characteristics = cm.getCameraCharacteristics(cameraID!!)
      if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
          continue //Skip front facing camera
      }
      mCameraID = cameraID
      openCamera()
      return
   }
}

在函数 openCamera 中,我们使用 CameraManager 打开选定的硬件摄像头。将 UI 布局中 TextView 中的 SurfaceTexture 对象分配一个大小。我们还构建预览的配置,设置预览的宽度、高度和方向。

val manager = getSystemService(CAMERA_SERVICE) as CameraManager
manager.openCamera(mCameraID!!, mCameraStateCallback!!, mBackgroundHandler)
val texture = camera_preview!!.surfaceTexture
texture!!.setDefaultBufferSize(previewSize!!.width, previewSize!!.height)
val previewSurface = Surface(texture)
mPreviewCaptureRequestBuilder =
   mCameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
mPreviewCaptureRequestBuilder!!.set(CaptureRequest.JPEG_ORIENTATION, mCameraOrientation)
mPreviewCaptureRequestBuilder!!.addTarget(previewSurface)

图像捕获会话

配置完成后,我们可以开始捕获会话。摄像头将激活,我们将开始接收来自它的更新图像帧。

mCameraDevice!!.createCaptureSession(
   Arrays.asList(previewSurface),
   object : CameraCaptureSession.StateCallback() {
       override fun onConfigured(session: CameraCaptureSession) {
           if (mCameraDevice == null) return
           mPreviewCaptureRequest = mPreviewCaptureRequestBuilder!!.build()
           mCameraCaptureSession = session
           mCameraCaptureSession!!.setRepeatingRequest(
                mPreviewCaptureRequest!!,
                mSessionCaptureCallback,
                mBackgroundHandler
           )
       }
   }, null
)

更新的帧被传递回之前声明的 SurfaceTextureListener。在其 onSurfceTextureUpdated 方法中,只需几行代码,我们就可以获得该帧的 Bitmap。可以将此 BitMap 传递给检测器以查找危害。

override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
   val bitmap = Bitmap.createBitmap(
       camera_preview!!.width,
       camera_preview!!.height,
       Bitmap.Config.ARGB_8888
   )
   camera_preview!!.getBitmap(bitmap)
}

获取速度更新

在本系列的早期部分中,我们提到我们不希望检测器在车辆未行驶时检测到危险时发送警报。Detector 实例必须接收有关当前速度的更新。在以下代码中,声明了一个 LocationListener,它仅更新检测器的速度。然后,使用位置提供程序请求将位置更新发送到我们的位置侦听器。

fun requestLocation() {
   locationListener = LocationListener { location ->
       detector.currentSpeedMPS = location.speed
   }

   val locationManager = this.getSystemService(LOCATION_SERVICE) as LocationManager
   val provider = locationManager.getProvider(LocationManager.GPS_PROVIDER)
   val criteria = Criteria()
   criteria.accuracy = Criteria.ACCURACY_FINE
   val providerName = locationManager.getBestProvider(criteria, true)
   val isProviderEnabled = locationManager.isProviderEnabled(providerName!!)
   if (isProviderEnabled) locationManager.requestLocationUpdates(
       providerName,
       1000,
       1f,
       locationListener!!
   )
}

结论

有了这个,我们现在有了一个可用的危害检测器。危害检测器代码的开发相对轻松。通过扩展其检测的数据集,可以改进检测器。这样做需要大量的带有标记数据集的危险照片(请参阅训练您自己的 TensorFlow 神经网络用于 Android)。

创建此数据集需要更多努力,因为您必须将照片的区域标记为包含感兴趣的对象。您可能在不知情的情况下通过完成识别道路图片中的所有人行横道、交通信号灯或其他对象的任务,为这项工作做出了贡献。完成这些测试有助于为自动驾驶汽车和汽车安全系统的开发构建一个大型标记数据数据集。

完成此项目后,您已经构建了一个包含自动驾驶车辆中的某些功能的应用程序!

© . All rights reserved.