gpt4 book ai didi

android - 如何将DJI H264 FPV Feed读取为OpenCV Mat对象?

转载 作者:行者123 更新时间:2023-11-29 23:08:35 34 4
gpt4 key购买 nike

TDLR:所有DJI开发人员都将从将原始H264视频流字节数组解码为与OpenCV兼容的格式中受益。

我花了很多时间寻找一种解决方案,以将DJI的FPV提要读取为OpenCV Mat对象。我可能忽略了一些基本知识,因为我对图像编码/解码不太熟悉。

将来遇到它的开发人员可能会遇到与我同样的问题。如果DJI开发人员可以直接使用opencv而不需要第3方库,那就太好了。

我愿意在必要时使用ffmpeg或JavaCV,但这对于大多数Android开发人员来说是一大障碍,因为我们将不得不使用cpp,ndk,终端进行测试等。这两个选项似乎都很耗时。 This JavaCV H264 conversion似乎不必要地复杂。我是从this relevant question找到的。

我认为问题在于我们需要同时解码长度为6的字节数组(信息数组)和具有当前帧信息的字节数组。

基本上,DJI的FPV供稿有多种格式。

VideoFeeder.VideoDataListener中的

  • Raw H264(MPEG4)
  •     // The callback for receiving the raw H264 video data for camera live view
    mReceivedVideoDataListener = new VideoFeeder.VideoDataListener() {
    @Override
    public void onReceive(byte[] videoBuffer, int size) {
    //Log.d("BytesReceived", Integer.toString(videoStreamFrameNumber));
    if (videoStreamFrameNumber++%30 == 0){
    //convert video buffer to opencv array
    OpenCvAndModelAsync openCvAndModelAsync = new OpenCvAndModelAsync();
    openCvAndModelAsync.execute(videoBuffer);
    }
    if (mCodecManager != null) {
    mCodecManager.sendDataToDecoder(videoBuffer, size);
    }
    }
    };

  • DJI还拥有自己的Android解码器示例,其中包含FFMPEG,可将其转换为YUV格式。
  •     @Override
    public void onYuvDataReceived(final ByteBuffer yuvFrame, int dataSize, final int width, final int height) {
    //In this demo, we test the YUV data by saving it into JPG files.
    //DJILog.d(TAG, "onYuvDataReceived " + dataSize);
    if (count++ % 30 == 0 && yuvFrame != null) {
    final byte[] bytes = new byte[dataSize];
    yuvFrame.get(bytes);
    AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
    if (bytes.length >= width * height) {
    Log.d("MatWidth", "Made it");
    YuvImage yuvImage = saveYuvDataToJPEG(bytes, width, height);
    Bitmap rgbYuvConvert = convertYuvImageToRgb(yuvImage, width, height);

    Mat yuvMat = new Mat(height, width, CvType.CV_8UC1);
    yuvMat.put(0, 0, bytes);
    //OpenCv Stuff
    }
    }
    });
    }
    }

    编辑:对于那些想看DJI的YUV到JPEG功能的人,这里来自示例应用程序:
    private YuvImage saveYuvDataToJPEG(byte[] yuvFrame, int width, int height){
    byte[] y = new byte[width * height];
    byte[] u = new byte[width * height / 4];
    byte[] v = new byte[width * height / 4];
    byte[] nu = new byte[width * height / 4]; //
    byte[] nv = new byte[width * height / 4];

    System.arraycopy(yuvFrame, 0, y, 0, y.length);
    Log.d("MatY", y.toString());
    for (int i = 0; i < u.length; i++) {
    v[i] = yuvFrame[y.length + 2 * i];
    u[i] = yuvFrame[y.length + 2 * i + 1];
    }
    int uvWidth = width / 2;
    int uvHeight = height / 2;
    for (int j = 0; j < uvWidth / 2; j++) {
    for (int i = 0; i < uvHeight / 2; i++) {
    byte uSample1 = u[i * uvWidth + j];
    byte uSample2 = u[i * uvWidth + j + uvWidth / 2];
    byte vSample1 = v[(i + uvHeight / 2) * uvWidth + j];
    byte vSample2 = v[(i + uvHeight / 2) * uvWidth + j + uvWidth / 2];
    nu[2 * (i * uvWidth + j)] = uSample1;
    nu[2 * (i * uvWidth + j) + 1] = uSample1;
    nu[2 * (i * uvWidth + j) + uvWidth] = uSample2;
    nu[2 * (i * uvWidth + j) + 1 + uvWidth] = uSample2;
    nv[2 * (i * uvWidth + j)] = vSample1;
    nv[2 * (i * uvWidth + j) + 1] = vSample1;
    nv[2 * (i * uvWidth + j) + uvWidth] = vSample2;
    nv[2 * (i * uvWidth + j) + 1 + uvWidth] = vSample2;
    }
    }
    //nv21test
    byte[] bytes = new byte[yuvFrame.length];
    System.arraycopy(y, 0, bytes, 0, y.length);
    for (int i = 0; i < u.length; i++) {
    bytes[y.length + (i * 2)] = nv[i];
    bytes[y.length + (i * 2) + 1] = nu[i];
    }
    Log.d(TAG,
    "onYuvDataReceived: frame index: "
    + DJIVideoStreamDecoder.getInstance().frameIndex
    + ",array length: "
    + bytes.length);
    YuvImage yuver = screenShot(bytes,Environment.getExternalStorageDirectory() + "/DJI_ScreenShot", width, height);
    return yuver;
    }

    /**
    * Save the buffered data into a JPG image file
    */
    private YuvImage screenShot(byte[] buf, String shotDir, int width, int height) {
    File dir = new File(shotDir);
    if (!dir.exists() || !dir.isDirectory()) {
    dir.mkdirs();
    }
    YuvImage yuvImage = new YuvImage(buf,
    ImageFormat.NV21,
    width,
    height,
    null);

    OutputStream outputFile = null;

    final String path = dir + "/ScreenShot_" + System.currentTimeMillis() + ".jpg";

    try {
    outputFile = new FileOutputStream(new File(path));
    } catch (FileNotFoundException e) {
    Log.e(TAG, "test screenShot: new bitmap output file error: " + e);
    //return;
    }
    if (outputFile != null) {
    yuvImage.compressToJpeg(new Rect(0,
    0,
    width,
    height), 100, outputFile);
    }
    try {
    outputFile.close();
    } catch (IOException e) {
    Log.e(TAG, "test screenShot: compress yuv image error: " + e);
    e.printStackTrace();
    }

    runOnUiThread(new Runnable() {
    @Override
    public void run() {
    displayPath(path);
    }
    });
    return yuvImage;
    }

  • DJI似乎也具有“getRgbaData”功能,但实际上没有在线或DJI的单个示例。继续吧,谷歌“DJI getRgbaData” ... api文档的参考说明了自解释参数和返回值,但仅此而已。我不知道该在哪里调用,并且似乎没有YUV那样的回调函数。您不能直接从h264b字节数组中调用它,但是也许可以从yuv数据中获取它。

  • 选项1比选项2更可取,因为YUV格式存在质量问题。选项3也可能涉及解码器。

    这是DJI自己的YUV转换产生的屏幕截图。 WalletPhoneYuv

    我已经看过很多有关如何改善YUV,消除绿色和黄色以及诸如此类的东西,但是在这一点上,如果DJI无法正确完成,我不想在那儿投入资源。

    关于选项1,我知道如果必须走视频解码路线,那么FFMPEG和JavaCV似乎是不错的选择。

    而且,据我了解,没有FFMPEG,OpenCV无法处理读写视频文件,但是我没有尝试读取视频文件,而是试图读取H264 / MPEG4 byte []数组。以下代码似乎获得了积极的结果。
        /* Async OpenCV Code */
    private class OpenCvAndModelAsync extends AsyncTask<byte[], Void, double[]> {
    @Override
    protected double[] doInBackground(byte[]... params) {//Background Code Executing. Don't touch any UI components
    //get fpv feed and convert bytes to mat array
    Mat videoBufMat = new Mat(4, params[0].length, CvType.CV_8UC4);
    videoBufMat.put(0,0, params[0]);
    //if I add this in it says the bytes are empty.
    //Mat videoBufMat = Imgcodecs.imdecode(encodeVideoBuf, Imgcodecs.IMREAD_ANYCOLOR);
    //encodeVideoBuf.release();
    Log.d("MatRgba", videoBufMat.toString());
    for (int i = 0; i< videoBufMat.rows(); i++){
    for (int j=0; j< videoBufMat.cols(); j++){
    double[] rgb = videoBufMat.get(i, j);
    Log.i("Matrix", "red: "+rgb[0]+" green: "+rgb[1]+" blue: "+rgb[2]+" alpha: "
    + rgb[3] + " Length: " + rgb.length + " Rows: "
    + videoBufMat.rows() + " Columns: " + videoBufMat.cols());
    }
    }
    double[] center = openCVThingy(videoBufMat);
    return center;
    }
    protected void onPostExecute(double[] center) {
    //handle ui or another async task if necessary
    }
    }


    行= 4,列> 30k。我得到许多看似有效的RGB值,例如,红色= 113,绿色= 75,蓝色= 90,alpha = 220等。但是,我得到了大量的0,0,0,0值。这应该可以,因为Black为0,0,0(尽管我原本以为alpha会更高),并且图像中有一个黑色物体。我也似乎没有得到任何白色值255、255、255,即使也有很多白色区域。我没有记录整个字节,因此它可能在那里,但是我还没有看到它。

    但是,当我尝试从该图像计算轮廓时,几乎总会得到力矩(中心x,y)正好在图像的中心。此错误与我的滤色镜或轮廓算法无关,因为我在python中编写了一个脚本,并测试了如何通过读取静止图像并在两个Python中获得完全相同数量的轮廓,位置等在Android中正确实现了该脚本和Android。

    我注意到这与videoBuffer字节大小有关(如果可以解释为什么其他所有长度都是6,则加分)
    2019-05-23 21:14:29.601 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 2425
    2019-05-23 21:14:29.802 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 2659
    2019-05-23 21:14:30.004 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6
    2019-05-23 21:14:30.263 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6015
    2019-05-23 21:14:30.507 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6
    2019-05-23 21:14:30.766 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 4682
    2019-05-23 21:14:31.005 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6
    2019-05-23 21:14:31.234 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 2840
    2019-05-23 21:14:31.433 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 4482
    2019-05-23 21:14:31.664 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6
    2019-05-23 21:14:31.927 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 4768
    2019-05-23 21:14:32.174 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6
    2019-05-23 21:14:32.433 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 4700
    2019-05-23 21:14:32.668 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6
    2019-05-23 21:14:32.864 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 4740
    2019-05-23 21:14:33.102 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 6
    2019-05-23 21:14:33.365 21431-22086/com.dji.simulatorDemo D/VideoBufferSize: 4640

    我的问题:

    I.这是将h264字节读取为mat的正确格式吗?
    假设格式为RGBA,则意味着row = 4,column = byte []。length和CvType.CV_8UC4。我的高度和宽度正确吗?某事告诉我YUV的高度和宽度已关闭。我得到了一些有意义的结果,但是轮廓正好在中心,就像使用H264一样。

    二。 OpenCV会这样处理Android中的MP4吗?如果不是,是否需要使用FFMPEG或JavaCV?

    三, int大小与它有关吗?为什么int大小有时为6,而其他时候为2400至6000?我已经听说过此框架信息与下一框架信息之间的区别,但是我不了解如何在此处应用它。

    我开始认为这就是问题所在。由于我需要获取6字节数组以获取有关下一帧的信息,因此我的模数30可能不正确。那么我应该将第29或31帧作为每个帧的格式字节传递吗?如何在opencv中完成,还是注定要使用复杂的ffmpeg?我将如何加入相邻的帧/字节数组?

    IV。我可以使用Imcodecs修复此问题吗?我希望opencv可以本地处理该帧是该帧的颜色还是下一帧的信息。我添加了以下代码,但是得到一个空数组:
    Mat videoBufMat = Imgcodecs.imdecode(new MatOfByte(params[0]), Imgcodecs.IMREAD_UNCHANGED);

    这也是空的:
    Mat encodeVideoBuf = new Mat(4, params[0].length, CvType.CV_8UC4);
    encodeVideoBuf.put(0,0, params[0]);
    Mat videoBufMat = Imgcodecs.imdecode(encodeVideoBuf, Imgcodecs.IMREAD_UNCHANGED);

    V.我应该尝试将字节转换为Android jpeg然后导入吗?为什么djis yuv解码器看起来如此复杂?这让我不必尝试使用ffmpeg或Javacv并仅坚持使用Android解码器或opencv解码器就变得审慎。

    VI。我应该在什么阶段调整框架的大小以加快计算速度?

    编辑: DJI支持返回给我,并确认他们没有用于执行我所描述的操作的任何示例。这是我们社区让所有人都能使用的时间!

    经过进一步的研究,我认为opencv将无法处理此问题,因为opencv的android sdk不具有视频文件/ URL的功能(自产的MJPEG编解码器除外)。

    那么在Android中有没有一种方法可以将其转换为mjpeg或类似格式以便进行读取?在我的应用程序中,我每秒只需要1或2帧,因此也许可以将图像另存为jpeg。

    但是对于实时应用程序,我们可能需要编写自己的解码器。请提供帮助,以便我们对所有人开放!这个 question看起来很有希望:

    最佳答案

    首先H264和h264是不同的。与h264 H264 x264 X264混合使用也是ez。上次使用时,我记得我在DJI设备上使用了h264选项。确保选择正确的编解码器

    ffmpeg和ffplay将直接起作用。我记得Opencv可以在这两个基础之上构建。因此使用FFMEPG / FFSHOW插件转换为cv::Mat应该不难。遵循文档

    OpenCV can use the FFmpeg library (http://ffmpeg.org/) as backend to record, convert and stream audio and video. FFMpeg is a complete, cross-reference solution. If you enable FFmpeg while configuring OpenCV than CMake will download and install the binaries in OPENCV_SOURCE_CODE/3rdparty/ffmpeg/. To use FFMpeg at runtime, you must deploy the FFMepg binaries with your application.

    https://docs.opencv.org/3.4/d0/da7/videoio_overview.html



    上次,我必须使用DJI PSDK。并且它们仅允许使用H.264的UDP端口udp://192.168.5.293:23003进行流传输
    因此,我编写了一个简单的ffmpeg接口(interface)以流式传输到PSDK。但是我必须事先调试它。因此,我使用ffplay来显示此网络流以证明其有效。这是显示流的脚本。因此,您必须在此之上作为opencv插件工作
    ffplay -f h264 -i udp://192.168.1.45:23003 

    关于android - 如何将DJI H264 FPV Feed读取为OpenCV Mat对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56284630/

    34 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com