Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
georgegach committed Jun 23, 2018
0 parents commit 3f246a1
Show file tree
Hide file tree
Showing 5 changed files with 429 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
datasets
results
*/__pycache__
28 changes: 28 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
import sys
import time
from argparse import ArgumentParser
sys.path.append("./src")
from src/faceAverage import Averager


if __name__ == '__main__' :
start = time.time()

parser = ArgumentParser()
parser.add_argument('-i', '--input-dir', dest="input", help="Specify the input directory", type=str, required=True)
parser.add_argument('-ow', '--output-width', dest="width", help="Specify the output file width. Default 300", type=int)
parser.add_argument('-oh', '--output-height', dest="height", help="Specify the output file height. Default 400", type=int)
parser.add_argument('-e', '--extensions', dest="ext", help="Specify the file extensions like *.jpg *.whatevs.file", nargs="+")
parser.add_argument('-o', '--output-path', dest="output", help="Specify the output path for writing result. Default ./results/[input-dir-name].jpg", type=str)
parser.add_argument('-w', '--window', dest="window", help="Shows window if set to True", action="store_true", default=False)
parser.add_argument('-nw', '--no-warps', dest="noWarps", help="Shows warping stage if set to True", action="store_true", default=False)
parser.add_argument('-nc', '--no-caching', dest="noCaching", help="Load shape features from cache of .txt files", action="store_true", default=False)
parser.add_argument('-wt', '--window-time', dest="windowTime", help="Specify how long should window stay on in miliseconds", type=int, default=500)

options = parser.parse_args()
print(options)
ext = options.ext or ["*.jpg", "*.jpeg"]
Averager().run(path=options.input, ext=ext, window=options.window, showWarps=not options.noWarps, windowTime=options.windowTime, useCaching=not options.noCaching).save(name=options.output)

print(f">>> Executed in {time.time()-start:.2f} seconds")
303 changes: 303 additions & 0 deletions src/faceAverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import os
import cv2
import numpy as np
import math
from PIL import Image
import sys
from faceFeaturesDetector import Detective
from tqdm import tqdm
import dlib
import time
import matplotlib.pyplot as plt
from skimage import io

class Averager(object):

def __init__(self, width=300, height=400):
self.width = width
self.height = height
self.detective = Detective()

def loadImages(self, detections):
pbar = tqdm(range(len(detections)))
for i in pbar:
pbar.set_description(f"Loading: ...{detections[i]['imgPath'][-22:]}")
if detections[i]['img'] is None:
detections[i]['img'] = io.imread(detections[i]['imgPath'])

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):


self.folder = os.path.basename(path)
self.images = self.detective.getImages(path, ext=ext).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.3 * w ), np.int(h / 3)), (np.int(0.7 * w ), np.int(h / 3)) ]

imagesNorm = []
pointsNorm = []

# Add boundary points for delaunay triangulation
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())

n = len(allPoints[0])

numImages = len(images)

# Warp images and trasnform landmarks to output coordinate system,
# and find average of transformed landmarks.
pbar = tqdm(images)
for i, _ in enumerate(pbar):
pbar.set_description(f"Warping: ...{self.images[i]['imgPath'][-22:]}")
points1 = allPoints[i]

# Corners of the eye in input image
eyecornerSrc = [ allPoints[i][36], allPoints[i][45] ]

# Compute similarity transform
tform = self.similarityTransform(eyecornerSrc, eyecornerDst)


# Apply similarity transformation
img = cv2.warpAffine(images[i], tform, (w,h))

# Apply similarity transform on points
points2 = np.reshape(np.array(points1), (68,1,2))

points = cv2.transform(points2, tform)

points = np.float32(np.reshape(points, (68, 2)))

# Append boundary points. Will be used in Delaunay Triangulation
points = np.append(points, boundaryPts, axis=0)

# Calculate location of average landmark points.
pointsAvg = pointsAvg + points / numImages

pointsNorm.append(points)
imagesNorm.append(img)



# Delaunay triangulation
rect = (0, 0, w, h)
dt = self.calculateDelaunayTriangles(rect, np.array(pointsAvg))

# Output image
output = np.zeros((h,w,3), np.float32())


if window:
if showWarps:
realImgWaitTime = int(windowTime * 0.25)
warpImgWaitTime = windowTime - realImgWaitTime
else:
realImgWaitTime = windowTime


cv2.namedWindow('Face Average', cv2.WINDOW_NORMAL)
cv2.resizeWindow('Face Average', w*2, h)
cv2.waitKey(1000)
# win = dlib.image_window()

# Warp input images to average image landmarks
for i in range(0, len(imagesNorm)) :
img = np.zeros((h,w,3), np.float32())
# Transform triangles one by one
for j in range(0, len(dt)) :
tin = []
tout = []

for k in range(0, 3) :
pIn = pointsNorm[i][dt[j][k]]
pIn = self.constrainPoint(pIn, w, h)

pOut = pointsAvg[dt[j][k]]
pOut = self.constrainPoint(pOut, w, h)

tin.append(pIn)
tout.append(pOut)


self.warpTriangle(imagesNorm[i], img, tin, tout)

if window:
oldImg = cv2.cvtColor(imagesNorm[i], cv2.COLOR_BGR2RGB)
resultImg = cv2.cvtColor(output / (i+1), cv2.COLOR_BGR2RGB)
theimg = np.hstack((resultImg, oldImg))
cv2.imshow('Face Average', theimg)
cv2.waitKey(realImgWaitTime)
if showWarps:
newImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
theimg = np.hstack((resultImg, newImg))
cv2.imshow('Face Average', theimg)
cv2.waitKey(warpImgWaitTime)




# Add image intensities for averaging
output = output + img


# Divide by numImages to get average
output = output / numImages
output = cv2.cvtColor(output, cv2.COLOR_BGR2RGB)

# Display result
if window:
cv2.resizeWindow('Face Average', w, h)
cv2.imshow('Face Average', output)
cv2.waitKey(10000)

self.result = output * 255


return self

def save(self, name=None):
if name is None:
name = './results/' + self.folder + '.jpg'

cv2.imwrite(name, self.result)
return self


def similarityTransform(self, inPoints, outPoints) :
s60 = math.sin(60*math.pi/180)
c60 = math.cos(60*math.pi/180)

inPts = np.copy(inPoints).tolist()
outPts = np.copy(outPoints).tolist()

xin = c60*(inPts[0][0] - inPts[1][0]) - s60*(inPts[0][1] - inPts[1][1]) + inPts[1][0]
yin = s60*(inPts[0][0] - inPts[1][0]) + c60*(inPts[0][1] - inPts[1][1]) + inPts[1][1]

inPts.append([np.int(xin), np.int(yin)])

xout = c60*(outPts[0][0] - outPts[1][0]) - s60*(outPts[0][1] - outPts[1][1]) + outPts[1][0]
yout = s60*(outPts[0][0] - outPts[1][0]) + c60*(outPts[0][1] - outPts[1][1]) + outPts[1][1]

outPts.append([np.int(xout), np.int(yout)])

tform = cv2.estimateRigidTransform(np.array([inPts]), np.array([outPts]), False)

return tform


# Check if a point is inside a rectangle
def rectContains(self, rect, point) :
if point[0] < rect[0] :
return False
elif point[1] < rect[1] :
return False
elif point[0] > rect[2] :
return False
elif point[1] > rect[3] :
return False
return True

# Calculate delanauy triangle
def calculateDelaunayTriangles(self, rect, points):
# Create subdiv
subdiv = cv2.Subdiv2D(rect)

# Insert points into subdiv
for p in points:
subdiv.insert((p[0], p[1]))


# List of triangles. Each triangle is a list of 3 points ( 6 numbers )
triangleList = subdiv.getTriangleList()

# Find the indices of triangles in the points array

delaunayTri = []

for t in triangleList:
pt = []
pt.append((t[0], t[1]))
pt.append((t[2], t[3]))
pt.append((t[4], t[5]))

pt1 = (t[0], t[1])
pt2 = (t[2], t[3])
pt3 = (t[4], t[5])

if self.rectContains(rect, pt1) and self.rectContains(rect, pt2) and self.rectContains(rect, pt3):
ind = []
for j in range(0, 3):
for k in range(0, len(points)):
if(abs(pt[j][0] - points[k][0]) < 1.0 and abs(pt[j][1] - points[k][1]) < 1.0):
ind.append(k)
if len(ind) == 3:
delaunayTri.append((ind[0], ind[1], ind[2]))



return delaunayTri


def constrainPoint(self, p, w, h) :
p = ( min( max( p[0], 0 ) , w - 1 ) , min( max( p[1], 0 ) , h - 1 ) )
return p

# Apply affine transform calculated using srcTri and dstTri to src and
# output an image of size.
def applyAffineTransform(self, src, srcTri, dstTri, size) :

# Given a pair of triangles, find the affine transform.
warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) )

# Apply the Affine Transform just found to the src image
dst = cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )

return dst


# Warps and alpha blends triangular regions from img1 and img2 to img
def warpTriangle(self, img1, img2, t1, t2) :

# Find bounding rectangle for each triangle
r1 = cv2.boundingRect(np.float32([t1]))
r2 = cv2.boundingRect(np.float32([t2]))

# Offset points by left top corner of the respective rectangles
t1Rect = []
t2Rect = []
t2RectInt = []

for i in range(0, 3):
t1Rect.append(((t1[i][0] - r1[0]),(t1[i][1] - r1[1])))
t2Rect.append(((t2[i][0] - r2[0]),(t2[i][1] - r2[1])))
t2RectInt.append(((t2[i][0] - r2[0]),(t2[i][1] - r2[1])))


# Get mask by filling triangle
mask = np.zeros((r2[3], r2[2], 3), dtype = np.float32)
cv2.fillConvexPoly(mask, np.int32(t2RectInt), (1.0, 1.0, 1.0), 16, 0)

# Apply warpImage to small rectangular patches
img1Rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]

size = (r2[2], r2[3])

img2Rect = self.applyAffineTransform(img1Rect, t1Rect, t2Rect, size)

img2Rect = img2Rect * mask

# Copy triangular region of the rectangular patch to the output image
img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] * ( (1.0, 1.0, 1.0) - mask )

img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] + img2Rect

Loading

0 comments on commit 3f246a1

Please sign in to comment.