193 lines
7.2 KiB
Python
193 lines
7.2 KiB
Python
"""
|
||
输入:原图,图中face框
|
||
输出:每张face的5点特征点的位置
|
||
"""
|
||
# from common.face_landmark_points_util import shape_index_process
|
||
# from common.face_detector_util import prior_box_forward, decode, nms_sorted
|
||
|
||
|
||
import cv2
|
||
import onnxruntime as ort
|
||
import numpy as np
|
||
|
||
# 设置ONNX Runtime的日志级别为ERROR
|
||
ort.set_default_logger_severity(3) # 3表示ERROR级别
|
||
m_origin_patch = [15, 15]
|
||
m_origin = [112, 112]
|
||
|
||
class HypeShape:
|
||
def __init__(self, shape):
|
||
self.m_shape = shape
|
||
self.m_weights = [0]*len(self.m_shape)
|
||
size = len(self.m_shape)
|
||
self.m_weights[size - 1] = self.m_shape[size - 1]
|
||
for times in range(size - 1):
|
||
self.m_weights[size - 1 - times - 1] = self.m_weights[size - 1 - times] * self.m_shape[size - 1 - times - 1]
|
||
|
||
def to_index(self, coordinate):
|
||
if len(coordinate) == 0:
|
||
return 0
|
||
size = len(coordinate)
|
||
weight_start = len(self.m_weights) - size + 1
|
||
index = 0
|
||
for times in range(size - 1):
|
||
index += self.m_weights[weight_start + times] * coordinate[times]
|
||
index += coordinate[size - 1]
|
||
return index
|
||
|
||
|
||
def shape_index_process(feat_data, pos_data):
|
||
feat_h = feat_data.shape[2]
|
||
feat_w = feat_data.shape[3]
|
||
|
||
landmarkx2 = pos_data.shape[1]
|
||
x_patch_h = int( m_origin_patch[0] * feat_data.shape[2] / float( m_origin[0] ) + 0.5 )
|
||
x_patch_w = int( m_origin_patch[1] * feat_data.shape[3] / float( m_origin[1] ) + 0.5 )
|
||
|
||
feat_patch_h = x_patch_h
|
||
feat_patch_w = x_patch_w
|
||
|
||
num = feat_data.shape[0]
|
||
channels = feat_data.shape[1]
|
||
|
||
r_h = ( feat_patch_h - 1 ) / 2.0
|
||
r_w = ( feat_patch_w - 1 ) / 2.0
|
||
landmark_num = int(landmarkx2 * 0.5)
|
||
|
||
pos_offset = HypeShape([pos_data.shape[0], pos_data.shape[1]])
|
||
feat_offset = HypeShape([feat_data.shape[0], feat_data.shape[1], feat_data.shape[2], feat_data.shape[3]])
|
||
nmarks = int( landmarkx2 * 0.5 )
|
||
out_shape = [feat_data.shape[0], feat_data.shape[1], x_patch_h, nmarks, x_patch_w]
|
||
out_offset = HypeShape([feat_data.shape[0], feat_data.shape[1], x_patch_h, nmarks, x_patch_w])
|
||
buff = np.zeros(out_shape)
|
||
zero = 0
|
||
|
||
buff = buff.reshape((-1))
|
||
pos_data = pos_data.reshape((-1))
|
||
feat_data = feat_data.reshape((-1))
|
||
|
||
for i in range(landmark_num):
|
||
for n in range(num):
|
||
# coordinate of the first patch pixel, scale to the feature map coordinate
|
||
y = int( pos_data[pos_offset.to_index( [n, 2 * i + 1] )] * ( feat_h - 1 ) - r_h + 0.5 )
|
||
x = int( pos_data[pos_offset.to_index( [n, 2 * i] )] * ( feat_w - 1 ) - r_w + 0.5 )
|
||
|
||
for c in range(channels):
|
||
for ph in range(feat_patch_h):
|
||
for pw in range(feat_patch_w):
|
||
y_p = y + ph
|
||
x_p = x + pw
|
||
# set zero if exceed the img bound
|
||
if y_p < 0 or y_p >= feat_h or x_p < 0 or x_p >= feat_w:
|
||
buff[out_offset.to_index( [n, c, ph, i, pw] )] = zero
|
||
else:
|
||
buff[out_offset.to_index( [n, c, ph, i, pw] )] = feat_data[feat_offset.to_index( [n, c, y_p, x_p] )]
|
||
|
||
return buff.reshape((1,-1,1,1)).astype(np.float32)
|
||
|
||
|
||
def crop_face(image:np.ndarray, face, H, W):
|
||
"""
|
||
Crop a face from an image with padding if the face is out of bounds.
|
||
|
||
Args:
|
||
image (np.ndarray): The input image as a numpy array of shape (H, W, C).
|
||
face (tuple): A tuple containing (x, y, w, h) for the face rectangle.
|
||
padding (list): Padding data for padding the image.
|
||
|
||
Returns:
|
||
np.ndarray: Cropped and padded image.
|
||
"""
|
||
x0, y0, x1, y1 = int(round(face[0])), int(round(face[1])), int(round(face[2])), int(round(face[3]))
|
||
|
||
# Calculate padding
|
||
pad_top = max(0, -y0)
|
||
pad_bottom = max(0, y1 - H)
|
||
pad_left = max(0, -x0)
|
||
pad_right = max(0, x1 - W)
|
||
|
||
# Apply padding
|
||
padded_image = np.pad(image, ((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)), mode='constant', constant_values=0)
|
||
|
||
# Update new coordinates after padding
|
||
new_x0, new_y0 = x0 + pad_left, y0 + pad_top
|
||
new_x1, new_y1 = x1 + pad_left, y1 + pad_top
|
||
|
||
# Crop the face
|
||
cropped_image = padded_image[new_y0:new_y1, new_x0:new_x1, :]
|
||
|
||
return cropped_image, (x0,y0,x1,y1)
|
||
|
||
|
||
class Landmark5er():
|
||
def __init__(self, onnx_path1, onnx_path2, num_threads=1) -> None:
|
||
session_options = ort.SessionOptions()
|
||
session_options.intra_op_num_threads = num_threads
|
||
# 初始化 InferenceSession 时传入 SessionOptions 对象
|
||
self.ort_session1 = ort.InferenceSession(onnx_path1, session_options=session_options)
|
||
self.ort_session2 = ort.InferenceSession(onnx_path2, session_options=session_options)
|
||
self.first_input_name = self.ort_session1.get_inputs()[0].name
|
||
self.second_input_name = self.ort_session2.get_inputs()[0].name
|
||
|
||
def inference(self, image, box):
|
||
# face_img = image[int(box[1]):int(box[3]), int(box[0]):int(box[2])] # 这种裁剪不对,不合适
|
||
H,W,C = image.shape
|
||
face_img, box = crop_face(image,box,H,W)
|
||
|
||
x1,y1,x2,y2 = int(box[0]) ,int(box[1]), int(box[2]),int(box[3])
|
||
if x1 < 0 or x1 > W-1 or x2 < 0 or x2 > W:
|
||
print("x超出边界")
|
||
if y1 < 0 or y1 > H-1 or y2 < 0 or y2 > H:
|
||
print("y超出边界")
|
||
|
||
face_img = cv2.resize(face_img,(112,112))
|
||
|
||
gray_img = cv2.cvtColor(face_img, cv2.COLOR_BGR2GRAY)
|
||
gray_img = gray_img.reshape((1, 1, 112, 112)).astype(np.float32) # 输入必须是(1,1,112,112)
|
||
# points5 net1
|
||
results_1 = self.ort_session1.run([], {self.first_input_name: gray_img})
|
||
|
||
# shape index process
|
||
feat_data = results_1[0]
|
||
pos_data = results_1[1]
|
||
shape_index_results = shape_index_process(feat_data, pos_data)
|
||
results_2 = self.ort_session2.run([], {self.second_input_name: shape_index_results})
|
||
|
||
landmarks = (results_2[0] + results_1[1]) * 112
|
||
# print("results_2[0] , results_1[1]: ",results_2[0], results_1[1])
|
||
# print("in find_landmarks, landmarks:", landmarks)
|
||
landmarks = landmarks.reshape((-1)).astype(np.int32)
|
||
|
||
scale_x = (box[2] - box[0]) / 112.0
|
||
scale_y = (box[3] - box[1]) / 112.0
|
||
mapped_landmarks = []
|
||
for i in range(landmarks.size // 2):
|
||
x = box[0] + landmarks[2 * i] * scale_x
|
||
y = box[1] + landmarks[2 * i + 1] * scale_y
|
||
x = max(0.01, min(x,W-0.01))
|
||
y = max(0.01, min(y,H-0.01))
|
||
mapped_landmarks.append((x, y))
|
||
|
||
return mapped_landmarks
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import sys
|
||
ld5 = Landmark5er(onnx_path1="./checkpoints/face_landmarker_pts5_net1.onnx",
|
||
onnx_path2="./checkpoints/face_landmarker_pts5_net2.onnx",
|
||
num_threads=1)
|
||
|
||
if len(sys.argv) > 1:
|
||
jpg_path = sys.argv[1]
|
||
else:
|
||
jpg_path = "asserts/1.jpg"
|
||
|
||
image = cv2.imread(jpg_path)
|
||
if image is None:
|
||
print("Error: Could not load image.")
|
||
exit()
|
||
|
||
box = (201.633087308643, 42.78490193881931, 319.49375572419393, 191.68867463550623)
|
||
landmarks5 = ld5.inference(image,box)
|
||
print(landmarks5)
|