diff options
Diffstat (limited to 'megapixels/commands/cv')
| -rw-r--r-- | megapixels/commands/cv/_old_files_to_face_rois.py | 168 | ||||
| -rw-r--r-- | megapixels/commands/cv/cluster.py | 47 | ||||
| -rw-r--r-- | megapixels/commands/cv/crop.py | 104 | ||||
| -rw-r--r-- | megapixels/commands/cv/csv_to_faces.py | 105 | ||||
| -rw-r--r-- | megapixels/commands/cv/csv_to_faces_mt.py | 105 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_3ddfa.py | 331 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_attributes.py | 136 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_frames.py | 82 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_landmark_2d_5.py | 146 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_landmark_2d_68.py | 150 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_landmark_3d_68.py | 147 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_pose.py | 164 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_roi.py | 187 | ||||
| -rw-r--r-- | megapixels/commands/cv/face_vector.py | 133 | ||||
| -rw-r--r-- | megapixels/commands/cv/mirror.py | 57 | ||||
| -rw-r--r-- | megapixels/commands/cv/resize.py | 150 | ||||
| -rw-r--r-- | megapixels/commands/cv/resize_dataset.py | 149 | ||||
| -rw-r--r-- | megapixels/commands/cv/videos_to_frames.py | 73 |
18 files changed, 0 insertions, 2434 deletions
diff --git a/megapixels/commands/cv/_old_files_to_face_rois.py b/megapixels/commands/cv/_old_files_to_face_rois.py deleted file mode 100644 index d92cbd74..00000000 --- a/megapixels/commands/cv/_old_files_to_face_rois.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Crop images to prepare for training -""" - -import click -# from PIL import Image, ImageOps, ImageFilter, ImageDraw - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -color_filters = {'color': 1, 'gray': 2, 'all': 3} - -@click.command() -@click.option('-i', '--input', 'opt_fp_files', required=True, - help='Input file meta CSV') -@click.option('-o', '--output', 'opt_fp_out', required=True, - help='Output CSV') -@click.option('-e', '--ext', 'opt_ext', - default='jpg', type=click.Choice(['jpg', 'png']), - help='File glob ext') -@click.option('--size', 'opt_size', - type=(int, int), default=(300, 300), - help='Output image size') -@click.option('-t', '--detector-type', 'opt_detector_type', - type=cfg.FaceDetectNetVar, - default=click_utils.get_default(types.FaceDetectNet.DLIB_CNN), - help=click_utils.show_help(types.FaceDetectNet)) -@click.option('-g', '--gpu', 'opt_gpu', default=0, - help='GPU index') -@click.option('--conf', 'opt_conf_thresh', default=0.85, type=click.FloatRange(0,1), - help='Confidence minimum threshold') -@click.option('-p', '--pyramids', 'opt_pyramids', default=0, type=click.IntRange(0,4), - help='Number pyramids to upscale for DLIB detectors') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('--display/--no-display', 'opt_display', is_flag=True, default=False, - help='Display detections to debug') -@click.option('--recursive/--no-recursive', 'opt_recursive', is_flag=True, default=False, - help='Use glob recursion (slower)') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('--color', 'opt_color_filter', - type=click.Choice(color_filters.keys()), default='color', - help='Filter to keep color or grayscale images (color = keep color') -@click.pass_context -def cli(ctx, opt_dirs_in, opt_fp_out, opt_ext, opt_size, opt_detector_type, - opt_gpu, opt_conf_thresh, opt_pyramids, opt_slice, opt_display, opt_recursive, opt_force, opt_color_filter): - """Converts frames with faces to CSV of ROIs""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import dlib # must keep a local reference for dlib - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils - from app.processors import face_detector - - # ------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - - if not opt_force and Path(opt_fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - if opt_detector_type == types.FaceDetectNet.CVDNN: - detector = face_detector.DetectorCVDNN() - elif opt_detector_type == types.FaceDetectNet.DLIB_CNN: - detector = face_detector.DetectorDLIBCNN(opt_gpu) - elif opt_detector_type == types.FaceDetectNet.DLIB_HOG: - detector = face_detector.DetectorDLIBHOG() - elif opt_detector_type == types.FaceDetectNet.MTCNN: - detector = face_detector.DetectorMTCNN() - elif opt_detector_type == types.FaceDetectNet.HAAR: - log.error('{} not yet implemented'.format(opt_detector_type.name)) - return - - - # ------------------------------------------------- - # process here - color_filter = color_filters[opt_color_filter] - - # get list of files to process - fp_ims = [] - for opt_dir_in in opt_dirs_in: - if opt_recursive: - fp_glob = join(opt_dir_in, '**/*.{}'.format(opt_ext)) - fp_ims += glob(fp_glob, recursive=True) - else: - fp_glob = join(opt_dir_in, '*.{}'.format(opt_ext)) - fp_ims += glob(fp_glob) - log.debug(fp_glob) - - - if opt_slice: - fp_ims = fp_ims[opt_slice[0]:opt_slice[1]] - log.debug('processing {:,} files'.format(len(fp_ims))) - - - data = [] - - for fp_im in tqdm(fp_ims): - im = cv.imread(fp_im) - - # filter out color or grayscale iamges - if color_filter != color_filters['all']: - try: - is_gray = im_utils.is_grayscale(im) - if is_gray and color_filter != color_filters['gray']: - log.debug('Skipping grayscale image: {}'.format(fp_im)) - continue - except Exception as e: - log.error('Could not check grayscale: {}'.format(fp_im)) - continue - - try: - bboxes = detector.detect(im, opt_size=opt_size, opt_pyramids=opt_pyramids) - except Exception as e: - log.error('could not detect: {}'.format(fp_im)) - log.error('{}'.format(e)) - fpp_im = Path(fp_im) - subdir = str(fpp_im.parent.relative_to(opt_dir_in)) - - for bbox in bboxes: - # log.debug('is square: {}'.format(bbox.w == bbox.h)) - nw,nh = int(bbox.w * im.shape[1]), int(bbox.h * im.shape[0]) - roi = { - 'fn': fpp_im.stem, - 'ext': fpp_im.suffix.replace('.',''), - 'x': bbox.x, - 'y': bbox.y, - 'w': bbox.w, - 'h': bbox.h, - 'image_height': im.shape[0], - 'image_width': im.shape[1], - 'subdir': subdir} - bbox_dim = bbox.to_dim(im.shape[:2][::-1]) # w,h - data.append(roi) - - # debug display - if opt_display and len(bboxes): - im_md = im_utils.resize(im, width=min(1200, opt_size[0])) - for bbox in bboxes: - bbox_dim = bbox.to_dim(im_md.shape[:2][::-1]) - cv.rectangle(im_md, bbox_dim.pt_tl, bbox_dim.pt_br, (0,255,0), 3) - cv.imshow('', im_md) - while True: - k = cv.waitKey(1) & 0xFF - if k == 27 or k == ord('q'): # ESC - cv.destroyAllWindows() - sys.exit() - elif k != 255: - # any key to continue - break - - # save date - file_utils.mkdirs(opt_fp_out) - df = pd.DataFrame.from_dict(data) - df.to_csv(opt_fp_out, index=False)
\ No newline at end of file diff --git a/megapixels/commands/cv/cluster.py b/megapixels/commands/cv/cluster.py deleted file mode 100644 index 419091a0..00000000 --- a/megapixels/commands/cv/cluster.py +++ /dev/null @@ -1,47 +0,0 @@ -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg -from app.utils.logger_utils import Logger - -@click.command() -@click.option('--data_store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.NAS), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--metadata', 'opt_metadata', required=True, - type=cfg.MetadataVar, - show_default=True, - help=click_utils.show_help(types.Metadata)) -@click.pass_context -def cli(ctx, opt_data_store, opt_dataset, opt_metadata): - """Display image info""" - - # cluster the embeddings - print("[INFO] clustering...") - clt = DBSCAN(metric="euclidean", n_jobs=args["jobs"]) - clt.fit(encodings) - - # determine the total number of unique faces found in the dataset - labelIDs = np.unique(clt.labels_) - numUniqueFaces = len(np.where(labelIDs > -1)[0]) - print("[INFO] # unique faces: {}".format(numUniqueFaces)) - # load and display image - im = cv.imread(fp_im) - cv.imshow('', im) - - while True: - k = cv.waitKey(1) & 0xFF - if k == 27 or k == ord('q'): # ESC - cv.destroyAllWindows() - sys.exit() - elif k != 255: - # any key to continue - break
\ No newline at end of file diff --git a/megapixels/commands/cv/crop.py b/megapixels/commands/cv/crop.py deleted file mode 100644 index 778be0c4..00000000 --- a/megapixels/commands/cv/crop.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Crop images to prepare for training -""" - -import click -from PIL import Image, ImageOps, ImageFilter, ImageDraw - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -@click.command() -@click.option('-i', '--input', 'opt_dir_in', required=True, - help='Input directory') -@click.option('-o', '--output', 'opt_dir_out', required=True, - help='Output directory') -@click.option('-e', '--ext', 'opt_ext', - default='jpg', type=click.Choice(['jpg', 'png']), - help='File glob ext') -@click.option('--size', 'opt_size', - type=(int, int), default=(256, 256), - help='Output image size') -@click.option('-t', '--crop-type', 'opt_crop_type', - default='center', type=click.Choice(['center', 'mirror', 'face', 'person', 'none']), - help='Force fit image center location') -@click.pass_context -def cli(ctx, opt_dir_in, opt_dir_out, opt_ext, opt_size, opt_crop_type): - """Crop, mirror images""" - - import os - from os.path import join - from pathlib import Path - from glob import glob - from tqdm import tqdm - - - from app.utils import logger_utils, file_utils, im_utils - - # ------------------------------------------------- - # process here - - log = logger_utils.Logger.getLogger() - log.info('crop images') - - # get list of files to process - fp_ims = glob(join(opt_dir_in, '*.{}'.format(opt_ext))) - log.debug('files: {}'.format(len(fp_ims))) - - # ensure output dir exists - file_utils.mkdirs(opt_dir_out) - - for fp_im in tqdm(fp_ims): - im = process_crop(fp_im, opt_size, opt_crop_type) - fp_out = join(opt_dir_out, Path(fp_im).name) - im.save(fp_out) - - -def process_crop(fp_im, opt_size, crop_type): - im = Image.open(fp_im) - if crop_type == 'center': - im = crop_square_fit(im, opt_size) - elif crop_type == 'mirror': - im = mirror_crop_square(im, opt_size) - return im - -def crop_square_fit(im, size, center=(0.5, 0.5)): - return ImageOps.fit(im, size, method=Image.BICUBIC, centering=center) - -def mirror_crop_square(im, size): - # force to even dims - if im.size[0] % 2 or im.size[1] % 2: - im = ImageOps.fit(im, ((im.size[0] // 2) * 2, (im.size[1] // 2) * 2)) - - # create new square image - min_size, max_size = (min(im.size), max(im.size)) - orig_w, orig_h = im.size - margin = (max_size - min_size) // 2 - w, h = (max_size, max_size) - im_new = Image.new('RGB', (w, h), color=(0, 0, 0)) - - #crop (l, t, r, b) - if orig_w > orig_h: - # landscape, mirror expand T/B - im_top = ImageOps.mirror(im.crop((0, 0, margin, w))) - im_bot = ImageOps.mirror(im.crop((orig_h - margin, 0, orig_h, w))) - im_new.paste(im_top, (0, 0)) - im_new.paste(im, (margin, 0, orig_h + margin, w)) - im_new.paste(im_bot, (h - margin, 0)) - elif orig_h > orig_w: - # portrait, mirror expand L/R - im_left = ImageOps.mirror(im.crop((0, 0, margin, h))) - im_right = ImageOps.mirror(im.crop((orig_w - margin, 0, orig_w, h))) - im_new.paste(im_left, (0, 0)) - im_new.paste(im, (margin, 0, orig_w + margin, h)) - im_new.paste(im_right, (w - margin, 0)) - - return im_new.resize(size) - - -def center_crop_face(): - pass - -def center_crop_person(): - pass
\ No newline at end of file diff --git a/megapixels/commands/cv/csv_to_faces.py b/megapixels/commands/cv/csv_to_faces.py deleted file mode 100644 index 64c8b965..00000000 --- a/megapixels/commands/cv/csv_to_faces.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -Reads in CSV of ROIs and extracts facial regions with padding -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', required=True, - help='Input CSV') -@click.option('-m', '--media', 'opt_dir_media', required=True, - help='Input image/video directory') -@click.option('-o', '--output', 'opt_dir_out', required=True, - help='Output directory for extracted ROI images') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('--padding', 'opt_padding', default=0.25, - help='Facial padding as percentage of face width') -@click.option('--ext', 'opt_ext_out', default='png', type=click.Choice(['jpg', 'png']), - help='Output image type') -@click.option('--min', 'opt_min', default=(60, 60), - help='Minimum original face size') -@click.pass_context -def cli(ctx, opt_fp_in, opt_dir_media, opt_dir_out, opt_slice, - opt_padding, opt_ext_out, opt_min): - """Converts ROIs to images""" - - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - from PIL import Image, ImageOps, ImageFilter, ImageDraw - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils - from app.models.bbox import BBox - - # ------------------------------------------------- - # process here - log = logger_utils.Logger.getLogger() - - df_rois = pd.read_csv(opt_fp_in, dtype={'subdir': str, 'fn': str}) - if opt_slice: - df_rois = df_rois[opt_slice[0]:opt_slice[1]] - - log.info('Processing {:,} rows'.format(len(df_rois))) - - file_utils.mkdirs(opt_dir_out) - - df_rois_grouped = df_rois.groupby(['fn']) # group by fn/filename - groups = df_rois_grouped.groups - skipped = [] - - for group in tqdm(groups): - # get image - group_rows = df_rois_grouped.get_group(group) - - row = group_rows.iloc[0] - fp_im = join(opt_dir_media, str(row['subdir']), '{fn}.{ext}'.format(**row)) # TODO change to ext - try: - im = Image.open(fp_im).convert('RGB') - im.verify() - except Exception as e: - log.warn('Could not open: {}'.format(fp_im)) - log.error(e) - continue - - for idx, roi in group_rows.iterrows(): - # get bbox to im dimensions - xywh = [roi['x'], roi['y'], roi['w'] , roi['h']] - bbox = BBox.from_xywh(*xywh) - dim = im.size - bbox_dim = bbox.to_dim(dim) - # expand - opt_padding_px = int(opt_padding * bbox_dim.width) - bbox_dim_exp = bbox_dim.expand_dim(opt_padding_px, dim) - # crop - x1y2 = bbox_dim_exp.pt_tl + bbox_dim_exp.pt_br - im_crop = im.crop(box=x1y2) - - # strip exif, create new image and paste data - im_crop_data = list(im_crop.getdata()) - im_crop_no_exif = Image.new(im_crop.mode, im_crop.size) - im_crop_no_exif.putdata(im_crop_data) - - # save - idx_zpad = file_utils.zpad(idx, zeros=3) - subdir = '' if roi['subdir'] == '.' else '{}_'.format(roi['subdir']) - subdir = subdir.replace('/', '_') - fp_im_out = join(opt_dir_out, '{}{}{}.{}'.format(subdir, roi['fn'], idx_zpad, opt_ext_out)) - # threshold size and save - if im_crop_no_exif.size[0] < opt_min[0] or im_crop_no_exif.size[1] < opt_min[1]: - skipped.append(fp_im_out) - log.info('Face too small: {}, idx: {}'.format(fp_im, idx)) - else: - im_crop_no_exif.save(fp_im_out) - - log.info('Skipped {:,} images'.format(len(skipped))) diff --git a/megapixels/commands/cv/csv_to_faces_mt.py b/megapixels/commands/cv/csv_to_faces_mt.py deleted file mode 100644 index 64c8b965..00000000 --- a/megapixels/commands/cv/csv_to_faces_mt.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -Reads in CSV of ROIs and extracts facial regions with padding -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', required=True, - help='Input CSV') -@click.option('-m', '--media', 'opt_dir_media', required=True, - help='Input image/video directory') -@click.option('-o', '--output', 'opt_dir_out', required=True, - help='Output directory for extracted ROI images') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('--padding', 'opt_padding', default=0.25, - help='Facial padding as percentage of face width') -@click.option('--ext', 'opt_ext_out', default='png', type=click.Choice(['jpg', 'png']), - help='Output image type') -@click.option('--min', 'opt_min', default=(60, 60), - help='Minimum original face size') -@click.pass_context -def cli(ctx, opt_fp_in, opt_dir_media, opt_dir_out, opt_slice, - opt_padding, opt_ext_out, opt_min): - """Converts ROIs to images""" - - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - from PIL import Image, ImageOps, ImageFilter, ImageDraw - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils - from app.models.bbox import BBox - - # ------------------------------------------------- - # process here - log = logger_utils.Logger.getLogger() - - df_rois = pd.read_csv(opt_fp_in, dtype={'subdir': str, 'fn': str}) - if opt_slice: - df_rois = df_rois[opt_slice[0]:opt_slice[1]] - - log.info('Processing {:,} rows'.format(len(df_rois))) - - file_utils.mkdirs(opt_dir_out) - - df_rois_grouped = df_rois.groupby(['fn']) # group by fn/filename - groups = df_rois_grouped.groups - skipped = [] - - for group in tqdm(groups): - # get image - group_rows = df_rois_grouped.get_group(group) - - row = group_rows.iloc[0] - fp_im = join(opt_dir_media, str(row['subdir']), '{fn}.{ext}'.format(**row)) # TODO change to ext - try: - im = Image.open(fp_im).convert('RGB') - im.verify() - except Exception as e: - log.warn('Could not open: {}'.format(fp_im)) - log.error(e) - continue - - for idx, roi in group_rows.iterrows(): - # get bbox to im dimensions - xywh = [roi['x'], roi['y'], roi['w'] , roi['h']] - bbox = BBox.from_xywh(*xywh) - dim = im.size - bbox_dim = bbox.to_dim(dim) - # expand - opt_padding_px = int(opt_padding * bbox_dim.width) - bbox_dim_exp = bbox_dim.expand_dim(opt_padding_px, dim) - # crop - x1y2 = bbox_dim_exp.pt_tl + bbox_dim_exp.pt_br - im_crop = im.crop(box=x1y2) - - # strip exif, create new image and paste data - im_crop_data = list(im_crop.getdata()) - im_crop_no_exif = Image.new(im_crop.mode, im_crop.size) - im_crop_no_exif.putdata(im_crop_data) - - # save - idx_zpad = file_utils.zpad(idx, zeros=3) - subdir = '' if roi['subdir'] == '.' else '{}_'.format(roi['subdir']) - subdir = subdir.replace('/', '_') - fp_im_out = join(opt_dir_out, '{}{}{}.{}'.format(subdir, roi['fn'], idx_zpad, opt_ext_out)) - # threshold size and save - if im_crop_no_exif.size[0] < opt_min[0] or im_crop_no_exif.size[1] < opt_min[1]: - skipped.append(fp_im_out) - log.info('Face too small: {}, idx: {}'.format(fp_im, idx)) - else: - im_crop_no_exif.save(fp_im_out) - - log.info('Skipped {:,} images'.format(len(skipped))) diff --git a/megapixels/commands/cv/face_3ddfa.py b/megapixels/commands/cv/face_3ddfa.py deleted file mode 100644 index ffc74180..00000000 --- a/megapixels/commands/cv/face_3ddfa.py +++ /dev/null @@ -1,331 +0,0 @@ -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', default=None, required=True, - help='Image filepath') -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='GIF output path') -@click.option('--size', 'opt_size', - type=(int, int), default=(300, 300), - help='Output image size') -@click.option('-g', '--gpu', 'opt_gpu', default=0, - help='GPU index') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('--display/--no-display', 'opt_display', is_flag=True, default=False, - help='Display detections to debug') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_gpu, opt_size, opt_force, opt_display): - """Face detector demo""" - - import sys - import os - from os.path import join - from pathlib import Path - import time - - from tqdm import tqdm - import numpy as np - import pandas as pd - import cv2 as cv - import dlib - - from app.utils import logger_utils, file_utils, im_utils, display_utils, draw_utils - from app.utils import plot_utils - from app.processors import face_detector, face_age - from app.models.data_store import DataStore - - # 3DDFA - # git clone https://github.com/cleardusk/3DDFA/ 3rdparty/ - - import torch - import torchvision.transforms as transforms - import mobilenet_v1 - from utils.ddfa import ToTensorGjz, NormalizeGjz, str2bool - import scipy.io as sio - from utils.inference import get_suffix, parse_roi_box_from_landmark, crop_img, predict_68pts, dump_to_ply, dump_vertex, \ - draw_landmarks, predict_dense, parse_roi_box_from_bbox, get_colors, write_obj_with_colors - from utils.cv_plot import plot_pose_box - from utils.estimate_pose import parse_pose - from utils.render import get_depths_image, cget_depths_image, cpncc - from utils.paf import gen_img_paf - import argparse - import torch.backends.cudnn as cudnn - - - log = logger_utils.Logger.getLogger() - - - # ------------------------------------------------- - # load image - - im = cv.imread(opt_fp_in) - im_resized = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - - # ---------------------------------------------------------------------------- - # detect face - - face_detector = face_detector.DetectorDLIBCNN(gpu=opt_gpu) # -1 for CPU - bboxes = face_detector.detect(im_resized, largest=True) - bbox = bboxes[0] - dim = im_resized.shape[:2][::-1] - bbox_dim = bbox.to_dim(dim) - if not bbox: - log.error('no face detected') - return - else: - log.info(f'face detected: {bbox_dim.to_xyxy()}') - - - # ---------------------------------------------------------------------------- - # age - - age_apparent_predictor = face_age.FaceAgeApparent() - age_real_predictor = face_age.FaceAgeReal() - - st = time.time() - age_real = age_real_predictor.age(im_resized, bbox_dim) - log.info(f'age real took: {(time.time()-st)/1000:.5f}s') - st = time.time() - age_apparent = age_apparent_predictor.age(im_resized, bbox_dim) - log.info(f'age apparent took: {(time.time()-st)/1000:.5f}s') - - - # ---------------------------------------------------------------------------- - # output - - log.info(f'Face coords: {bbox_dim} face') - log.info(f'Age (real): {(age_real):.2f}') - log.info(f'Age (apparent): {(age_apparent):.2f}') - - - # ---------------------------------------------------------------------------- - # draw - - # draw real age - im_age_real = im_resized.copy() - draw_utils.draw_bbox(im_age_real, bbox_dim) - txt = f'{(age_real):.2f}' - draw_utils.draw_text(im_age_real, bbox_dim.pt_tl, txt) - - # apparent - im_age_apparent = im_resized.copy() - draw_utils.draw_bbox(im_age_apparent, bbox_dim) - txt = f'{(age_apparent):.2f}' - draw_utils.draw_text(im_age_apparent, bbox_dim.pt_tl, txt) - - - # ---------------------------------------------------------------------------- - # save - - if opt_fp_out: - # save pose only - fpp_out = Path(opt_fp_out) - - fp_out = join(fpp_out.parent, f'{fpp_out.stem}_real{fpp_out.suffix}') - cv.imwrite(fp_out, im_age_real) - - fp_out = join(fpp_out.parent, f'{fpp_out.stem}_apparent{fpp_out.suffix}') - cv.imwrite(fp_out, im_age_apparent) - - - # ---------------------------------------------------------------------------- - # display - - if opt_display: - # show all images here - cv.imshow('real', im_age_real) - cv.imshow('apparent', im_age_apparent) - display_utils.handle_keyboard() - - - - - -STD_SIZE = 120 - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='3DDFA inference pipeline') - parser.add_argument('-f', '--files', nargs='+', - help='image files paths fed into network, single or multiple images') - parser.add_argument('-m', '--mode', default='cpu', type=str, help='gpu or cpu mode') - parser.add_argument('--show_flg', default='true', type=str2bool, help='whether show the visualization result') - parser.add_argument('--bbox_init', default='one', type=str, - help='one|two: one-step bbox initialization or two-step') - parser.add_argument('--dump_res', default='true', type=str2bool, help='whether write out the visualization image') - parser.add_argument('--dump_vertex', default='true', type=str2bool, - help='whether write out the dense face vertices to mat') - parser.add_argument('--dump_ply', default='true', type=str2bool) - parser.add_argument('--dump_pts', default='true', type=str2bool) - parser.add_argument('--dump_roi_box', default='true', type=str2bool) - parser.add_argument('--dump_pose', default='true', type=str2bool) - parser.add_argument('--dump_depth', default='true', type=str2bool) - parser.add_argument('--dump_pncc', default='true', type=str2bool) - parser.add_argument('--dump_paf', default='true', type=str2bool) - parser.add_argument('--paf_size', default=3, type=int, help='PAF feature kernel size') - parser.add_argument('--dump_obj', default='true', type=str2bool) - parser.add_argument('--dlib_bbox', default='true', type=str2bool, help='whether use dlib to predict bbox') - parser.add_argument('--dlib_landmark', default='true', type=str2bool, - help='whether use dlib landmark to crop image') - - args = parser.parse_args() - main(args) - - - -def main(args): - # 1. load pre-tained model - checkpoint_fp = 'models/phase1_wpdc_vdc_v2.pth.tar' - arch = 'mobilenet_1' - - checkpoint = torch.load(checkpoint_fp, map_location=lambda storage, loc: storage)['state_dict'] - model = getattr(mobilenet_v1, arch)(num_classes=62) # 62 = 12(pose) + 40(shape) +10(expression) - model_dict = model.state_dict() - # because the model is trained by multiple gpus, prefix module should be removed - for k in checkpoint.keys(): - model_dict[k.replace('module.', '')] = checkpoint[k] - model.load_state_dict(model_dict, strict=False) - if args.mode == 'gpu': - cudnn.benchmark = True - model = model.cuda() - model.eval() - - # 2. load dlib model for face detection and landmark used for face cropping - if args.dlib_landmark: - dlib_landmark_model = 'models/shape_predictor_68_face_landmarks.dat' - face_regressor = dlib.shape_predictor(dlib_landmark_model) - if args.dlib_bbox: - face_detector = dlib.get_frontal_face_detector() - - # 3. forward - tri = sio.loadmat('visualize/tri.mat')['tri'] - transform = transforms.Compose([ToTensorGjz(), NormalizeGjz(mean=127.5, std=128)]) - for img_fp in args.files: - img_ori = cv2.imread(img_fp) - if args.dlib_bbox: - rects = face_detector(img_ori, 1) - else: - rects = [] - - if len(rects) == 0: - rects = dlib.rectangles() - rect_fp = img_fp + '.bbox' - lines = open(rect_fp).read().strip().split('\n')[1:] - for l in lines: - l, r, t, b = [int(_) for _ in l.split(' ')[1:]] - rect = dlib.rectangle(l, r, t, b) - rects.append(rect) - - pts_res = [] - Ps = [] # Camera matrix collection - poses = [] # pose collection, [todo: validate it] - vertices_lst = [] # store multiple face vertices - ind = 0 - suffix = get_suffix(img_fp) - for rect in rects: - # whether use dlib landmark to crop image, if not, use only face bbox to calc roi bbox for cropping - if args.dlib_landmark: - # - use landmark for cropping - pts = face_regressor(img_ori, rect).parts() - pts = np.array([[pt.x, pt.y] for pt in pts]).T - roi_box = parse_roi_box_from_landmark(pts) - else: - # - use detected face bbox - bbox = [rect.left(), rect.top(), rect.right(), rect.bottom()] - roi_box = parse_roi_box_from_bbox(bbox) - - img = crop_img(img_ori, roi_box) - - # forward: one step - img = cv2.resize(img, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) - input = transform(img).unsqueeze(0) - with torch.no_grad(): - if args.mode == 'gpu': - input = input.cuda() - param = model(input) - param = param.squeeze().cpu().numpy().flatten().astype(np.float32) - - # 68 pts - pts68 = predict_68pts(param, roi_box) - - # two-step for more accurate bbox to crop face - if args.bbox_init == 'two': - roi_box = parse_roi_box_from_landmark(pts68) - img_step2 = crop_img(img_ori, roi_box) - img_step2 = cv2.resize(img_step2, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) - input = transform(img_step2).unsqueeze(0) - with torch.no_grad(): - if args.mode == 'gpu': - input = input.cuda() - param = model(input) - param = param.squeeze().cpu().numpy().flatten().astype(np.float32) - - pts68 = predict_68pts(param, roi_box) - - pts_res.append(pts68) - P, pose = parse_pose(param) - Ps.append(P) - poses.append(pose) - - # dense face 3d vertices - if args.dump_ply or args.dump_vertex or args.dump_depth or args.dump_pncc or args.dump_obj: - vertices = predict_dense(param, roi_box) - vertices_lst.append(vertices) - if args.dump_ply: - dump_to_ply(vertices, tri, '{}_{}.ply'.format(img_fp.replace(suffix, ''), ind)) - if args.dump_vertex: - dump_vertex(vertices, '{}_{}.mat'.format(img_fp.replace(suffix, ''), ind)) - - # save .mat for 3d Face - wfp = '{}_{}_face3d.mat'.format(img_fp.replace(suffix, ''), ind) - colors = get_colors(img_ori, vertices) - sio.savemat(wfp, {'vertices': vertices, 'colors': colors, 'triangles': tri}) - - if args.dump_pts: - wfp = '{}_{}.txt'.format(img_fp.replace(suffix, ''), ind) - np.savetxt(wfp, pts68, fmt='%.3f') - print('Save 68 3d landmarks to {}'.format(wfp)) - if args.dump_roi_box: - wfp = '{}_{}.roibox'.format(img_fp.replace(suffix, ''), ind) - np.savetxt(wfp, roi_box, fmt='%.3f') - print('Save roi box to {}'.format(wfp)) - if args.dump_paf: - wfp_paf = '{}_{}_paf.jpg'.format(img_fp.replace(suffix, ''), ind) - wfp_crop = '{}_{}_crop.jpg'.format(img_fp.replace(suffix, ''), ind) - paf_feature = gen_img_paf(img_crop=img, param=param, kernel_size=args.paf_size) - - cv2.imwrite(wfp_paf, paf_feature) - cv2.imwrite(wfp_crop, img) - print('Dump to {} and {}'.format(wfp_crop, wfp_paf)) - if args.dump_obj: - wfp = '{}_{}.obj'.format(img_fp.replace(suffix, ''), ind) - colors = get_colors(img_ori, vertices) - write_obj_with_colors(wfp, vertices, tri, colors) - print('Dump obj with sampled texture to {}'.format(wfp)) - ind += 1 - - if args.dump_pose: - # P, pose = parse_pose(param) # Camera matrix (without scale), and pose (yaw, pitch, roll, to verify) - img_pose = plot_pose_box(img_ori, Ps, pts_res) - wfp = img_fp.replace(suffix, '_pose.jpg') - cv2.imwrite(wfp, img_pose) - print('Dump to {}'.format(wfp)) - if args.dump_depth: - wfp = img_fp.replace(suffix, '_depth.png') - # depths_img = get_depths_image(img_ori, vertices_lst, tri-1) # python version - depths_img = cget_depths_image(img_ori, vertices_lst, tri - 1) # cython version - cv2.imwrite(wfp, depths_img) - print('Dump to {}'.format(wfp)) - if args.dump_pncc: - wfp = img_fp.replace(suffix, '_pncc.png') - pncc_feature = cpncc(img_ori, vertices_lst, tri - 1) # cython version - cv2.imwrite(wfp, pncc_feature[:, :, ::-1]) # cv2.imwrite will swap RGB -> BGR - print('Dump to {}'.format(wfp)) - if args.dump_res: - draw_landmarks(img_ori, pts_res, wfp=img_fp.replace(suffix, '_3DDFA.jpg'), show_flg=args.show_flg) diff --git a/megapixels/commands/cv/face_attributes.py b/megapixels/commands/cv/face_attributes.py deleted file mode 100644 index 01fe3bd1..00000000 --- a/megapixels/commands/cv/face_attributes.py +++ /dev/null @@ -1,136 +0,0 @@ -""" - -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', default=None, - help='Override enum input filename CSV') -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='Override enum output filename CSV') -@click.option('-m', '--media', 'opt_dir_media', default=None, - help='Override enum media directory') -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--size', 'opt_size', - type=(int, int), default=cfg.DEFAULT_SIZE_FACE_DETECT, - help='Processing size for detection') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('-d', '--display', 'opt_display', is_flag=True, - help='Display image for debugging') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_dir_media, opt_data_store, opt_dataset, - opt_size, opt_slice, opt_force, opt_display): - """Creates 2D 68-point landmarks""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils, display_utils, draw_utils - from app.processors import face_age_gender - from app.models.data_store import DataStore - from app.models.bbox import BBox - - # ------------------------------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - # init face processors - age_estimator_apnt = face_age_gender.FaceAgeApparent() - age_estimator_real = face_age_gender.FaceAgeReal() - gender_estimator = face_age_gender.FaceGender() - - # init filepaths - data_store = DataStore(opt_data_store, opt_dataset) - # set file output path - metadata_type = types.Metadata.FACE_ATTRIBUTES - fp_out = data_store.metadata(metadata_type) if opt_fp_out is None else opt_fp_out - if not opt_force and Path(fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - # ------------------------------------------------------------------------- - # load filepath data - fp_record = data_store.metadata(types.Metadata.FILE_RECORD) - df_record = pd.read_csv(fp_record, dtype=cfg.FILE_RECORD_DTYPES).set_index('index') - # load ROI data - fp_roi = data_store.metadata(types.Metadata.FACE_ROI) - df_roi = pd.read_csv(fp_roi).set_index('index') - # slice if you want - if opt_slice: - df_roi = df_roi[opt_slice[0]:opt_slice[1]] - # group by image index (speedup if multiple faces per image) - df_img_groups = df_roi.groupby('record_index') - log.debug('processing {:,} groups'.format(len(df_img_groups))) - - # store landmarks in list - results = [] - - # ------------------------------------------------------------------------- - # iterate groups with file/record index as key - - for record_index, df_img_group in tqdm(df_img_groups): - - # access file_record DataSeries - file_record = df_record.iloc[record_index] - - # load image - fp_im = data_store.face(file_record.subdir, file_record.fn, file_record.ext) - im = cv.imread(fp_im) - im_resized = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - dim = im_resized.shape[:2][::-1] - - # iterate ROIs in this image - for roi_index, df_img in df_img_group.iterrows(): - - # find landmarks - bbox_norm = BBox.from_xywh(df_img.x, df_img.y, df_img.w, df_img.h) - bbox_dim = bbox_norm.to_dim(dim) - - age_apnt = age_estimator_apnt.predict(im_resized, bbox_norm) - age_real = age_estimator_real.predict(im_resized, bbox_norm) - gender = gender_estimator.predict(im_resized, bbox_norm) - - attr_obj = { - 'age_real':float(f'{age_real:.2f}'), - 'age_apparent': float(f'{age_apnt:.2f}'), - 'm': float(f'{gender["m"]:.4f}'), - 'f': float(f'{gender["f"]:.4f}'), - 'roi_index': roi_index - } - results.append(attr_obj) - - - # create DataFrame and save to CSV - file_utils.mkdirs(fp_out) - df = pd.DataFrame.from_dict(results) - df.index.name = 'index' - df.to_csv(fp_out) - - # save script - file_utils.write_text(' '.join(sys.argv), '{}.sh'.format(fp_out))
\ No newline at end of file diff --git a/megapixels/commands/cv/face_frames.py b/megapixels/commands/cv/face_frames.py deleted file mode 100644 index 76f23af1..00000000 --- a/megapixels/commands/cv/face_frames.py +++ /dev/null @@ -1,82 +0,0 @@ -from glob import glob -import os -from os.path import join -from pathlib import Path - -import click - - - - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', required=True, - help='Input directory to glob') -@click.option('-o', '--output', 'opt_fp_out', required=True, - help='Output directory for face frames') -@click.option('--size', 'opt_size', - type=(int, int), default=(300, 300), - help='Output image size') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_size, opt_slice): - """Split video to face frames""" - - from tqdm import tqdm - import dlib - import pandas as pd - from PIL import Image, ImageOps, ImageFilter - import cv2 as cv - import numpy as np - - from app.processors import face_detector - from app.utils import logger_utils, file_utils, im_utils - from app.settings import types - from app.utils import click_utils - from app.settings import app_cfg as cfg - from app.models.bbox import BBox - - log = logger_utils.Logger.getLogger() - - # ------------------------------------------------- - # process - - detector = face_detector.DetectorDLIBCNN() - - # get file list - fp_videos = glob(join(opt_fp_in, '*.mp4')) - fp_videos += glob(join(opt_fp_in, '*.webm')) - fp_videos += glob(join(opt_fp_in, '*.mkv')) - - min_distance_per = .025 # minimum distance percentage to save new face image - face_interval = 5 - frame_interval_count = 0 - frame_count = 0 - bbox_prev = BBox(0,0,0,0) - file_utils.mkdirs(opt_fp_out) - dnn_size = opt_size - max_dim = max(dnn_size) - px_thresh = int(max_dim * min_distance_per) - - for fp_video in tqdm(fp_videos): - # load video - video = cv.VideoCapture(fp_video) - # iterate through frames - while video.isOpened(): - res, frame = video.read() - if not res: - break - # increment frames, save frame if interval has passed - frame_count += 1 # for naming - frame_interval_count += 1 # for interval - bboxes = detector.detect(frame, opt_size=dnn_size, opt_pyramids=0) - if len(bboxes) > 0 and frame_interval_count >= face_interval: - dim = frame.shape[:2][::-1] - d = bboxes[0].to_dim(dim).distance(bbox_prev) - if d > px_thresh: - # save frame - zfc = file_utils.zpad(frame_count) - fp_frame = join(opt_fp_out, '{}_{}.jpg'.format(Path(fp_video).stem, zfc)) - cv.imwrite(fp_frame, frame) - frame_interval_count = 0 - bbox_prev = bboxes[0] diff --git a/megapixels/commands/cv/face_landmark_2d_5.py b/megapixels/commands/cv/face_landmark_2d_5.py deleted file mode 100644 index 40ec6f41..00000000 --- a/megapixels/commands/cv/face_landmark_2d_5.py +++ /dev/null @@ -1,146 +0,0 @@ -""" - -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -color_filters = {'color': 1, 'gray': 2, 'all': 3} - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', default=None, - help='Override enum input filename CSV') -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='Override enum output filename CSV') -@click.option('-m', '--media', 'opt_dir_media', default=None, - help='Override enum media directory') -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('-d', '--detector', 'opt_detector_type', - type=cfg.FaceLandmark2D_5Var, - default=click_utils.get_default(types.FaceLandmark2D_5.DLIB), - help=click_utils.show_help(types.FaceLandmark2D_5)) -@click.option('--size', 'opt_size', - type=(int, int), default=(300, 300), - help='Output image size') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('-d', '--display', 'opt_display', is_flag=True, - help='Display image for debugging') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_dir_media, opt_data_store, opt_dataset, opt_detector_type, - opt_size, opt_slice, opt_force, opt_display): - """Creates 2D 5-point landmarks""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils, display_utils, draw_utils - from app.processors import face_landmarks - from app.models.data_store import DataStore - from app.models.bbox import BBox - - # ------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - # init filepaths - data_store = DataStore(opt_data_store, opt_dataset) - # set file output path - metadata_type = types.Metadata.FACE_LANDMARK_2D_5 - fp_out = data_store.metadata(metadata_type) if opt_fp_out is None else opt_fp_out - if not opt_force and Path(fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - # init face landmark processors - if opt_detector_type == types.FaceLandmark2D_5.DLIB: - # use dlib 68 point detector - landmark_detector = face_landmarks.Dlib2D_5() - elif opt_detector_type == types.FaceLandmark2D_5.MTCNN: - # use dlib 5 point detector - landmark_detector = face_landmarks.MTCNN2D_5() - else: - log.error('{} not yet implemented'.format(opt_detector_type.name)) - return - - log.info(f'Using landmark detector: {opt_detector_type.name}') - - # load filepath data - fp_record = data_store.metadata(types.Metadata.FILE_RECORD) - df_record = pd.read_csv(fp_record).set_index('index') - # load ROI data - fp_roi = data_store.metadata(types.Metadata.FACE_ROI) - df_roi = pd.read_csv(fp_roi).set_index('index') - # slice if you want - if opt_slice: - df_roi = df_roi[opt_slice[0]:opt_slice[1]] - # group by image index (speedup if multiple faces per image) - df_img_groups = df_roi.groupby('record_index') - log.debug('processing {:,} groups'.format(len(df_img_groups))) - - # store landmarks in list - results = [] - - # iterate groups with file/record index as key - for record_index, df_img_group in tqdm(df_img_groups): - - # acces file record - ds_record = df_record.iloc[record_index] - - # load image - fp_im = data_store.face(ds_record.subdir, ds_record.fn, ds_record.ext) - im = cv.imread(fp_im) - im_resized = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - - # iterate image group dataframe with roi index as key - for roi_index, df_img in df_img_group.iterrows(): - - # get bbox - x, y, w, h = df_img.x, df_img.y, df_img.w, df_img.h - dim = im_resized.shape[:2][::-1] - bbox = BBox.from_xywh(x, y, w, h).to_dim(dim) - - # get landmark points - points = landmark_detector.landmarks(im_resized, bbox) - points_norm = landmark_detector.normalize(points, dim) - points_flat = landmark_detector.flatten(points_norm) - - # display to screen if optioned - if opt_display: - draw_utils.draw_landmarks2D(im_resized, points) - draw_utils.draw_bbox(im_resized, bbox) - cv.imshow('', im_resized) - display_utils.handle_keyboard() - - results.append(points_flat) - - # create DataFrame and save to CSV - file_utils.mkdirs(fp_out) - df = pd.DataFrame.from_dict(results) - df.index.name = 'index' - df.to_csv(fp_out) - - # save script - file_utils.write_text(' '.join(sys.argv), '{}.sh'.format(fp_out))
\ No newline at end of file diff --git a/megapixels/commands/cv/face_landmark_2d_68.py b/megapixels/commands/cv/face_landmark_2d_68.py deleted file mode 100644 index c6978a40..00000000 --- a/megapixels/commands/cv/face_landmark_2d_68.py +++ /dev/null @@ -1,150 +0,0 @@ -""" - -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', default=None, - help='Override enum input filename CSV') -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='Override enum output filename CSV') -@click.option('-m', '--media', 'opt_dir_media', default=None, - help='Override enum media directory') -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('-d', '--detector', 'opt_detector_type', - type=cfg.FaceLandmark2D_68Var, - default=click_utils.get_default(types.FaceLandmark2D_68.DLIB), - help=click_utils.show_help(types.FaceLandmark2D_68)) -@click.option('--size', 'opt_size', - type=(int, int), default=(300, 300), - help='Output image size') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('-d', '--display', 'opt_display', is_flag=True, - help='Display image for debugging') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_dir_media, opt_data_store, opt_dataset, opt_detector_type, - opt_size, opt_slice, opt_force, opt_display): - """Creates 2D 68-point landmarks""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils, display_utils, draw_utils - from app.processors import face_landmarks - from app.models.data_store import DataStore - from app.models.bbox import BBox - - # ------------------------------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - # init filepaths - data_store = DataStore(opt_data_store, opt_dataset) - # set file output path - metadata_type = types.Metadata.FACE_LANDMARK_2D_68 - fp_out = data_store.metadata(metadata_type) if opt_fp_out is None else opt_fp_out - if not opt_force and Path(fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - # init face landmark processors - if opt_detector_type == types.FaceLandmark2D_68.DLIB: - # use dlib 68 point detector - landmark_detector = face_landmarks.Dlib2D_68() - elif opt_detector_type == types.FaceLandmark2D_68.FACE_ALIGNMENT: - # use dlib 5 point detector - landmark_detector = face_landmarks.FaceAlignment2D_68() - else: - log.error('{} not yet implemented'.format(opt_detector_type.name)) - return - - log.info(f'Using landmark detector: {opt_detector_type.name}') - - # ------------------------------------------------------------------------- - # load filepath data - fp_record = data_store.metadata(types.Metadata.FILE_RECORD) - df_record = pd.read_csv(fp_record).set_index('index') - # load ROI data - fp_roi = data_store.metadata(types.Metadata.FACE_ROI) - df_roi = pd.read_csv(fp_roi).set_index('index') - # slice if you want - if opt_slice: - df_roi = df_roi[opt_slice[0]:opt_slice[1]] - # group by image index (speedup if multiple faces per image) - df_img_groups = df_roi.groupby('record_index') - log.debug('processing {:,} groups'.format(len(df_img_groups))) - - # store landmarks in list - results = [] - - # ------------------------------------------------------------------------- - # iterate groups with file/record index as key - - for record_index, df_img_group in tqdm(df_img_groups): - - # access file_record DataSeries - file_record = df_record.iloc[record_index] - - # load image - fp_im = data_store.face(file_record.subdir, file_record.fn, file_record.ext) - im = cv.imread(fp_im) - im_resized = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - dim = im_resized.shape[:2][::-1] - - # iterate ROIs in this image - for roi_index, df_img in df_img_group.iterrows(): - - # find landmarks - x, y, w, h = df_img.x, df_img.y, df_img.w, df_img.h # normalized values - #dim = (file_record.width, file_record.height) # original w,h - bbox = BBox.from_xywh(x, y, w, h).to_dim(dim) - points = landmark_detector.landmarks(im_resized, bbox) - points_norm = landmark_detector.normalize(points, dim) - points_str = landmark_detector.to_str(points_norm) - - # display if optioned - if opt_display: - dst = im_resized.copy() - draw_utils.draw_landmarks2D(dst, points) - draw_utils.draw_bbox(dst, bbox) - cv.imshow('', dst) - display_utils.handle_keyboard() - - # add to results for CSV - results.append({'vec': points_str, 'roi_index':roi_index}) - - - # create DataFrame and save to CSV - file_utils.mkdirs(fp_out) - df = pd.DataFrame.from_dict(results) - df.index.name = 'index' - df.to_csv(fp_out) - - # save script - file_utils.write_text(' '.join(sys.argv), '{}.sh'.format(fp_out))
\ No newline at end of file diff --git a/megapixels/commands/cv/face_landmark_3d_68.py b/megapixels/commands/cv/face_landmark_3d_68.py deleted file mode 100644 index a2d14d72..00000000 --- a/megapixels/commands/cv/face_landmark_3d_68.py +++ /dev/null @@ -1,147 +0,0 @@ -""" - -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -color_filters = {'color': 1, 'gray': 2, 'all': 3} - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', default=None, - help='Override enum input filename CSV') -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='Override enum output filename CSV') -@click.option('-m', '--media', 'opt_dir_media', default=None, - help='Override enum media directory') -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('-d', '--detector', 'opt_detector_type', - type=cfg.FaceLandmark3D_68Var, - default=click_utils.get_default(types.FaceLandmark3D_68.FACE_ALIGNMENT), - help=click_utils.show_help(types.FaceLandmark3D_68)) -@click.option('--size', 'opt_size', - type=(int, int), default=(300, 300), - help='Output image size') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('-d', '--display', 'opt_display', is_flag=True, - help='Display image for debugging') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_dir_media, opt_data_store, opt_dataset, opt_detector_type, - opt_size, opt_slice, opt_force, opt_display): - """Generate 3D 68-point landmarks""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils, display_utils, draw_utils - from app.utils import plot_utils - from app.processors import face_landmarks - from app.models.data_store import DataStore - from app.models.bbox import BBox - - # -------------------------------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - log.warn('not normalizing points') - # init filepaths - data_store = DataStore(opt_data_store, opt_dataset) - # set file output path - metadata_type = types.Metadata.FACE_LANDMARK_3D_68 - fp_out = data_store.metadata(metadata_type) if opt_fp_out is None else opt_fp_out - if not opt_force and Path(fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - # init face landmark processors - if opt_detector_type == types.FaceLandmark3D_68.FACE_ALIGNMENT: - # use FaceAlignment 68 point 3D detector - landmark_detector = face_landmarks.FaceAlignment3D_68() - else: - log.error('{} not yet implemented'.format(opt_detector_type.name)) - return - - log.info(f'Using landmark detector: {opt_detector_type.name}') - - # ------------------------------------------------------------------------- - # load data - - fp_record = data_store.metadata(types.Metadata.FILE_RECORD) # file_record.csv - df_record = pd.read_csv(fp_record).set_index('index') - fp_roi = data_store.metadata(types.Metadata.FACE_ROI) # face_roi.csv - df_roi = pd.read_csv(fp_roi).set_index('index') - if opt_slice: - df_roi = df_roi[opt_slice[0]:opt_slice[1]] # slice if you want - df_img_groups = df_roi.groupby('record_index') # groups by image index (load once) - log.debug('processing {:,} groups'.format(len(df_img_groups))) - - # store landmarks in list - results = [] - - # iterate groups with file/record index as key - for record_index, df_img_group in tqdm(df_img_groups): - - # acces file record - ds_record = df_record.iloc[record_index] - - # load image - fp_im = data_store.face(ds_record.subdir, ds_record.fn, ds_record.ext) - im = cv.imread(fp_im) - im_resized = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - - # iterate image group dataframe with roi index as key - for roi_index, df_img in df_img_group.iterrows(): - - # get bbox - x, y, w, h = df_img.x, df_img.y, df_img.w, df_img.h - dim = im_resized.shape[:2][::-1] - bbox = BBox.from_xywh(x, y, w, h).to_dim(dim) - - # get landmark points - points = landmark_detector.landmarks(im_resized, bbox) - # NB can't really normalize these points, but are normalized against 3D space - #points_norm = landmark_detector.normalize(points, dim) # normalized using 200 - points_flattenend = landmark_detector.flatten(points) - - # display to screen if optioned - if opt_display: - draw_utils.draw_landmarks3D(im_resized, points) - draw_utils.draw_bbox(im_resized, bbox) - cv.imshow('', im_resized) - display_utils.handle_keyboard() - - #plot_utils.generate_3d_landmark_anim(points, '/home/adam/Downloads/3d.gif') - - results.append(points_flattenend) - - # create DataFrame and save to CSV - file_utils.mkdirs(fp_out) - df = pd.DataFrame.from_dict(results) - df.index.name = 'index' - df.to_csv(fp_out) - - # save script - file_utils.write_text(' '.join(sys.argv), '{}.sh'.format(fp_out))
\ No newline at end of file diff --git a/megapixels/commands/cv/face_pose.py b/megapixels/commands/cv/face_pose.py deleted file mode 100644 index cb7ec56c..00000000 --- a/megapixels/commands/cv/face_pose.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -NB: This only works with the DLIB 68-point landmarks. - -Converts ROIs to pose: yaw, roll, pitch -pitch: looking down or up in yes gesture -roll: tilting head towards shoulder -yaw: twisting head left to right in no gesture - -""" - -""" -TODO -- check compatibility with MTCNN 68 point detector -- improve accuracy by using MTCNN 5-point -- refer to https://github.com/jerryhouuu/Face-Yaw-Roll-Pitch-from-Pose-Estimation-using-OpenCV/ -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', default=None, - help='Override enum input filename CSV') -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='Override enum output filename CSV') -@click.option('-m', '--media', 'opt_dir_media', default=None, - help='Override enum media directory') -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--size', 'opt_size', - type=(int, int), default=(300, 300), - help='Output image size') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('-d', '--display', 'opt_display', is_flag=True, - help='Display image for debugging') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_dir_media, opt_data_store, opt_dataset, opt_size, - opt_slice, opt_force, opt_display): - """Converts ROIs to pose: roll, yaw, pitch""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import dlib # must keep a local reference for dlib - import cv2 as cv - import pandas as pd - - from app.models.bbox import BBox - from app.utils import logger_utils, file_utils, im_utils, display_utils, draw_utils - from app.processors.face_landmarks import Dlib2D_68 - from app.processors.face_pose import FacePoseDLIB - from app.models.data_store import DataStore - - # ------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - - # set data_store - data_store = DataStore(opt_data_store, opt_dataset) - - # get filepath out - fp_out = data_store.metadata(types.Metadata.FACE_POSE) if opt_fp_out is None else opt_fp_out - if not opt_force and Path(fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - # init face processors - face_pose = FacePoseDLIB() - face_landmarks = Dlib2D_68() - - # ------------------------------------------------- - # load data - - fp_record = data_store.metadata(types.Metadata.FILE_RECORD) - df_record = pd.read_csv(fp_record, dtype=cfg.FILE_RECORD_DTYPES).set_index('index') - # load ROI data - fp_roi = data_store.metadata(types.Metadata.FACE_ROI) - df_roi = pd.read_csv(fp_roi).set_index('index') - # slice if you want - if opt_slice: - df_roi = df_roi[opt_slice[0]:opt_slice[1]] - # group by image index (speedup if multiple faces per image) - df_img_groups = df_roi.groupby('record_index') - log.debug('processing {:,} groups'.format(len(df_img_groups))) - - # store poses and convert to DataFrame - results = [] - - # ------------------------------------------------- - # iterate groups with file/record index as key - for record_index, df_img_group in tqdm(df_img_groups): - - # access the file_record - file_record = df_record.iloc[record_index] # pands.DataSeries - - # load image - fp_im = data_store.face(file_record.subdir, file_record.fn, file_record.ext) - im = cv.imread(fp_im) - im_resized = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - - # iterate image group dataframe with roi index as key - for roi_index, df_img in df_img_group.iterrows(): - - # get bbox - x, y, w, h = df_img.x, df_img.y, df_img.w, df_img.h - #dim = (file_record.width, file_record.height) - dim = im_resized.shape[:2][::-1] - bbox_norm = BBox.from_xywh(x, y, w, h) - bbox_dim = bbox_norm.to_dim(dim) - - # get pose - landmarks = face_landmarks.landmarks(im_resized, bbox_norm) - pose_data = face_pose.pose(landmarks, dim) - #pose_degrees = pose_data['degrees'] # only keep the degrees data - #pose_degrees['points_nose'] = pose_data - - # draw landmarks if optioned - if opt_display: - draw_utils.draw_pose(im_resized, pose_data['point_nose'], pose_data['points']) - draw_utils.draw_degrees(im_resized, pose_data) - cv.imshow('', im_resized) - display_utils.handle_keyboard() - - # add image index and append to result CSV data - pose_data['roi_index'] = roi_index - for k, v in pose_data['points'].items(): - pose_data[f'point_{k}_x'] = v[0] / dim[0] - pose_data[f'point_{k}_y'] = v[1] / dim[1] - - # rearrange data structure for DataFrame - pose_data.pop('points') - pose_data['point_nose_x'] = pose_data['point_nose'][0] / dim[0] - pose_data['point_nose_y'] = pose_data['point_nose'][1] / dim[1] - pose_data.pop('point_nose') - results.append(pose_data) - - # create DataFrame and save to CSV - file_utils.mkdirs(fp_out) - df = pd.DataFrame.from_dict(results) - df.index.name = 'index' - df.to_csv(fp_out) - - # save script - file_utils.write_text(' '.join(sys.argv), '{}.sh'.format(fp_out))
\ No newline at end of file diff --git a/megapixels/commands/cv/face_roi.py b/megapixels/commands/cv/face_roi.py deleted file mode 100644 index e83b0f61..00000000 --- a/megapixels/commands/cv/face_roi.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -Crop images to prepare for training -""" - -import click -# from PIL import Image, ImageOps, ImageFilter, ImageDraw - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -color_filters = {'color': 1, 'gray': 2, 'all': 3} - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', default=None, - help='Override enum input filename CSV') -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='Override enum output filename CSV') -@click.option('-m', '--media', 'opt_dir_media', default=None, - help='Override enum media directory') -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--size', 'opt_size', - type=(int, int), default=(480, 480), - help='Output image size') -@click.option('-d', '--detector', 'opt_detector_type', - type=cfg.FaceDetectNetVar, - default=click_utils.get_default(types.FaceDetectNet.CVDNN), - help=click_utils.show_help(types.FaceDetectNet)) -@click.option('-g', '--gpu', 'opt_gpu', default=0, - help='GPU index') -@click.option('--conf', 'opt_conf_thresh', default=0.85, type=click.FloatRange(0,1), - help='Confidence minimum threshold') -@click.option('-p', '--pyramids', 'opt_pyramids', default=0, type=click.IntRange(0,4), - help='Number pyramids to upscale for DLIB detectors') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('--display/--no-display', 'opt_display', is_flag=True, default=False, - help='Display detections to debug') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('--color', 'opt_color_filter', - type=click.Choice(color_filters.keys()), default='all', - help='Filter to keep color or grayscale images (color = keep color') -@click.option('--keep', 'opt_largest', type=click.Choice(['largest', 'all']), default='all', - help='Only keep largest face') -@click.option('--zone', 'opt_zone', default=(0.0, 0.0), type=(float, float), - help='Face center must be located within zone region (0.5 = half width/height)') -@click.pass_context -def cli(ctx, opt_fp_in, opt_dir_media, opt_fp_out, opt_data_store, opt_dataset, opt_size, opt_detector_type, - opt_gpu, opt_conf_thresh, opt_pyramids, opt_slice, opt_display, opt_force, opt_color_filter, - opt_largest, opt_zone): - """Converts frames with faces to CSV of ROIs""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import dlib # must keep a local reference for dlib - import cv2 as cv - import pandas as pd - - from app.utils import logger_utils, file_utils, im_utils, display_utils, draw_utils - from app.processors import face_detector - from app.models.data_store import DataStore - - # ------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - - # set data_store - data_store = DataStore(opt_data_store, opt_dataset) - - # get filepath out - fp_out = data_store.metadata(types.Metadata.FACE_ROI) if opt_fp_out is None else opt_fp_out - if not opt_force and Path(fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - # set detector - if opt_detector_type == types.FaceDetectNet.CVDNN: - detector = face_detector.DetectorCVDNN() - elif opt_detector_type == types.FaceDetectNet.DLIB_CNN: - detector = face_detector.DetectorDLIBCNN(gpu=opt_gpu) - elif opt_detector_type == types.FaceDetectNet.DLIB_HOG: - detector = face_detector.DetectorDLIBHOG() - elif opt_detector_type == types.FaceDetectNet.MTCNN_TF: - detector = face_detector.DetectorMTCNN_TF(gpu=opt_gpu) - elif opt_detector_type == types.FaceDetectNet.HAAR: - log.error('{} not yet implemented'.format(opt_detector_type.name)) - return - - - # get list of files to process - fp_record = data_store.metadata(types.Metadata.FILE_RECORD) if opt_fp_in is None else opt_fp_in - df_record = pd.read_csv(fp_record, dtype=cfg.FILE_RECORD_DTYPES).set_index('index') - if opt_slice: - df_record = df_record[opt_slice[0]:opt_slice[1]] - log.debug('processing {:,} files'.format(len(df_record))) - - # filter out grayscale - color_filter = color_filters[opt_color_filter] - # set largest flag, to keep all or only largest - opt_largest = (opt_largest == 'largest') - - data = [] - skipped_files = [] - processed_files = [] - - for df_record in tqdm(df_record.itertuples(), total=len(df_record)): - fp_im = data_store.face(str(df_record.subdir), str(df_record.fn), str(df_record.ext)) - try: - im = cv.imread(fp_im) - im_resized = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - except Exception as e: - log.debug(f'could not read: {fp_im}') - return - # filter out color or grayscale iamges - if color_filter != color_filters['all']: - try: - is_gray = im_utils.is_grayscale(im) - if is_gray and color_filter != color_filters['gray']: - log.debug('Skipping grayscale image: {}'.format(fp_im)) - continue - except Exception as e: - log.error('Could not check grayscale: {}'.format(fp_im)) - continue - - try: - bboxes_norm = detector.detect(im_resized, pyramids=opt_pyramids, largest=opt_largest, - zone=opt_zone, conf_thresh=opt_conf_thresh) - except Exception as e: - log.error('could not detect: {}'.format(fp_im)) - log.error('{}'.format(e)) - continue - - if len(bboxes_norm) == 0: - skipped_files.append(fp_im) - log.warn(f'no faces in: {fp_im}') - log.warn(f'skipped: {len(skipped_files)}. found:{len(processed_files)} files') - else: - processed_files.append(fp_im) - for bbox in bboxes_norm: - roi = { - 'record_index': int(df_record.Index), - 'x': bbox.x, - 'y': bbox.y, - 'w': bbox.w, - 'h': bbox.h - } - data.append(roi) - - # if display optined - if opt_display and len(bboxes_norm): - # draw each box - for bbox_norm in bboxes_norm: - dim = im_resized.shape[:2][::-1] - bbox_dim = bbox.to_dim(dim) - if dim[0] > 1000: - im_resized = im_utils.resize(im_resized, width=1000) - im_resized = draw_utils.draw_bbox(im_resized, bbox_norm) - - # display and wait - cv.imshow('', im_resized) - display_utils.handle_keyboard() - - # create DataFrame and save to CSV - file_utils.mkdirs(fp_out) - df = pd.DataFrame.from_dict(data) - df.index.name = 'index' - df.to_csv(fp_out) - - # save script - file_utils.write_text(' '.join(sys.argv), '{}.sh'.format(fp_out))
\ No newline at end of file diff --git a/megapixels/commands/cv/face_vector.py b/megapixels/commands/cv/face_vector.py deleted file mode 100644 index cb155d08..00000000 --- a/megapixels/commands/cv/face_vector.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Converts ROIs to face vector -NB: the VGG Face2 extractor should be used with MTCNN ROIs (not square) - the DLIB face extractor should be used with DLIB ROIs (square) -see https://github.com/ox-vgg/vgg_face2 for TAR@FAR -""" - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -@click.command() -@click.option('-o', '--output', 'opt_fp_out', default=None, - help='Override enum output filename CSV') -@click.option('-m', '--media', 'opt_dir_media', default=None, - help='Override enum media directory') -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--size', 'opt_size', - type=(int, int), default=cfg.DEFAULT_SIZE_FACE_DETECT, - help='Output image size') -@click.option('-e', '--extractor', 'opt_extractor', - default=click_utils.get_default(types.FaceExtractor.VGG), - type=cfg.FaceExtractorVar, - help='Type of extractor framework/network to use') -@click.option('-j', '--jitters', 'opt_jitters', default=cfg.DLIB_FACEREC_JITTERS, - help='Number of jitters (only for dlib') -@click.option('-p', '--padding', 'opt_padding', default=cfg.FACEREC_PADDING, - help='Percentage ROI padding') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice list of files') -@click.option('-f', '--force', 'opt_force', is_flag=True, - help='Force overwrite file') -@click.option('-g', '--gpu', 'opt_gpu', default=0, - help='GPU index') -@click.pass_context -def cli(ctx, opt_fp_out, opt_dir_media, opt_data_store, opt_dataset, opt_size, - opt_extractor, opt_slice, opt_force, opt_gpu, opt_jitters, opt_padding): - """Converts face ROIs to vectors""" - - import sys - import os - from os.path import join - from pathlib import Path - from glob import glob - - from tqdm import tqdm - import numpy as np - import dlib # must keep a local reference for dlib - import cv2 as cv - import pandas as pd - - from app.models.bbox import BBox - from app.models.data_store import DataStore - from app.utils import logger_utils, file_utils, im_utils - from app.processors import face_extractor - - - # ------------------------------------------------- - # init here - - log = logger_utils.Logger.getLogger() - # set data_store - data_store = DataStore(opt_data_store, opt_dataset) - - # get filepath out - fp_out = data_store.metadata(types.Metadata.FACE_VECTOR) if opt_fp_out is None else opt_fp_out - if not opt_force and Path(fp_out).exists(): - log.error('File exists. Use "-f / --force" to overwite') - return - - # init face processors - if opt_extractor == types.FaceExtractor.DLIB: - log.debug('set dlib') - extractor = face_extractor.ExtractorDLIB(gpu=opt_gpu, jitters=opt_jitters) - elif opt_extractor == types.FaceExtractor.VGG: - extractor = face_extractor.ExtractorVGG() - - # load data - fp_record = data_store.metadata(types.Metadata.FILE_RECORD) - df_record = pd.read_csv(fp_record, dtype=cfg.FILE_RECORD_DTYPES).set_index('index') - fp_roi = data_store.metadata(types.Metadata.FACE_ROI) - df_roi = pd.read_csv(fp_roi).set_index('index') - - if opt_slice: - df_roi = df_roi[opt_slice[0]:opt_slice[1]] - - # ------------------------------------------------- - # process images - - df_img_groups = df_roi.groupby('record_index') - log.debug('processing {:,} groups'.format(len(df_img_groups))) - - vecs = [] - for record_index, df_img_group in tqdm(df_img_groups): - # make fp - ds_record = df_record.iloc[record_index] - fp_im = data_store.face(ds_record.subdir, ds_record.fn, ds_record.ext) - im = cv.imread(fp_im) - im = im_utils.resize(im, width=opt_size[0], height=opt_size[1]) - for roi_index, df_img in df_img_group.iterrows(): - # get bbox - x, y, w, h = df_img.x, df_img.y, df_img.w, df_img.h - dim = (ds_record.width, ds_record.height) - # get face vector - bbox = BBox.from_xywh(x, y, w, h) # norm - # compute vec - vec = extractor.extract(im, bbox) # use normalized BBox - vec_str = extractor.to_str(vec) - vec_obj = {'vec':vec_str, 'roi_index': roi_index, 'record_index':record_index} - vecs.append(vec_obj) - - # ------------------------------------------------- - # save data - - # create DataFrame and save to CSV - df = pd.DataFrame.from_dict(vecs) - df.index.name = 'index' - file_utils.mkdirs(fp_out) - df.to_csv(fp_out) - - # save script - file_utils.write_text(' '.join(sys.argv), '{}.sh'.format(fp_out))
\ No newline at end of file diff --git a/megapixels/commands/cv/mirror.py b/megapixels/commands/cv/mirror.py deleted file mode 100644 index 9ca1cac7..00000000 --- a/megapixels/commands/cv/mirror.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Crop images to prepare for training -""" - -import click -import cv2 as cv -from PIL import Image, ImageOps, ImageFilter - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - - -@click.command() -@click.option('-i', '--input', 'opt_dir_in', required=True, - help='Input directory') -@click.option('-o', '--output', 'opt_dir_out', required=True, - help='Output directory') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice the input list') -@click.pass_context -def cli(ctx, opt_dir_in, opt_dir_out, opt_slice): - """Mirror augment image directory""" - - import os - from os.path import join - from pathlib import Path - from glob import glob - from tqdm import tqdm - - from app.utils import logger_utils, file_utils, im_utils - - # ------------------------------------------------- - # init - - log = logger_utils.Logger.getLogger() - - # ------------------------------------------------- - # process here - - # get list of files to process - fp_ims = glob(join(opt_dir_in, '*.jpg')) - fp_ims += glob(join(opt_dir_in, '*.png')) - - if opt_slice: - fp_ims = fp_ims[opt_slice[0]:opt_slice[1]] - log.info('processing {:,} files'.format(len(fp_ims))) - - # ensure output dir exists - file_utils.mkdirs(opt_dir_out) - - # resize and save images - for fp_im in tqdm(fp_ims): - im = Image.open(fp_im) - fpp_im = Path(fp_im) - fp_out = join(opt_dir_out, '{}_mirror{}'.format(fpp_im.stem, fpp_im.suffix)) - im.save(fp_out)
\ No newline at end of file diff --git a/megapixels/commands/cv/resize.py b/megapixels/commands/cv/resize.py deleted file mode 100644 index 7409ee6f..00000000 --- a/megapixels/commands/cv/resize.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Crop images to prepare for training -""" - -import click -import cv2 as cv -from PIL import Image, ImageOps, ImageFilter - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -""" -Filter Q-Down Q-Up Speed -NEAREST ⭐⭐⭐⭐⭐ -BOX ⭐ ⭐⭐⭐⭐ -BILINEAR ⭐ ⭐ ⭐⭐⭐ -HAMMING ⭐⭐ ⭐⭐⭐ -BICUBIC ⭐⭐⭐ ⭐⭐⭐ ⭐⭐ -LANCZOS ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐ -""" -methods = { - 'lanczos': Image.LANCZOS, - 'bicubic': Image.BICUBIC, - 'hamming': Image.HAMMING, - 'bileaner': Image.BILINEAR, - 'box': Image.BOX, - 'nearest': Image.NEAREST - } -centerings = { - 'tl': (0.0, 0.0), - 'tc': (0.5, 0.0), - 'tr': (0.0, 0.0), - 'lc': (0.0, 0.5), - 'cc': (0.5, 0.5), - 'rc': (1.0, 0.5), - 'bl': (0.0, 1.0), - 'bc': (1.0, 0.5), - 'br': (1.0, 1.0) -} - -@click.command() -@click.option('-i', '--input', 'opt_dir_in', required=True, - help='Input directory') -@click.option('-o', '--output', 'opt_dir_out', required=True, - help='Output directory') -@click.option('-e', '--ext', 'opt_glob_ext', - default='png', type=click.Choice(['jpg', 'png']), - help='File glob ext') -@click.option('--size', 'opt_size', - type=(int, int), default=(256, 256), - help='Max output size') -@click.option('--method', 'opt_scale_method', - type=click.Choice(methods.keys()), - default='lanczos', - help='Scaling method to use') -@click.option('--equalize', 'opt_equalize', is_flag=True, - help='Equalize historgram') -@click.option('--sharpen', 'opt_sharpen', is_flag=True, - help='Unsharp mask') -@click.option('--center', 'opt_center', default='cc', type=click.Choice(centerings.keys()), - help='Crop focal point') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice the input list') -@click.option('-t', '--threads', 'opt_threads', default=8, - help='Number of threads') -@click.pass_context -def cli(ctx, opt_dir_in, opt_dir_out, opt_glob_ext, opt_size, opt_scale_method, - opt_equalize, opt_sharpen, opt_center, opt_slice, opt_threads): - """Crop, mirror images""" - - import os - from os.path import join - from pathlib import Path - from glob import glob - from tqdm import tqdm - from multiprocessing.dummy import Pool as ThreadPool - from functools import partial - - from app.utils import logger_utils, file_utils, im_utils - - # ------------------------------------------------- - # init - - log = logger_utils.Logger.getLogger() - - - # ------------------------------------------------- - # process here - - def pool_resize(fp_im, opt_size, scale_method): - # Threaded image resize function - try: - pbar.update(1) - try: - im = Image.open(fp_im).convert('RGB') - im.verify() - except Exception as e: - log.warn('Could not open: {}'.format(fp_im)) - log.error(e) - return False - - #im = ImageOps.fit(im, opt_size, method=scale_method, centering=centering) - - if opt_equalize: - im_np = im_utils.pil2np(im) - im_np_eq = eq_hist_yuv(im_np) - im_np = cv.addWeighted(im_np_eq, 0.35, im_np, 0.65, 0) - im = im_utils.np2pil(im_np) - - if opt_sharpen: - im = im.filter(ImageFilter.UnsharpMask) - - fp_out = join(opt_dir_out, Path(fp_im).name) - im.save(fp_out) - return True - except: - return False - - #centering = centerings[opt_center] - #scale_method = methods[opt_scale_method] - - # get list of files to process - fp_ims = glob(join(opt_dir_in, '*.{}'.format(opt_glob_ext))) - if opt_slice: - fp_ims = fp_ims[opt_slice[0]:opt_slice[1]] - log.info('processing {:,} files'.format(len(fp_ims))) - - - # ensure output dir exists - file_utils.mkdirs(opt_dir_out) - - # setup multithreading - pbar = tqdm(total=len(fp_ims)) - #pool_resize = partial(pool_resize, opt_size=opt_size, scale_method=scale_method, centering=centering) - pool_resize = partial(pool_resize, opt_size=opt_size) - #result_list = pool.map(prod_x, data_list) - pool = ThreadPool(opt_threads) - with tqdm(total=len(fp_ims)) as pbar: - results = pool.map(pool_resize, fp_ims) - pbar.close() - - log.info('Resized: {} / {} images'.format(results.count(True), len(fp_ims))) - - - -def eq_hist_yuv(im): - im_yuv = cv.cvtColor(im, cv.COLOR_BGR2YUV) - im_yuv[:,:,0] = cv.equalizeHist(im_yuv[:,:,0]) - return cv.cvtColor(im_yuv, cv.COLOR_YUV2BGR) diff --git a/megapixels/commands/cv/resize_dataset.py b/megapixels/commands/cv/resize_dataset.py deleted file mode 100644 index 3a6ec15f..00000000 --- a/megapixels/commands/cv/resize_dataset.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -Crop images to prepare for training -""" - -import click -import cv2 as cv -from PIL import Image, ImageOps, ImageFilter - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg - -cv_resize_algos = { - 'area': cv.INTER_AREA, - 'lanco': cv.INTER_LANCZOS4, - 'linear': cv.INTER_LINEAR, - 'linear_exact': cv.INTER_LINEAR_EXACT, - 'nearest': cv.INTER_NEAREST -} -""" -Filter Q-Down Q-Up Speed -NEAREST ⭐⭐⭐⭐⭐ -BOX ⭐ ⭐⭐⭐⭐ -BILINEAR ⭐ ⭐ ⭐⭐⭐ -HAMMING ⭐⭐ ⭐⭐⭐ -BICUBIC ⭐⭐⭐ ⭐⭐⭐ ⭐⭐ -LANCZOS ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐ -""" -pil_resize_algos = { - 'antialias': Image.ANTIALIAS, - 'lanczos': Image.LANCZOS, - 'bicubic': Image.BICUBIC, - 'hamming': Image.HAMMING, - 'bileaner': Image.BILINEAR, - 'box': Image.BOX, - 'nearest': Image.NEAREST - } - -@click.command() -@click.option('--dataset', 'opt_dataset', - type=cfg.DatasetVar, - required=True, - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('--store', 'opt_data_store', - type=cfg.DataStoreVar, - default=click_utils.get_default(types.DataStore.HDD), - show_default=True, - help=click_utils.show_help(types.Dataset)) -@click.option('-o', '--output', 'opt_dir_out', required=True, - help='Output directory') -@click.option('-e', '--ext', 'opt_glob_ext', - default='png', type=click.Choice(['jpg', 'png']), - help='File glob ext') -@click.option('--size', 'opt_size', - type=(int, int), default=(256, 256), - help='Output image size max (w,h)') -@click.option('--interp', 'opt_interp_algo', - type=click.Choice(pil_resize_algos.keys()), - default='bicubic', - help='Interpolation resizing algorithms') -@click.option('--slice', 'opt_slice', type=(int, int), default=(None, None), - help='Slice the input list') -@click.option('-t', '--threads', 'opt_threads', default=8, - help='Number of threads') -@click.option('--recursive/--no-recursive', 'opt_recursive', is_flag=True, default=False, - help='Use glob recursion (slower)') -@click.pass_context -def cli(ctx, opt_dataset, opt_data_store, opt_dir_out, opt_glob_ext, opt_size, opt_interp_algo, - opt_slice, opt_threads, opt_recursive): - """Resize dataset images""" - - import os - from os.path import join - from pathlib import Path - from glob import glob - from tqdm import tqdm - from multiprocessing.dummy import Pool as ThreadPool - from functools import partial - import pandas as pd - import numpy as np - - from app.utils import logger_utils, file_utils, im_utils - from app.models.data_store import DataStore - - # ------------------------------------------------- - # init - - log = logger_utils.Logger.getLogger() - - - # ------------------------------------------------- - # process here - - def pool_resize(fp_in, dir_in, dir_out, im_size, interp_algo): - # Threaded image resize function - pbar.update(1) - try: - im = Image.open(fp_in).convert('RGB') - im.verify() # throws error if image is corrupt - im.thumbnail(im_size, interp_algo) - fp_out = fp_in.replace(dir_in, dir_out) - file_utils.mkdirs(fp_out) - im.save(fp_out, quality=100) - except Exception as e: - log.warn(f'Could not open: {fp_in}, Error: {e}') - return False - return True - - - data_store = DataStore(opt_data_store, opt_dataset) - fp_records = data_store.metadata(types.Metadata.FILE_RECORD) - df_records = pd.read_csv(fp_records, dtype=cfg.FILE_RECORD_DTYPES).set_index('index') - dir_in = data_store.media_images_original() - - # get list of files to process - #fp_ims = file_utils.glob_multi(opt_dir_in, ['jpg', 'png'], recursive=opt_recursive) - fp_ims = [] - for ds_record in df_records.itertuples(): - fp_im = data_store.face(ds_record.subdir, ds_record.fn, ds_record.ext) - fp_ims.append(fp_im) - - if opt_slice: - fp_ims = fp_ims[opt_slice[0]:opt_slice[1]] - if not fp_ims: - log.error('No images. Try with "--recursive"') - return - log.info(f'processing {len(fp_ims):,} images') - - # algorithm to use for resizing - interp_algo = pil_resize_algos[opt_interp_algo] - log.info(f'using {interp_algo} for interpoloation') - - # ensure output dir exists - file_utils.mkdirs(opt_dir_out) - - # setup multithreading - pbar = tqdm(total=len(fp_ims)) - # fixed arguments for pool function - map_pool_resize = partial(pool_resize, dir_in=dir_in, dir_out=opt_dir_out, im_size=opt_size, interp_algo=interp_algo) - #result_list = pool.map(prod_x, data_list) # simple - pool = ThreadPool(opt_threads) - # start multithreading - with tqdm(total=len(fp_ims)) as pbar: - results = pool.map(map_pool_resize, fp_ims) - # end multithreading - pbar.close() - - log.info(f'Resized: {results.count(True)} / {len(fp_ims)} images')
\ No newline at end of file diff --git a/megapixels/commands/cv/videos_to_frames.py b/megapixels/commands/cv/videos_to_frames.py deleted file mode 100644 index 0b56c46a..00000000 --- a/megapixels/commands/cv/videos_to_frames.py +++ /dev/null @@ -1,73 +0,0 @@ -from glob import glob -import os -from os.path import join -from pathlib import Path - -import click - -from app.settings import types -from app.utils import click_utils -from app.settings import app_cfg as cfg -from app.utils import logger_utils - -import dlib -import pandas as pd -from PIL import Image, ImageOps, ImageFilter -from app.utils import file_utils, im_utils - - -log = logger_utils.Logger.getLogger() - -@click.command() -@click.option('-i', '--input', 'opt_fp_in', required=True, - help='Input directory') -@click.option('-o', '--output', 'opt_fp_out', required=True, - help='Output directory') -@click.option('--size', 'opt_size', default=(320, 240), - help='Inference size for face detection' ) -@click.option('--interval', 'opt_frame_interval', default=20, - help='Number of frames before saving next face') -@click.pass_context -def cli(ctx, opt_fp_in, opt_fp_out, opt_size, opt_frame_interval): - """Converts videos to frames with faces""" - - # ------------------------------------------------- - # process - - from tqdm import tqdm - import cv2 as cv - from tqdm import tqdm - from app.processors import face_detector - - detector = face_detector.DetectorDLIBCNN() - - # get file list - fp_videos = glob(join(opt_fp_in, '*.mp4')) - fp_videos += glob(join(opt_fp_in, '*.webm')) - fp_videos += glob(join(opt_fp_in, '*.mkv')) - - frame_interval_count = 0 - frame_count = 0 - - file_utils.mkdirs(opt_fp_out) - - for fp_video in tqdm(fp_videos): - - video = cv.VideoCapture(fp_video) - - while video.isOpened(): - res, frame = video.read() - if not res: - break - - frame_count += 1 # for naming - frame_interval_count += 1 # for interval - - bboxes = detector.detect(frame, opt_size=opt_size, opt_pyramids=0) - if len(bboxes) > 0 and frame_interval_count >= opt_frame_interval: - # save frame - fname = file_utils.zpad(frame_count) - fp_frame = join(opt_fp_out, '{}_{}.jpg'.format(Path(fp_video).stem, fname)) - cv.imwrite(fp_frame, frame) - frame_interval_count = 0 - |
