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开发中的一些问题,欢迎转载,转载请注明出处,感谢!