PyQt5+Opencv上位机若干问题记录

2020年3月14日:

Q1.图像在QLable中显示不正常

原图片为640*480,lable大小为261*111,显示图片后色彩不正常,图像歪曲

    def show_camera(self):
        flag, self.image = self.cap.read()  # 从视频流中读取照片

        show = cv2.resize(self.image, (210, 160))  # 把读到的帧的大小重新设置为 640x480
        show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)  # 视频色彩转换回RGB,这样才是现实的颜色
        showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], show.shape[1] * 3,QtGui.QImage.Format_RGB888)  # 把读取到的视频数据变成QImage形式show.shape[1]*3表明图象是三色
        self.label_display.setPixmap(QtGui.QPixmap.fromImage(showImage))  # 往显示视频的Label里 显示QImage
        self.label_display.setScaledContents(True)

其中,参数show.shape[1] * 3非常重要,它表示图像是彩色的

Q2.使用Tkinter控件解决图片保存不正确问题

    def pushButton_takephotos_clicked(self):
        if self.cap.isOpened():  # 如果摄像头成功打开
            print("Program is runing")
            flag, frame = self.cap.read()  # 读取相机照片
            # 打开文件保存对话框
            filename = tkinter.filedialog.asksaveasfilename(title=u'保存照片',
                                                            filetypes=[("PNG", "*.png"),
                                                                       ("JPG", "*.jpg"),
                                                                       ("JPEG", "*.jpeg"),
                                                                       ("JPE", "*.jpe"),
                                                                       ("BMP", "*.bmp"),
                                                                       ("All Files", "*.*")
                                                                       ])
            self.textBrowser_receivedata.setText(filename)
            if filename != "":
                cv2.imwrite(filename, frame)
        else:  # 未成功打开摄像头
            # 显示警告
            msg = QtWidgets.QMessageBox.warning(self, 'warning', "请检查相机于电脑是否连接正确", buttons=QtWidgets.QMessageBox.Ok)

 filetypes=[("PNG", ".png")不正确,应该是 filetypes=[("PNG", "*.png"),因为缺少“*”号,文件名读取不正确,无法被cv2.imwrite(filename, frame)函数读取。

Q3.在Qlable中显示RGB(彩色)/GRAY(灰度)/BIN(二值化)图像,因为OpenCVu读取的图片必须转换为QImage类型才能显示在Lable上,所以需要如下转换。

PS:此问题和Q1类似

   def show_camera(self):
        global photo_flag
        flag, self.image = self.cap.read()  # 从视频流中读取照片
        if flag:
            show = cv2.resize(self.image, (210, 160))  # 把读到的帧的大小重新设置为 640x480
            # 图像转换
            # 灰度图
            if self.radioButton_GrayChange.isChecked():
                show = cv2.cvtColor(show, cv2.COLOR_BGR2GRAY)
                showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], show.strides[0],
                                         QtGui.QImage.Format_Indexed8)
            # RGB彩色图
            elif self.radioButton_ColorChange.isChecked():
                show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)  # 视频色彩转换回RGB,这样才是现实的颜色
                showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], show.shape[1] * 3,
                                         QtGui.QImage.Format_RGB888)  # 把读取到的图片数据变成QImage形式show.shape[1]*3表明图象是三色
            # Binary方式二值化照片
            elif self.radioButton_BinChange.isChecked():
                show = cv2.cvtColor(show, cv2.COLOR_BGR2GRAY)  # 先灰度处理
                ret, binary = cv2.threshold(show, 128, 220, 0)  # 二值化处理
                showImage = QtGui.QImage(binary.data, show.shape[1], show.shape[0], show.strides[0],
                                         QtGui.QImage.Format_Indexed8)  # 把读取到的图片数据变成QImage形式show.shape[1]*3表明图象是三色

            self.label_display.setPixmap(QtGui.QPixmap.fromImage(showImage))  # 往显示视频的Label里 显示QImage
            self.label_display.setScaledContents(True)
            # self.label_photoMsg.setText("Photo Message:")

            # 按R、G、B三个通道分别计算颜色直方图
            # print("get data")
            # b_hist = cv2.calcHist([self.image], [0], None, [256], [0, 255])
            # g_hist = cv2.calcHist([self.image], [1], None, [256], [0, 255])
            # r_hist = cv2.calcHist([self.image], [2], None, [256], [0, 255])

            hist_np, bins = np.histogram(self.image.ravel(), 256, [0, 256])
            print(hist_np)
        else:
            print("hello")
            self.My_Message()
        # photo_msg = cap.get(self.CAM_NUM, 5)
        # self.textBrowser_receivedata.append(str(photo_msg))

Q4:使用RadioButton,即单选按钮,如果要一组内多个RadioButton互斥的话,在布局的时候,在designer中使用horizontlLayout控件将他们放在一起就可以了。或者使用代码添加,例子:

self.radioButton_switch1 = QtWidgets.QRadioButton(self.horizontalLayoutWidget)
        self.radioButton_switch1.setObjectName("radioButton_switch1")
        self.horizontalLayout.addWidget(self.radioButton_switch1)
        self.radioButton_switch2 = QtWidgets.QRadioButton(self.horizontalLayoutWidget)
        self.radioButton_switch2.setObjectName("radioButton_switch2")
        self.horizontalLayout.addWidget(self.radioButton_switch2)
        self.radioButton_switch3 = QtWidgets.QRadioButton(self.horizontalLayoutWidget)
        self.radioButton_switch3.setObjectName("radioButton_switch3")
        self.horizontalLayout.addWidget(self.radioButton_switch3)

对于Radio控件,当选中时产生toggle 信号,通过connect传递给事件函数,其中lambda方法可以传递控件参数,比如radioButton.Text,radioButton.isChecked等状态

self.radioButton_switch1.toggled.connect(lambda: self.radioButton_GrayChange_checked)

 Q5:在Timer服务函数中使用pyqt5graph控件绘图,才不会产生堆叠

  def Update_graph(self):
        # 绘图属性设置
        pg.setConfigOption('background', 'w')  # 设置背景颜色
        pg.setConfigOption('foreground', 'k')  # 线条颜色
        pg.setConfigOptions(antialias=True)  # 启用抗锯齿
        # 绘图区域
        self.plt1 = pg.PlotWidget(self.widget_graph, title="RGB histogram")  # 将绘图区与widget_graph绑定
        self.plt1.setLabel(axis='left', text='Points')  # 设置轴标题
        # 从布局中添加绘图区域
        # para:Qwidget_name,Row,Col,T_Row,T_Col
        self.gridLayout.addWidget(self.plt1, 0, 0, 1, 1, )
        # 向绘图区添加数据
        # self.curve1 = self.plt1.plot(np.random.normal(size=255), pen=(255, 0, 0), name="Red curve")
        # self.curve2 = self.plt1.plot(np.random.normal(size=255), pen=(0, 255, 0), name="Red curve")
        # self.curve3 = self.plt1.plot(np.random.normal(size=255), pen=(0, 0, 255), name="Red curve")
        # 添加一组数据,x,y,是否填充brush,画笔颜色
        self.plt1.plot(np.random.normal(size=255), fillLevel=-5, brush=(255, 50, 200, 100), pen=(255, 0, 0),
                       name="Red curve")
        self.plt1.plot(np.random.normal(size=255)+2, fillLevel=-5, brush=(50, 255, 200, 100), pen=(0, 255, 0),
                       name="Green curve")
        self.plt1.plot(np.random.normal(size=255)+5, fillLevel=-5, brush=(200, 50, 255, 100), pen=(0, 0, 255),
                       name="Blue curve")

Q6:一堆奇怪的警告,表现为摄像头无法打开,cv2.imshow()函数警告

[ WARN:0] global C:\projects\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (-126674) SourceReade

在尝试了CSDN其他博主的方法:

修改为:capture=cv2.VideoCapture(0,cv2.CAP_DSHOW)(python 3.5)

并未解决我的问题

于是直接卸载重装opencv4.2.0,使用pip uninstall 和 pip install 命令,错误解除

Q7:使用cv2.calcHist()函数计算直方图数据格式问题:

cv2.calcHist:[[123][15][55][88][44]....],M*1矩阵

使用numpy.ravel()多维降一维,就可以在图表中显示,记得提前把OpenCV的BGR转成RGB

            self.r_hist = (cv2.calcHist([hist_img], [0], None, [256], [0, 255])).ravel()  # 红色
            self.g_hist = (cv2.calcHist([hist_img], [1], None, [256], [0, 255])).ravel()  # 绿色
            self.b_hist = (cv2.calcHist([hist_img], [2], None, [256], [0, 255])).ravel()  # 蓝色

Q8:打开文件保存对话框的两种方式及其比较:

Tkinter方式:可以自动添加后缀名,但是引入第三方控件,使得package很大,需要隐藏

self.root = Tk()
self.root.withdraw()  # 将Tkinter.Tk()实例隐藏

Qt方式:官方自带的库,但是不能添加自动添加后缀名,可以使用判断方式

            filename = tkinter.filedialog.asksaveasfilename(defaultextension=True,  # 自动添加扩展名
                                                            filetypes=[("PNG", ".png"),  # 保存的文件类型
                                                                       ("JPG", ".jpg"),
                                                                       ("JPEG", ".jpeg"),
                                                                       ("JPE", ".jpe"),
                                                                       ("BMP", ".bmp"),
                                                                       ])
            # Qt5方式,无法添加后缀
             filename = QFileDialog.getSaveFileName(self,
                                                    "文件保存",
                                                    "./",
                                                    "All Files (*);;Images (*.png);;Images (*.jpeg);;Images ("
                                                    "*.jpg);;Images (*.bmp)")

Q9:关于Python类内变量的问题:

在_init_内定义的变量属于类内全局变量,需要加self修饰,作用域为类内

# 定义类内全局白变量
 def __init__(self):        
    self.r_hist = []      #RED
    self.g_hist = []      #GREEN
    self.b_hist = []      #BLUE

def fun_example(self):
    self.r_hist = [1,2,3]
    print(r_hist)
result: 1 2 3

Q10:_init_()方法和super()函数的使用方法

_init_(),正如它的名字一样,_init_()函数用于初始化新创建对象的状态

super()函数,它会查找所有的超类,以及超类的超类,直到找到所需的特性为止。

'''方法1'''
class MainCode(QMainWindow, test.Ui_mainWindow): # 继承两个类:QMainWindow, test.Ui_mainWindow
    def __init__(self):
        # 旧版本:调用未绑定的超类构造方法
        # 绑定QMainWindow
        # QMainWindow.__init__(self)
        # 绑定Ui_mainWindow
        # test.Ui_mainWindow.__init__(self)
        # 该句必不可少
        self.setupUi(self)  # 加载UI

'''方法2'''
class MainCode(QMainWindow, test.Ui_mainWindow): # 继承两个类:QMainWindow, test.Ui_mainWindow
    def __init__(self):
        # 新版本,使用super函数,使用递归查找所有的超类
        super(MainCode, self).__init__()
        self.setupUi(self)  # 加载UI

Q11:上位机和下位机数据解析

下位机:Ardiuno Nano + MPU6050,使用DMP,更新,使用union方式,拆分float型数据


// 定义共用体用以拆分float型数据->unsigned char
// float占用四个字节,unsigned char占用一个字节
// typedef 是别名
typedef union {
float f;  //float
unsigned char b[4];  //unsigned char array
}float2byte;
// 用户自定义
float yell, pitch, roll; //全局变量,来自寄存器
// 发送打包函数
void Send_package(float yell, float pitch, float roll)
{
  float2byte y,p,r;   //定义union
  y.f = yell;         //传值
  p.f = pitch;
  r.f = roll; 
  unsigned char sum = 0;  //求和variable
  // 2.set a valueable to save data
  unsigned char SendPackage[18];  //定义数组
  SendPackage[0] = 0xb7; //帧头
  SendPackage[1] = 0xb8; //包长
  SendPackage[2] = 0x0b; //数据类型
  SendPackage[3] = 0x0c; //数据长度
  // Yell
  SendPackage[4] = y.b[0];
  SendPackage[5] = y.b[1];
  SendPackage[6] = y.b[2];
  SendPackage[7] = y.b[3];
  //Pitch
  SendPackage[8] = p.b[0];
  SendPackage[9] = p.b[1];
  SendPackage[10] = p.b[2];
  SendPackage[11] = p.b[3];
  //Roll
  SendPackage[12] = r.b[0];
  SendPackage[13] = r.b[1];
  SendPackage[14] = r.b[2];
  SendPackage[15] = r.b[3];
   //简单和检验
  for(int i=4; i<16; i++)
  {
    sum += SendPackage[i]; 
  }
  //end bit
  SendPackage[16] = sum;
  SendPackage[17] = 0xed;
  //send by serial port
// 发送
  for(int j=0; j<=17; j++)
  {
    // serial.write
   Serial.write(SendPackage[j]);
  }
//为了上位机串口使用readline读取一行
  Serial.write("\r\n");
}

上位机:PyQt5 Qserial控件

数据格式问题:Bytes->Hex->Int->Float,更新:

   # 旧版本,冗余复杂
 self.Usart_Receive_Process(((self.com.read(1)).hex())) # 从串口得到数据
    def Usart_Receive_Process(self, rec_byte):
        checksum = 0  # 校验和
        if rec_byte == 'b7':  # 帧头
            self.begin = True
        if self.begin:  # 开始接收数据
            self.receive_data.append(rec_byte)
            if len(self.receive_data) == 18:  # 校验
                if self.receive_data[17] == 'ed':  # 检查尾部
                    self.begin = False
                    for i in range(4, 16):
                        checksum += int('0x' + self.receive_data[i], 16)
                    checksum &= 0x00ff  # 强制截断
                    if checksum == int(self.receive_data[16], 16):  # 校验成功
                        yaw_temp = self.receive_data[4:8]  # 截取yaw数据段
                        yaw_temp = ''.join(yaw_temp)  # 字符数组转第一字符串
                        s = struct.unpack('>f', bytes.fromhex(yaw_temp))[0]  # 转换为浮点数
                        print(s)
                        self.receive_data.clear()
                else:  # 头尾校验不正确,丢弃
                    self.receive_data.clear()
                    return
# 新版本
            data = self.m_serialPort.readline().hex()
            checksum = 0  # 校验和
            # rec_byte = self.com.readLine(maxlen=21).hex()
            if data[0:2] == 'b7' and data[34:36] == 'ed':  # 头尾完整
                self.receive_data = re.findall(r'.{2}', data)  #
                for i in range(4, 16):
                    checksum += int('0x' + self.receive_data[i], 16)
                checksum &= 0x00ff  # 强制截断
                if checksum == int(self.receive_data[16], 16):  # 校验成功
                    yaw_temp = ''.join(self.receive_data[4:8])  # 截取yaw数据段
                    print(yaw_temp)
                    s = struct.unpack('<f', bytes.fromhex(yaw_temp))[0]  # 转换为浮点数
                    print(round(s, 2))

插入截图留念(2020年4月11日16点23分)

Q12:使用多线程解决Qserial数据问题

在串口接收事件里处理数据,会占用主线程的资源,同时打开其他操作会导致卡慢,因此需要另外开启一个线程来单独完成数据接收和处理工作,QT中线程使用QThread类

self.thread_test = ReceiveThread(self.com)  # 创建线程
self.thread_test.start()  # 启动线程

# 重写run
qmut1 = QMutex() # 线程锁保护数据
class ReceiveThread(QtCore.QThread):
    def __init__(self, com):
        super(ReceiveThread, self).__init__()
        # self.com = QSerialPort()  # 串口
        self.com = com
        self.receive_data = []  # 数据接收
        self.begin = False
        # print('new thread')

    def run(self):
        # while True:
        # 速度有待提高
        while self.com.isOpen():  # 确认串口打开
            qmut1.lock()
            if self.com.waitForReadyRead(10):
                # rec_byte = self.com.read(1).hex()
                checksum = 0  # 校验和
                rec_byte = self.com.readLine(maxlen=21).hex()
                # print()
                if rec_byte[0:2] == 'b7' and rec_byte[34:36] == 'ed':  # 头尾完整
                    self.receive_data = re.findall(r'.{2}', rec_byte)  #
                    for i in range(4, 16):
                        checksum += int('0x' + self.receive_data[i], 16)
                    checksum &= 0x00ff  # 强制截断
                    if checksum == int(self.receive_data[16], 16):  # 校验成功
                        yaw_temp = self.receive_data[4:8]  # 截取yaw数据段
                        yaw_temp = ''.join(yaw_temp)  # 字符数组转第一字符串
                        s = struct.unpack('>f', bytes.fromhex(yaw_temp))[0]  # 转换为浮点数
                        print(s)

            qmut1.unlock()
        else:
            return

开启线程完成了,那么怎么关闭呢:

'''方法一:使用win32API函数'''
 stop_signal = pyqtSignal()   # 自定义信号,用于停止串口线程
 self.stop_signal.connect(lambda: self.closeSerialThread()) # 必须使用lamble方法
 self.stop_signal.emit() # 发射自定义函数
import win32con
    # run函数
    def run(self):
        self.handle = ctypes.windll.kernel32.OpenThread(  # @UndefinedVariable
            win32con.PROCESS_ALL_ACCESS, False, int(QThread.currentThreadId()))
    
    # 关闭线程,点击关闭串口按钮,发送一个自定义信号
    def closeSerialThread(self):
        qmut1.unlock()  # !!!必须先解锁
        ret = ctypes.windll.kernel32.TerminateThread(  # @UndefinedVariable
             self.thread_test.handle, 0)
        print('终止线程', self.thread_test.handle, ret)

'''方法二:使用win32API函数'''
    def closeSerialThread(self):

        qmut1.unlock() # !!!必须先解锁
        self.thread_test.terminate()   #  强制关闭
        # self.thread_test.quit() # 没用

更新:上面采用了UI界面里创建串口,新线程中传入串口类的方法,以及self.com.waitForReadyRead(10)函数,出现的问题是,打开摄像头,串口线程会被阻塞(挂起),阅读文档发现,这是阻塞方式。参看Qt文档,发现下面一句:要通过选定的串行端口同步接收数据,请使用read()readAll()方法以及readyRead()信号。

Q13:关于pyqt5-graph绑定的问题

Q:重新修改了UI之后,图表显示位置不正确

A:pg.PlotWidget(self.widget_graph, title="RGB histogram")函数默认使用gridLayout,graph自动从gridLayout开始,而不是gridLayout_x.

PS:希望这篇文章能够解决你在PyQt5开发中的一些问题,欢迎转载,转载请注明出处,感谢!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vicssic

与你一起成长

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值