在之前的系列文章中,已经为Android准备了训练有素的网络模型,创建了使用TensorFlow Lite的项目,并为解决方案的其他组件进行了工作。但直到现在,开发一直是使用静态图像进行的。在本篇文章中,将从使用静态图像切换到使用摄像头的实时视频流。编写的大部分代码无需修改即可工作。如果已经按照之前的文章进行操作,那么应该已经设置了应用程序的权限,以允许访问摄像头。
本项目部分的UI将在AndroidStudio创建的全屏活动中构建。界面的内容将包含几个项目。一个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,然后将该Bitmap传递给Detector。当TextureView准备好显示内容时,它会通知SurfaceTextureListener。创建一个SurfaceTextureListener并选择并打开摄像头。SurfaceTextureListener的接口如下所示。
class 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)
}
onSurfaceTextureAvailable和onSurfaceTextureUpdated函数是最感兴趣的函数。在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!!
)
}