From c64815b9abdaad8713e964429891d3f477204357 Mon Sep 17 00:00:00 2001 From: dks <2905485206@qq.com> Date: 星期二, 10 六月 2025 09:57:53 +0800 Subject: [PATCH] 项目总结提交 --- Server/杜康生/code/gather_examples.py | 174 +++++++++++++++++++++ Server/杜康生/code/main.py | 18 ++ Server/杜康生/code/shape_predictor_68_face_landmarks.dat | 0 Server/杜康生/code/face_detector.py | 239 +++++++++++++++++++++++++++++ Server/杜康生/document/活体检测模块-项目总结.docx | 0 5 files changed, 431 insertions(+), 0 deletions(-) diff --git "a/Server/\346\235\234\345\272\267\347\224\237/code/face_detector.py" "b/Server/\346\235\234\345\272\267\347\224\237/code/face_detector.py" new file mode 100644 index 0000000..f0d3c20 --- /dev/null +++ "b/Server/\346\235\234\345\272\267\347\224\237/code/face_detector.py" @@ -0,0 +1,239 @@ +import cv2 +import imutils +import dlib +import numpy as np +import random +import time +from imutils import face_utils + + +class FaceDetector: + def __init__(self, cap, win_width, win_height): + self.cap = cap + self.WIN_WIDTH = win_width + self.WIN_HEIGHT = win_height + + self.detector = None # 浜鸿劯妫�娴嬪櫒 + self.predictor = None # 鐗瑰緛鐐规娴嬪櫒 + # 闂儊闃堝�� + self.EAR_THRESH = None + self.MOUTH_THRESH = None + # 鎬婚棯鐑佹鏁� + self.eye_flash_counter = None + self.mouth_open_counter = None + self.turn_left_counter = None + self.turn_right_counter = None + # 杩炵画甯ф暟闃堝�� + self.EAR_CONSTANT_FRAMES = None + self.MOUTH_CONSTANT_FRAMES = None + self.LEFT_CONSTANT_FRAMES = None + self.RIGHT_CONSTANT_FRAMES = None + # 杩炵画甯ц鏁板櫒 + self.eye_flash_continuous_frame = 0 + self.mouth_open_continuous_frame = 0 + self.turn_left_continuous_frame = 0 + self.turn_right_continuous_frame = 0 + + # 璁$畻鐪奸暱瀹芥瘮渚� EAR鍊� + @staticmethod + def count_EAR(eye): + A = np.linalg.norm(eye[1] - eye[5]) + B = np.linalg.norm(eye[2] - eye[4]) + C = np.linalg.norm(eye[0] - eye[3]) + EAR = (A + B) / (2.0 * C) + return EAR + + # 璁$畻鍢撮暱瀹芥瘮渚� MAR鍊� + @staticmethod + def count_MAR(mouth): + A = np.linalg.norm(mouth[1] - mouth[11]) + B = np.linalg.norm(mouth[2] - mouth[10]) + C = np.linalg.norm(mouth[3] - mouth[9]) + D = np.linalg.norm(mouth[4] - mouth[8]) + E = np.linalg.norm(mouth[5] - mouth[7]) + F = np.linalg.norm(mouth[0] - mouth[6]) # 姘村钩娆у嚑閲屽痉璺濈 + ratio = (A + B + C + D + E) / (5.0 * F) + return ratio + + # 璁$畻宸﹀彸鑴歌浆鍔ㄦ瘮渚� FR鍊� + @staticmethod + def count_FR(face): + rightA = np.linalg.norm(face[0] - face[27]) + rightB = np.linalg.norm(face[2] - face[30]) + rightC = np.linalg.norm(face[4] - face[48]) + leftA = np.linalg.norm(face[16] - face[27]) + leftB = np.linalg.norm(face[14] - face[30]) + leftC = np.linalg.norm(face[12] - face[54]) + ratioA = rightA / leftA + ratioB = rightB / leftB + ratioC = rightC / leftC + ratio = (ratioA + ratioB + ratioC) / 3 + return ratio + + # 鏈湴娲讳綋妫�娴� + def detect_face_local(self): + self.detect_start_time = time.time() + + # 鐗瑰緛鐐规娴嬪櫒棣栨鍔犺浇姣旇緝鎱紝閫氳繃鍒ゆ柇鍑忓皯鍚庨潰鍔犺浇鐨勯�熷害 + if self.detector is None: + self.detector = dlib.get_frontal_face_detector() + if self.predictor is None: + self.predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') + + # 闂儊闃堝�� + self.EAR_THRESH = 0.25 + self.MOUTH_THRESH = 0.7 + + # 鎬婚棯鐑佹鏁� + self.eye_flash_counter = 0 + self.mouth_open_counter = 0 + self.turn_left_counter = 0 + self.turn_right_counter = 0 + + # 杩炵画甯ф暟闃堝�� + self.EAR_CONSTANT_FRAMES = 2 + self.MOUTH_CONSTANT_FRAMES = 2 + self.LEFT_CONSTANT_FRAMES = 4 + self.RIGHT_CONSTANT_FRAMES = 4 + + # 杩炵画甯ц鏁板櫒 + self.eye_flash_continuous_frame = 0 + self.mouth_open_continuous_frame = 0 + self.turn_left_continuous_frame = 0 + self.turn_right_continuous_frame = 0 + + print("娲讳綋妫�娴� 鍒濆鍖栨椂闂�:", time.time() - self.detect_start_time) + + # 褰撳墠鎬诲抚鏁� + total_frame_counter = 0 + + # 璁剧疆闅忔満鍊� + now_flag = 0 + random_type = [0, 1, 2, 3] + random.shuffle(random_type) + + random_eye_flash_number = random.randint(4, 6) + random_mouth_open_number = random.randint(2, 4) + print('璇锋寜鐓ф寚绀烘墽琛岀浉鍏冲姩浣�') # 绠�鍗曡緭鍑烘彁绀轰俊鎭� + + # 鎶撳彇闈㈤儴鐗瑰緛鐐圭殑绱㈠紩 + (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"] + (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"] + (mStart, mEnd) = face_utils.FACIAL_LANDMARKS_IDXS["mouth"] + + while self.cap.isOpened(): + ret, frame = self.cap.read() + total_frame_counter += 1 + frame = imutils.resize(frame) + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + rects = self.detector(gray, 0) + + if len(rects) == 1: + shape = self.predictor(gray, rects[0]) + shape = face_utils.shape_to_np(shape) + + # 鎻愬彇闈㈤儴鍧愭爣 + left_eye = shape[lStart:lEnd] + right_eye = shape[rStart:rEnd] + mouth = shape[mStart:mEnd] + + # 璁$畻闀垮姣� + left_EAR = self.count_EAR(left_eye) + right_EAR = self.count_EAR(right_eye) + mouth_MAR = self.count_MAR(mouth) + leftRight_FR = self.count_FR(shape) + average_EAR = (left_EAR + right_EAR) / 2.0 + + # 璁$畻宸︾溂銆佸彸鐪笺�佸槾宸寸殑鍑稿寘 + left_eye_hull = cv2.convexHull(left_eye) + right_eye_hull = cv2.convexHull(right_eye) + mouth_hull = cv2.convexHull(mouth) + + # 鍙鍖� + cv2.drawContours(frame, [left_eye_hull], -1, (0, 255, 0), 1) + cv2.drawContours(frame, [right_eye_hull], -1, (0, 255, 0), 1) + cv2.drawContours(frame, [mouth_hull], -1, (0, 255, 0), 1) + + if now_flag >= 4: + return True + + if random_type[now_flag] == 0: + if self.turn_left_counter > 0: + now_flag += 1 + else: + self.check_left_turn(leftRight_FR) + self.turn_right_counter = 0 + self.mouth_open_counter = 0 + self.eye_flash_counter = 0 + + elif random_type[now_flag] == 1: + if self.turn_right_counter > 0: + now_flag += 1 + else: + self.check_right_turn(leftRight_FR) + self.turn_left_counter = 0 + self.mouth_open_counter = 0 + self.eye_flash_counter = 0 + + elif random_type[now_flag] == 2: + if self.mouth_open_counter >= random_mouth_open_number: + now_flag += 1 + + else: + self.check_mouth_open(mouth_MAR) + self.turn_right_counter = 0 + self.turn_left_counter = 0 + self.eye_flash_counter = 0 + + elif random_type[now_flag] == 3: + if self.eye_flash_counter >= random_eye_flash_number: + now_flag += 1 + else: + self.check_eye_flash(average_EAR) + self.turn_right_counter = 0 + self.turn_left_counter = 0 + self.mouth_open_counter = 0 + + elif len(rects) == 0: + pass # 杩欓噷鍙互娣诲姞鏈娴嬪埌浜鸿劯鐨勬彁绀洪�昏緫 + + elif len(rects) > 1: + pass # 杩欓噷鍙互娣诲姞妫�娴嬪埌澶氬紶浜鸿劯鐨勬彁绀洪�昏緫 + + show_video = cv2.cvtColor(cv2.resize(frame, (self.WIN_WIDTH, self.WIN_HEIGHT)), cv2.COLOR_BGR2RGB) + # 杩欓噷鍋囪浣犳湁鍦版柟鏄剧ず鐢婚潰锛屽疄闄呴渶瑕佸叧鑱斿埌瀵瑰簲鐨勭晫闈㈠厓绱� + + if total_frame_counter >= 1000.0: + return False + + def check_eye_flash(self, average_EAR): + if average_EAR < self.EAR_THRESH: + self.eye_flash_continuous_frame += 1 + else: + if self.eye_flash_continuous_frame >= self.EAR_CONSTANT_FRAMES: + self.eye_flash_counter += 1 + self.eye_flash_continuous_frame = 0 + + def check_mouth_open(self, mouth_MAR): + if mouth_MAR > self.MOUTH_THRESH: + self.mouth_open_continuous_frame += 1 + else: + if self.mouth_open_continuous_frame >= self.MOUTH_CONSTANT_FRAMES: + self.mouth_open_counter += 1 + self.mouth_open_continuous_frame = 0 + + def check_right_turn(self, leftRight_FR): + if leftRight_FR <= 0.5: + self.turn_right_continuous_frame += 1 + else: + if self.turn_right_continuous_frame >= self.RIGHT_CONSTANT_FRAMES: + self.turn_right_counter += 1 + self.turn_right_continuous_frame = 0 + + def check_left_turn(self, leftRight_FR): + if leftRight_FR >= 2.0: + self.turn_left_continuous_frame += 1 + else: + if self.turn_left_continuous_frame >= self.LEFT_CONSTANT_FRAMES: + self.turn_left_counter += 1 + self.turn_left_continuous_frame = 0 \ No newline at end of file diff --git "a/Server/\346\235\234\345\272\267\347\224\237/code/gather_examples.py" "b/Server/\346\235\234\345\272\267\347\224\237/code/gather_examples.py" new file mode 100644 index 0000000..70c366f --- /dev/null +++ "b/Server/\346\235\234\345\272\267\347\224\237/code/gather_examples.py" @@ -0,0 +1,174 @@ +import cv2 +import dlib +import os +import numpy as np + +# 璁$畻鐪肩潧绾垫í姣� +def eye_aspect_ratio(eye): + A = np.linalg.norm(eye[1] - eye[5]) + B = np.linalg.norm(eye[2] - eye[4]) + C = np.linalg.norm(eye[0] - eye[3]) + ear = (A + B) / (2.0 * C) + return ear + +# 妫�娴嬪ご閮ㄨ繍鍔紙绠�鍗曠ず渚嬶紝鍙牴鎹疄闄呮儏鍐典紭鍖栵級 +def detect_head_movement(prev_face_landmarks, face_landmarks): + if prev_face_landmarks is None: + return False + prev_nose = prev_face_landmarks[30] + current_nose = face_landmarks[30] + movement_threshold = 10 # 璋冩暣闃堝�� + return abs(current_nose[0] - prev_nose[0]) > movement_threshold or abs( + current_nose[1] - prev_nose[1] + ) > movement_threshold + +# 鐩存帴鎸囧畾璺緞鍜屽弬鏁� +input_path = None # 濡傛灉闇�瑕佷粠鏂囦欢璇诲彇鍙互鎸囧畾璺緞锛岃繖閲岃涓篘one琛ㄧず浣跨敤鎽勫儚澶� +# 浣跨敤鍘熷瀛楃涓叉潵瀹氫箟璺緞锛岄伩鍏嶈浆涔夐棶棰� +output_path = r"H:\python_charm\python_project\database" +detector_path = r"H:\python_charm\python_project\face_detector" +confidence = 0.5 + +# 鍔犺浇浜鸿劯妫�娴嬫ā鍨� +print("[INFO] loading face detector...") +protoPath = os.path.sep.join([detector_path, "deploy.prototxt"]) +modelPath = os.path.sep.join([detector_path, "res10_300x300_ssd_iter_140000.caffemodel"]) +net = cv2.dnn.readNetFromCaffe(protoPath, modelPath) + +# 鍒涘缓杈撳嚭鐩綍 +if not os.path.exists(output_path): + os.makedirs(output_path) +real_output_dir = os.path.join(output_path, "real") +fake_output_dir = os.path.join(output_path, "fake") +if not os.path.exists(real_output_dir): + os.makedirs(real_output_dir) +if not os.path.exists(fake_output_dir): + os.makedirs(fake_output_dir) + +# 鍒濆鍖杁lib鐨勯潰閮ㄥ叧閿偣妫�娴嬪櫒 +detector = dlib.get_frontal_face_detector() +predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") + +# 鍒濆鍖栧弬鏁� +EYE_AR_THRESH = 0.3 +EYE_AR_CONSEC_FRAMES = 3 +COUNTER = 0 +TOTAL = 0 +prev_face_landmarks = None +has_shaken_head = False +has_blinked = False +saved_fake = 0 +saved_real = 0 +# 鍚勮嚜閲囬泦500寮� +target_count = 500 +# 鏂板鏍囧織鍙橀噺锛岀敤浜庤褰曟槸鍚︽鍦ㄨ繛缁噰闆� +is_continuous_capturing = False +capture_type = None # 璁板綍褰撳墠閲囬泦鐨勭被鍨嬶紙'real' 鎴� 'fake'锛� + +# 鎵撳紑鎽勫儚澶� +cap = cv2.VideoCapture(0) + +print("[INFO] Automatically taking pictures...") + +while True: + ret, frame = cap.read() + if not ret: + break + + (h, w) = frame.shape[:2] + blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0, + (300, 300), (104.0, 177.0, 123.0)) + net.setInput(blob) + detections = net.forward() + + # 妫�娴嬪埌鐨勪汉鑴� + if len(detections) > 0: + i = np.argmax(detections[0, 0, :, 2]) + detection_confidence = detections[0, 0, i, 2] # 閲嶅懡鍚嶅彉閲忥紝閬垮厤娣锋穯 + + if detection_confidence > confidence: + box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) + (startX, startY, endX, endY) = box.astype("int") + + # 鎻愬彇闈㈤儴鍏抽敭鐐� + rect = dlib.rectangle(int(startX), int(startY), int(endX), int(endY)) + face_landmarks = predictor(frame, rect) + face_landmarks = np.array([[part.x, part.y] for part in face_landmarks.parts()]) + + # 妫�娴嬪ご閮ㄨ繍鍔� + has_shaken_head = has_shaken_head or detect_head_movement(prev_face_landmarks, face_landmarks) + prev_face_landmarks = face_landmarks + + # 妫�娴嬬湪鐪煎姩浣� + left_eye = face_landmarks[42:48] + right_eye = face_landmarks[36:42] + left_ear = eye_aspect_ratio(left_eye) + right_ear = eye_aspect_ratio(right_eye) + ear = (left_ear + right_ear) / 2.0 + + if ear < EYE_AR_THRESH: + COUNTER += 1 + else: + if COUNTER >= EYE_AR_CONSEC_FRAMES: + TOTAL += 1 + has_blinked = has_blinked or True + COUNTER = 0 + + # 淇濆瓨浜鸿劯鍖哄煙 + face = frame[startY:endY, startX:endX] + + if is_continuous_capturing: + if capture_type == "real": + label = "real" + saved = saved_real + output_dir = real_output_dir + else: + label = "fake" + saved = saved_fake + output_dir = fake_output_dir + + p = os.path.sep.join([output_dir, f"{label}_{saved}.png"]) + cv2.imwrite(p, face) + print(f"[INFO] saved {p} to disk") + + if label == "fake": + saved_fake += 1 + else: + saved_real += 1 + + if (capture_type == "real" and saved_real >= target_count) or \ + (capture_type == "fake" and saved_fake >= target_count): + is_continuous_capturing = False + print(f"[INFO] Reached the target count for {capture_type} images.") + else: + print("[INFO] Please enter 'r' for real or 'f' for fake (q to quit): ") + key = cv2.waitKey(0) & 0xFF + if key == ord("r"): + if saved_real < target_count: + is_continuous_capturing = True + capture_type = "real" + else: + print("[INFO] Already reached the target count for real images. Skipping.") + elif key == ord("f"): + if saved_fake < target_count: + is_continuous_capturing = True + capture_type = "fake" + else: + print("[INFO] Already reached the target count for fake images. Skipping.") + elif key == ord("q"): + break + + # 鏄剧ず鐢婚潰 + cv2.imshow("Frame", frame) + + key = cv2.waitKey(1) & 0xFF + if key == ord("q"): + break + + # 褰撶湡瀹炲拰铏氬亣鍥剧墖閮介噰闆嗗埌500寮犳椂缁撴潫 + if saved_fake >= target_count and saved_real >= target_count: + print("[INFO] Reached the target count for both real and fake images. Exiting...") + break + +cap.release() +cv2.destroyAllWindows() diff --git "a/Server/\346\235\234\345\272\267\347\224\237/code/main.py" "b/Server/\346\235\234\345\272\267\347\224\237/code/main.py" new file mode 100644 index 0000000..60f6db6 --- /dev/null +++ "b/Server/\346\235\234\345\272\267\347\224\237/code/main.py" @@ -0,0 +1,18 @@ +from flask import Flask, jsonify +from face_detector import FaceDetector +import cv2 + +app = Flask(__name__) + +@app.route('/perform_face_detection', methods=['GET']) +def perform_face_detection(): + cap = cv2.VideoCapture(0) + win_width = 640 + win_height = 480 + detector = FaceDetector(cap, win_width, win_height) + result = detector.detect_face_local() + cap.release() + return jsonify({"is_live": result}) + +if __name__ == '__main__': + app.run(debug=False, host='127.0.0.1', port=5000) \ No newline at end of file diff --git "a/Server/\346\235\234\345\272\267\347\224\237/code/shape_predictor_68_face_landmarks.dat" "b/Server/\346\235\234\345\272\267\347\224\237/code/shape_predictor_68_face_landmarks.dat" new file mode 100644 index 0000000..e0ec20d --- /dev/null +++ "b/Server/\346\235\234\345\272\267\347\224\237/code/shape_predictor_68_face_landmarks.dat" Binary files differ diff --git "a/Server/\346\235\234\345\272\267\347\224\237/document/\346\264\273\344\275\223\346\243\200\346\265\213\346\250\241\345\235\227-\351\241\271\347\233\256\346\200\273\347\273\223.docx" "b/Server/\346\235\234\345\272\267\347\224\237/document/\346\264\273\344\275\223\346\243\200\346\265\213\346\250\241\345\235\227-\351\241\271\347\233\256\346\200\273\347\273\223.docx" new file mode 100644 index 0000000..c4741eb --- /dev/null +++ "b/Server/\346\235\234\345\272\267\347\224\237/document/\346\264\273\344\275\223\346\243\200\346\265\213\346\250\241\345\235\227-\351\241\271\347\233\256\346\200\273\347\273\223.docx" Binary files differ -- Gitblit v1.8.0