解码JPG字节数组为位图

2 人关注

我有一个配备了摄像头的树莓派4,一个过程是使用OpenCV库捕捉图像。这个图像然后被转发到两个目的地。

  • Standard laptop equipped with Arch Linux
  • Android smartphone
  • 这是Raspberry PI上的图像服务器(C++)。

    #define MAX_IMAGESIZE 40090
    typedef struct __attribute__((packed))
        uint16_t len;
        uint8_t data[MAX_IMAGESIZE];
    } image_msg;
    void __attribute__((noreturn)) camera_task()
        cv::VideoCapture capture(0);
        int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        struct sockaddr_in pcaddr, phaddr;
        memset(&pcaddr, 0x00, sizeof(struct sockaddr_in));
        memset(&phaddr, 0x00, sizeof(struct sockaddr_in));
        pcaddr.sin_family = AF_INET;
        pcaddr.sin_addr.s_addr = inet_addr(PC_ADDRESS);
        pcaddr.sin_port = htons(4321);
        phaddr.sin_family = AF_INET;
        phaddr.sin_addr.s_addr = inet_addr(PH_ADDRESS);
        phaddr.sin_port = htons(4321);
        if (!capture.isOpened())
            exit(EXIT_FAILURE);
        cv::Mat frame;
        while (1)
            if (!capture.read(frame))
                exit(EXIT_FAILURE);
            cv::flip(frame, frame, -1);
            std::vector<int> params;
            std::vector<uint8_t> buffer;
            params.push_back(cv::IMWRITE_JPEG_QUALITY);
            params.push_back(60);
            cv::imencode(".jpg", frame, buffer, params);
            image_msg outmsg;
            outmsg.len = static_cast<uint16_t>(buffer.size());
            for(uint16_t i = 0; i < outmsg.len; i++)
                outmsg.data[i] = buffer[i];
            if(sendto(sock, reinterpret_cast<char*>(&outmsg), sizeof(outmsg), 0, reinterpret_cast<struct sockaddr*>(&pcaddr), sizeof(pcaddr)) > 0)
                 //printf("OK sendto PC\n");
                 perror("Camera task to PC");
            if(sendto(sock, reinterpret_cast<char*>(&outmsg), sizeof(outmsg), 0, reinterpret_cast<struct sockaddr*>(&phaddr), sizeof(phaddr)) > 0)
                //printf("OK sendto PHONE\n");
                 perror("Camera task to PHONE");
    

    这是笔记本电脑上的图像客户端(C++)。

    cv::Mat *imagewindow;
    void init_window()
        init_localsock(&imu_socket, &imu_saddr, IMUPORT); //not related with camera
        init_localsock(&speed_socket, &speed_saddr, VELPORT); //not related with camera
        init_localsock(&attitude_socket, &attitude_saddr, ATTPORT); //not related with camera
        init_localsock(&radiation_socket, &radiation_saddr, RADPORT); //not related with camera
        init_localsock(&throttle_socket, &throttle_saddr, THRPORT); //not related with camera
        init_localsock(&image_socket, &image_saddr, RENPORT); //initialize socket on port 4321
        imagewindow = new cv::Mat(600, 600, CV_8UC3, cv::Scalar(0, 0, 0));
        cv::imshow(PROJNAME, *imagewindow);
    void main_loop(const char* board_address)
        while(true)
            imu_task();
            speed_task();
            attitude_task();
            radiation_task();
            throttle_task();
            image_task(); //this is the image receiver task
            render_window();
            cmd_out_task(board_address);
    void image_task()
        image_msg recv; //the same struct defined on the raspberry
        socklen_t len;
        ssize_t bytes_recv = recvfrom(image_socket, &recv, sizeof(image_msg), 0, (struct sockaddr*)&image_saddr, &len);
        if(bytes_recv > 0)
            memset(imagewindow->data, 0x00, imagewindow->dataend - imagewindow->data);
            update_image(recv);
    void update_image(image_msg image)
        std::vector<char> data(image.data, image.data + image.len);
        *imagewindow = cv::imdecode(cv::Mat(data), 1);
    

    而且工作正常。这里是正在发生的情况的截图。

    下面我们来看看问题所在。安卓系统上的接收器是这样的。

    private byte[] recv_image(DatagramSocket sock) throws Exception
        byte[] b = new byte[40090];
        DatagramPacket p = new DatagramPacket(b, b.length);
        byte[] recv_length = new byte[4];
        length[3] = b[0];
        length[2] = b[1];
        int recv_len = ByteBuffer.wrap(length).getInt();
        byte[] image_bytes = new byte[recv_len];
        for(int i = 0; i < recv_len; i++)
            image_bytes[i] = b[4 + i];
        return image_bytes;
    

    接收线程正在做。

    initialize_videosocket(); //init a local UDP socket on port 4321
    while(true)
             final byte[] imagebytes = recv_image(video_sock);
             Bitmap bmp = BitmapFactory.decodeByteArray(imagebytes, 0, imagebytes.length);
         catch(Exception e)
             Log.d("Exception in image thread: " + e.toString();
    

    问题是,Bitmap bmp总是为空。从文档中可以看出,如果JPG是无效的,decodeByteArray返回null。这些是我做的尝试。

  • 试着在我的安卓应用中使用opencv。

     Mat mat = Imgcodecs.imdecode(new MatOfByte(imagebytes), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
     Bitmap bmp = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888);
     Utils.matToBitmap(rgb, bmp);
    

    mat.cols()mat.rows()都返回-1。 我强制参数为480,640(因为它应该是),我在执行Utils.matToBitmap时得到一个断言失败。

  • 试着比较笔记本和手机上收到的jpg缓冲区。它们是相等的。

  • 试着在一个.jpg文件上写jpg缓冲区,然后用Gimp把它作为标准的.jpg文件打开。它起作用了,所以缓冲区包含一个正确的.jpg文件,但如果缓冲区不是一个有效的.jpg文件,decodeByteArray应该返回null。

    我真的被困于此,希望得到任何帮助。谢谢

  • 5 个评论
    【替换代码0你的意思是:如果缓冲区包含一个有效的.jpg文件,并且图像的分辨率不高,以至于位图对可用内存来说太大,那么decodeByteArray应该返回一个位图。它的分辨率是多少?试着发送一张小图片作为测试。
    Tried to write jpg buffer on a file .jpg 你在你的安卓设备上用安卓代码做的?
    至于第一个问题,分辨率是640x480(宽,高),这里有一个例子。 ibb.co/RccSkTY 至于第二个问题,我是在PC上做的。在安卓系统中,我无法访问应用程序的/data文件夹,这是另一个有趣的问题。
    那是非常小的。byte[] image_bytes = new byte[recv_len]; 所以recv_len是服务器发送的jpg的字节数?你检查过了吗?
    Yes I checked it, recv_len is correct
    java
    android
    c++
    opencv
    Alex Foglia
    Alex Foglia
    发布于 2021-03-17
    1 个回答
    Alex Foglia
    Alex Foglia
    发布于 2021-03-17
    已采纳
    0 人赞同

    解决了这个问题,这很微不足道。

    接收图像的安卓功能正在做。

    private byte[] recv_image(DatagramSocket sock) throws Exception
        byte[] b = new byte[40090];
        DatagramPacket p = new DatagramPacket(b, b.length);
        byte[] recv_length = new byte[4];
        length[3] = b[0];
        length[2] = b[1];
        int recv_len = ByteBuffer.wrap(length).getInt();
        byte[] image_bytes = new byte[recv_len];
        for(int i = 0; i < recv_len; i++)
            image_bytes[i] = b[4 + i]; //HERE it is the problem
        return image_bytes;
    

    该结构的len字段是一个uint16_t。我需要在Java中把它转换为int(32),以避免在Java程序中使用ByteBuffer.wrap(b, 0, 2).getShort()时可能被评估为负值,因为java的短类型是有符号的。服务器发送的C++结构被声明为__attribute__((packed))

    typedef struct __attribute__((packed))
        uint16_t len;
        uint8_t data[MAX_IMAGESIZE];
    } image_msg;
    

    因此,data字段从基数+0x02开始,而不是基数+0x04。

    private byte[] recv_image(DatagramSocket sock) throws Exception
        byte[] b = new byte[40090];
        DatagramPacket p = new DatagramPacket(b, b.length);
        byte[] recv_length = new byte[4];
        length[3] = b[0];
        length[2] = b[1];
        int recv_len = ByteBuffer.wrap(length).getInt();
        byte[] image_bytes = new byte[recv_len];
        for(int i = 0; i < recv_len; i++)
            image_bytes[i] = b[2 + i]; //2 + i instead of 4 + i