Files
2024-05-18 23:25:27 +09:00

351 lines
13 KiB
Python

try:
from .dwpose import *
except ImportError:
print(f"node dwpose is import failed.")
from ultralytics import YOLO
import numpy as np
import torch
import cv2
from PIL import Image, ImageOps, ImageSequence
import os
import datetime
import statistics
import folder_paths
OUTPUTS_DIRECTORY = 'IsNiceParts'
MODELS_DIRECTORY = 'models'
MODEL_NAME_YOLO = 'hand_yolov8n.pt'
# create ditector.
pose = DWposeDetector()
# create output directory.
outputs_dir = os.path.join(folder_paths.get_output_directory(), OUTPUTS_DIRECTORY)
if not os.path.exists(outputs_dir):
os.makedirs(outputs_dir)
# set YOLO model.
current_path = os.path.dirname(os.path.realpath(__file__))
models_path = os.path.join(current_path, MODELS_DIRECTORY)
yolo_file_path = os.path.join(models_path, MODEL_NAME_YOLO)
yolo_model = YOLO(yolo_file_path)
# JudgeHand ***********************************************************************************
class NiceHand:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE", {}),
"filename_prefix": ("STRING", {"default": "IsNiceParts"}),
"confidence": ("FLOAT", {"default": 0.9, "min":0, "max": 1, "step":0.01}),
},
}
FUNCTION = "run"
OUTPUT_NODE = True
RETURN_TYPES = ( "BOOLEAN","IMAGE",)
RETURN_NAMES = ( "bool","image",)
CATEGORY = "IsNiceParts"
# return nice image.
def run(self, image, confidence, filename_prefix="IsNiceParts"):
# create output list.
result_images = list()
hand_dict = dict(
origin_width = 0,
origin_height = 0,
hand_regions = None,
bone_regions = None,
confidence = confidence
)
# get file info.
subfolder = os.path.dirname(os.path.normpath(filename_prefix))
# save original image.
save_info = self.save_images(subfolder = subfolder, prefix = filename_prefix, images = image, image_type = "origin_image")
origin_img_path = save_info["save_path"]
result_images.append({"filename": save_info["file_name"], "subfolder": subfolder, "type": "output",})
# load original image.
output_image, output_mask = self.load_image(origin_img_path)
hand_dict["origin_width"] = output_image.shape[2]
hand_dict["origin_height"] = output_image.shape[1]
# detect hand_images and saving.
if len(image) > 0:
hand_images, hand_xywhns = self.getHandImages_v2(model = yolo_model, image_path = origin_img_path)
hand_regions = self.getHandRegions(xywhns = hand_xywhns)
hand_dict["hand_regions"] = hand_regions
save_info = self.save_images(subfolder = subfolder, prefix = filename_prefix, images = hand_images, image_type = "hand_images")
result_images.append({"filename": save_info["file_name"], "subfolder": subfolder, "type": "output",})
# detect bone_image and saving.
if len(hand_images) > 0:
bone_image, bone_regions = self.detectBone_v4(image_path = origin_img_path)
hand_dict["bone_regions"] = bone_regions
save_info = self.save_images(subfolder = subfolder, prefix = filename_prefix, images = bone_image, image_type = "bone_image")
result_images.append({"filename": save_info["file_name"], "subfolder": subfolder, "type": "output",})
# judge isNice.
if hand_dict["origin_width"] > 0 and hand_dict["origin_height"] > 0:
isNice, judge_scores = self.judgeNice(hand_dict)
for i in range(len(judge_scores)):
print(f"score[{i}] = {judge_scores[i]}")
else:
isNice = False
print(f"isNice = {isNice}. hand = {len(hand_images)}, bone = {len(bone_regions)}.")
return {"ui": {"images": result_images}, "result": (isNice,output_image, output_mask, )}
# return isNice(True or False).
def judgeNice(self, hand_dict):
# read dict.
W = hand_dict["origin_width"]
H = hand_dict["origin_height"]
conf = hand_dict["confidence"]
# degree of deviation.
dd_dict = []
for i in range(len(hand_dict["hand_regions"])):
isNice = True
h_region = hand_dict["hand_regions"][i]
dd_candidate = []
for j in range(len(hand_dict["bone_regions"])):
b_region = hand_dict["bone_regions"][j]
# calc param.
dd_center_x = abs(h_region["x_center"] - b_region["x_center"]) / W
dd_center_y = abs(h_region["y_center"] - b_region["y_center"]) / H
dd_width = abs(h_region["width"] - b_region["width"]) / W
dd_height = abs(h_region["height"] - b_region["height"]) / H
dd_average = 1 - statistics.mean([dd_center_x, dd_center_y, dd_width, dd_height])
# create dict_temp.
dd_temp = dict(
dd_average = int(dd_average * 1000) / 1000,
dd_center_x = int(dd_center_x * 1000) / 1000,
dd_center_y = int(dd_center_y * 1000) / 1000,
dd_width = int(dd_width * 1000) / 1000,
dd_height = int(dd_height * 1000) / 1000
)
dd_candidate.append(dd_temp)
# add dd_dict
try:
# Take the best score.
dd_dict.append(dd_candidate[0])
for k in range(len(dd_candidate)):
if dd_dict[i]["dd_average"] < dd_candidate[k]["dd_average"]:
dd_dict[i] = dd_candidate[k]
except Exception as e:
isNice = False
# return isNice.
for dd in dd_dict:
dd_score = dd["dd_average"]
if dd_score < conf:
isNice = False
return isNice, dd_dict
# return hand regions.
def getHandRegions(self, xywhns):
hand_regions = []
for xywhn in xywhns:
region = torch.round(xywhn).int()
center_x = region[0].item()
center_y = region[1].item()
width = region[2].item()
height = region[3].item()
# append parameter.
hand_regions.append(dict(
x_min = center_x - int(width * 0.5),
x_max = center_x + int(width * 0.5),
x_center = int(center_x),
y_min = center_y - int(height * 0.5),
y_max = center_y + int(height * 0.5),
y_center = int(center_y),
width = width,
height = height
))
return hand_regions
# return cropped image.
def crop_image(self, image_path, region):
# read image.
img = cv2.imread(f"{image_path}")
h, w = img.shape[:2]
# get region.
x_center, y_center, width, height = region
x_center *= w
y_center *= h
width *= w
height *= h
# proc region.
top_left = (int(x_center - width / 2), int(y_center - height / 2))
bottom_right = (int(x_center + width / 2), int(y_center + height / 2))
# cropping hand.
cropped_img = img[top_left[1]:bottom_right[1], top_left[0]:bottom_right[0]]
return cropped_img
# return hand images with yolo.
def getHandImages_v2(self, model, image_path):
image = cv2.imread(f"{image_path}")
image_tensor = torch.clamp(torch.from_numpy(image).float() / 255., 0, 1)
image_tensor = image_tensor[..., [2, 1, 0]]
# Run inference on image.
image_bchw = image
results_yolo = model.predict(image_bchw, imgsz=640, conf=0.5, max_det=4)
# cropping hands.
hand_images = []
hand_regions = []
for r in results_yolo:
for xywhn in r.boxes.xywhn:
# Add HandImage.
crop_img = self.crop_image(image_path = image_path, region = xywhn)
# crop_imgはRGBなのでそのまま渡す
hand_images.append(crop_img)
hand_regions.append(xywhn)
return hand_images, hand_regions
# detectBone_v4.
def detectBone_v4(self, image_path):
# read image.
orig_image = cv2.imread(image_path)
# 骨格推定
bone_image, poses = pose(orig_image)
# handsのX座標とY座標を取得
hands = poses['hands']
bone_regions = []
for hand in hands:
# 大きさを取得
H, W, C = orig_image.shape
# 手の情報を取得
x_coords = hand[:, 0]
y_coords = hand[:, 1]
# 見えない部位(-1)を除外
visible_x_coords = x_coords[x_coords != -1]
visible_y_coords = y_coords[y_coords != -1]
# 領域のパラメータを初期化
min_x = max_x = width = 0
min_y = max_y = height = 0
# visible_x_coordsとvisible_y_coordsが空でないことを確認
if visible_x_coords.size > 0 and visible_y_coords.size > 0:
# X座標の最大と最小、Y座標の最大と最小を計算
min_x, max_x = visible_x_coords.min(), visible_x_coords.max()
min_y, max_y = visible_y_coords.min(), visible_y_coords.max()
width = max_x - min_x
height = max_y - min_y
center_x = (min_x + max_x) * 0.5
center_y = (min_y + max_y) * 0.5
bone_regions.append(dict(
x_min = int(min_x * W),
x_max = int(max_x * W),
x_center = int(center_x * W),
y_min = int(min_y * H),
y_max = int(max_y * H),
y_center = int(center_y * H),
width = int(width * W),
height = int(height * H)
))
return bone_image, bone_regions
# 画像を読み込んで返す関数(LoadImageをコピペした)
def load_image(self, image_path):
img = Image.open(image_path)
output_images = []
output_masks = []
for i in ImageSequence.Iterator(img):
i = ImageOps.exif_transpose(i)
if i.mode == 'I':
i = i.point(lambda i: i * (1 / 255))
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
output_images.append(image)
output_masks.append(mask.unsqueeze(0))
if len(output_images) > 1:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]
return (output_image, output_mask)
# 受け取った画像を保存し辞書を返す関数
def save_images(self, subfolder, prefix, images, image_type):
# get save info.
output_dir = folder_paths.get_output_directory()
full_output_folder = os.path.join(output_dir, subfolder)
formatted_now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# save original image.
if image_type == "origin_image":
file_name = f"{prefix}_{formatted_now}.png"
save_path = os.path.join(full_output_folder, file_name)
for (batch_number, image) in enumerate(images):
# i = 255. * image.cpu().numpy()
image = image * 255
image = image.numpy().astype(np.uint8)
pil_img = Image.fromarray(image)
# pil_img = Image.fromarray(np.clip(image.numpy(), 0, 255).astype(np.uint8))
pil_img.save(save_path, **{'compress_level': 4}, pnginfo=None)
# save hand image.
elif image_type == "hand_images":
for i in range(len(images)):
file_name = f"{prefix}_{formatted_now}_hand{i}.png"
save_path = os.path.join(full_output_folder, file_name)
img = cv2.cvtColor(images[i], cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8))
pil_img.save(save_path, **{'compress_level': 4}, pnginfo=None)
# save bone image.
elif image_type == "bone_image":
file_name = f"{prefix}_{formatted_now}_bone.png"
save_path = os.path.join(full_output_folder, file_name)
img = images
pil_img = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8))
pil_img.save(save_path, **{'compress_level': 4}, pnginfo=None)
else:
pass
# create result from image.
result = dict(save_path = save_path, file_name = file_name)
return result
# Mappings ***********************************************************************************
NODE_CLASS_MAPPINGS = {
"NiceHand": NiceHand,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"NiceHand": "NiceHand",
}