【问题标题】:Decode JPG byte array to Bitmap将 JPG 字节数组解码为位图
【发布时间】:2021-06-14 01:09:48
【问题描述】:

我有一个配备摄像头的树莓派 4,一个进程正在使用 OpenCV 库捕获图像。然后将此图像转发到两个目的地:

  1. 配备 Arch Linux 的标准笔记本电脑
  2. Android 智能手机

这是 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");
        }
        else
        {
             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");
        }
        else
        {
             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);
}

而且效果很好。这是正在发生的事情的屏幕截图:

我们来解决问题。 android上的receiver如下:

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)
{
     try
     {
         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。这些是我所做的尝试:

  1. 尝试在我的 android 应用中使用 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 时得到一个断言失败。

  2. 尝试比较笔记本电脑和手机上收到的 jpg 缓冲区。他们是平等的。

  3. 尝试在 .jpg 文件上写入 jpg 缓冲区,并使用 Gimp 将其作为标准 .jpg 文件打开。它有效,因此缓冲区包含正确的 .jpg 文件,但如果缓冲区不是有效的 .jpg 文件,则 decodeByteArray 应返回 null。

我真的陷入了困境,感谢任何帮助。谢谢

【问题讨论】:

  • but the decodeByteArray should return null if the buffer is not a valid .jpg file. 你的意思是:decodeByteArray 应该返回一个位图,如果缓冲区包含一个有效的 .jpg 文件并且图像的分辨率不是那么大,以至于位图对于可用内存来说太大了。它的分辨率是多少?尝试发送一张小图片作为测试。
  • Tried to write jpg buffer on a file .jpg 你是在你的安卓设备上用安卓代码做的吗?
  • 第一个问题,分辨率是640x480(宽,高),这里举个例子:ibb.co/RccSkTY 第二个,我是用PC做的。在 android 中我无法访问应用程序的 /data 文件夹,这是另一件有趣的事情
  • 那非常小。应该不是问题。字节[] image_bytes = 新字节[recv_len];所以 recv_len 是服务器发送的 jpg 的字节大小?你检查了吗?
  • 是的,我查过了,recv_len 是正确的

标签: java android c++ opencv


【解决方案1】:

解决了这个问题,这很简单。

接收图像的android函数在做什么:

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),以避免在我使用 ByteBuffer.wrap(b, 0, 2).getShort() 时可能在 Java 程序中评估的负值,因为 java 短类型已签名。服务器发送的 C++ 结构声明为 __attribute__((packed)):

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

因此data 字段从base+0x02 开始,而不是base+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
    }
    return image_bytes;
}

这个解决方案解决了我的问题,你可以看到here。感谢您的支持。

【讨论】:

  • 不错的视频。一切都那么快。赛车 raspi!
  • 很遗憾,我的锂电池没电了。它本来可以更有趣:)
猜你喜欢
  • 2014-09-14
  • 1970-01-01
  • 1970-01-01
  • 2011-07-11
  • 2010-09-26
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
相关资源
最近更新 更多