• 如何在 pyqt 中读取串口传输的图像


    前言

    这学期选修了嵌入式系统的课程,大作业选择的题目是人脸口罩检测。由于课程提供的开发板搭载的芯片是 STM32F103ZET6,跑不动神经网络,所以打算将 OV7725 拍摄到的图像通过串口传输给上位机处理。关于人脸口罩检测可以参见上一篇博客《如何使用 Yolov4 训练人脸口罩检测模型》,本篇博客的代码放在了 https://github.com/zhiyiYo/Face-Mask-Detector,下面进入正题。

    串口传输图像

    为了让上位机知道什么时候开始传输一张图像,需要在传输像素之前先发一段 header,这里使用的是 image:。在 header 的最后添加换行符的理由是方便上位机可以一次读入一行字符串来和 image: 进行比对。

    发送完 header 就可以开始传输像素了。OV7725 输出的图像色彩模式为 RGB565,图像深度只有 16 位,用 2 个字节表示一个像素。在读取摄像头的 FIFO 芯片中存储的像素时,先读到的是像素的高 8 位(R<<5 | G[:3]),接着是低 8 位(G[3:]<<5 | B)。使用 HAL_UART_Transmit() 将像素发送给上位机即可,这里没有使用 printf 来传输,因为 printf 速度慢一些。每传输完一列像素就发送一个换行符,这样上位机通过 serial.readline() 读取 header 的时候就不会把上一张图像最后一列的像素给读进来了。

    IDR & 0XFF;
            high = GPIOC->IDR & 0XFF;
            setReadClock(GPIO_PIN_SET);
    
            // 读颜色的低八位
            setReadClock(GPIO_PIN_RESET);
            low = GPIOC->IDR & 0XFF;
            setReadClock(GPIO_PIN_SET);
    
            lcd_->drawPoint((high << 8) | low);
    
            // 发送像素给上位机
            HAL_UART_Transmit(&huart1, &high, 1, 100);
            HAL_UART_Transmit(&huart1, &low, 1, 100);
        }
        printf("\n");
    }
    ">复制// 发送帧头
    printf("image:\n");
    
    // 在 LCD 上显示图像并将像素发给上位机
    uint8_t high, low;
    for (uint32_t i = 0; i < HEIGHT; ++i)
    {
        // 一列数据
        for (uint32_t j = 0; j < WIDTH; ++j)
        {
            // 读颜色的高八位
            setReadClock(GPIO_PIN_RESET);
            // high = GPIOC->IDR & 0XFF;
            high = GPIOC->IDR & 0XFF;
            setReadClock(GPIO_PIN_SET);
    
            // 读颜色的低八位
            setReadClock(GPIO_PIN_RESET);
            low = GPIOC->IDR & 0XFF;
            setReadClock(GPIO_PIN_SET);
    
            lcd_->drawPoint((high << 8) | low);
    
            // 发送像素给上位机
            HAL_UART_Transmit(&huart1, &high, 1, 100);
            HAL_UART_Transmit(&huart1, &low, 1, 100);
        }
        printf("\n");
    }
    

    串口读取图像

    如果直接在主线程读取串口的数据会造成主界面卡顿,所以创建一个子线程 SerialThread 来读图,每读完一张图像就发送 loadImageFinished 信号给主界面来显示图像。

    在读取图像的像素之前需要将 s.readline()[:-1].decode("utf-8", "replace")image: 进行比较,如果相等就说明下面拿到的就是图像数据,否则当前读取的是还没传输完成的图像的像素。之后使用 s.read(column_len) 读入一列像素并将其添加到列表 data 中,由于 OV7725 工作在 QVGA 模式,输出的图像是 320*240 分辨率的,一个像素 2 字节,data 的长度等于 320*240*2 即 153600 就表明完成了一张完整图像的读取。接下来把 RGB565 的像素缩放到 RGB888 的像素并组装为 320*240的图像即可。

    实验过程中发现波特率不能取太高,否则读取的图像会有奇怪的条纹,所以这里使用的波特率为 1500000。

     QPixmap:
        """ 将 RGB565 图像转换为 RGB888 """
        image = []
        for i in range(0, len(pixels), 2):
            pixel = (pixels[i] << 8) | pixels[i+1]
            r = pixel >> 11
            g = (pixel >> 5) & 0x3f
            b = pixel & 0x1f
            r = r * 255.0 / 31.0
            g = g * 255.0 / 63.0
            b = b * 255.0 / 31.0
            image.append([r, g, b])
    
        image = np.array(image, dtype=np.uint8).reshape(
            (240, 320, 3)).transpose((1, 0, 2))
        return imageToQPixmap(Image.fromarray(image))
    
    
    class SerialThread(QThread):
        """ 串口线程 """
    
        loadImageFinished = pyqtSignal(QPixmap)
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self.serial = Serial(baudrate=1500000)
            self.isStopped = False
    
        def run(self):
            """ 将串口传输的字节转换为图像 """
            data = []
            self.serial.port = config.get(config.serialPort)
    
            with self.serial as s:
                while not self.isStopped:
                    if not s.isOpen():
                        s.open()
    
                    # 等待 header
                    header = s.readline()[:-1]
                    if header.decode("utf-8", "replace") != "image:":
                        continue
    
                    # 读入像素,丢弃换行符
                    column_len = 320*2+1
                    while len(data) < 2*320*240:
                        image_line = s.read(column_len)
                        data.extend(image_line[:-1])
    
                    self.loadImageFinished.emit(rgb565ToImage(data))
                    data.clear()
    
        def stop(self):
            """ 停止从串口读取图像 """
            self.isStopped = True
            self.serial.close()
    
        def loadImage(self):
            """ 开始从串口读取图像 """
            self.isStopped = False
            self.start()
    ">复制# coding: utf-8
    from PIL import Image
    import numpy as np
    from PyQt5.QtCore import QThread, pyqtSignal
    from PyQt5.QtGui import QPixmap, QImage
    from serial import Serial
    
    
    def imageToQPixmap(image: Image.Image):
        """ 将图像转换为 `QPixmap`
    
        Parameters
        ----------
        image: `~PIL.Image` or `np.ndarray`
            RGB 图像
        """
        image = np.array(image)  # type:np.ndarray
        h, w, c = image.shape
        format = QImage.Format_RGB888 if c == 3 else QImage.Format_RGBA8888
        return QPixmap.fromImage(QImage(image.data, w, h, c * w, format))
    
    
    def rgb565ToImage(pixels: list) -> QPixmap:
        """ 将 RGB565 图像转换为 RGB888 """
        image = []
        for i in range(0, len(pixels), 2):
            pixel = (pixels[i] << 8) | pixels[i+1]
            r = pixel >> 11
            g = (pixel >> 5) & 0x3f
            b = pixel & 0x1f
            r = r * 255.0 / 31.0
            g = g * 255.0 / 63.0
            b = b * 255.0 / 31.0
            image.append([r, g, b])
    
        image = np.array(image, dtype=np.uint8).reshape(
            (240, 320, 3)).transpose((1, 0, 2))
        return imageToQPixmap(Image.fromarray(image))
    
    
    class SerialThread(QThread):
        """ 串口线程 """
    
        loadImageFinished = pyqtSignal(QPixmap)
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self.serial = Serial(baudrate=1500000)
            self.isStopped = False
    
        def run(self):
            """ 将串口传输的字节转换为图像 """
            data = []
            self.serial.port = config.get(config.serialPort)
    
            with self.serial as s:
                while not self.isStopped:
                    if not s.isOpen():
                        s.open()
    
                    # 等待 header
                    header = s.readline()[:-1]
                    if header.decode("utf-8", "replace") != "image:":
                        continue
    
                    # 读入像素,丢弃换行符
                    column_len = 320*2+1
                    while len(data) < 2*320*240:
                        image_line = s.read(column_len)
                        data.extend(image_line[:-1])
    
                    self.loadImageFinished.emit(rgb565ToImage(data))
                    data.clear()
    
        def stop(self):
            """ 停止从串口读取图像 """
            self.isStopped = True
            self.serial.close()
    
        def loadImage(self):
            """ 开始从串口读取图像 """
            self.isStopped = False
            self.start()
    

    软件界面如下图所示,只要点击工具栏最左侧的摄像头按钮就能从串口读取图像。

    后记

    至此使用 pyqt 读取串口传输图像的方法就介绍完毕了,以上~~

  • 相关阅读:
    1496. 判断路径是否相交
    java进行系统的限流实现--Guava RateLimiter、简单计数、滑窗计数、信号量、令牌桶
    go语言|数据结构:二叉树可视化(svg树形图改进版)
    latex编译生成的pdf文件,图片出现浅色的线
    单点登录以及实现(前后端分离和前后端不分离方式)
    基础三层架构的配置类以及基础实现(crud)
    Cell:水平基因转移在昆虫中广泛存在,增强鳞翅目雄性昆虫求偶行为
    瑜伽组织怎么运用智能机器人提高员工敬业度
    ubuntu16.04下thrift安装及遇到的问题
    【002_音频开发_基础篇_Linux音频架构简介】
  • 原文地址:https://www.cnblogs.com/zhiyiYo/p/16794819.html