透過Dlib的臉部81個關鍵點去抓取額頭、臉頰、人中ROI區塊各自劃分22個區域

採用程式語言: python3.11
套件:
  • dlib-bin 20.0.1
  • opencv-python4.11.0.86
  • numpy2.2.6

81個關鍵點下載網址
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()





留言

這個網誌中的熱門文章

SAP物料主數據(Material Master Data)

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

外貿Payment Term 付款條件(方式)常見的英文縮寫與定義