Pyqt completes UDP to receive RGB565 data and display images

Recently, because of a project, I tried to use Pyqt to make a UDP video receiver. The video frame size is 800x480. The sender is an FPGA board. Since the size of UDP packets is limited, each packet only sends one line of data. The length of the first line of data is The data length of the last row is different from the data length of the middle row, which is used to judge whether it is a complete frame of data.

During the period, I stepped on many pitfalls. First of all, I don't know the image format that Qlabel can display. At the beginning, I converted a frame of image received from RGB565 to RGB888, and then converted it to the format of RGB three-dimensional array, then saved it locally, and then read it out with Pyqt function and displayed it on the terminal. The speed after this processing is very slow, only two or three frames per second.

After checking the information, I found that the RGB data format can be encapsulated into BMP, and then read out as QPixmap type by Pyqt's built-in function, which can be directly displayed on the Qlabel. BMP file header analysis, specific Baidu search

 

Then process and add the BMP file header information

    def add_565bytes_header(self):
        self.calc_565data_size()        # 计算RGB的数据大小,返回self.dataSize和self.fileSize
        reserved = 0
        offset = 54                     # 位图数据在文件中的偏移值,等于 “文件信息+位图信息+调色板信息”。
        file_tag = 19778                # 转为16进制,文件标识,BMP 文件值固定为 0x4D42,存储为小端模式,转换成 ASCII 就是 “BM”。
        bitmap_info_size = 40           # 位图信息的大小,固定为 40
        planes = 1                      # 位图的位面数,固定为 1
        image_depth = 16                # 位图的图像深度
        compression = 0                 # 位图压缩方式
        x_pels_permeter = 0             # 指定位图目标设备的水平打印分辨率,表示水平方向每米的像素点数量,可以是 0
        y_pels_permeter = 0             # 指定位图目标设备的垂直打印分辨率,表示垂直方向每米的像素点数量,可以是 0
        color_used = 0                  # 位图实际使用调色板的颜色数量,图像深度少于或等于 8 bits 时,值有效。值为 0 表示使用了整个调色板的颜色
        color_important = 0             # 重要的颜色数量,值通常等于 color_used,值为 0 时表示所有颜色都重要
        # 添加文件头
        file_info_head = file_tag.to_bytes(2, byteorder="little")
        file_info_head = file_info_head + self.fileSize.to_bytes(4, byteorder="little")
        file_info_head = file_info_head + reserved.to_bytes(4, byteorder="little")
        file_info_head = file_info_head + offset.to_bytes(4, byteorder="little")
        # 添加位图信息
        bitmap_info_head = bitmap_info_size.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + self.w.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + self.h.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + planes.to_bytes(2, byteorder="little")
        bitmap_info_head = bitmap_info_head + image_depth.to_bytes(2, byteorder="little")
        bitmap_info_head = bitmap_info_head + compression.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + self.dataSize.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + x_pels_permeter.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + y_pels_permeter.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + color_used.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + color_important.to_bytes(4, byteorder="little")

        bmp_bytes = file_info_head + bitmap_info_head + self.pic_data
        # print(len(bmp_bytes))
        return bmp_bytes

Then it was displayed and found that the color of the image had a color difference, and then checked the data and found that it was a problem with the display of the BMP format. I am using the RGB565 format, and the bitmap compression method needs to be modified to 3.

compression = 0                 # 位图压缩方式

        0 - no compression (use BI_RGB for representation)

        1 - RLE 8 - use 8-bit RLE compression (denoted by BI_RLE8)

        2 - RLE 4 - use 4-bit RLE compression (indicated by BI_RLE4)

        3 - Bitfields-bit field storage method (indicated by BI_BITFIELDS)

In addition to 565, RGB16bit also has 555 format, so the palette information needs to be added. It should be noted that after the palette information is added here, the number of bytes in the file header increases, and the offset of the byte changes. The following is the modified file header information.

    def add_565bytes_header(self):
        self.calc_565data_size()        # 计算RGB的数据大小,返回self.dataSize和self.fileSize
        reserved = 0
        offset = 66                     # 位图数据在文件中的偏移值,等于 “文件信息14+位图信息50+调色板信息12”。
        file_tag = 19778                # 转为16进制,文件标识,BMP 文件值固定为 0x4D42,存储为小端模式,转换成 ASCII 就是 “BM”。
        bitmap_info_size = 40           # 位图信息的大小,固定为 40
        planes = 1                      # 位图的位面数,固定为 1
        image_depth = 16                # 位图的图像深度
        compression = 3                 # 位图压缩方式
        x_pels_permeter = 0             # 指定位图目标设备的水平打印分辨率,表示水平方向每米的像素点数量,可以是 0
        y_pels_permeter = 0             # 指定位图目标设备的垂直打印分辨率,表示垂直方向每米的像素点数量,可以是 0
        color_used = 0                  # 位图实际使用调色板的颜色数量,图像深度少于或等于 8 bits 时,值有效。值为 0 表示使用了整个调色板的颜色
        color_important = 0             # 重要的颜色数量,值通常等于 color_used,值为 0 时表示所有颜色都重要

        # 调色板相关掩码位
        R = 63488
        G = 2016
        B = 31

        # 添加文件头
        file_info_head = file_tag.to_bytes(2, byteorder="little")
        file_info_head = file_info_head + self.fileSize.to_bytes(4, byteorder="little")
        file_info_head = file_info_head + reserved.to_bytes(4, byteorder="little")
        file_info_head = file_info_head + offset.to_bytes(4, byteorder="little")
        # 添加位图信息
        bitmap_info_head = bitmap_info_size.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + self.w.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + self.h.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + planes.to_bytes(2, byteorder="little")
        bitmap_info_head = bitmap_info_head + image_depth.to_bytes(2, byteorder="little")
        bitmap_info_head = bitmap_info_head + compression.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + self.dataSize.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + x_pels_permeter.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + y_pels_permeter.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + color_used.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + color_important.to_bytes(4, byteorder="little")
        # 添加调色板信息
        bitmap_info_head = bitmap_info_head + R.to_bytes(4, byteorder="little")
        # print(str(R.to_bytes(4, byteorder="little").hex()))
        bitmap_info_head = bitmap_info_head + G.to_bytes(4, byteorder="little")
        bitmap_info_head = bitmap_info_head + B.to_bytes(4, byteorder="little")


        bmp_bytes = file_info_head + bitmap_info_head + self.pic_data
        return bmp_bytes

Red space RGB565 to fill: 0x0000F800
Green space RGB565 to fill: 0x000007E0
Blue space RGB565 to fill: 0x0000001F

That is, the mask of the RGB565 placeholder.

A total of 12 bytes, so the offset changes to 54+12=66.

So far the problem is solved. The project address UDP_video_terminal: Use Pyqt to implement UDP to receive 800x480 RGB565 video, each packet is a line of data, and the format is converted and displayed after framing.

Guess you like

Origin blog.csdn.net/qq_62426375/article/details/125290445