| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- """ Face Cluster """
- import tensorflow as tf
- import numpy as np
- import importlib
- import argparse
- import facenet
- import os
- import math
- def face_distance(face_encodings, face_to_compare):
- """
- Given a list of face encodings, compare them to a known face encoding and get a euclidean distance
- for each comparison face. The distance tells you how similar the faces are.
- :param faces: List of face encodings to compare
- :param face_to_compare: A face encoding to compare against
- :return: A numpy ndarray with the distance for each face in the same order as the 'faces' array
- """
- import numpy as np
- if len(face_encodings) == 0:
- return np.empty((0))
- #return 1/np.linalg.norm(face_encodings - face_to_compare, axis=1)
- return np.sum(face_encodings*face_to_compare,axis=1)
- def load_model(model_dir, meta_file, ckpt_file):
- model_dir_exp = os.path.expanduser(model_dir)
- saver = tf.train.import_meta_graph(os.path.join(model_dir_exp, meta_file))
- saver.restore(tf.get_default_session(), os.path.join(model_dir_exp, ckpt_file))
- def _chinese_whispers(encoding_list, threshold=0.55, iterations=20):
- """ Chinese Whispers Algorithm
- Modified from Alex Loveless' implementation,
- http://alexloveless.co.uk/data/chinese-whispers-graph-clustering-in-python/
- Inputs:
- encoding_list: a list of facial encodings from face_recognition
- threshold: facial match threshold,default 0.6
- iterations: since chinese whispers is an iterative algorithm, number of times to iterate
- Outputs:
- sorted_clusters: a list of clusters, a cluster being a list of imagepaths,
- sorted by largest cluster to smallest
- """
- #from face_recognition.api import _face_distance
- from random import shuffle
- import networkx as nx
- # Create graph
- nodes = []
- edges = []
- image_paths, encodings = zip(*encoding_list)
- if len(encodings) <= 1:
- print ("No enough encodings to cluster!")
- return []
- for idx, face_encoding_to_check in enumerate(encodings):
- # Adding node of facial encoding
- node_id = idx+1
- # Initialize 'cluster' to unique value (cluster of itself)
- node = (node_id, {'cluster': image_paths[idx], 'path': image_paths[idx]})
- nodes.append(node)
- # Facial encodings to compare
- if (idx+1) >= len(encodings):
- # Node is last element, don't create edge
- break
- compare_encodings = encodings[idx+1:]
- distances = face_distance(compare_encodings, face_encoding_to_check)
- encoding_edges = []
- for i, distance in enumerate(distances):
- if distance > threshold:
- # Add edge if facial match
- edge_id = idx+i+2
- encoding_edges.append((node_id, edge_id, {'weight': distance}))
- edges = edges + encoding_edges
- G = nx.Graph()
- G.add_nodes_from(nodes)
- G.add_edges_from(edges)
- # Iterate
- for _ in range(0, iterations):
- cluster_nodes = G.nodes()
- shuffle(cluster_nodes)
- for node in cluster_nodes:
- neighbors = G[node]
- clusters = {}
- for ne in neighbors:
- if isinstance(ne, int):
- if G.node[ne]['cluster'] in clusters:
- clusters[G.node[ne]['cluster']] += G[node][ne]['weight']
- else:
- clusters[G.node[ne]['cluster']] = G[node][ne]['weight']
- # find the class with the highest edge weight sum
- edge_weight_sum = 0
- max_cluster = 0
- #use the max sum of neighbor weights class as current node's class
- for cluster in clusters:
- if clusters[cluster] > edge_weight_sum:
- edge_weight_sum = clusters[cluster]
- max_cluster = cluster
- # set the class of target node to the winning local class
- G.node[node]['cluster'] = max_cluster
- clusters = {}
- # Prepare cluster output
- for (_, data) in G.node.items():
- cluster = data['cluster']
- path = data['path']
- if cluster:
- if cluster not in clusters:
- clusters[cluster] = []
- clusters[cluster].append(path)
- # Sort cluster output
- sorted_clusters = sorted(clusters.values(), key=len, reverse=True)
- return sorted_clusters
- def cluster_facial_encodings(facial_encodings):
- """ Cluster facial encodings
- Intended to be an optional switch for different clustering algorithms, as of right now
- only chinese whispers is available.
- Input:
- facial_encodings: (image_path, facial_encoding) dictionary of facial encodings
- Output:
- sorted_clusters: a list of clusters, a cluster being a list of imagepaths,
- sorted by largest cluster to smallest
- """
- if len(facial_encodings) <= 1:
- print ("Number of facial encodings must be greater than one, can't cluster")
- return []
- # Only use the chinese whispers algorithm for now
- sorted_clusters = _chinese_whispers(facial_encodings.items())
- return sorted_clusters
- def compute_facial_encodings(sess,images_placeholder,embeddings,phase_train_placeholder,image_size,
- embedding_size,nrof_images,nrof_batches,emb_array,batch_size,paths):
- """ Compute Facial Encodings
- Given a set of images, compute the facial encodings of each face detected in the images and
- return them. If no faces, or more than one face found, return nothing for that image.
- Inputs:
- image_paths: a list of image paths
- Outputs:
- facial_encodings: (image_path, facial_encoding) dictionary of facial encodings
- """
- for i in range(nrof_batches):
- start_index = i*batch_size
- end_index = min((i+1)*batch_size, nrof_images)
- paths_batch = paths[start_index:end_index]
- images = facenet.load_data(paths_batch, False, False, image_size)
- feed_dict = { images_placeholder:images, phase_train_placeholder:False }
- emb_array[start_index:end_index,:] = sess.run(embeddings, feed_dict=feed_dict)
- facial_encodings = {}
- for x in range(nrof_images):
- facial_encodings[paths[x]] = emb_array[x,:]
- return facial_encodings
- def get_onedir(paths):
- dataset = []
- path_exp = os.path.expanduser(paths)
- if os.path.isdir(path_exp):
- images = os.listdir(path_exp)
- image_paths = [os.path.join(path_exp,img) for img in images]
- for x in image_paths:
- if os.path.getsize(x)>0:
- dataset.append(x)
-
- return dataset
- def main(args):
- """ Main
- Given a list of images, save out facial encoding data files and copy
- images into folders of face clusters.
- """
- from os.path import join, basename, exists
- from os import makedirs
- import numpy as np
- import shutil
- import sys
- if not exists(args.output):
- makedirs(args.output)
- with tf.Graph().as_default():
- with tf.Session() as sess:
- image_paths = get_onedir(args.input)
- #image_list, label_list = facenet.get_image_paths_and_labels(train_set)
- meta_file, ckpt_file = facenet.get_model_filenames(os.path.expanduser(args.model_dir))
-
- print('Metagraph file: %s' % meta_file)
- print('Checkpoint file: %s' % ckpt_file)
- load_model(args.model_dir, meta_file, ckpt_file)
-
- # Get input and output tensors
- images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0")
- embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
- phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0")
-
- image_size = images_placeholder.get_shape()[1]
- print("image_size:",image_size)
- embedding_size = embeddings.get_shape()[1]
-
- # Run forward pass to calculate embeddings
- print('Runnning forward pass on images')
- nrof_images = len(image_paths)
- nrof_batches = int(math.ceil(1.0*nrof_images / args.batch_size))
- emb_array = np.zeros((nrof_images, embedding_size))
- facial_encodings = compute_facial_encodings(sess,images_placeholder,embeddings,phase_train_placeholder,image_size,
- embedding_size,nrof_images,nrof_batches,emb_array,args.batch_size,image_paths)
- sorted_clusters = cluster_facial_encodings(facial_encodings)
- num_cluster = len(sorted_clusters)
-
- # Copy image files to cluster folders
- for idx, cluster in enumerate(sorted_clusters):
- #save all the cluster
- cluster_dir = join(args.output, str(idx))
- if not exists(cluster_dir):
- makedirs(cluster_dir)
- for path in cluster:
- shutil.copy(path, join(cluster_dir, basename(path)))
- def parse_args():
- """Parse input arguments."""
- import argparse
- parser = argparse.ArgumentParser(description='Get a shape mesh (t-pose)')
- parser.add_argument('--model_dir', type=str, help='model dir', required=True)
- parser.add_argument('--batch_size', type=int, help='batch size', required=30)
- parser.add_argument('--input', type=str, help='Input dir of images', required=True)
- parser.add_argument('--output', type=str, help='Output dir of clusters', required=True)
- args = parser.parse_args()
- return args
- if __name__ == '__main__':
- """ Entry point """
- main(parse_args())
|