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视频,每一个包为一行数据,组帧后进行格式转换显示出来。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
在使用PyQt接收串口数据时,可以使用QSerialPort类来实现。如果需要接收大量连续数据,可以采用以下方法: 1.设置缓冲区大小。QSerialPort类提供了readBufferSize()和setReadBufferSize()方法来设置缓冲区大小。可以将缓冲区大小设置为需要接收数据的最大长度。 2.使用readyRead信号。当有数据到达串口时,QSerialPort类会发出readyRead信号。我们可以通过连接该信号的槽函数来读取接收到的数据。在槽函数中,可以使用readAll()方法一次性读取所有数据,也可以使用read()方法读取指定长度的数据。 3.使用线程。如果需要在接收数据的同时进行其他操作,可以使用线程来实现。可以将接收数据的操作放在一个线程中,然后在主线程中进行其他操作。 示例代码如下: ```python import sys from PyQt5.QtCore import QSerialPort, QSerialPortInfo, QObject, pyqtSignal, pyqtSlot, QThread class SerialPort(QObject): dataReceived = pyqtSignal(str) def __init__(self, port, baudRate): super().__init__() self.serialPort = QSerialPort() self.serialPort.setPortName(port) self.serialPort.setBaudRate(baudRate) self.serialPort.readyRead.connect(self.readData) self.serialPort.open(QSerialPort.ReadWrite) @pyqtSlot() def readData(self): data = self.serialPort.readAll() self.dataReceived.emit(str(data, encoding='utf-8')) class WorkerThread(QThread): def __init__(self): super().__init__() self.serialPort = SerialPort('COM1', 9600) def run(self): while True: pass # do something else if __name__ == '__main__': app = QApplication(sys.argv) thread = WorkerThread() thread.start() app.exec_() ``` 在上面的代码中,SerialPort类用于接收串口数据,并发出dataReceived信号。WorkerThread类是一个线程类,用于在接收数据的同时进行其他操作。在主程序中,启动WorkerThread线程并进入事件循环。当有数据到达时,SerialPort类会发出dataReceived信号,可以连接该信号的槽函数来处理接收到的数据
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值