Android应用中如何从无人机相机流获取Bitmap/RGBA数据?
从DJI无人机相机流提取Bitmap/RGBA数据用于目标检测
我来帮你搞定这个需求——刚好我之前在Android项目里做过DJI视频流和目标检测的集成,给你一步步拆解实现方法:
1. 初始化视频流监听与DJICodecManager
首先你需要注册VideoFeeder的视频数据监听,同时初始化DJICodecManager来处理编码后的视频帧。记得要在DJI SDK初始化完成后做这些操作:
private DJICodecManager mCodecManager; // 在SDK初始化成功后调用 private void setupVideoStream() { // 获取主相机视频流 VideoFeed primaryFeed = VideoFeeder.getInstance().getPrimaryVideoFeed(); if (primaryFeed != null) { primaryFeed.addVideoDataListener(mVideoDataListener); } // 初始化CodecManager,传入Context和Surface(如果不需要预览可以传null) mCodecManager = new DJICodecManager(this, null, new DJICodecManager.OnFrameAvailableListener() { @Override public void onFrameAvailable(byte[] yuvData, int width, int height) { // 这里拿到解码后的YUV420SP格式数据,接下来转换为Bitmap/RGBA processYUVFrame(yuvData, width, height); } }); } private final VideoDataListener mVideoDataListener = new VideoDataListener() { @Override public void onReceive(byte[] videoBuffer, int size) { // 将原始编码数据传给CodecManager解码 if (mCodecManager != null) { mCodecManager.sendDataToDecoder(videoBuffer, size); } } };
2. 将YUV数据转换为Bitmap/RGBA数组
DJICodecManager输出的是YUV420SP格式的数据(也就是NV21),我们需要把它转换成目标检测需要的Bitmap或者RGBA字节数组。这里推荐两种方式:
方式一:转换为Bitmap
如果你的目标检测算法可以直接接收Bitmap,用这个方法:
private Bitmap processYUVFrame(byte[] yuvData, int width, int height) { // 创建YUVImage对象 YuvImage yuvImage = new YuvImage(yuvData, ImageFormat.NV21, width, height, null); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // 压缩为JPEG(如果不需要压缩可以调整参数) yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, outputStream); byte[] jpegData = outputStream.toByteArray(); // 转换为Bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); // 注意:如果需要竖屏显示,可能需要旋转Bitmap(根据无人机相机方向调整) bitmap = rotateBitmap(bitmap, 90); // 示例旋转90度,根据实际情况调整 return bitmap; } private Bitmap rotateBitmap(Bitmap bitmap, int degrees) { Matrix matrix = new Matrix(); matrix.postRotate(degrees); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); }
方式二:直接转换为RGBA字节数组
如果目标检测算法需要原始RGBA数据(比如TensorFlow Lite常用的格式),用RenderScript来做转换会更高效:
private RenderScript mRenderScript; private ScriptIntrinsicYuvToRGB mYuvToRgbScript; private Allocation mYuvAllocation; private Allocation mRgbAllocation; private byte[] mRgbaData; // 在Activity onCreate中初始化RenderScript private void initRenderScript() { mRenderScript = RenderScript.create(this); mYuvToRgbScript = ScriptIntrinsicYuvToRGB.create(mRenderScript, Element.U8_4(mRenderScript)); } private byte[] convertYUVToRGBA(byte[] yuvData, int width, int height) { // 初始化Allocation(只需要第一次做) if (mYuvAllocation == null) { Type.Builder yuvType = new Type.Builder(mRenderScript, Element.U8(mRenderScript)) .setX(width) .setY(height) .setYuvFormat(ImageFormat.NV21); mYuvAllocation = Allocation.createTyped(mRenderScript, yuvType.create(), Allocation.USAGE_SCRIPT); Type.Builder rgbType = new Type.Builder(mRenderScript, Element.U8_4(mRenderScript)) .setX(width) .setY(height); mRgbAllocation = Allocation.createTyped(mRenderScript, rgbType.create(), Allocation.USAGE_SCRIPT); mRgbaData = new byte[width * height * 4]; } // 传入YUV数据 mYuvAllocation.copyFrom(yuvData); // 执行转换 mYuvToRgbScript.setInput(mYuvAllocation); mYuvToRgbScript.forEach(mRgbAllocation); // 取出RGBA数据 mRgbAllocation.copyTo(mRgbaData); return mRgbaData; }
3. 性能优化要点
- 缩小分辨率:如果目标检测不需要4K/2K的高分辨率,可以在解码前通过
DJICodecManager的setVideoSize方法设置更小的分辨率,减少计算量。 - 线程处理:视频帧回调和转换操作都在非UI线程,目标检测的推理也应该放到单独的后台线程(比如用
AsyncTask或者Coroutine),避免阻塞UI。 - 资源释放:在Activity销毁或者退出页面时,记得移除视频流监听并释放资源:
@Override protected void onDestroy() { super.onDestroy(); VideoFeed primaryFeed = VideoFeeder.getInstance().getPrimaryVideoFeed(); if (primaryFeed != null) { primaryFeed.removeVideoDataListener(mVideoDataListener); } if (mCodecManager != null) { mCodecManager.cleanup(); } if (mRenderScript != null) { mRenderScript.destroy(); } }
注意事项
- 无人机相机的方向可能会影响画面旋转,你需要根据实际的飞行状态调整Bitmap的旋转角度。
- 如果是使用TensorFlow Lite等框架,建议直接传入RGBA数组并调整为模型要求的输入尺寸(比如224x224),这样比转换为Bitmap更高效。
内容的提问来源于stack exchange,提问作者Nils Lorentzon
相关产品推荐
相关产品推荐

