基于机器学习的人脸口罩识别核酸检测大系统

人脸识别(基于opencv python实现)

识别有没有戴口罩

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
# 需要注意的是:训练模型是基于haarcascades 需要自己去下载一下
"""
# File : mask_check.py
# Time :2021/6/10 15:02
# Author :Meng
# version :python 3.10
# Description:
"""
import cv2 # 导入opencv
import time # 导入time

"""实现鼻子检测"""
def nose_dection(img):
img = cv2.GaussianBlur(img,(5,5),0)#高斯滤波
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将图片转化成灰度
nose_cascade = cv2.CascadeClassifier("./haarcascades/haarcascade_mcs_nose.xml")
nose_cascade.load("./haarcascades/haarcascade_mcs_nose.xml") # 文件所在的具体位置
'''此文件是opencv的haar鼻子特征分类器'''
noses = nose_cascade.detectMultiScale(gray, 1.3, 5) # 鼻子检测
for(x,y,w,h) in noses:
cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) # 画框标识鼻子
flag = 0 # 检测到鼻子的标志位,如果监测到鼻子,则判断未带口罩
if len(noses)>0:
flag = 1
return img,flag

""""实现眼睛检测"""
def eye_dection(img):
img = cv2.GaussianBlur(img,(5,5),0)#高斯滤波
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将图片转化成灰度
eyes_cascade = cv2.CascadeClassifier("./haarcascades/haarcascade_eye_tree_eyeglasses.xml")
eyes_cascade.load("./haarcascades/haarcascade_eye_tree_eyeglasses.xml") # 文件所在的具体位置
'''此文件是opencv的haar眼睛特征分类器'''
eyes = eyes_cascade.detectMultiScale(gray, 1.3, 5) # 眼睛检测
for (x,y,w,h) in eyes:
frame = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2) # 画框标识眼部
print("x y w h is",(x,y,w,h))
# frame = cv2.rectangle(img, (x, y+h), (x + 3*w, y + 3*h), (255, 0, 0), 2) # 画框标识眼部
return img,eyes

def empty(a):
pass

def main():
image = cv2.imread("images/backgound.png") # 读取背景照片
cv2.imshow('skin', image) # 展示
cv2.createTrackbar("Hmin", "skin", 0, 90, empty) # 创建bar
cv2.createTrackbar("Hmax", "skin", 25, 90, empty)
capture = cv2.VideoCapture(0) # 打开摄像头,其中0为自带摄像头,
while True:
ref,img=capture.read() # 打开摄像头
# img = cv2.imread("./images/train_301.jpg") # 读取一张图片
img_hsv = img
image_nose,flag_nose = nose_dection(img) # 进行口罩检测,返回检测之后的图形以及标志位
if flag_nose == 1: # 当检测到鼻子的时候,判断未戴口罩
frame = cv2.putText(image_nose, "NO MASK", (10, 30), cv2.FONT_HERSHEY_COMPLEX, 0.9,(0, 0, 255), 1) # 在图片上写字
cv2.imshow('img', image_nose) # 展示图片
if flag_nose == 0: # 未检测鼻子,进行眼睛检测
img_eye,eyes = eye_dection(img) # 进行眼睛检测,返回检测之后的图形以及标志位
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 将图片转化成HSV格式
H, S, V = cv2.split(hsv) #
Hmin= cv2.getTrackbarPos("Hmin", 'skin') # 获取bar
Hmax= cv2.getTrackbarPos("Hmax", 'skin')
if Hmin> Hmax:
Hmax= Hmin
thresh_h = cv2.inRange(H, Hmin, Hmax) # 提取人体肤色区域
if len(eyes) > 1: # 判断是否检测到两个眼睛,其中eyes[0]为左眼坐标
# 口罩区域的提取
mask_x_begin = min(eyes[0][0],eyes[1][0]) # 把左眼的x坐标作为口罩区域起始x坐标
mask_x_end = max(eyes[0][0],eyes[1][0]) + eyes[list([eyes[0][0], eyes[1][0]]).index(max(list([eyes[0][0], eyes[1][0]])))][2] # 把右眼x坐标 + 右眼宽度作为口罩区域x的终止坐标
mask_y_begin = max(eyes[0][1] + eyes[0][3],eyes[1][1] + eyes[1][3]) + 20 # 把眼睛高度最大的作为口罩区域起始y坐标
if mask_y_begin > img_eye.shape[1]: # 判断是否出界
mask_y_begin = img_eye.shape[1]
mask_y_end = max(eyes[0][1] + 3 * eyes[0][3],eyes[1][1] + 3 * eyes[1][3]) + 20 # 同理
if mask_y_end > img_eye.shape[1]:
mask_y_end = img_eye.shape[1]
frame = cv2.rectangle(img_eye, (mask_x_begin, mask_y_begin), (mask_x_end, mask_y_end), (255, 0, 0), 2) # 画口罩区域的框
total_mask_pixel = 0
total_face_pixel = 0
# 遍历二值图,为0则total_mask_pixel+1,否则total_face_pixel+1
for i in range(mask_x_begin,mask_x_end):
for j in range(mask_y_begin,mask_y_end):
if 0<i<480 and 0<j<480:
if thresh_h[i,j] == 0:
total_mask_pixel += 1
else:
total_face_pixel += 1
print("total_mask_pixel",total_mask_pixel)
print("total_face_pixel", total_face_pixel)
if total_mask_pixel > total_face_pixel:
frame = cv2.putText(img_eye, "HAVE MASK", (mask_x_begin, mask_y_begin - 10),cv2.FONT_HERSHEY_COMPLEX, 0.9, (0, 0, 255), 1) # 绘制
if total_mask_pixel < total_face_pixel:
frame = cv2.putText(img_eye, "NO MASK", (mask_x_begin, mask_y_begin - 10), cv2.FONT_HERSHEY_COMPLEX,0.9, (0, 0, 255), 1) # 绘制
cv2.imshow("skin", thresh_h) # 显示肤色图
cv2.imshow("img", img_eye) # 显示肤色图
# cv2.imwrite('005_result.jpg',img_eye) 保存图片
c = cv2.waitKey(10)
if c==27:
break
capture.release() #
cv2.destroyAllWindows() # 关闭所有窗口


if __name__ == '__main__':
main()

口罩人脸训练+识别

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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# 实验环境:python 3.6 + opencv-python 3.4.14.51

import cv2
import numpy as np
import os
import shutil
import threading
import tkinter as tk
from PIL import Image, ImageTk

# 首先读取config文件,第一行代表当前已经储存的人名个数,接下来每一行是(id,name)标签和对应的人名
id_dict = {} # 字典里存的是id——name键值对
Total_face_num = 999 # 已经被识别有用户名的人脸个数,


def init(): # 将config文件内的信息读入到字典中
f = open('config.txt')#打开目录文件
global Total_face_num
Total_face_num = int(f.readline())

for i in range(int(Total_face_num)):
line = f.readline()
id_name = line.split(' ')
id_dict[int(id_name[0])] = id_name[1]
f.close()


init()
pathf ='haarcascade_frontalface_default.xml'

# 加载OpenCV人脸检测分类器Haar
face_cascade = cv2.CascadeClassifier(pathf)

# 准备好识别方法LBPH方法
recognizer = cv2.face.LBPHFaceRecognizer_create()

# 打开标号为0的摄像头
camera = cv2.VideoCapture(0) # 摄像头
success, img = camera.read() # 从摄像头读取照片
W_size = 0.1 * camera.get(3)
H_size = 0.1 * camera.get(4)

system_state_lock = 0 # 标志系统状态的量 0表示无子线程在运行 1表示正在刷脸 2表示正在录入新面孔。
# 相当于mutex锁,用于线程同步


'''
============================================================================================
以上是初始化
============================================================================================
'''


def Get_new_face():
print("正在从摄像头录入新人脸信息 \n")

# 存在目录data就清空,不存在就创建,确保最后存在空的data目录
filepath = "data"
#灰度图存放位置
if not os.path.exists(filepath):
os.mkdir(filepath)#错误信息提示:未找到对应文件
else:
shutil.rmtree(filepath)
os.mkdir(filepath)

sample_num = 0 # 已经获得的样本数

while True: # 从摄像头读取图片

global success
global img # 因为要显示在可视化的控件内,所以要用全局的
success, img = camera.read()

# 转为灰度图片
if success is True:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
break

# 检测人脸,将每一帧摄像头记录的数据带入OpenCv中,让Classifier判断人脸
# 其中gray为要检测的灰度图像,1.3为每次图像尺寸减小的比例,5为minNeighbors
face_detector = face_cascade
faces = face_detector.detectMultiScale(gray, 1.3, 5)

# 框选人脸,for循环保证一个能检测的实时动态视频流
for (x, y, w, h) in faces:
# xy为左上角的坐标,w为宽,h为高,用rectangle为人脸标记画框
cv2.rectangle(img, (x, y), (x + w, y + w), (255, 0, 0))
# 样本数加1
sample_num += 1
# 保存图像,把灰度图片看成二维数组来检测人脸区域,这里是保存在data缓冲文件夹内
T = Total_face_num
cv2.imwrite("./data/User." + str(T) + '.' + str(sample_num) + '.jpg', gray[y:y + h, x:x + w])

pictur_num = 30 # 表示摄像头拍摄取样的数量,越多效果越好,但获取以及训练的越慢

cv2.waitKey(1)
if sample_num > pictur_num:
break
else: # 控制台内输出进度条
l = int(sample_num / pictur_num * 50)
r = int((pictur_num - sample_num) / pictur_num * 50)
print("\r" + "%{:.1f}".format(sample_num / pictur_num * 100) + "=" * l + "->" + "_" * r, end="")
var.set("%{:.1f}".format(sample_num / pictur_num * 100)) # 控件可视化进度信息
# tk.Tk().update()
window.update() # 刷新控件以实时显示进度

#人脸训练函数
def Train_new_face():
print("\n正在训练")
# cv2.destroyAllWindows()
path = 'data'

# 初始化识别的方法
recog = cv2.face.LBPHFaceRecognizer_create()

# 调用函数并将数据喂给识别器训练
faces, ids = get_images_and_labels(path)
print('本次用于训练的识别码为:') # 调试信息
print(ids) # 输出识别码

# 训练模型 #将输入的所有图片转成四维数组
recog.train(faces, np.array(ids))
# 保存模型

yml = str(Total_face_num) + ".yml"
rec_f = open(yml, "w+")
rec_f.close()
recog.save(yml)#yml文件默认保存在与代码同一文件夹下

# recog.save('aaa.yml')


# 创建一个函数,用于从数据集文件夹中获取训练图片,并获取id
# 注意图片的命名格式为User.id.sampleNum
def get_images_and_labels(path):
image_paths = [os.path.join(path, f) for f in os.listdir(path)]
# 新建连个list用于存放
face_samples = []
ids = []

# 遍历图片路径,导入图片和id添加到list中
for image_path in image_paths:

# 通过图片路径将其转换为灰度图片
img = Image.open(image_path).convert('L')

# 将图片转化为数组
img_np = np.array(img, 'uint8')

if os.path.split(image_path)[-1].split(".")[-1] != 'jpg':
continue

# 为了获取id,将图片和路径分裂并获取
id = int(os.path.split(image_path)[-1].split(".")[1])

# 调用熟悉的人脸分类器
detector = cv2.CascadeClassifier(pathf)

faces = detector.detectMultiScale(img_np)

# 将获取的图片和id添加到list中
for (x, y, w, h) in faces:
face_samples.append(img_np[y:y + h, x:x + w])
ids.append(id)
return face_samples, ids


def write_config():
print("新人脸训练结束")
f = open('config.txt', "a")
T = Total_face_num
f.write(str(T) + " User" + str(T) + " \n")
f.close()
id_dict[T] = "User" + str(T)

# 这里修改文件的方式是先读入内存,然后修改内存中的数据,最后写回文件
f = open('config.txt', 'r+')
flist = f.readlines()
flist[0] = str(int(flist[0]) + 1) + " \n"
f.close()

f = open('config.txt', 'w+')
f.writelines(flist)
f.close()


'''
============================================================================================
以上是录入新人脸信息功能的实现
============================================================================================
'''

#人脸识别代码 (高并发主要需要代码)
#通过进程stdin传图片
#通过http传id
#通过http传yml
#通过进程stdout返回结果
def scan_face():
# 使用之前训练好的模型
for i in range(Total_face_num): # 每个识别器(yml)都要用 与每个yml做比较 比较10次
i += 1
yml = str(i) + ".yml"
print("\n本次:" + yml) # 调试信息
recognizer.read(yml)

ave_poss = 0
for times in range(10): # 每个识别器扫描十遍
times += 1
cur_poss = 0
global success
global img

global system_state_lock
while system_state_lock == 2: # 如果正在录入新面孔就阻塞
print("\r刷脸被录入面容阻塞", end="")
pass

success, img = camera.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 识别人脸
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.2,
minNeighbors=5,
minSize=(int(W_size), int(H_size))
)
# 进行校验
for (x, y, w, h) in faces:

# global system_state_lock
while system_state_lock == 2: # 如果正在录入新面孔就阻塞
print("\r刷脸被录入面容阻塞", end="")
pass
# 这里调用Cv2中的rectangle函数 在人脸周围画一个矩形
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
# 调用分类器的预测函数,接收返回值标签和置信度
idnum, confidence = recognizer.predict(gray[y:y + h, x:x + w])
conf = confidence
# 计算出一个检验结果
if confidence < 100: # 可以识别出已经训练的对象——直接输出姓名在屏幕上
if idnum in id_dict:
user_name = id_dict[idnum]
else:
# print("无法识别的ID:{}\t".format(idnum), end="")
user_name = "Untagged user:" + str(idnum)
confidence = "{0}%", format(round(100 - confidence))
else: # 无法识别此对象,//注释代码:那么就开始训练
user_name = "unknown"
# print("检测到陌生人脸\n")

# cv2.destroyAllWindows()
# global Total_face_num
# Total_face_num += 1
# Get_new_face() # 采集新人脸
# Train_new_face() # 训练采集到的新人脸
# write_config() # 修改配置文件
# recognizer.read('aaa.yml') # 读取新识别器

# 加载一个字体用于输出识别对象的信息
font = cv2.FONT_HERSHEY_SIMPLEX

# 输出检验结果以及用户名
cv2.putText(img, str(user_name), (x + 5, y - 5), font, 1, (0, 0, 255), 1)
cv2.putText(img, str(confidence), (x + 5, y + h - 5), font, 1, (0, 0, 0), 1)

# 展示结果
# cv2.imshow('camera', img)
#调整conf可以调整精度,通过调整置信度区间
print("conf=" + str(conf), end="\t")
if 15 > conf > 0:
cur_poss = 1 # 表示可以识别
elif 60 > conf > 35:
cur_poss = 1 # 表示可以识别
else:
cur_poss = 0 # 表示不可以识别

k = cv2.waitKey(1)
if k == 27:
# cam.release() # 释放资源
cv2.destroyAllWindows()
break

ave_poss += cur_poss

if ave_poss >= 5: # 有一半以上识别说明可行则返回 可以更改来调整精度
return i #返回第几个模型通过 这里也有问题 如果有一个很相似的yml训练模型通过,会导致之后的真正模型不能通过

return 0 # 全部过一遍还没识别出说明无法识别


'''
============================================================================================
以上是关于刷脸功能的设计
============================================================================================
'''


def f_scan_face_thread():
# 使用之前训练好的模型
#recognizer.read('aaa.yml')
var.set('刷脸')
ans = scan_face()
if ans == 0:
print("最终结果:无法识别")
var.set("最终结果:无法识别")

else:
ans_name = "最终结果:" + str(ans) + id_dict[ans]
print(ans_name)
var.set(ans_name)

global system_state_lock
print("锁被释放0")
system_state_lock = 0 # 修改system_state_lock,释放资源


def f_scan_face():
global system_state_lock
print("\n当前锁的值为:" + str(system_state_lock))
if system_state_lock == 1:
print("阻塞,因为正在刷脸")
return 0
elif system_state_lock == 2: # 如果正在录入新面孔就阻塞
print("\n刷脸被录入面容阻塞\n"
"")
return 0
system_state_lock = 1
p = threading.Thread(target=f_scan_face_thread)
p.setDaemon(True) # 把线程P设置为守护线程 若主线程退出 P也跟着退出
p.start()


def f_rec_face_thread():
var.set('录入')
cv2.destroyAllWindows()
global Total_face_num
Total_face_num += 1
Get_new_face() # 采集新人脸
print("采集完毕,开始训练")
global system_state_lock # 采集完就可以解开锁
print("锁被释放0")
system_state_lock = 0

Train_new_face() # 训练采集到的新人脸
write_config() # 修改配置文件


# recognizer.read('aaa.yml') # 读取新识别器

# global system_state_lock
# print("锁被释放0")
# system_state_lock = 0 # 修改system_state_lock,释放资源


def f_rec_face():
global system_state_lock
print("当前锁的值为:" + str(system_state_lock))
if system_state_lock == 2:
print("阻塞,因为正在录入面容")
return 0
else:
system_state_lock = 2 # 修改system_state_lock
print("改为2", end="")
print("当前锁的值为:" + str(system_state_lock))

p = threading.Thread(target=f_rec_face_thread)
p.setDaemon(True) # 把线程P设置为守护线程 若主线程退出 P也跟着退出
p.start()
# tk.Tk().update()


# system_state_lock = 0 # 修改system_state_lock,释放资源


def f_exit(): # 退出按钮
exit()


'''
============================================================================================
以上是关于多线程的设计
============================================================================================
'''

window = tk.Tk()
window.title('Cheney\' Face_rec 3.0') # 窗口标题
window.geometry('1000x500') # 这里的乘是小x

# 在图形界面上设定标签,类似于一个提示窗口的作用
var = tk.StringVar()
l = tk.Label(window, textvariable=var, bg='green', fg='white', font=('Arial', 12), width=50, height=4)
# 说明: bg为背景,fg为字体颜色,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height=2,就是标签有2个字符这么高
l.pack() # 放置l控件

# 在窗口界面设置放置Button按键并绑定处理函数
button_a = tk.Button(window, text='开始刷脸', font=('Arial', 12), width=10, height=2, command=f_scan_face)
button_a.place(x=800, y=120)

button_b = tk.Button(window, text='录入人脸', font=('Arial', 12), width=10, height=2, command=f_rec_face)
button_b.place(x=800, y=220)

button_b = tk.Button(window, text='退出', font=('Arial', 12), width=10, height=2, command=f_exit)
button_b.place(x=800, y=320)

panel = tk.Label(window, width=500, height=350) # 摄像头模块大小
panel.place(x=10, y=100) # 摄像头模块的位置
window.config(cursor="arrow")


def video_loop(): # 用于在label内动态展示摄像头内容(摄像头嵌入控件)
# success, img = camera.read() # 从摄像头读取照片
global success
global img
if success:
cv2.waitKey(1)
cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) # 转换颜色从BGR到RGBA
current_image = Image.fromarray(cv2image) # 将图像转换成Image对象
imgtk = ImageTk.PhotoImage(image=current_image)
panel.imgtk = imgtk
panel.config(image=imgtk)
window.after(1, video_loop)


video_loop()

# 窗口循环,用于显示
window.mainloop()

'''
============================================================================================
以上是关于界面的设计
============================================================================================
'''


用户前端界面设计

前端是基于原生的html+css+js 并没有用框架

登录界面

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
173
174
175
176
177
178
<!--需要注意的是 前端部分背景图片 和js工具的引入需要自行引入 这里并没有提供-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="http://at.alicdn.com/t/font_1786038_m62pqneyrzf.css">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}

html {
height: 100%;
}

body {
height: 100%;
font-family: JetBrains Mono Medium;
display: flex;
align-items: center;
justify-content: center;
/* background-color: #0e92b3; */
background: url('./images/about_background.jpg') no-repeat;
background-size: 100% 100%;
}

.form-wrapper {
width: 300px;
background-color: rgba(41, 45, 62, .8);
color: #fff;
border-radius: 2px;
padding: 50px;
}

.form-wrapper .header {
text-align: center;
font-size: 35px;
text-transform: uppercase;
line-height: 100px;
}

.form-wrapper .input-wrapper input {
background-color: rgb(41, 45, 62);
border: 0;
width: 100%;
text-align: center;
font-size: 15px;
color: #fff;
outline: none;
}

.form-wrapper .input-wrapper input::placeholder {
text-transform: uppercase;
}

.form-wrapper .input-wrapper .border-wrapper {
background-image: linear-gradient(to right, #e8198b, #0eb4dd);
width: 100%;
height: 50px;
margin-bottom: 20px;
border-radius: 30px;
display: flex;
align-items: center;
justify-content: center;
}

.form-wrapper .input-wrapper .border-wrapper .border-item {
height: calc(100% - 4px);
width: calc(100% - 4px);
border-radius: 30px;
}

.form-wrapper .action {
display: flex;
justify-content: center;
}

.form-wrapper .action .btn {
width: 60%;
text-transform: uppercase;
border: 2px solid #0e92b3;
text-align: center;
line-height: 50px;
border-radius: 30px;
cursor: pointer;
}

.form-wrapper .action .btn:hover {
background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
}

.form-wrapper .icon-wrapper {
text-align: center;
width: 60%;
margin: 0 auto;
margin-top: 20px;
border-top: 1px dashed rgb(146, 146, 146);
padding: 20px;
}

.form-wrapper .icon-wrapper i {
font-size: 20px;
color: rgb(187, 187, 187);
cursor: pointer;
border: 1px solid #fff;
padding: 5px;
border-radius: 20px;
}

.form-wrapper .icon-wrapper i:hover {
background-color: #0e92b3;
}
</style>
</head>
<body>

<div class="form-wrapper">
<div class="header">
核酸登录系统
</div>
<div class="input-wrapper">
<div class="border-wrapper">
<input type="text" name="username" placeholder="身份证" class="border-item" autocomplete="off">
</div>
<div class="border-wrapper">
<input type="password" name="password" placeholder="密码" class="border-item" autocomplete="off">
</div>
</div>
<div class="action">
<div class="btn" onclick="login()">登录</div>
<div class="btn" onclick="register()">注册</div> <!-- Added registration button -->
</div>
</div>

<script src="js/jquery.js"></script>
<script>
function login() {
var username = $('input[name="username"]').val();
var password = $('input[name="password"]').val();
var data = {
"national_id": username,
"password": password
}
$.ajax({
type: "post",
url: "https://10.136.0.8:443/users/user_login",
contentType: "application/json; charset=utf-8",
dataType:'json',//json 返回值类型
data:JSON.stringify(data),
success: function(datas) {
if (datas.success === true) {
// Login successful, redirect to another page with id and token
var token = datas.data.token;
var id = datas.data.id;

// Redirect with query parameters
var redirectUrl = "user_data_show.html?id=" + id + "&token=" + token;
window.location.href = redirectUrl;

} else {
// Login unsuccessful, display error message
alert(datas.errorMsg);
}
}
});
}
function register() {
// Redirect to registration page
window.location.href = "register.html";
}
</script>

</body>
</html>

注册界面

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
173
174
175
176
177
178
179
180
181
182
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="http://at.alicdn.com/t/font_1786038_m62pqneyrzf.css">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}

html {
height: 100%;
}

body {
height: 100%;
font-family: JetBrains Mono Medium;
display: flex;
align-items: center;
justify-content: center;
/* background-color: #0e92b3; */
background: url('/images/about_background.jpg') no-repeat;
background-size: 100% 100%;
}

.form-wrapper {
width: 300px;
background-color: rgba(41, 45, 62, .8);
color: #fff;
border-radius: 2px;
padding: 50px;
}

.form-wrapper .header {
text-align: center;
font-size: 35px;
text-transform: uppercase;
line-height: 100px;
}

.form-wrapper .input-wrapper input {
background-color: rgb(41, 45, 62);
border: 0;
width: 100%;
text-align: center;
font-size: 15px;
color: #fff;
outline: none;
}

.form-wrapper .input-wrapper input::placeholder {
text-transform: uppercase;
}

.form-wrapper .input-wrapper .border-wrapper {
background-image: linear-gradient(to right, #e8198b, #0eb4dd);
width: 100%;
height: 50px;
margin-bottom: 20px;
border-radius: 30px;
display: flex;
align-items: center;
justify-content: center;
}

.form-wrapper .input-wrapper .border-wrapper .border-item {
height: calc(100% - 4px);
width: calc(100% - 4px);
border-radius: 30px;
}

.form-wrapper .action {
display: flex;
justify-content: center;
}

.form-wrapper .action .btn {
width: 60%;
text-transform: uppercase;
border: 2px solid #0e92b3;
text-align: center;
line-height: 50px;
border-radius: 30px;
cursor: pointer;
}

.form-wrapper .action .btn:hover {
background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
}

.form-wrapper .icon-wrapper {
text-align: center;
width: 60%;
margin: 0 auto;
margin-top: 20px;
border-top: 1px dashed rgb(146, 146, 146);
padding: 20px;
}

.form-wrapper .icon-wrapper i {
font-size: 20px;
color: rgb(187, 187, 187);
cursor: pointer;
border: 1px solid #fff;
padding: 5px;
border-radius: 20px;
}

.form-wrapper .icon-wrapper i:hover {
background-color: #0e92b3;
}
</style>
</head>
<body>
<div class="form-wrapper">
<div class="header">
注册
</div>
<div class="input-wrapper">
<div class="border-wrapper">
<input type="text" name="username" placeholder="名字" class="border-item" autocomplete="off">
</div>
<div class="border-wrapper">
<input type="password" name="password" placeholder="密码" class="border-item" autocomplete="off">
</div>
<div class="border-wrapper">
<input type="text" name="idcard" placeholder="身份证" class="border-item" autocomplete="off">
</div>
<div class="border-wrapper">
<input type="text" name="phone" placeholder="电话" class="border-item" autocomplete="off">
</div>
<div class="border-wrapper">
<input type="text" name="password_verify" placeholder="确认密码" class="border-item" autocomplete="off">
</div>
</div>
<div class="action">
<div class="btn" onclick="validation()">下一步</div> <!-- Added registration button -->
</div>

</div>

<script src="js/jquery.js"></script>
<script>

function validation() {
var username = $('input[name="username"]').val();
var password = $('input[name="password"]').val();
var idcard = $('input[name="idcard"]').val();
var phone = $('input[name="phone"]').val();
var passdata = {
"name": username,
"password": password,
"national_id": idcard,
"phone": phone,
"password_verify": password
}
console.log(passdata);
$.ajax({
type: "post",
url: "https://10.136.0.8:443/users/user_signin",
contentType: "application/json; charset=utf-8",
dataType:'json',//json 返回值类型
data: JSON.stringify(passdata),//转化为json字符串
success: function(data) {
if (data.success == true) {
// Login successful, redirect to another page
alert("成功!");
window.location.href = "face.html";
} else {
// Login unsuccessful, display error message
alert(data.errorMsg);
}
}
});
}
</script>
</body>
</html>

用户信息展示界面

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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
.container {
background-color: rgb(243, 238, 238,.5);
width: 800px;
margin: 20px auto;
font-size: 30px;
}
html {
height: 100%;
}
body{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: url('/images/about_background.jpg') no-repeat;
background-size: 100% 100%;
}
h2 {
margin-top: 0;
}

label {
font-weight: bold;
}

input[type="text"] {
width: 100%;
padding: 5px;
margin-bottom: 10px;
}

button {
padding: 5px 10px;
font-size: 50px; /* 调整按钮的字体大小,可以根据需要调整数值 */
margin-left: 20%;
}

#confirmation {
margin-top: 20px;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
<h2>用户信息</h2>
<label for="name">名字:</label>
<span id="name">Loading...</span>

<br>

<label for="national_id">身份证:</label>
<span id="national_id">Loading...</span>

<br>

<label for="phone">电话:</label>
<span id="phone">Loading...</span>

<br>

<label for="password">密码:</label>
<span id="password">Loading...</span>

<br>
<br>
<a href="edit_information.html"><button>修改全部信息</button></a>
</div>
<div class="container">
<!-- User information section (existing code) -->

<h2>历史记录</h2>
<div id="historyContainer">
<!-- Historical records will be dynamically added here -->
</div>
</div>
<script>
// Get the query parameters from the URL
var queryString = window.location.search;
var urlParams = new URLSearchParams(queryString);

// Get the values of id and token from the query parameters
var id = urlParams.get("id");
var token = urlParams.get("token");
console.log(id + " "+token); // Output the username to the console

$(document).ready(function() {
// Retrieve user information from the backend using Ajax POST request
$.ajax({
url: 'https://10.136.0.8:443/users/me', // Replace with your backend endpoint URL
type: 'get',
// data: {
// param1: 'value1', // 请求参数1
// param2: 'value2' // 请求参数2
// },
headers: {
'Authorization': token, // 请求头,用于传递认证信息等
},
success: function(msg) {
// Update user information in the HTML
$('#name').text(msg.data.name);
$('#phone').text(msg.data.phone);
$('#national_id').text(msg.data.national_id);
console.log(msg.data.national_id);
$('#password').text(msg.data.password);
console.log(msg.errorMsg);
},
error: function(msg) {
console.log('Failed to retrieve user information.');
console.log(msg.errorMsg);
}
});

// Retrieve historical records from the backend using Ajax GET request
$.ajax({
url: 'https://10.136.0.8:443/users/user_get_orders',
type: 'get',
data: {
id: id, // 请求参数1
},
headers: {
'Authorization': token, // 请求头,用于传递认证信息等
},
success: function(records) {
// Update historical records in the HTML
var historyContainer = $('#historyContainer');
historyContainer.empty(); // Clear any existing records

if (records.success === false) {
historyContainer.append('<p>No historical records found.</p>');
} else {
// Loop through each record and add it to the container
// 获取 results 数组
var resultsArray = records.data.results;
console.log(resultsArray);
if(resultsArray != null){
// 遍历 results 数组,处理每个 result 对象
for (var i = 0; i < resultsArray.length; i++) {
var resultObject = resultsArray[i];
// 从每个 result 对象中获取所需的数据
var tubeId = resultObject.tube_id;
var resultTime = resultObject.result_time;
var institution = resultObject.institution;
var result = resultObject.result;
var foundTime = resultObject.found_time;
var userId = resultObject.user_id;
// 拼接每个 result 数据为 HTML 语句
var resultHTML =
'<p><strong>Tube ID:</strong> ' + tubeId +
' | <strong>Result Time:</strong> ' + resultTime +
' | <strong>Institution:</strong> ' + institution +
' | <strong>Result:</strong> ' + result +
' | <strong>Found Time:</strong> ' + foundTime +
' | <strong>User ID:</strong> ' + userId + '</p>';
historyContainer.append(resultHTML);
}
}else{
historyContainer.append('<p>No historical records found.</p>');
}

// $.each(records, function(index, record) {
// var recordHTML =
// '<p><strong>Name:</strong> ' + record.name +
// ' | <strong>Nucleic Acid Test Time:</strong> ' + record.testTime +
// ' | <strong>Result Time:</strong> ' + record.resultTime +
// ' | <strong>Status:</strong> ' + record.status + '</p>';

// historyContainer.append(recordHTML);
// });
}
},
error: function() {
console.log('Failed to retrieve historical records.');
//historyContainer.append('No historical records found.');
}
});
});



</script>
</body>
</html>

编辑用户信息界面

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>修改用户信息</title>
<style>
.container {
width: 800px;
margin: 20px auto;
margin-left: 50px;
}
html {
font-size: 50px; /* Adjust the font size as needed */
height: 100%;
}
body{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: url('/images/about_background.jpg') no-repeat;
/* background-size: 150% 100%; */
}
h2 {
margin-top: 0;
}

label {
font-weight: bold;
}

input[type="text"] {
font-size: 40px; /* Adjust the font size as needed */
width: 100%;
padding: 30px;
margin-bottom: 10px;
}

.button {
width: 60%;
text-transform: uppercase;
border: 2px solid #0e92b3;
text-align: center;
margin-left: 20%;
line-height: 50px;
border-radius: 30px;
cursor: pointer;
}
.button:hover {
background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
}

#confirmation {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h2>修改用户信息</h2>
<form id="editForm">
<label for="name">名字:</label>
<input type="text" id="nameInput" name="username" required>
<br>

<label for="idcard">身份证:</label>
<input type="text" id="idInput" name="useridcard" required>

<br>

<label for="passward">密码:</label>
<input type="text" id="passwardInput" name="userpassward" required>

<br>

<label for="phone">电话:</label>
<input type="text" id="phoneInput" name="userphone" required>

<br>

<label for="password_verify">确认密码:</label>
<input type="text" id="password_verifyInput" name="password_verify" required>

<br>

<!-- <button type="submit">保存</button> -->
<div class="button" onclick="save()">保存</div>
</form>
</div>

<script src="js/jquery.js"></script>
<link rel="stylesheet" href="http://at.alicdn.com/t/font_1786038_m62pqneyrzf.css">
<script>
function save() {
//event.preventDefault();

// Retrieve user input

var newName = $('input[name="username"]').val();
var newidcard = $('input[name="useridcard"]').val();
var newpassward = $('input[name="userpassward"]').val();
var newPhone = $('input[name="userphone"]').val();
// Create JSON object
var userdata = {
"name": newName,
"national_id": newidcard,
"phone": newPhone,
"password": newpassward,
"password_verify": newpassward,
}

console.log(userdata);

// Send data to the backend
$.ajax({
type: "post",
url: "https://10.136.0.8:443/users/user_change_info",
contentType: "application/json; charset=utf-8",
dataType:'json',//json 返回值类型
data: userdata,//转化为json字符串
success: function(response) {
// Handle success response from the backend
$("#confirmation").text("修改成功");
window.location.href = "log_test.html"; // Replace with the URL of the success page
console.log(response);
},
error: function(error) {
// Handle error response from the backend
$("#confirmation").text("修改失败");
console.error(error);
}
});
}

</script>

</body>
</html>

人脸录入界面

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>正式摄像头录入人脸</title>
</head>
<style>
html {
height: 100%;
}
body {
height: 100%;
font-family: JetBrains Mono Medium;
display: flex;
align-items: center;
justify-content: center;
/* background-color: #0e92b3; */
background: url('./images/about_background.jpg') no-repeat;
background-size: 100% 100%;
}
#container {
display: flex;
flex-direction: column;
align-items: center;
}
#container div {
font-size: 60px; /* 调整文本的字体大小,可以根据需要调整数值 */
}
#startButton {
font-size: 50px; /* 调整按钮的字体大小,可以根据需要调整数值 */
margin-top: 20px;
padding: 10px 20px; /* 调整按钮的内边距,可以根据需要调整数值 */
}
#countdownText {
font-size: 72px; /* 调整倒计时文本的字体大小,可以根据需要调整数值 */
}
</style>
<body>

<div id = "container">
<div>正式摄像头录入人脸</div>
<video id="video"></video>
<canvas id="canvas" style="display: none"></canvas>
<button id="startButton">开始录入</button>
<p id="countdownText"></p>
</div>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
const width = 900;
const height = 600;
const video = document.getElementById('video');
const startButton = document.getElementById('startButton');
const countdownText = document.getElementById('countdownText');
let countdownInterval = null;
let imageCaptureInterval = null;
let countdownTime = 5; // Countdown time in seconds
let capturedImages = []; // Array to store captured images

// Check if the browser supports accessing media devices
if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {
// Call the getUserMedia function to access the webcam
getUserMedia({video: {width: width, height: height}}, success, error);
} else {
alert('不支持访问用户媒体');
}

// Access user media devices compatibility method
function getUserMedia(constraints, success, error) {
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
} else if (navigator.webkitGetUserMedia) {
navigator.webkitGetUserMedia(constraints, success, error);
} else if (navigator.mozGetUserMedia) {
navigator.mozGetUserMedia(constraints, success, error);
} else if (navigator.getUserMedia) {
navigator.getUserMedia(constraints, success, error);
}
}

// Success callback
function success(stream) {
console.log('成功');
video.srcObject = stream;
video.play();
}

// Failure callback
function error(error) {
console.log('失败');
console.log("访问用户媒体设备失败", error);
}

// Event listener for the "开始录入" button
startButton.addEventListener('click', () => {
// Start the countdown
startCountdown();
});

function startCountdown() {
countdownText.innerText = countdownTime;
countdownInterval = setInterval(() => {
countdownTime--;
countdownText.innerText = countdownTime;

if (countdownTime <= 0) {
clearInterval(countdownInterval);
clearInterval(imageCaptureInterval); // Stop capturing images after countdown
sendDataToBackend();
// Show "注册成功"提示信息
alert('录入人脸完成 恭喜注册成功!');

// 页面跳转到另外一个网页
window.location.href = "log_test.html"; // 将URL替换为目标网页的地址
}
}, 1000);

// Start capturing face every second during the countdown
imageCaptureInterval = setInterval(captureFace, 500);
}

function captureFace() {
const canvas = document.getElementById('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, width, height, 0, 0, width, height);
// Getting the image data as a base64-encoded string
const imageData = canvas.toDataURL("image/png");
console.log(imageData);
// Storing the captured image data in the array
capturedImages.push(imageData);
}

function sendDataToBackend() {
// Prepare JSON data with captured images
const jsonData = { images: capturedImages };
console.log(jsonData);
// Make the AJAX POST request using jQuery
$.ajax({
url: 'YOUR_BACKEND_URL', // Replace with your backend URL
type: 'POST',
data: JSON.stringify(jsonData),
contentType: 'application/json',
success: function(response) {
console.log('Data sent successfully!');
console.log(response); // Optional: Log the response from the server
},
error: function(error) {
console.log('Error sending data to the backend:', error);
}
});
}
</script>
</body>
</html>

后端

这里就不做一一展示了(当时主要参与的是接口的定义和设计)

框架springboot

实现了数据加密传输 脱敏和加密等操作

数据库mysql