목차
머리말
얼마 전 연구 끝에 LeNet-5 손글씨 숫자의 도입부터 최근 날씨 인식 연구에 이르기까지. 저는 C++/Qt 개발에 종사하고 있고 Qt에 비교적 익숙하기 때문에 큰 투표를 하고 싶습니다. 그래서 인터페이스 기반 얼굴 인식을 구현하고 싶습니다.
Convolutional Neural Networks의 개념이 생소 하다면 Convolution은 실제로 무엇을합니까?
신경망의 훈련 과정이나 환경을 구축하는 방법을 이해하고 싶다면 이것을 읽을 수 있습니다. 기사: 환경 구축 및 교육 과정
이 프로젝트에 대해 배우고 싶다면
첫 번째 기사 로 이동하십시오 : Convolutional Neural Network (tensorflow) 기반 얼굴 인식 프로젝트 (1) Part 2: Convolutional Neural Network (tensorflow)
기반 얼굴 인식 프로젝트 (2)
Part 2 Three : Convolutional Neural Network(tensorflow) 기반 얼굴 인식 프로젝트 (3)
기본 아이디어
구체적인 단계는 다음과 같습니다.
- 먼저 데이터를 수집해야 하는 제 생각은 OpenCV를 통해 카메라를 호출하여 얼굴 사진을 수집하는 것입니다.
- 그런 다음 주로 데이터 세트, 훈련 세트, 검증 세트 및 테스트 세트를 분류하기 위해 전처리를 수행합니다.
- 모델 학습을 시작하려면 미리 레이블 키-값 쌍을 만드십시오.
- 얼굴 인식 효과를 테스트하고 OpenCV를 통해 얼굴 사진을 캡처한 다음 이미지를 전처리하고 최종적으로 모델에 전달한 다음 인식 결과를 텍스트 형식으로 화면에 인쇄하고 q를 입력할 때까지 순환하여 종료합니다.
이 문서에서는 주로 위의 단계에서 네 번째 단계를 구현합니다. 마지막으로 전체 프로세스의 전체 프로젝트 코드를 공유합니다.
얼굴 인식 효과 테스트
디자인 아이디어
OpenCV를 통해 카메라를 열어 얼굴 영역을 캡처한 다음 이미지(예: 그레이스케일, 정규화 등)를 사전 처리한 다음 모델을 로드하고 처리된 이미지를 예측을 위해 모델에 넣은 다음 결과에 대해 예측을 수행합니다. 정밀도 필터는 인식률이 부정확한 결과의 90% 미만인 것으로 간주하고 높게 인식할 수 없음을 나타내기 위해 기타를 출력합니다. 정상적인 상황에서는 각 파일에 해당하는 레이블이 출력됩니다.
상세 코드
로드 모델
앞서 load_model 메소드를 통해 학습한 모델을 로드합니다.
MODEL_PATH = './me.face.model.h5'
def load_model(self, file_path=MODEL_PATH):
self.model = load_model(file_path)
얼굴 예측
여기에서 이미지는 정규화되고 예측 결과는 정밀 필터링됩니다.
알아채다: K.image_dim_ordering() =\= 'th'
: 오류가 보고되면 원본 " "에 해당하는 " "로, 원본 " "에 해당하는 " " 로 교체 K.image_data_format() == 'channels_first'
하십시오 .channels_last
tf
channels_first
th
def face_predict(self, image):
if K.image_data_format() == 'channels_first' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
image = resize_image(image) # 尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE
image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) # 与模型训练不同,这次只是针对1张图片进行预测
elif K.image_data_format() == 'channels_last' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
image = resize_image(image)
image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))
# 浮点并归一化
image = image.astype('float32')
image /= 255
# 给出输入属于各个类别的概率
result = self.model.predict_proba(image)
print('result:', result)
my_result = list(result[0]).index(max(result[0]))
max_result = max(result[0])
print("result最大值下标:", my_result)
if max_result > 0.90:
return my_result
else:
return -1
주요 논리
주로 다음 단계로 나뉩니다.
- 로드 모델
- 전처리 (카메라, 얼굴 영역의 테두리 색상, haar 분류기의 경로, 레이블 배열 등 설정)
- 사진 프레임 캡처
- 사진을 그레이스케일
- 얼굴 면적을 계산하고 프레임
- 인식을 위해 얼굴 영역을 인식 모델에 넘겨줍니다.
- 인식 결과의 스크린 인쇄
- 그런 다음 3-7단계를 순환합니다.
- q를 입력하여 인식을 종료한 다음 점유된 리소스를 해제합니다.
이 로직은 그다지 복잡하지 않습니다.코드는 바로 아래에 표시되며 코드에 자세한 설명도 있습니다.
import cv2
from keras_train import Model
if __name__ == '__main__':
model = Model()# 加载模型
model.load_model(file_path='./model/me.face.model.h5')
color = (0, 255, 0)# 框住人脸的矩形边框颜色
cap = cv2.VideoCapture(0)# 捕获指定摄像头的实时视频流
cascade_path = "./model/haarcascade_frontalface_alt2.xml"# 人脸识别分类器本地存储路径
# 循环检测识别人脸
while True:
_, frame = cap.read() # 读取一帧视频
# 图像灰化,降低计算复杂度
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 使用人脸识别分类器,读入分类器
cascade = cv2.CascadeClassifier(cascade_path)
# 利用分类器识别出哪个区域为人脸
faceRects = cascade.detectMultiScale(frame_gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
if len(faceRects) > 0:
for faceRect in faceRects:
x, y, w, h = faceRect
# 截取脸部图像提交给模型识别这是谁
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
faceID = model.face_predict(image)
#
human = {
0: 'use2', 1: 'use3', 2: 'use4', -1: 'others', 3: 'use5', 4: 'LinXi07',
5: 'use7', 6: 'use8', 7: 'use1', 8: 'use9'}
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness=2)
# 文字提示是谁
cv2.putText(frame, human[faceID],
(x + 30, y + 30), # 坐标
cv2.FONT_HERSHEY_SIMPLEX, # 字体
1, # 字号
(255, 0, 255), # 颜色
2) # 字的线宽
cv2.imshow("Face Identification System", frame)
# 等待10毫秒看是否有按键输入
k = cv2.waitKey(10)
# 如果输入q则退出循环
if k & 0xFF == ord('q'):
break
# 释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows()
테스트 효과
이 모델은 여기에서 테스트되었으며 동시에 여러 얼굴 인식을 지원하며 인식률이 높습니다. ( 얼굴 무시 )
요약하다
초기 데이터 수집에서 중간 전처리, 후기 모델 구축, 최종 테스트 효과까지, 데이터 형식이 오류와 일치하지 않거나 버전이 앞뒤가 맞지 않아 환경을 다시 설치하는 등 도로가 매우 울퉁불퉁합니다. 그리고 나서 모델 인식률. 가지 마세요. 드디어 완성해서 길은 험난하지만 수확은 싸지 않다. 같이 하자! ! ! !
모든 코드
여기에서 파일별로 파일을 분류하는데 총 4개의 파일이 있습니다.
파일 이름 | 설명하다 |
---|---|
frame_capture.py | 데이터세트 만들기 |
keras_train.py | 모델 학습 모델 평가 |
load_data.py | 데이터 로드, 전처리 |
face_predict_use_keras.py | 예측 모델 |
그런 다음 deep_learning을 사용하여 각 사람의 사진을 저장합니다. model은 모델과 harr 분류기를 저장하는 데 사용됩니다.
face_predict_use_keras.py
import cv2
import sys
def catch_usb_video(window_name, camera_idx):
'''使用cv2.imshow()的时候,如果图片太大,会显示不全并且无法调整。
因此在cv2.imshow()的前面加上这样的一个语句:cv2.namedWindow('image', 0),
得到的图像框就可以自行调整大小,可以拉伸进行自由调整。'''
cv2.namedWindow(window_name, 0)
# 视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
cap = cv2.VideoCapture(camera_idx)
# 告诉OpenCV使用人脸识别分类器 级联分类器
'''
Haar特征是一种反映图像的灰度变化的,像素分模块求差值的一种特征。它分为三类:边缘特征、线性特征、中心特征和对角线特征。
'''
classfier = cv2.CascadeClassifier("./model/haarcascade_frontalface_alt2.xml")
# 识别出人脸后要画的边框的颜色,RGB格式
color = (0, 0, 255)
num = 0
while cap.isOpened():
ok, frame = cap.read() # 读取一帧数据
if not ok:
break
# 将当前帧转换成灰度图像
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
faceRects = classfier.detectMultiScale(grey, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
# faceRects = [405 237 222 222]
if len(faceRects) > 0: # 大于0则检测到人脸
for faceRect in faceRects: # 单独框出每一张人脸
# 在原图上框出需要保存的图
x, y, w, h = faceRect
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
# 将当前帧保存为图片
# frame 是原图,(x - 10, y - 10) 是图片的左上角的那个点,(x + w + 10, y + h + 10)是图片右下角的点
# color, 2 颜色和线的宽度
img_name = '%s/%d.jpg' % ('./deep_learning/zm', num)
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
cv2.imwrite(img_name, image)
num += 1
if num > (500): # 如果超过指定最大保存数量退出循环
break
# 画出矩形框
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
# 显示当前捕捉到了多少人脸图片了,这样站在那里被拍摄时心里有个数,不用两眼一抹黑傻等着
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame, 'num:%d' % (num), (x + 30, y + 30), font, 1, (255, 0, 255), 4)
# 超过指定最大保存数量结束程序
if num > (500):
break
# 显示图像
cv2.imshow(window_name, frame)
c = cv2.waitKey(1)
if c & 0xFF == ord('q'):
break
# 释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
catch_usb_video("face", 0)
keras_train.py
import random
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.models import load_model
from keras import backend as K
from load_data import load_dataset, resize_image, IMAGE_SIZE
class Dataset:
def __init__(self, path_name):
# 训练集
self.train_images = None
self.train_labels = None
# 验证集
self.valid_images = None
self.valid_labels = None
# 测试集
self.test_images = None
self.test_labels = None
# 数据集加载路径
self.path_name = path_name
# 当前库采用的维度顺序
self.input_shape = None
# 加载数据集并按照交叉验证的原则划分数据集并进行相关预处理工作
def load(self, img_rows=IMAGE_SIZE, img_cols=IMAGE_SIZE,
img_channels=3, nb_classes=3):
# 加载数据集到内存
images, labels = load_dataset(self.path_name)
train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size=0.2,
random_state=random.randint(0, 100))
_, test_images, _, test_labels = train_test_split(images, labels, test_size=0.5,
random_state=random.randint(0, 100))
# 当前的维度顺序如果为'th',则输入图片数据时的顺序为:channels,rows,cols,否则:rows,cols,channels
# 这部分代码就是根据keras库要求的维度顺序重组训练数据集
# if K.image_dim_ordering() == 'th': “channels_last”对应原本的“tf”,“channels_first”对应原本的“th”。
if K.image_data_format() == 'channels_first':
train_images = train_images.reshape(train_images.shape[0], img_channels, img_rows, img_cols)
valid_images = valid_images.reshape(valid_images.shape[0], img_channels, img_rows, img_cols)
test_images = test_images.reshape(test_images.shape[0], img_channels, img_rows, img_cols)
self.input_shape = (img_channels, img_rows, img_cols)
else:
train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, img_channels)
valid_images = valid_images.reshape(valid_images.shape[0], img_rows, img_cols, img_channels)
test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, img_channels)
self.input_shape = (img_rows, img_cols, img_channels)
# 输出训练集、验证集、测试集的数量
print(train_images.shape[0], 'train samples')
print(valid_images.shape[0], 'valid samples')
print(test_images.shape[0], 'test samples')
# 我们的模型使用categorical_crossentropy作为损失函数,因此需要根据类别数量nb_classes将
# 类别标签进行one-hot编码使其向量化,在这里我们的类别只有两种,经过转化后标签数据变为二维
train_labels = np_utils.to_categorical(train_labels, nb_classes)
valid_labels = np_utils.to_categorical(valid_labels, nb_classes)
test_labels = np_utils.to_categorical(test_labels, nb_classes)
# 像素数据浮点化以便归一化
train_images = train_images.astype('float32')
valid_images = valid_images.astype('float32')
test_images = test_images.astype('float32')
# 将其归一化,图像的各像素值归一化到0~1区间
train_images /= 255
valid_images /= 255
test_images /= 255
self.train_images = train_images
self.valid_images = valid_images
self.test_images = test_images
self.train_labels = train_labels
self.valid_labels = valid_labels
self.test_labels = test_labels
# CNN网络模型类
class Model:
def __init__(self):
self.model = None
# 建立模型
def build_model(self, dataset, nb_classes=3):
# 构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
self.model = Sequential()
# 以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层
self.model.add(Convolution2D(32, 3, 3, border_mode='same',
input_shape=dataset.input_shape)) # 1 2维卷积层
self.model.add(Activation('relu')) # 2 激活函数层
self.model.add(Convolution2D(32, 3, 3)) # 3 2维卷积层
self.model.add(Activation('relu')) # 4 激活函数层
self.model.add(MaxPooling2D(pool_size=(2, 2))) # 5 池化层
self.model.add(Dropout(0.25)) # 6 Dropout层
self.model.add(Convolution2D(64, 3, 3, border_mode='same')) # 7 2维卷积层
self.model.add(Activation('relu')) # 8 激活函数层
self.model.add(Convolution2D(64, 3, 3)) # 9 2维卷积层
self.model.add(Activation('relu')) # 10 激活函数层
self.model.add(MaxPooling2D(pool_size=(2, 2))) # 11 池化层
self.model.add(Dropout(0.25)) # 12 Dropout层
self.model.add(Flatten()) # 13 Flatten层
self.model.add(Dense(512)) # 14 Dense层,又被称作全连接层
self.model.add(Activation('relu')) # 15 激活函数层
self.model.add(Dropout(0.5)) # 16 Dropout层
self.model.add(Dense(nb_classes)) # 17 Dense层
self.model.add(Activation('softmax')) # 18 分类层,输出最终结果
# 输出模型概况
self.model.summary()
# 训练模型
def train(self, dataset, batch_size=20, nb_epoch=10, data_augmentation=True):
# 参数batch_size的作用即在于此,其指定每次迭代训练样本的数量
# nb_epoch 训练轮换次数
sgd = SGD(lr=0.01, decay=1e-6,
momentum=0.9, nesterov=True) # 采用SGD+momentum的优化器进行训练,首先生成一个优化器对象
self.model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy']) # 完成实际的模型配置工作
# 不使用数据提升,所谓的提升就是从我们提供的训练数据中利用旋转、翻转、加噪声等方法创造新的
# 训练数据,有意识的提升训练数据规模,增加模型训练量
if not data_augmentation:
self.model.fit(dataset.train_images,
dataset.train_labels,
batch_size=batch_size,
nb_epoch=nb_epoch,
validation_data=(dataset.valid_images, dataset.valid_labels),
shuffle=True)
# 使用实时数据提升
else:
# 定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一
# 次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器
datagen = ImageDataGenerator(
featurewise_center=False, # 是否使输入数据去中心化(均值为0),
samplewise_center=False, # 是否使输入数据的每个样本均值为0
featurewise_std_normalization=False, # 是否数据标准化(输入数据除以数据集的标准差)
samplewise_std_normalization=False, # 是否将每个样本数据除以自身的标准差
zca_whitening=False, # 是否对输入数据施以ZCA白化
rotation_range=20, # 数据提升时图片随机转动的角度(范围为0~180)
width_shift_range=0.2, # 数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数)
height_shift_range=0.2, # 同上,只不过这里是垂直
horizontal_flip=True, # 是否进行随机水平翻转
vertical_flip=False) # 是否进行随机垂直翻转
# 计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理
datagen.fit(dataset.train_images)
# 利用生成器开始训练模型
self.model.fit_generator(datagen.flow(dataset.train_images, dataset.train_labels,
batch_size=batch_size),
samples_per_epoch=dataset.train_images.shape[0],
nb_epoch=nb_epoch,
validation_data=(dataset.valid_images, dataset.valid_labels))
MODEL_PATH = './me.face.model.h5'
def save_model(self, file_path=MODEL_PATH):
self.model.save(file_path)
def load_model(self, file_path=MODEL_PATH):
self.model = load_model(file_path)
def evaluate(self, dataset):
score = self.model.evaluate(dataset.test_images, dataset.test_labels, verbose=1)
print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))
def face_predict(self, image):
# if K.image_dim_ordering() == 'th': “channels_last”对应原本的“tf”,“channels_first”对应原本的“th”。
if K.image_data_format() == 'channels_first' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
image = resize_image(image) # 尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE
image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) # 与模型训练不同,这次只是针对1张图片进行预测
elif K.image_data_format() == 'channels_last' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
image = resize_image(image)
image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))
# 浮点并归一化
image = image.astype('float32')
image /= 255
# 给出输入属于各个类别的概率
result = self.model.predict_proba(image)
print('result:', result)
my_result = list(result[0]).index(max(result[0]))
max_result = max(result[0])
print("result最大值下标:", my_result)
if max_result > 0.90:
return my_result
else:
return -1
if __name__ == '__main__':
dataset = Dataset('.\\deep_learning')
dataset.load(nb_classes=9)
# 训练模型
model = Model()
model.build_model(dataset, nb_classes=9)
model.train(dataset)
model.save_model(file_path='./model/me.face.model.h5')
# 评估模型,确认模型的精度是否能达到要求
model = Model()
model.load_model(file_path='./model/me.face.model.h5')
model.evaluate(dataset)
load_data.py
import os
import numpy as np
import cv2
IMAGE_SIZE = 64
# 按照指定图像大小调整尺寸
def resize_image(image, height=IMAGE_SIZE, width=IMAGE_SIZE):
top, bottom, left, right = (0, 0, 0, 0)
# 获取图像尺寸
h, w, _ = image.shape
# 对于长宽不相等的图片,找到最长的一边
longest_edge = max(h, w)
# 计算短边需要增加多上像素宽度使其与长边等长
if h < longest_edge:
dh = longest_edge - h
top = dh // 2
bottom = dh - top
elif w < longest_edge:
dw = longest_edge - w
left = dw // 2
right = dw - left
else:
pass
# RGB颜色
BLACK = [0, 0, 0]
# 给图像增加边界,是图片长、宽等长,cv2.BORDER_CONSTANT指定边界颜色由value指定
constant = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=BLACK)
# 调整图像大小并返回
return cv2.resize(constant, (height, width))
# 读取训练数据
images = []
labels = []
def read_path(path_name):
for dir_item in os.listdir(path_name):
# 从初始路径开始叠加,合并成可识别的操作路径
full_path = os.path.abspath(os.path.join(path_name, dir_item))
#
if os.path.isdir(full_path): # 如果是文件夹,继续递归调用
read_path(full_path)
else: # 文件
if dir_item.endswith('.jpg'):
image = cv2.imread(full_path)
image = resize_image(image, IMAGE_SIZE, IMAGE_SIZE)
images.append(image)
labels.append(path_name)
return images, labels
# 给文件夹后缀,加标注,使其向量化,如果添加新的人,就可以对应文件夹和下标
# 这里我随便写的label 因为涉及到隐私。
def indentify(label):
if label.endswith('use'):
return 0
elif label.endswith('use'):
return 1
elif label.endswith('use'):
return 2
elif label.endswith('use'):
return 3
elif label.endswith('use'):
return 4
elif label.endswith('use'):
return 5
elif label.endswith('use'):
return 6
elif label.endswith('use'):
return 7
elif label.endswith('use'):
return 8
# 从指定路径读取训练数据
def load_dataset(path_name):
images, labels = read_path(path_name)
# 将输入的所有图片转成四维数组,尺寸为(图片数量*IMAGE_SIZE*IMAGE_SIZE*3)
# 图片为64 * 64像素,一个像素3个颜色值(RGB)
images = np.array(images)
print(images.shape)
labels = np.array([indentify(label) for label in labels])
print(images,labels)
return images, labels
if __name__ == '__main__':
images, labels = load_dataset('./deep_learning')
face_predict_use_keras.py
import cv2
from keras_train import Model
if __name__ == '__main__':
# 加载模型
model = Model()
model.load_model(file_path='./model/me.face.model.h5')
# 框住人脸的矩形边框颜色
color = (0, 255, 0)
# 捕获指定摄像头的实时视频流
cap = cv2.VideoCapture(0)
# 人脸识别分类器本地存储路径
cascade_path = "./model/haarcascade_frontalface_alt2.xml"
# 循环检测识别人脸
while True:
_, frame = cap.read() # 读取一帧视频
# 图像灰化,降低计算复杂度
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 使用人脸识别分类器,读入分类器
cascade = cv2.CascadeClassifier(cascade_path)
# 利用分类器识别出哪个区域为人脸
faceRects = cascade.detectMultiScale(frame_gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
if len(faceRects) > 0:
for faceRect in faceRects:
x, y, w, h = faceRect
# 截取脸部图像提交给模型识别这是谁
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
faceID = model.face_predict(image)
# 这里我随便写的,因为涉及到隐私。
human = {
0: 'use3', 1: 'use4', 2: 'use8', -1: 'others', 3: 'use88', 4: 'LinXi07',
5: 'use2', 6: 'use', 7: 'use1', 8: 'use7'}
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness=2)
# 文字提示是谁
cv2.putText(frame, human[faceID],
(x + 30, y + 30), # 坐标
cv2.FONT_HERSHEY_SIMPLEX, # 字体
1, # 字号
(255, 0, 255), # 颜色
2) # 字的线宽
cv2.imshow("Face Identification System", frame)
# 等待10毫秒看是否有按键输入
k = cv2.waitKey(10)
# 如果输入q则退出循环
if k & 0xFF == ord('q'):
break
# 释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows()