如何在Java中将byte[]转为Mat?DJI无人机视频流处理求助
解决DJI视频流结合OpenCV滤镜的问题
首先帮你理清几个关键问题,再针对两种方案给出具体的解决步骤:
一、先搞懂int size的作用
这个size参数是videoBuffer中有效视频数据的实际长度。因为DJI的SDK会预先分配一个足够大的byte数组作为缓冲区,但每次收到的视频帧数据不一定会填满整个数组,所以必须用size来读取有效部分,不能直接用videoBuffer.length,否则会读到末尾的无效数据,导致视频解析错误或滤镜处理出问题。
二、方案一:Java中转换为Mat再传递给C类
这种方案的核心是先在Java层把YUV格式的byte数组转成OpenCV的Mat,再将Mat的原生地址传给C层处理。步骤如下:
1. 确认DJI视频流格式
Phantom系列无人机的视频流通常是NV21格式的YUV420数据,所以转换Mat时要对应正确的类型:
// 假设已经通过DJI的回调获取到视频宽高mWidth、mHeight Mat yuvMat = new Mat(mHeight + mHeight/2, mWidth, CvType.CV_8UC1); // 只写入size长度的有效数据,而不是整个数组 yuvMat.put(0, 0, videoBuffer, 0, size);
2. 转换为RGB格式供滤镜处理
YUV格式不适合直接做滤镜,先转成RGB:
Mat rgbMat = new Mat(); Imgproc.cvtColor(yuvMat, rgbMat, Imgproc.COLOR_YUV2RGB_NV21);
3. 传递Mat地址给C类
通过rgbMat.getNativeObjAddr()获取Mat的原生内存地址,传给C层:
// 调用你的C类方法,传入Mat地址 YourCClass.processMat(rgbMat.getNativeObjAddr());
4. C层接收并处理
在C++中通过地址还原Mat对象,然后应用滤镜:
extern "C" JNIEXPORT void JNICALL Java_com_your_package_YourCClass_processMat(JNIEnv *env, jobject thiz, jlong mat_addr) { // 从地址还原Mat cv::Mat* rgb_mat = (cv::Mat*)mat_addr; if (rgb_mat == nullptr || rgb_mat->empty()) { return; } // 应用滤镜示例:灰度化 cv::Mat gray_mat; cv::cvtColor(*rgb_mat, gray_mat, cv::COLOR_RGB2GRAY); // 如果需要将处理后的结果回写到原Mat(供Java层使用) gray_mat.copyTo(*rgb_mat); }
注意事项
- 要确保Java层的
rgbMat在C层处理完成前不会被GC回收,可以通过全局引用或者保持局部引用的方式延长生命周期。 - 如果处理后需要将图像传回DJI的解码器,还要把RGB转回NV21格式,再写入byte数组。
三、方案二:直接在C层转换byte数组为Mat(推荐,性能更好)
这种方案避免了Java层到C层的数据拷贝,直接在JNI中处理原始byte数组,性能更优,适合实时视频处理:
1. 定义JNI方法
在Java中声明native方法:
public class VideoProcessor { static { System.loadLibrary("video-processing"); } public native void processVideoFrame(byte[] videoBuffer, int size, int width, int height); }
2. JNI层实现转换与滤镜处理
#include <jni.h> #include <opencv2/opencv.hpp> using namespace cv; extern "C" JNIEXPORT void JNICALL Java_com_your_package_VideoProcessor_processVideoFrame( JNIEnv *env, jobject thiz, jbyteArray video_buffer, jint size, jint width, jint height) { // 获取byte数组的指针 jbyte* buffer_ptr = env->GetByteArrayElements(video_buffer, nullptr); if (buffer_ptr == nullptr) { return; } // 构造NV21格式的YUV Mat Mat yuv_mat(height + height/2, width, CV_8UC1, (unsigned char*)buffer_ptr); Mat rgb_mat; // 转RGB格式 cvtColor(yuv_mat, rgb_mat, COLOR_YUV2RGB_NV21); // 应用滤镜示例:高斯模糊 GaussianBlur(rgb_mat, rgb_mat, Size(5,5), 0); // 如果需要将处理后的图像转回YUV,供DJI解码器使用 Mat processed_yuv; cvtColor(rgb_mat, processed_yuv, COLOR_RGB2YUV_NV21); // 将处理后的数据复制回原byte数组 memcpy(buffer_ptr, processed_yuv.data, processed_yuv.total() * processed_yuv.elemSize()); // 释放byte数组指针 env->ReleaseByteArrayElements(video_buffer, buffer_ptr, 0); }
3. 在DJI的回调中调用JNI方法
首先要获取视频的宽高,可以通过CodecManager的帧回调获取:
private int mVideoWidth = 0; private int mVideoHeight = 0; // 初始化时注册帧回调 mCodecManager.setDecoderFrameCallback(new CodecManager.DecoderFrameCallback() { @Override public void onDecodedFrame(Frame frame) { mVideoWidth = frame.getWidth(); mVideoHeight = frame.getHeight(); } }); // 在视频数据回调中处理 mReceivedVideoDataCallBack = new VideoFeeder.VideoDataCallback() { @Override public void onReceive(byte[] videoBuffer, int size) { if (mCodecManager != null && mVideoWidth > 0 && mVideoHeight > 0) { // 先调用JNI处理视频帧 new VideoProcessor().processVideoFrame(videoBuffer, size, mVideoWidth, mVideoHeight); // 再传给解码器渲染 mCodecManager.sendDataToDecoder(videoBuffer, size); } } };
常见问题排查
- 图像颜色异常/花屏:大概率是YUV格式判断错误,确认DJI视频流是NV21还是YV12,调整
cvtColor的参数(比如COLOR_YUV2RGB_YV12)。 - 性能卡顿:尽量用方案二,减少Java-C之间的数据拷贝;滤镜算法尽量优化,避免复杂运算。
- 内存泄漏:JNI中务必调用
ReleaseByteArrayElements释放指针;Java层的Mat对象要及时回收。
内容的提问来源于stack exchange,提问作者Jaguar
相关产品推荐
相关产品推荐

