| import argparse
|
| import cv2 as cv
|
| import os
|
| from tqdm import tqdm
|
| import concurrent.futures
|
| import multiprocessing
|
|
|
| def generate_mask(image_path):
|
| img = cv.imread(image_path)
|
|
|
|
|
| clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
|
| lab = cv.cvtColor(img, cv.COLOR_BGR2LAB)
|
| lab_planes = list(cv.split(lab))
|
| lab_planes[0] = clahe.apply(lab_planes[0])
|
| lab = cv.merge(lab_planes)
|
| clahe_img = cv.cvtColor(lab, cv.COLOR_LAB2BGR)
|
|
|
|
|
| grayscale = cv.cvtColor(clahe_img, cv.COLOR_BGR2GRAY)
|
| kernel = cv.getStructuringElement(1, (9, 9))
|
| blackhat = cv.morphologyEx(grayscale, cv.MORPH_BLACKHAT, kernel)
|
| blurred = cv.GaussianBlur(blackhat, (3, 3), cv.BORDER_DEFAULT)
|
|
|
| _, hair_mask = cv.threshold(
|
| blurred, 20, 255, cv.THRESH_BINARY
|
| )
|
| masked_img = cv.bitwise_and(clahe_img, clahe_img, mask=255 - hair_mask)
|
|
|
|
|
| hsv = cv.cvtColor(masked_img, cv.COLOR_BGR2HSV)
|
| _, _, v = cv.split(hsv)
|
| v = cv.GaussianBlur(v, (5, 5), 0)
|
| _, v_thresh = cv.threshold(v, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
|
| thresh = v_thresh
|
|
|
| kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3))
|
| thresh = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=5)
|
| thresh = cv.dilate(thresh, kernel, iterations=5)
|
| thresh = cv.morphologyEx(thresh, cv.MORPH_CLOSE, kernel, iterations=5)
|
|
|
| thresh = cv.bitwise_or(thresh, hair_mask)
|
|
|
| return thresh
|
|
|
| def process_image(args):
|
| image_path, mask_path = args
|
| if os.path.exists(mask_path):
|
| return False
|
|
|
| try:
|
| mask = generate_mask(image_path)
|
| cv.imwrite(mask_path, mask)
|
| return True
|
| except Exception as e:
|
| return False
|
|
|
| if __name__ == "__main__":
|
| parser = argparse.ArgumentParser()
|
| parser.add_argument("--source_path", help="dataset root path")
|
| parser.add_argument("--num_workers", type=int, default=None,
|
| help="Number of parallel workers (default: number of CPU cores)")
|
|
|
| args = parser.parse_args()
|
|
|
| dataset_root = args.source_path
|
| num_workers = args.num_workers or max(1, multiprocessing.cpu_count() - 1)
|
|
|
| print(f"Using {num_workers} workers")
|
| os.makedirs(os.path.join(dataset_root, "masks"), exist_ok=True)
|
|
|
| tasks = []
|
| for folder in os.listdir(dataset_root):
|
| if folder in ["train", "valid", "test"]:
|
| os.makedirs(os.path.join(dataset_root, "masks", folder), exist_ok=True)
|
| for subfolder in os.listdir(os.path.join(dataset_root, folder)):
|
| os.makedirs(os.path.join(dataset_root, "masks", folder, subfolder), exist_ok=True)
|
| for image_file in os.listdir(os.path.join(dataset_root, folder, subfolder)):
|
| image_path = os.path.join(dataset_root, folder, subfolder, image_file)
|
| mask_path = os.path.join(dataset_root, "masks", folder, subfolder, image_file.replace(".jpg", "_mask.jpg"))
|
| tasks.append((image_path, mask_path))
|
|
|
| processed_count = 0
|
| with concurrent.futures.ProcessPoolExecutor(max_workers=num_workers) as executor:
|
| results = list(tqdm(executor.map(process_image, tasks), total=len(tasks), desc="Generating masks"))
|
| processed_count = sum(results)
|
|
|
| print(f"Processed {processed_count} new images. {len(tasks) - processed_count} were already processed or failed.") |