facelandmark - 황금 비율 측정(PFLD)

2024. 2. 25. 17:17파이썬

PFLD

  • PFLD는 "Parallel Facial Landmark Detection"의 약어로, 얼굴의 랜드마크(landmark)를 탐지하는 모델입니다. 이 모델은 병렬로 동작하여 얼굴의 다양한 특징 포인트(랜드마크)를 감지하고 추정합니다. 주로 얼굴 인식, 표정 분석, 동작 인식 등의 응용 프로그램에서 사용됩니다.
  • PFLD는 여러 가지 랜드마크를 예측하여 얼굴의 다양한 부분을 포착할 수 있습니다. 이를 통해 얼굴의 특징을 추출하고, 예를 들어 눈, 코, 입 등의 위치를 식별할 수 있습니다. 이러한 랜드마크는 보통 2D 이미지에서 얼굴의 구조를 설명하거나 3D 얼굴 모델링에 활용됩니다.
  • PFLD와 같은 얼굴 랜드마크 감지 모델은 주로 컴퓨터 비전 및 인공지능 분야에서 얼굴 관련 작업에 사용되며, 얼굴 기반 응용프로그램에서 인간의 얼굴 특징을 이해하고 활용하는 데 도움을 줍니다.

0. 데이터 준비 및 전처리(구글 코랩)

from google.colab import drive
drive.mount('/content/drive')
%cd /content/drive/MyDrive/facelandmark
!git clone https://github.com/polarisZhao/PFLD-pytorch
%cd /content/drive/MyDrive/facelandmark/PFLD-pytorch/data/WFLW

GitHub - polarisZhao/PFLD-pytorch: PFLD pytorch 구현

 

GitHub - polarisZhao/PFLD-pytorch: PFLD pytorch Implementation

PFLD pytorch Implementation. Contribute to polarisZhao/PFLD-pytorch development by creating an account on GitHub.

github.com

위 링크에서 데이터셋(WFLW) 다운로드 후 위 경로에 폴더 생성

!tar -zxvf WFLW_images.tar.gz #압축 해제
!tar -zxvf WFLW_annotations.tar.gz #압축 해제
%cd /content/drive/MyDrive/facelandmark/PFLD-pytorch/data
!python SetPreparation.py #데이터 전처리

1. 학습(구글 코랩)

!python train.py --dataroot data/train_data/list.txt --train_batchsize 320 --val_batchsize 320

개인 컴퓨터로 학습(메모리 부족으로 인한 오류 때문에 에포크와 배치 사이즈를 줄였음)

2. 하이퍼 파라미터 설정(vscode 가상환경)

가상환경 설정

2024.02.24 - [파이썬] - face dection-Retina face, Dlib

cd C:PFLD-pytorch #PFLD-pytorch 경로
import numpy as np
import cv2
import torch
import torchvision
import matplotlib.pyplot as plt
from mtcnn.detector import detect_faces
from collections import OrderedDict

from models.pfld import PFLDInference, AuxiliaryNet
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model_path = 'tar 경로'

#PFLD Inference 함수 (모델 불러오기 포함)
def landmark_detection(img, det, model_path):
    # 모델을 불러옵니다.
    checkpoint = torch.load(model_path, map_location=device)
    pfld_backbone = PFLDInference().to(device)
    pfld_backbone.load_state_dict(checkpoint['pfld_backbone'])
    pfld_backbone.eval()
    pfld_backbone = pfld_backbone.to(device)

    # 이미지를 변환합니다.
    transform = torchvision.transforms.Compose(
        [torchvision.transforms.ToTensor()])

    # 얼굴을 둘러싸는 bounding box 좌표를 가져옵니다.
    height, width = img.shape[:2]
    x1, y1, x2, y2 = (det[:4] + 0.5).astype(np.int32)

    # bounding box를 조정합니다.(얼굴보다 여유가 있는 정사각형)
    w = x2 - x1 + 1
    h = y2 - y1 + 1
    cx = x1 + w // 2
    cy = y1 + h // 2

    size = int(max([w, h]) * 1.1)
    x1 = cx - size // 2
    x2 = x1 + size
    y1 = cy - size // 2
    y2 = y1 + size

    # 이미지 경계를 벗어나지 않도록 보정합니다.
    x1 = max(0, x1)
    y1 = max(0, y1)
    x2 = min(width, x2)
    y2 = min(height, y2)

    edx1 = max(0, -x1)
    edy1 = max(0, -y1)
    edx2 = max(0, x2 - width)
    edy2 = max(0, y2 - height)

    cropped = img[y1:y2, x1:x2] #행과 열로 바꿔야 하는데 OPENCV에선 HEIGHT가 행, WIDTH가 열이므로 서로 바꿔서 대입

    # 이미지가 bounding box를 벗어날 경우 경계를 0으로 채웁니다.
    if (edx1 > 0 or edy1 > 0 or edx2 > 0 or edy2 > 0):
        cropped = cv2.copyMakeBorder(cropped, edy1, edy2, edx1, edx2,
                                        cv2.BORDER_CONSTANT, 0)

    # 이미지를 모델에 적합한 크기로 조정하고 변환합니다.
    input = cv2.resize(cropped, (112, 112))
    input = transform(input).unsqueeze(0).to(device)

    # 얼굴 랜드마크를 예측합니다.
    _, landmarks = pfld_backbone(input)
    pre_landmark = landmarks[0]
    pre_landmark = pre_landmark.cpu().detach().numpy().reshape(
        -1, 2) * [size, size] - [edx1, edy1]

    result = []
    # 예측된 랜드마크를 원래 이미지 좌표로 변환합니다.
    for p in pre_landmark:
        x = p[0] + x1
        y = p[1] + y1
        result.append([int(x), int(y)])

    return result
    
#황금 비율 계산 함수
def calc_gr(pts, bounding_boxes, n=4):
    # 비율 계산 결과를 저장할 리스트
    result = []

    # 랜드마크 지점을 사용하여 비율 계산
    A = pts[64][1] - pts[76][1]
    B = pts[76][1] - pts[16][1]
    result.append(round(A / B, n))

    A = bounding_boxes[1] - pts[59][1]
    B = pts[59][1] - pts[16][1]
    result.append(round(A / B, n))

    A = pts[64][0] - pts[0][0]
    B = pts[68][0] - pts[64][0]
    result.append(round(A / B, n))

    A = pts[32][0] - pts[68][0]
    B = pts[68][0] - pts[64][0]
    result.append(round(A / B, n))

    # 계산된 비율의 평균을 계산
    average_ratio = sum(result) / len(result)

    # 결과 반환
    return result, average_ratio

#테스트 이미지
test_img_path = 'C:test.jpg 경로'

# 테스트 이미지 로드 및 얼굴 감지
test_img = cv2.imread(test_img_path)
test_img_gray = cv2.cvtColor(test_img, cv2.COLOR_BGR2RGB)
bounding_boxes, _ = detect_faces(test_img_gray)  # bounding boxes: [x1, y1, x2, y2, confidence]

# 얼굴 랜드마크 감지
landmark = landmark_detection(test_img, bounding_boxes[0][:4], model_path)

# 랜드마크 시각적으로 표시
for p in landmark:
    cv2.circle(test_img, (p[0], p[1]), 2, (255, 0, 0), -1)

# 황금 비율 계산 및 시각적으로 나타내기
result_list, result = calc_gr(landmark, bounding_boxes[0][:4])
cv2.putText(test_img, "golden ratio : " + str(round(result, 3)), (10, 20), cv2.FONT_HERSHEY_DUPLEX, 0.5, (255, 0, 0),
            thickness=1, lineType=cv2.LINE_AA)

# 결과 이미지 저장
cv2.imwrite("../result.png", test_img)

# 결과 출력
print(f"황금 비율 결과 리스트: {result_list}, 황금 비율 결과 평균값: {result:.3f}")

# 결과 이미지 시각화
plt.imshow(cv2.cvtColor(test_img, cv2.COLOR_BGR2RGB))
plt.show()

3. 결과

황금비율이 1.61인데 보정된 사진이라 그런지 모르겠지만 매우 근접하게 결과가 나왔다