透過Dlib的臉部81個關鍵點去抓取額頭、臉頰、人中ROI區塊各自劃分22個區域
採用程式語言: python3.11
套件:
- dlib-bin 20.0.1
- opencv-python4.11.0.86
- numpy2.2.6
https://github.com/codeniko/shape_predictor_81_face_landmarks/blob/master/shape_predictor_81_face_landmarks.dat
除了原來的 68 個臉部標誌外,作者還額外添加了 13 個標誌來覆蓋前額區域。
第一階段測試程式
from pathlib import Path import cv2 import dlib import numpy as np VIDEO_PATH = R"F:\DeepfakeTIMIT.tar\DeepfakeTIMIT\higher_quality\fjem0\sa1-video-fkms0.avi" MODEL_PATH = R"C:\img\shape_predictor_81_face_landmarks.dat" #將一個大矩形切成多個小矩形 def split_rectangle(box, rows, cols, region_name): """ box: (x1, y1, x2, y2) rows: 垂直方向切幾列 cols: 水平方向切幾欄 """ x1, y1, x2, y2 = box # 防止座標順序顛倒 x1, x2 = sorted((int(x1), int(x2))) y1, y2 = sorted((int(y1), int(y2))) # 無效矩形不處理 if x2 <= x1 or y2 <= y1: return [] xs = np.linspace(x1, x2, cols + 1, dtype=int) ys = np.linspace(y1, y2, rows + 1, dtype=int) small_rois = [] for row in range(rows): for col in range(cols): small_rois.append( ( region_name, xs[col], ys[row], xs[col + 1], ys[row + 1] ) ) return small_rois # 使用 81 點座標建立 22 個 ROI def build_22_rois(points, frame_width, frame_height): """ points.shape = (81, 2) ROI 配置: 額頭 2 × 3 = 6 畫面左臉頰 2 × 3 = 6 畫面右臉頰 2 × 3 = 6 人中 2 × 2 = 4 合計 = 22 """ # 根據臉部尺寸產生少量內縮距離,避免碰到眼睛、嘴唇和邊界 face_width = points[:, 0].max() - points[:, 0].min() face_height = points[:, 1].max() - points[:, 1].min() margin_x = max(2, int(face_width * 0.02)) margin_y = max(2, int(face_height * 0.02)) # ----------------------------------------------------- # 額頭 # 19、24:左右眉毛附近 # 70~72:81 點模型新增的中央額頭點 # ----------------------------------------------------- forehead = ( points[19, 0] + margin_x, points[70:73, 1].max() + margin_y, points[24, 0] - margin_x, min(points[19, 1], points[24, 1]) - margin_y ) # ----------------------------------------------------- # 畫面左側臉頰 # 2:左側臉部輪廓 # 31:鼻子左側 # 40、41:左眼下方 # 48:左嘴角 # ----------------------------------------------------- left_cheek = ( points[2, 0] + margin_x, max(points[40, 1], points[41, 1]) + margin_y, points[31, 0] - margin_x, points[48, 1] - margin_y ) # ----------------------------------------------------- # 畫面右側臉頰 # 35:鼻子右側 # 14:右側臉部輪廓 # 46、47:右眼下方 # 54:右嘴角 # ----------------------------------------------------- right_cheek = ( points[35, 0] + margin_x, max(points[46, 1], points[47, 1]) + margin_y, points[14, 0] - margin_x, points[54, 1] - margin_y ) # ----------------------------------------------------- # 人中 # 33:鼻子下方中央 # 49、53:上嘴唇左右位置 # 51:上嘴唇中央 # ----------------------------------------------------- philtrum = ( points[49, 0] + margin_x // 2, points[33, 1] + margin_y // 2, points[53, 0] - margin_x // 2, points[51, 1] - margin_y // 2 ) rois = [] rois += split_rectangle(forehead, 2, 3, "forehead") rois += split_rectangle(left_cheek, 2, 3, "left_cheek") rois += split_rectangle(right_cheek, 2, 3, "right_cheek") rois += split_rectangle(philtrum, 2, 2, "philtrum") # 將座標限制在影像範圍內 clipped_rois = [] for region, x1, y1, x2, y2 in rois: x1 = np.clip(x1, 0, frame_width - 1) x2 = np.clip(x2, 0, frame_width - 1) y1 = np.clip(y1, 0, frame_height - 1) y2 = np.clip(y2, 0, frame_height - 1) if x2 > x1 and y2 > y1: clipped_rois.append((region, x1, y1, x2, y2)) return clipped_rois if not Path(VIDEO_PATH).exists(): raise FileNotFoundError(f"找不到影片:{VIDEO_PATH}") if not Path(MODEL_PATH).exists(): raise FileNotFoundError(f"找不到 81 點模型:{MODEL_PATH}") face_detector = dlib.get_frontal_face_detector() landmark_predictor = dlib.shape_predictor(MODEL_PATH) cap = cv2.VideoCapture(VIDEO_PATH) if not cap.isOpened(): raise RuntimeError("影片開啟失敗") # 不同主要 ROI 使用不同顏色,OpenCV 使用 BGR ROI_COLORS = { "forehead": (255, 255, 0), "left_cheek": (0, 255, 0), "right_cheek": (0, 165, 255), "philtrum": (255, 0, 0) } while True: success, frame = cap.read() if not success: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 每一幀重新偵測,ROI 因而能隨著臉部移動 faces = face_detector(gray, 0) if len(faces) > 0: # 若畫面有多張臉,只處理面積最大的人臉 face = max(faces,key=lambda rect: rect.width() * rect.height()) shape = landmark_predictor(gray, face) # dlib 座標轉成 NumPy 陣列,形狀應為 (81, 2) points = np.array([[point.x, point.y] for point in shape.parts()],dtype=np.int32) if len(points) == 81: height, width = frame.shape[:2] rois = build_22_rois(points,frame_width=width,frame_height=height) # 畫出全部 81 個特徵點 for index, (x, y) in enumerate(points): cv2.circle(frame,(x, y),2,(0, 255, 255),-1) # 只標示新增的 13 個額頭點,避免畫面太亂 if index >= 68: cv2.putText(frame,str(index),(x + 2, y - 2),cv2.FONT_HERSHEY_SIMPLEX,0.32,(0, 0, 255),1) # 畫出 22 個小 ROI for roi_number, roi in enumerate(rois, start=1): region, x1, y1, x2, y2 = roi color = ROI_COLORS[region] cv2.rectangle(frame,(x1, y1),(x2, y2),color,1) cv2.putText(frame,str(roi_number),(x1 + 2, y1 + 8),cv2.FONT_HERSHEY_PLAIN,0.45,color,1,cv2.LINE_AA) cv2.putText(frame,f"Landmarks: {len(points)} ROIs: {len(rois)}",(20, 30),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0, 255, 0),2) cv2.imshow("81 Landmarks and 22 ROIs", frame) # 按 q 離開 if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()
留言
張貼留言