forked from PyQt5/PyQt
-
Notifications
You must be signed in to change notification settings - Fork 6
/
FacePoints.py
172 lines (154 loc) · 6.55 KB
/
FacePoints.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on 2018年1月29日
@author: Irony
@site: https://pyqt.site , https://github.com/PyQt5
@email: [email protected]
@file: FacePoints
@description: 人脸特征点
"""
import cgitb
import os
import sys
from bz2 import BZ2Decompressor
import cv2 # @UnresolvedImport
import dlib
import numpy
try:
from PyQt5.QtCore import QTimer, QUrl, QFile, QIODevice
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PyQt5.QtWidgets import QLabel, QMessageBox, QApplication
except ImportError:
from PySide2.QtCore import QTimer, QUrl, QFile, QIODevice
from PySide2.QtGui import QImage, QPixmap
from PySide2.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PySide2.QtWidgets import QLabel, QMessageBox, QApplication
DOWNSCALE = 4
URL = 'http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2'
class OpencvWidget(QLabel):
def __init__(self, *args, **kwargs):
super(OpencvWidget, self).__init__(*args, **kwargs)
self.httpRequestAborted = False
self.fps = 24
self.resize(800, 600)
if not os.path.exists("Data/shape_predictor_68_face_landmarks.dat"):
self.setText("正在下载数据文件。。。")
self.outFile = QFile(
"Data/shape_predictor_68_face_landmarks.dat.bz2")
if not self.outFile.open(QIODevice.WriteOnly):
QMessageBox.critical(self, '错误', '无法写入文件')
return
self.qnam = QNetworkAccessManager(self)
self._reply = self.qnam.get(QNetworkRequest(QUrl(URL)))
self._reply.finished.connect(self.httpFinished)
self._reply.readyRead.connect(self.httpReadyRead)
self._reply.downloadProgress.connect(self.updateDataReadProgress)
else:
self.startCapture()
def httpFinished(self):
self.outFile.close()
if self.httpRequestAborted or self._reply.error():
self.outFile.remove()
self._reply.deleteLater()
del self._reply
# 下载完成解压文件并加载摄像头
self.setText("正在解压数据。。。")
try:
bz = BZ2Decompressor()
data = bz.decompress(
open('Data/shape_predictor_68_face_landmarks.dat.bz2', 'rb').read())
open('Data/shape_predictor_68_face_landmarks.dat', 'wb').write(data)
except Exception as e:
self.setText('解压失败:' + str(e))
return
self.setText('正在开启摄像头。。。')
self.startCapture()
def httpReadyRead(self):
self.outFile.write(self._reply.readAll())
self.outFile.flush()
def updateDataReadProgress(self, bytesRead, totalBytes):
self.setText('已下载:{} %'.format(round(bytesRead / 64040097 * 100, 2)))
def startCapture(self):
self.setText("请稍候,正在初始化数据和摄像头。。。")
try:
# 检测相关
self.detector = dlib.get_frontal_face_detector()
self.predictor = dlib.shape_predictor(
"Data/shape_predictor_68_face_landmarks.dat")
cascade_fn = "Data/lbpcascades/lbpcascade_frontalface.xml"
self.cascade = cv2.CascadeClassifier(cascade_fn)
if not self.cascade:
return QMessageBox.critical(self, "错误", cascade_fn + " 无法找到")
self.cap = cv2.VideoCapture(0)
if not self.cap or not self.cap.isOpened():
return QMessageBox.critical(self, "错误", "打开摄像头失败")
# 开启定时器定时捕获
self.timer = QTimer(self, timeout=self.onCapture)
self.timer.start(1000 / self.fps)
except Exception as e:
QMessageBox.critical(self, "错误", str(e))
def closeEvent(self, event):
if hasattr(self, "_reply") and self._reply:
self.httpRequestAborted = True
self._reply.abort()
try:
os.unlink("Data/shape_predictor_68_face_landmarks.dat.bz2")
except:
pass
try:
os.unlink("Data/shape_predictor_68_face_landmarks.dat")
except:
pass
if hasattr(self, "timer"):
self.timer.stop()
self.timer.deleteLater()
self.cap.release()
del self.predictor, self.detector, self.cascade, self.cap
super(OpencvWidget, self).closeEvent(event)
self.deleteLater()
def onCapture(self):
_, frame = self.cap.read()
minisize = (
int(frame.shape[1] / DOWNSCALE), int(frame.shape[0] / DOWNSCALE))
tmpframe = cv2.resize(frame, minisize)
tmpframe = cv2.cvtColor(tmpframe, cv2.COLOR_BGR2GRAY) # 做灰度处理
tmpframe = cv2.equalizeHist(tmpframe)
# minNeighbors表示每一个目标至少要被检测到5次
faces = self.cascade.detectMultiScale(tmpframe, minNeighbors=5)
del tmpframe
if len(faces) < 1: # 没有检测到脸
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = QImage(
frame.data, frame.shape[1], frame.shape[0], frame.shape[1] * 3, QImage.Format_RGB888)
del frame
return self.setPixmap(QPixmap.fromImage(img))
# 特征点检测描绘
for x, y, w, h in faces:
x, y, w, h = x * DOWNSCALE, y * DOWNSCALE, w * DOWNSCALE, h * DOWNSCALE
# 画脸矩形
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0))
# 截取的人脸部分
tmpframe = frame[y:y + h, x:x + w]
# 进行特征点描绘
rects = self.detector(tmpframe, 1)
if len(rects) > 0:
landmarks = numpy.matrix(
[[p.x, p.y] for p in self.predictor(tmpframe, rects[0]).parts()])
for _, point in enumerate(landmarks):
pos = (point[0, 0] + x, point[0, 1] + y)
# 在原来画面上画点
cv2.circle(frame, pos, 3, color=(0, 255, 0))
# 转成Qt能显示的
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = QImage(
frame.data, frame.shape[1], frame.shape[0], frame.shape[1] * 3, QImage.Format_RGB888)
del frame
self.setPixmap(QPixmap.fromImage(img))
if __name__ == "__main__":
cgitb.enable(format='text')
app = QApplication(sys.argv)
w = OpencvWidget()
w.show()
sys.exit(app.exec_())