如何在TensorFlow Lite安卓项目中录制识别画面
实现TensorFlow Lite目标检测安卓项目的录制功能
前提说明
基于你已搭建的TensorFlow Lite目标检测安卓项目(使用CameraX架构),以下是添加录制带目标检测框的摄像头画面并保存到本地的完整方案:
1. 配置权限
在AndroidManifest.xml中添加必要的录制和存储权限:
<!-- Android 12及以下存储权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13+存储权限 --> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- 音频录制权限(不需要可删除) --> <uses-permission android:name="android.permission.RECORD_AUDIO" />
确保已存在CAMERA权限(原项目已配置)。
2. 修改布局
在activity_main.xml中添加录制控制按钮(示例放在PreviewView下方):
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp" android:gravity="center"> <Button android:id="@+id/btn_start_record" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始录制" /> <Button android:id="@+id/btn_stop_record" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="停止录制" android:enabled="false" android:layout_marginStart="16dp" /> </LinearLayout>
3. 代码实现(分两种方案)
方案一:录制带检测框的摄像头预览区域(简单快捷)
此方案直接录制屏幕上显示的PreviewView和检测框OverlayView,无需修改目标检测逻辑:
3.1 声明变量
在MainActivity中添加:
private var mediaProjectionManager: MediaProjectionManager? = null private var mediaProjection: MediaProjection? = null private var virtualDisplay: VirtualDisplay? = null private var mediaRecorder: MediaRecorder? = null private lateinit var outputDirectory: File
3.2 初始化输出目录
在onCreate中添加:
outputDirectory = getOutputDirectory() mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
添加目录获取方法:
private fun getOutputDirectory(): File { val mediaDir = externalMediaDirs.firstOrNull()?.let { File(it, "TFLiteDetection").apply { mkdirs() } } return if (mediaDir != null && mediaDir.exists()) mediaDir else filesDir }
3.3 处理录制权限与控制逻辑
添加权限请求与录制逻辑:
// 开始录制按钮点击 btn_start_record.setOnClickListener { val intent = mediaProjectionManager?.createScreenCaptureIntent() startActivityForResult(intent, 1001) } // 停止录制按钮点击 btn_stop_record.setOnClickListener { mediaRecorder?.stop() mediaRecorder?.release() mediaProjection?.stop() virtualDisplay?.release() btn_start_record.isEnabled = true btn_stop_record.isEnabled = false Toast.makeText(this, "录制文件已保存至${outputDirectory.absolutePath}", Toast.LENGTH_LONG).show() }
3.4 处理权限回调
重写onActivityResult(AndroidX可用registerForActivityResult替代):
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 1001 && resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, data!!) setupMediaRecorder() startRecordingScreen() btn_start_record.isEnabled = false btn_stop_record.isEnabled = true } } private fun setupMediaRecorder() { mediaRecorder = MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) // 不需要音频可删除 setVideoSource(MediaRecorder.VideoSource.SURFACE) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setVideoEncoder(MediaRecorder.VideoEncoder.H264) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) // 不需要音频可删除 setVideoSize(previewView.width, previewView.height) setVideoFrameRate(30) setOutputFile(File(outputDirectory, "detection_${System.currentTimeMillis()}.mp4").absolutePath) prepare() } } private fun startRecordingScreen() { val surface = mediaRecorder?.surface ?: return virtualDisplay = mediaProjection?.createVirtualDisplay( "DetectionRecording", previewView.width, previewView.height, resources.displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null ) }
方案二:录制处理后的帧(精准无UI干扰)
此方案直接在目标检测的帧上绘制检测框,再将处理后的帧录制保存,适合需要纯净画面的场景:
3.1 替换CameraX的VideoCapture逻辑
在原项目的setupCamera方法中,移除默认的VideoCapture,改用自定义MediaRecorder处理ImageAnalysis的帧:
// 原项目的ImageAnalyzer保持不变,添加帧录制逻辑 private val imageAnalyzer = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { it.setAnalyzer(cameraExecutor) { imageProxy -> val results = detector.processImage(imageProxy) // 绘制检测框到帧上 drawDetectionResults(imageProxy, results) // 将处理后的帧写入MediaRecorder writeFrameToRecorder(imageProxy) imageProxy.close() } }
3.2 添加帧绘制与录制方法
private fun drawDetectionResults(imageProxy: ImageProxy, results: List<Detection>) { val bitmap = imageProxy.toBitmap() val canvas = Canvas(bitmap) // 复用原项目的OverlayView绘制逻辑,直接画到bitmap上 results.forEach { detection -> val boundingBox = detection.boundingBox val paint = Paint().apply { color = Color.RED style = Paint.Style.STROKE strokeWidth = 4f } canvas.drawRect(boundingBox, paint) // 绘制标签与置信度 val labelPaint = Paint().apply { color = Color.WHITE textSize = 32f typeface = Typeface.DEFAULT_BOLD } canvas.drawText( "${detection.label} %.2f".format(detection.score), boundingBox.left, boundingBox.top - 10, labelPaint ) } // 将bitmap转为YUV格式写入MediaRecorder(需要自行实现格式转换) } private fun writeFrameToRecorder(imageProxy: ImageProxy) { // 实现将处理后的Bitmap转为MediaRecorder可接受的格式并写入 // 可参考Android MediaRecorder的帧写入API }
注:此方案需要处理YUV格式转换,相对复杂,适合有Android多媒体开发经验的场景。
4. 测试与验证
运行项目后,点击开始录制按钮,录制完成后可在设备的Android/media/[你的应用包名]/TFLiteDetection目录下找到录制的视频文件。
内容的提问来源于stack exchange,提问作者milstrike
相关产品推荐
相关产品推荐

