Pyqt完成UDP接收RGB565数据并显示图像

近期因为一个项目,尝试用了Pyqt做了一个UDP视频视频接收端,视频帧大小是800x480,发送方是一块FPGA板子,由于UDP包限制大小,所以每个包只发送一行数据,首行数据长度和尾行的数据长度与中间行的数据长度不同,用此来判断是否是完整的一帧数据。

期间踩了许多坑,首先就是不知道Qlabel可以显示的图像格式。刚开始是把收到的一帧图像RGB565转成RGB888,之后又转RGB三维数组的格式,然后保存到本地,然后再用Pyqt的函数读出来再显示到终端。这样处理之后的速度非常慢,只有两三帧每秒。

之后查了资料之后才发现,RGB的数据格式可以封装成BMP,然后再由Pyqt的内置函数读出为QPixmap类型可以直接显示到Qlabel上。BMP文件头解析,具体的可以百度搜索

然后对BMP的文件头信息进行处理添加上去

    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

然后显示出来发现图像的颜色有色差,然后查资料发现是BMP的格式显示问题。我使用的是RGB565格式,这个位图压缩方式需要修改为3。

compression = 0                 # 位图压缩方式

        0 - 不压缩 (使用BI_RGB表示)

        1 - RLE 8-使用8位RLE压缩方式(用BI_RLE8表示)

        2 - RLE 4-使用4位RLE压缩方式(用BI_RLE4表示)

        3 - Bitfields-位域存放方式(用BI_BITFIELDS表示)

RGB16bit除了565还有555格式,所以需要增加调色板信息,要注意这里增加了调色板信息后,文件头字节数增加,字节的偏移量发生变化,以下是修改后的文件头信息。

    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

红色占位 RGB565 要填:0x0000F800
绿色占位RGB565 要填:0x000007E0
蓝色占位RGB565 要填:0x0000001F

也就是RGB565占位的掩码。

总共12个字节,所以偏移量改为54+12 = 66。

至此问题解决。该项目地址UDP_video_terminal: 使用Pyqt实现UDP接收800x480的RGB565视频,每一个包为一行数据,组帧后进行格式转换显示出来。

猜你喜欢

转载自blog.csdn.net/qq_62426375/article/details/125290445