new file: haar_cascade_models/animeface.xml
new file: haar_cascade_models/hand_gesture.xml modified: mikey_nodes.py new FaceFixerOpenCV
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+225
@@ -3095,6 +3095,229 @@ class MikeySamplerTiledBaseOnly(MikeySamplerTiled):
|
||||
#final_image = pil2tensor(tiled_image)
|
||||
return (tiled_image,)
|
||||
|
||||
"""
|
||||
import cv2
|
||||
|
||||
# Load a pre-trained face detection model
|
||||
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
||||
|
||||
# Read the image where you want to detect faces
|
||||
image_path = 'path_to_your_image.jpg' # Replace with your image path
|
||||
image = cv2.imread(image_path)
|
||||
|
||||
# Convert the image to grayscale (needed for face detection)
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Detect faces in the image
|
||||
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
|
||||
|
||||
# Draw rectangles around each face
|
||||
for (x, y, w, h) in faces:
|
||||
cv2.rectangle(image, (x, y), (x+w, y+h), (255, 0, 0), 2)
|
||||
|
||||
# Display the output
|
||||
cv2.imshow('Face Detection', image)
|
||||
cv2.waitKey(0)
|
||||
cv2.destroyAllWindows()
|
||||
"""
|
||||
|
||||
class FaceFixerOpenCV:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
classifiers = ['animeface','combined','haarcascade_frontalface_default.xml', 'haarcascade_profileface.xml',
|
||||
'haarcascade_frontalface_alt.xml', 'haarcascade_frontalface_alt2.xml',
|
||||
'haarcascade_upperbody.xml', 'haarcascade_fullbody.xml', 'haarcascade_lowerbody.xml',
|
||||
'haarcascade_frontalcatface.xml', 'hands']
|
||||
return {"required": {"image": ("IMAGE",), "base_model": ("MODEL",), "vae": ("VAE",),
|
||||
"positive_cond_base": ("CONDITIONING",), "negative_cond_base": ("CONDITIONING",),
|
||||
#"model_name": (folder_paths.get_filename_list("upscale_models"), ), USING LANCZOS INSTEAD OF MODEL
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
#"upscale_by": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 10.0, "step": 0.1}),
|
||||
"face_img_resolution": ("INT", {"default": 1024, "min": 512, "max": 2048}),
|
||||
"padding": ("INT", {"default": 32, "min": 0, "max": 512}),
|
||||
"scale_factor": ("FLOAT", {"default": 1.2, "min": 0.1, "max": 10.0, "step": 0.1}),
|
||||
"min_neighbors": ("INT", {"default": 8, "min": 1, "max": 100}),
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"classifier": (classifiers, {"default": 'combined'}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {'default': 'dpmpp_2m_sde'}),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {'default': 'karras'}),
|
||||
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 1000.0, "step": 0.1}),
|
||||
"steps": ("INT", {"default": 30, "min": 1, "max": 1000})}}
|
||||
|
||||
RETURN_TYPES = ('IMAGE',)
|
||||
RETURN_NAMES = ('image',)
|
||||
FUNCTION = 'run'
|
||||
CATEGORY = 'Mikey/Utils'
|
||||
|
||||
def calculate_iou(self, box1, box2):
|
||||
"""
|
||||
Calculate the Intersection over Union (IoU) of two bounding boxes.
|
||||
|
||||
Parameters:
|
||||
box1, box2: The bounding boxes, each defined as [x, y, width, height]
|
||||
|
||||
Returns:
|
||||
iou: Intersection over Union as a float.
|
||||
"""
|
||||
# Determine the coordinates of each of the boxes
|
||||
x1_min, y1_min, x1_max, y1_max = box1[0], box1[1], box1[0] + box1[2], box1[1] + box1[3]
|
||||
x2_min, y2_min, x2_max, y2_max = box2[0], box2[1], box2[0] + box2[2], box2[1] + box2[3]
|
||||
|
||||
# Calculate the intersection area
|
||||
intersect_x_min = max(x1_min, x2_min)
|
||||
intersect_y_min = max(y1_min, y2_min)
|
||||
intersect_x_max = min(x1_max, x2_max)
|
||||
intersect_y_max = min(y1_max, y2_max)
|
||||
|
||||
intersect_area = max(0, intersect_x_max - intersect_x_min) * max(0, intersect_y_max - intersect_y_min)
|
||||
|
||||
# Calculate the union area
|
||||
box1_area = (x1_max - x1_min) * (y1_max - y1_min)
|
||||
box2_area = (x2_max - x2_min) * (y2_max - y2_min)
|
||||
union_area = box1_area + box2_area - intersect_area
|
||||
|
||||
# Calculate the IoU
|
||||
iou = intersect_area / union_area if union_area != 0 else 0
|
||||
|
||||
return iou
|
||||
|
||||
def detect_faces(self, image, classifier, scale_factor, min_neighbors):
|
||||
# before running check if cv2 is installed
|
||||
try:
|
||||
import cv2
|
||||
except ImportError:
|
||||
raise Exception('OpenCV is not installed. Please install it using "pip install opencv-python"')
|
||||
# detect face
|
||||
if classifier == 'animeface':
|
||||
p = os.path.dirname(os.path.realpath(__file__))
|
||||
p = os.path.join(p, 'haar_cascade_models/animeface.xml')
|
||||
elif classifier == 'hands':
|
||||
p = os.path.dirname(os.path.realpath(__file__))
|
||||
p = os.path.join(p, 'haar_cascade_models/hand_gesture.xml')
|
||||
else:
|
||||
p = cv2.data.haarcascades + classifier
|
||||
face_cascade = cv2.CascadeClassifier(p)
|
||||
# convert to numpy array
|
||||
image_np = np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)
|
||||
# Convert the image to grayscale (needed for face detection)
|
||||
gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)
|
||||
# Detect faces in the image
|
||||
faces = face_cascade.detectMultiScale(gray, scaleFactor=scale_factor, minNeighbors=min_neighbors, minSize=(32, 32))
|
||||
return faces
|
||||
|
||||
def combo_detection(self, image, scale_factor, min_neighbors):
|
||||
# front faces
|
||||
front_faces = self.detect_faces(image, 'haarcascade_frontalface_default.xml', scale_factor, min_neighbors)
|
||||
# profile faces
|
||||
profile_faces = self.detect_faces(image, 'haarcascade_profileface.xml', scale_factor, min_neighbors)
|
||||
# anime faces
|
||||
anime_faces = self.detect_faces(image, 'animeface', scale_factor, min_neighbors)
|
||||
# if no faces detected
|
||||
if front_faces == () and profile_faces == () and anime_faces == ():
|
||||
return front_faces
|
||||
if front_faces == () and profile_faces != () and anime_faces == ():
|
||||
return profile_faces
|
||||
if front_faces != () and profile_faces == () and anime_faces == ():
|
||||
return front_faces
|
||||
if front_faces == () and profile_faces == () and anime_faces != ():
|
||||
return anime_faces
|
||||
# combined faces
|
||||
arrays = []
|
||||
if front_faces != ():
|
||||
arrays.append(front_faces)
|
||||
if profile_faces != ():
|
||||
arrays.append(profile_faces)
|
||||
if anime_faces != ():
|
||||
arrays.append(anime_faces)
|
||||
combined_faces = np.concatenate(arrays, axis=0)
|
||||
# removing duplicates
|
||||
iou_threshold = 0.2
|
||||
faces = []
|
||||
for face in combined_faces:
|
||||
if len(faces) == 0:
|
||||
faces.append(face)
|
||||
else:
|
||||
iou = [self.calculate_iou(face, f) for f in faces]
|
||||
if max(iou) < iou_threshold:
|
||||
faces.append(face)
|
||||
return faces
|
||||
|
||||
def run(self, image, base_model, vae, positive_cond_base, negative_cond_base, seed, face_img_resolution=768, padding=8, scale_factor=1.2, min_neighbors=6, denoise=0.25,
|
||||
classifier='haarcascade_frontalface_default.xml', sampler_name='dpmpp_3m_sde_gpu', scheduler='exponential', cfg=7.0, steps=30):
|
||||
# tools
|
||||
image_scaler = ImageScale()
|
||||
vaeencoder = VAEEncode()
|
||||
vaedecoder = VAEDecode()
|
||||
# detect faces
|
||||
if classifier == 'combined':
|
||||
faces = self.combo_detection(image, scale_factor, min_neighbors)
|
||||
else:
|
||||
faces = self.detect_faces(image, classifier, scale_factor, min_neighbors)
|
||||
# if no faces detected
|
||||
if faces == ():
|
||||
return (image,)
|
||||
result = image.clone()
|
||||
# Draw rectangles around each face
|
||||
for (x, y, w, h) in faces:
|
||||
# factor in padding
|
||||
x -= padding
|
||||
y -= padding
|
||||
w += padding * 2
|
||||
h += padding * 2
|
||||
# Check if padded region is within bounds of the original image
|
||||
x = max(0, x)
|
||||
y = max(0, y)
|
||||
w = min(w, image.shape[2] - x)
|
||||
h = min(h, image.shape[1] - y)
|
||||
# crop face
|
||||
og_crop = image[:, y:y+h, x:x+w]
|
||||
# original size
|
||||
org_width, org_height = og_crop.shape[2], og_crop.shape[1]
|
||||
# upscale face
|
||||
crop = image_scaler.upscale(og_crop, 'lanczos', face_img_resolution, face_img_resolution, 'center')[0]
|
||||
samples = vaeencoder.encode(vae, crop)[0]
|
||||
samples = common_ksampler(base_model, seed, steps, cfg, sampler_name, scheduler, positive_cond_base, negative_cond_base, samples,
|
||||
start_step=int((1-(steps*denoise)) // 1), last_step=steps, force_full_denoise=False)[0]
|
||||
crop = vaedecoder.decode(vae, samples)[0]
|
||||
# resize face back to original size
|
||||
crop = image_scaler.upscale(crop, 'lanczos', org_width, org_height, 'center')[0]
|
||||
# calculate feather size
|
||||
feather = crop.shape[2] // 8
|
||||
# the image has 4 dimensions, 1st is the number of images in the batch, 2nd is the height, 3rd is the width, 4th is the number of channels
|
||||
mask = torch.ones(1, crop.shape[1], crop.shape[2], crop.shape[3])
|
||||
# feather on all sides
|
||||
# top feather
|
||||
for t in range(feather):
|
||||
mask[:, t:t+1, :] *= (1.0 / feather) * (t + 1)
|
||||
# left feather
|
||||
for t in range(feather):
|
||||
mask[:, :, t:t+1] *= (1.0 / feather) * (t + 1)
|
||||
# Right feather
|
||||
for t in range(feather):
|
||||
right_edge_start = crop.shape[2] - feather + t
|
||||
mask[:, :, right_edge_start:right_edge_start+1] *= (1.0 - (1.0 / feather) * (t + 1))
|
||||
# Bottom feather
|
||||
for t in range(feather):
|
||||
bottom_edge_start = crop.shape[1] - feather + t
|
||||
mask[:, bottom_edge_start:bottom_edge_start+1, :] *= (1.0 - (1.0 / feather) * (t + 1))
|
||||
# Apply the feathered mask to the cropped face
|
||||
crop = crop * mask
|
||||
# Extract the corresponding area on the original image
|
||||
original_area = result[:, y:y+h, x:x+w]
|
||||
# Apply inverse of the mask to the original area
|
||||
inverse_mask = 1 - mask
|
||||
original_area = original_area * inverse_mask
|
||||
# Add the processed face to the original area
|
||||
blended_face = original_area + crop
|
||||
# Place the blended face back into the result image
|
||||
result[:, y:y+h, x:x+w] = blended_face
|
||||
|
||||
# Convert the result back to the original format if needed
|
||||
# (This step depends on how you want to return the image, adjust as necessary)
|
||||
|
||||
# Return the final image
|
||||
return (result,)
|
||||
|
||||
class PromptWithSDXL:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -4486,6 +4709,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
'Mikey Sampler Base Only Advanced': MikeySamplerBaseOnlyAdvanced,
|
||||
'Mikey Sampler Tiled': MikeySamplerTiled,
|
||||
'Mikey Sampler Tiled Base Only': MikeySamplerTiledBaseOnly,
|
||||
'FaceFixerOpenCV': FaceFixerOpenCV,
|
||||
'AddMetaData': AddMetaData,
|
||||
'SaveMetaData': SaveMetaData,
|
||||
'SearchAndReplace': SearchAndReplace,
|
||||
@@ -4546,6 +4770,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
'MikeySamplerTiledAdvanced': 'Mikey Sampler Tiled Advanced',
|
||||
'MikeySamplerTiledAdvancedBaseOnly': 'Mikey Sampler Tiled Advanced Base Only',
|
||||
'Mikey Sampler Tiled Base Only': 'Mikey Sampler Tiled Base Only',
|
||||
'FaceFixerOpenCV': 'Face Fixer OpenCV (Mikey)',
|
||||
'AddMetaData': 'AddMetaData (Mikey)',
|
||||
'SaveMetaData': 'SaveMetaData (Mikey)',
|
||||
'SearchAndReplace': 'Search And Replace (Mikey)',
|
||||
|
||||
Reference in New Issue
Block a user