因为想做一个自己的多标签图像识别算法的训练库,需要用到摄像头拍照。另外,想着后面可能会用Qt来开发一些跨平台的应用,所以先学着用pyqt来开发一个摄像头的拍照软件作为入门。整体感觉,用python+qt开发桌面应用的效率还蛮高的,总共100行左右的代码就可以实现了。
01—效果
先放一个最终的效果图,如下:
02—开发环境准备效果
操作系统 | Ubuntu 20.04Lts |
---|---|
IDE | Pycharm2021.3.1 |
Python | 3.9.7,pyqt5 |
简单起见,python直接安装了一个Anaconda,常用的一些包都自带了。
pyqt使用命令安装:
pip install PyQt5 -i https://pypi.douban.com/simple #安装pyqt的库
pip install PyQt5-tools -i https://pypi.douban.com/simple
#安装pyqt的工具,主要就是QtDesigner
2.2 Pycharm安装
去官网下载pycharm professional的deb文件安装。或者,直接在Ubuntu的Software安装;或者,使用“sudo nano”命令安装应该也是可以的。不过,后面这两种安装方法我就没有尝试了。
安装完Pycharm,就可以创建工程了。创建完一个pure python的工程后呢,找到Pycharm的Settings菜单,在随后弹出的对话框的右侧找到Tools->External Tools,然后两次点击“+”图标添加QtDesigner和PyUI工具。
2.3 给pycharm添加QtDesiner和PyUI工具
QtDesigner和PyUI都是在已经安装的anaconda3的bin目录下。QtDesigner(注意:在anaconda3的bin目录下,Qtesigner的名字叫designer)、PyUI的配置分别如下面两个图所示。
PyUI的Arguments的参数为:
$FileName$ -o $FileNameWithoutExtension$.py
03—写代码
开发环境准备好后,就开始写代码。这里,QtDesigner是用来可视化的编辑程序界面的,而PyUI是用来将QtDesigner设计的*.ui文件转化为python文件的。 3.1界面设计
用QtDesigner设计简单的Qt设计界面是傻瓜式的,比较简单。可以参观相关的网文教程。
设计过程中需要注意的就是各个控件的命名、大小调整。
我这里,将主界面的layout设置为in a grid的方式,然后拖入3个frame,左边一个frame用来显示摄像头的实时画面;右上frame存放一些拍照、开关摄像头、设置拍照存放路径等操作的控件;右下frame只放了一个只读的text显示控件用来显示一些程序的信息。(注意:左边frame中那个又大又黑的方框其实是一个label,然后把label的背景色设置成了黑色,待会用它来存放摄像头过来的监控图像。)
3.2 转ui文件为python文件
编辑完ui文件并保存后,在对应的ui文件上右键选择External tools子菜单,就可以选择PyUI将ui文件转换为python文件了。要注意的是:如果前面配置开发环境添加External tools的步骤中没有设置PyUI的Arguments属性,这一步会提示找不到文件的错误,也不用着急,回到添加External tools的对话框,选中PyUI之后点击那个铅笔的小图标,再给它添加上Arguments属性即可。
因为PyUI每次将更改的*.ui文件转为python文件的时候都会把原来的文件覆盖掉,所以,我们需要给它再配套另外一个python文件用来写一些槽函数、功能实现的代码等。
代码实现的思想比较有意思,使用opencv获取电脑的视频流,然后将摄像头塞入视频流获取摄像头的图片。而为了实现视频的效果,就开启一个定时器,每隔一个固定的时间(好比10ms)将摄像头获得的图像塞入UI中的一个label中显示。拍照的实现思路也就简单了,将摄像头的图片拿到之后直接用opencv的imwrite函数写入对应目录中就行了。
还有要注意的就是,槽函数的绑定,就是将ui中的控件的一些事件和我们这个python文件中的函数绑在一起,当控件被点击(或其它用户操作)时就会调用这个函数。我这里功能比较少,所以槽函数的绑定初始化就只有短短几行。下面代码段的最后一行,是绑定timer的时间到响应函数为show_camera
def slot_init(self):
self.FilePathBt.clicked.connect(self.SetFilePath)
self.ShowBt.clicked.connect(self.show_btn_clicked)
self.ExitBt.clicked.connect(self.ExitApp)
self.PotoBtn.clicked.connect(self.photo_btn_clicked)
self.timer_camera.timeout.connect(self.show_camera)
当开摄像头被点击的时候就开启监控摄像头的timer。因为开摄像头、关摄像头公用一个个按钮,所以用一个if-else语句实现。
def show_btn_clicked(self):
if not self.timer_camera.isActive(): # 若定时器未启动
flag = self.cap.open(self.CAM_NUM) # 参数是0,表示打开内置摄像头,参数是视频文件路径则打开视频
if not flag: # flag表示open()成不成功
self.MsgTE.clear()
self.MsgTE.setPlainText('请检查摄像头与电脑是否连接正确。')
else:
self.timer_camera.start(50) # 定时器开始计时30ms,结果是每过30ms从摄像头中取一帧显示
self.time_flag = time.perf_counter() # 记录刚开始开启监控计时器的时间,用于后面计算帧率
self.ShowBt.setText('关摄像头')
self.MsgTE.setPlainText('摄像头连接中。。。')
self.PotoBtn.setEnabled(True)
else:
self.timer_camera.stop() # 关闭定时器
self.cap.release() # 释放视频流
self.DispLb.clear() # 清空视频显示区域
self.ShowBt.setText('开摄像头')
self.MsgTE.setText('摄像头已关闭。。。')
self.PotoBtn.setEnabled(False)
self.ImgWidthLCD.display(0)
self.ImgHeightLCD.display(0)
self.FmRateLCD.display(0)
对应的,定时器时间到的响应函数如下面代码段。下面这段代码比较有意思的就是对监控帧率的计算,就是用这一次刷新监控画面的系统时间减去上一次刷新监控画面的系统时间,得到两次相邻画面刷新的时间差,然后取倒数就可以得到刷新频率了。
def show_camera(self):
flag, self.image = self.cap.read() # 从视频流中读取
show = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) # 视频色彩转换回RGB,这样才是现实的颜色
show_image = QtGui.QImage(show.data, show.shape[1], show.shape[0],
QtGui.QImage.Format_RGB888) # 把读取到的视频数据变成QImage形式
self.DispLb.setPixmap(QtGui.QPixmap.fromImage(show_image)) # 往显示视频的Label里 显示QImage
frame_rate = 1 / (time.perf_counter() - self.time_flag) # 计算视频帧率
self.time_flag = time.perf_counter() # 用于下一次计算帧率使用,即相邻两次显示监控图像时间差的倒数
self.ImgWidthLCD.display(self.cap.get(3))
self.ImgHeightLCD.display(self.cap.get(4))
self.FmRateLCD.display(int(frame_rate))
接下来,就是拍照功能的实现。这段代码里面,值得注意的就是每次保存完图片之后呢,还要在图片上打上“image saved”的标签然后再送到图片显示label上显示(当然,因为画面刷新较快,这个文字在程序界面上会一闪而过)。
def photo_btn_clicked(self):
"""
拍照按钮响应函数
:return:
"""
if self.timer_camera.isActive():
now_time = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))
self.MsgTE.setText('拍照中...' + now_time)
if cv2.imwrite(self.RecordPath + str(self.photo_num + 1) + '.jpg', self.image): # 如果保存图像成功
cv2.putText(self.image, 'image saved',
(int(self.image.shape[1] / 2 - 130), int(self.image.shape[0] / 2)),
cv2.FONT_HERSHEY_SCRIPT_COMPLEX,
1.0, (255, 0, 0), 1)
show = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) # 视频色彩转换回RGB,这样才是现实的颜色
show_image = QtGui.QImage(show.data, show.shape[1], show.shape[0],
QtGui.QImage.Format_RGB888) # 把读取到的视频数据变成QImage形式
self.DispLb.setPixmap(QtGui.QPixmap.fromImage(show_image)) # 往显示视频的Label里 显示QImage
self.photo_num += 1
self.MsgTE.setText('第' + str(self.photo_num) + '张照片拍摄完成')
else:
self.MsgTE.setText('拍照失败,可能是路径选择错误')
至此,几个主要的功能就都实现了。
04—碰到的一些问题 1. import cv2的时候可能会报错,“(Linux) qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.”这个问题应该是qt的安装路径没有加到系统环境变量中引起的错误。可以在代码前面设置,
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = envpath
当然,这样并不好,最好还是重新安装qt或者配置系统环境变量。
2. pycharm的注释不能输入中文的问题,在Pycharm->help菜单选择“Edit Custom VM options ”, 在打开“pychar64.vmoptions”文件最后一行添加:“-Drecreate.x11.input.method=true”然后重新启动pycharm即可。