diff --git a/README.md b/README.md index 4423f13..47a1ee4 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ usage: run.py [-h] -i INPUT [-ow WIDTH] [-oh HEIGHT] [-e EXT [EXT ...]] -nc, --no-caching Ignores .ff file cache if specified -wt WINDOWTIME, --window-time WINDOWTIME Duration of each frame in debug window + -t TEMPLATE, --template TEMPLATE + Template input image to set as the main face shape + rather than the total average ``` diff --git a/run.py b/run.py index d96ab53..fa68fe9 100644 --- a/run.py +++ b/run.py @@ -19,10 +19,11 @@ parser.add_argument('-nw', '--no-warps', dest="noWarps", help="Hides warping stage if specified", action="store_true", default=False) parser.add_argument('-nc', '--no-caching', dest="noCaching", help="Ignores .ff file cache if specified", action="store_true", default=False) parser.add_argument('-wt', '--window-time', dest="windowTime", help="Duration of each frame in debug window", type=int, default=500) + parser.add_argument('-t', '--template', dest="template", help="Template input image to set as the main face shape rather than the total average", type=str, default=None) options = parser.parse_args() print(options) ext = options.ext or ["*.jpg", "*.jpeg"] - Averager(width=options.width, height=options.height).run(path=options.input, ext=ext, window=options.window, showWarps=not options.noWarps, windowTime=options.windowTime, useCaching=not options.noCaching).save(name=options.output) + Averager(width=options.width, height=options.height).run(path=options.input, ext=ext, window=options.window, showWarps=not options.noWarps, windowTime=options.windowTime, useCaching=not options.noCaching, template=options.template).save(name=options.output) print(f">>> Executed in {time.time()-start:.2f} seconds") diff --git a/src/faceAverage.py b/src/faceAverage.py index 43c9e02..2d8204b 100644 --- a/src/faceAverage.py +++ b/src/faceAverage.py @@ -17,6 +17,11 @@ def __init__(self, width=600, height=800): self.width = width self.height = height self.detective = Detective() + self.params = { + 'eyeDistance' : 0.3, + 'eyeRatioY' : 2.5 + } + def loadImages(self, detections): pbar = tqdm(range(len(detections))) @@ -28,17 +33,36 @@ def loadImages(self, detections): return [np.float32(im['img'])/255.0 for im in detections] - def run(self, path, ext=['*.jpg','*.jpeg'], window=False, windowTime=500, showWarps=False, useCaching=True): - + def run(self, path, ext=['*.jpg','*.jpeg'], window=False, windowTime=500, showWarps=False, useCaching=True, template=None): + self.windowTime = windowTime self.inputpath = path - self.images = self.detective.getImages(path, ext=ext).features(useCaching=useCaching).detections + self.images = self.detective.getImages(path, ext=ext, template=template).features(useCaching=useCaching).detections w, h = self.width, self.height allPoints = [im['shape'] for im in self.images] images = self.loadImages(self.images) - # Eye corners - eyecornerDst = [ (np.int(0.38 * w ), np.int(h / 2.5)), (np.int(0.62 * w ), np.int(h / 2.5)) ] + # Place a given template in a correct position on canvas + if template != None: + imEyeDistX = allPoints[0][45][0] - allPoints[0][36][0] + scale = (w*self.params['eyeDistance']) / imEyeDistX + allPoints[0] = np.multiply(allPoints[0], scale).astype(np.int) + images[0] = (cv2.resize(images[0], (0,0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC).astype(np.float64) * 255).astype(np.uint8) + imEyeMid = ((allPoints[0][45][0] + allPoints[0][36][0])//2, (allPoints[0][45][1] + allPoints[0][36][1])//2) + x_start = np.int(w/2 - imEyeMid[0]) + y_start = np.int(h/self.params['eyeRatioY'] - imEyeMid[1]) + allPoints[0] += (x_start, y_start) + canvas = Image.fromarray(np.zeros((h, w, 3), images[0].dtype)) + canvas.paste(Image.fromarray(images[0]), (x_start, y_start)) + canvas = np.array(canvas).astype(np.float32)/255 + canvas = cv2.medianBlur(canvas, 3) + images[0] = canvas + imEyeDistX = allPoints[0][45][0] - allPoints[0][36][0] + imEyeDistY = allPoints[0][45][1] - allPoints[0][36][1] + eyecornerDst = [ (np.int(w/2 - imEyeDistX/2), np.int(h / self.params['eyeRatioY'])), (np.int(w/2 + imEyeDistX/2), np.int(h / self.params['eyeRatioY']) + imEyeDistY), ] + + else: + eyecornerDst = [ (np.int(w/2 - w*(self.params['eyeDistance']/2)), np.int(h / self.params['eyeRatioY'])), (np.int(w/2 + w*(self.params['eyeDistance']/2) ), np.int(h / self.params['eyeRatioY'])) ] imagesNorm = [] pointsNorm = [] @@ -47,7 +71,15 @@ def run(self, path, ext=['*.jpg','*.jpeg'], window=False, windowTime=500, showWa boundaryPts = np.array([(0,0), (w/2,0), (w-1,0), (w-1,h/2), ( w-1, h-1 ), ( w/2, h-1 ), (0, h-1), (0,h/2) ]) # Initialize location of average points to 0s - pointsAvg = np.array([(0,0)]* ( len(allPoints[0]) + len(boundaryPts) ), np.float32()) + if template != None: + points1 = allPoints[0] + tform = self.similarityTransform(eyecornerDst, eyecornerDst) + points2 = np.reshape(np.array(points1), (68,1,2)) + points = cv2.transform(points2, tform) + pointsAvg = np.float32(np.reshape(points, (68, 2))) + pointsAvg = np.append(pointsAvg, boundaryPts, axis=0) + else: + pointsAvg = np.array([(0,0)]* ( len(allPoints[0]) + len(boundaryPts) ), np.float32()) n = len(allPoints[0]) @@ -81,7 +113,8 @@ def run(self, path, ext=['*.jpg','*.jpeg'], window=False, windowTime=500, showWa points = np.append(points, boundaryPts, axis=0) # Calculate location of average landmark points. - pointsAvg = pointsAvg + points / numImages + if template == None: + pointsAvg = pointsAvg + points / numImages pointsNorm.append(points) imagesNorm.append(img) @@ -132,6 +165,10 @@ def run(self, path, ext=['*.jpg','*.jpeg'], window=False, windowTime=500, showWa if window: oldImg = cv2.cvtColor(imagesNorm[i], cv2.COLOR_BGR2RGB) + + for j, point in enumerate(pointsNorm[i]): + cv2.putText(oldImg, str(j), (int(point[0]), int(point[1])), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 2) + resultImg = cv2.cvtColor(output / (i+1), cv2.COLOR_BGR2RGB) theimg = np.hstack((resultImg, oldImg)) cv2.imshow('Face Average', theimg) @@ -157,7 +194,7 @@ def run(self, path, ext=['*.jpg','*.jpeg'], window=False, windowTime=500, showWa if window: cv2.resizeWindow('Face Average', w, h) cv2.imshow('Face Average', output) - cv2.waitKey(10000) + cv2.waitKey(self.windowTime*2) self.result = output * 255 diff --git a/src/faceFeaturesDetector.py b/src/faceFeaturesDetector.py index 7aa4629..328ef33 100644 --- a/src/faceFeaturesDetector.py +++ b/src/faceFeaturesDetector.py @@ -13,10 +13,15 @@ def __init__(self, predictor_path="src/shape_predictor_68_face_landmarks.dat"): self.predictor = dlib.shape_predictor(predictor_path) - def getImages(self, path, ext=["*jpg"]): + def getImages(self, path, ext=["*jpg"], template=None): self.files = [] for e in ext: self.files.extend(glob.glob(os.path.join(path, e))) + if template != None: + index = [i for i, s in enumerate(self.files) if template in s] + assert index != [], "> Template '{t}' name was not found".format(t=template) + index = index[0] + self.files = [self.files[index]] + self.files[0:index] + self.files[index+1:] return self # returns image, d