From 889fd933e85d005d9e65103977d83899e29c8b6a Mon Sep 17 00:00:00 2001 From: Shawn-Shan Date: Wed, 1 Jul 2020 21:16:03 -0500 Subject: [PATCH] 0.01 Former-commit-id: 268fb7e6825ddfc1165fa7adc7c216f9d61005da [formerly 06376993a831c060c337ec6e7540252f0b2dfe09] Former-commit-id: c4812d40187a76a878e7d215d22ee84811b41896 --- fawkes/Untitled.ipynb.REMOVED.git-id | 1 + fawkes/__pycache__/align_face.cpython-36.pyc | Bin 0 -> 2303 bytes fawkes/__pycache__/detect_face.cpython-36.pyc | Bin 0 -> 22110 bytes .../__pycache__/differentiator.cpython-36.pyc | Bin 0 -> 9154 bytes fawkes/__pycache__/utils.cpython-36.pyc | Bin 0 -> 18326 bytes fawkes/align_face.py | 80 ++ fawkes/detect_face.py | 794 ++++++++++++++++++ fawkes/differentiator.py | 112 ++- fawkes/foo.jpg.REMOVED.git-id | 1 + fawkes/protection.py | 98 ++- fawkes/utils.py | 162 +++- fawkes_dev/README.md | 1 + fawkes_dev/azure.py | 399 +++++++++ fawkes_dev/config.py | 5 +- fawkes_dev/differentiator.py | 430 ---------- fawkes_dev/eval_cloak.py | 154 ++-- fawkes_dev/prepare_feature_extractor.py | 41 +- fawkes_dev/protection.py | 95 --- fawkes_dev/utils.py | 373 -------- 19 files changed, 1647 insertions(+), 1099 deletions(-) create mode 100644 fawkes/Untitled.ipynb.REMOVED.git-id create mode 100644 fawkes/__pycache__/align_face.cpython-36.pyc create mode 100644 fawkes/__pycache__/detect_face.cpython-36.pyc create mode 100644 fawkes/__pycache__/differentiator.cpython-36.pyc create mode 100644 fawkes/__pycache__/utils.cpython-36.pyc create mode 100644 fawkes/align_face.py create mode 100644 fawkes/detect_face.py create mode 100644 fawkes/foo.jpg.REMOVED.git-id create mode 100644 fawkes_dev/azure.py delete mode 100644 fawkes_dev/differentiator.py delete mode 100644 fawkes_dev/protection.py delete mode 100644 fawkes_dev/utils.py diff --git a/fawkes/Untitled.ipynb.REMOVED.git-id b/fawkes/Untitled.ipynb.REMOVED.git-id new file mode 100644 index 0000000..c26ad05 --- /dev/null +++ b/fawkes/Untitled.ipynb.REMOVED.git-id @@ -0,0 +1 @@ +58d500da850206b845bdd0150fa182a0ff8c50f0 \ No newline at end of file diff --git a/fawkes/__pycache__/align_face.cpython-36.pyc b/fawkes/__pycache__/align_face.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f843b43c0c238a9803d6e6dbd55c1a5990d77fce GIT binary patch literal 2303 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 literal 0 HcmV?d00001 diff --git a/fawkes/__pycache__/differentiator.cpython-36.pyc b/fawkes/__pycache__/differentiator.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc557807a1b347bb0b7051c62c08d8beb692714b GIT binary patch literal 9154 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 J*jErn{Xd2RIuQT> literal 0 HcmV?d00001 diff --git a/fawkes/__pycache__/utils.cpython-36.pyc b/fawkes/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1e14d5b9f257f7dc017ec7677fe1a608e20133d GIT binary patch literal 18326 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 0: + det = bounding_boxes[:, 0:4] + det_arr = [] + img_size = np.asarray(orig_img.shape)[0:2] + if nrof_faces > 1: + margin = margin / 1.5 + if detect_multiple_faces: + for i in range(nrof_faces): + det_arr.append(np.squeeze(det[i])) + else: + bounding_box_size = (det[:, 2] - det[:, 0]) * (det[:, 3] - det[:, 1]) + img_center = img_size / 2 + offsets = np.vstack([(det[:, 0] + det[:, 2]) / 2 - img_center[1], + (det[:, 1] + det[:, 3]) / 2 - img_center[0]]) + offset_dist_squared = np.sum(np.power(offsets, 2.0), 0) + index = np.argmax(bounding_box_size - offset_dist_squared * 2.0) # some extra weight on the centering + det_arr.append(det[index, :]) + else: + det_arr.append(np.squeeze(det)) + cropped_arr = [] + bounding_boxes_arr = [] + for i, det in enumerate(det_arr): + det = np.squeeze(det) + bb = np.zeros(4, dtype=np.int32) + side_1 = int((det[2] - det[0]) * margin) + side_2 = int((det[3] - det[1]) * margin) + + bb[0] = np.maximum(det[0] - side_1 / 2, 0) + bb[1] = np.maximum(det[1] - side_1 / 2, 0) + bb[2] = np.minimum(det[2] + side_2 / 2, img_size[1]) + bb[3] = np.minimum(det[3] + side_2 / 2, img_size[0]) + cropped = orig_img[bb[1]:bb[3], bb[0]:bb[2], :] + cropped_arr.append(cropped) + bounding_boxes_arr.append([bb[0], bb[1], bb[2], bb[3]]) + # scaled = misc.imresize(cropped, (image_size, image_size), interp='bilinear') + return cropped_arr, bounding_boxes_arr + else: + return None +# +# if __name__ == '__main__': +# orig_img = misc.imread('orig_img.jpeg') +# cropped_arr, bounding_boxes_arr = align(orig_img) +# misc.imsave('test_output.jpeg', cropped_arr[0]) +# print(bounding_boxes_arr) +# diff --git a/fawkes/detect_face.py b/fawkes/detect_face.py new file mode 100644 index 0000000..54c67a2 --- /dev/null +++ b/fawkes/detect_face.py @@ -0,0 +1,794 @@ +""" Tensorflow implementation of the face detection / alignment algorithm found at +https://github.com/kpzhang93/MTCNN_face_detection_alignment +""" +# MIT License +# +# Copyright (c) 2016 David Sandberg +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +# from math import floor +import cv2 +import numpy as np +import tensorflow as tf +from six import string_types, iteritems + + +def layer(op): + """Decorator for composable network layers.""" + + def layer_decorated(self, *args, **kwargs): + # Automatically set a name if not provided. + name = kwargs.setdefault('name', self.get_unique_name(op.__name__)) + # Figure out the layer inputs. + if len(self.terminals) == 0: + raise RuntimeError('No input variables found for layer %s.' % name) + elif len(self.terminals) == 1: + layer_input = self.terminals[0] + else: + layer_input = list(self.terminals) + # Perform the operation and get the output. + layer_output = op(self, layer_input, *args, **kwargs) + # Add to layer LUT. + self.layers[name] = layer_output + # This output is now the input for the next layer. + self.feed(layer_output) + # Return self for chained calls. + return self + + return layer_decorated + + +class Network(object): + + def __init__(self, inputs, trainable=True): + # The input nodes for this network + self.inputs = inputs + # The current list of terminal nodes + self.terminals = [] + # Mapping from layer names to layers + self.layers = dict(inputs) + # If true, the resulting variables are set as trainable + self.trainable = trainable + + self.setup() + + def setup(self): + """Construct the network. """ + raise NotImplementedError('Must be implemented by the subclass.') + + def load(self, data_path, session, ignore_missing=False): + """Load network weights. + data_path: The path to the numpy-serialized network weights + session: The current TensorFlow session + ignore_missing: If true, serialized weights for missing layers are ignored. + """ + data_dict = np.load(data_path, encoding='latin1').item() # pylint: disable=no-member + + for op_name in data_dict: + with tf.variable_scope(op_name, reuse=True): + for param_name, data in iteritems(data_dict[op_name]): + try: + var = tf.get_variable(param_name) + session.run(var.assign(data)) + except ValueError: + if not ignore_missing: + raise + + def feed(self, *args): + """Set the input(s) for the next operation by replacing the terminal nodes. + The arguments can be either layer names or the actual layers. + """ + assert len(args) != 0 + self.terminals = [] + for fed_layer in args: + if isinstance(fed_layer, string_types): + try: + fed_layer = self.layers[fed_layer] + except KeyError: + raise KeyError('Unknown layer name fed: %s' % fed_layer) + self.terminals.append(fed_layer) + return self + + def get_output(self): + """Returns the current network output.""" + return self.terminals[-1] + + def get_unique_name(self, prefix): + """Returns an index-suffixed unique name for the given prefix. + This is used for auto-generating layer names based on the type-prefix. + """ + ident = sum(t.startswith(prefix) for t, _ in self.layers.items()) + 1 + return '%s_%d' % (prefix, ident) + + def make_var(self, name, shape): + """Creates a new TensorFlow variable.""" + return tf.get_variable(name, shape, trainable=self.trainable) + + def validate_padding(self, padding): + """Verifies that the padding is one of the supported ones.""" + assert padding in ('SAME', 'VALID') + + @layer + def conv(self, + inp, + k_h, + k_w, + c_o, + s_h, + s_w, + name, + relu=True, + padding='SAME', + group=1, + biased=True): + # Verify that the padding is acceptable + self.validate_padding(padding) + # Get the number of channels in the input + c_i = int(inp.get_shape()[-1]) + # Verify that the grouping parameter is valid + assert c_i % group == 0 + assert c_o % group == 0 + # Convolution for a given input and kernel + convolve = lambda i, k: tf.nn.conv2d(i, k, [1, s_h, s_w, 1], padding=padding) + with tf.variable_scope(name) as scope: + kernel = self.make_var('weights', shape=[k_h, k_w, c_i // group, c_o]) + # This is the common-case. Convolve the input without any further complications. + output = convolve(inp, kernel) + # Add the biases + if biased: + biases = self.make_var('biases', [c_o]) + output = tf.nn.bias_add(output, biases) + if relu: + # ReLU non-linearity + output = tf.nn.relu(output, name=scope.name) + return output + + @layer + def prelu(self, inp, name): + with tf.variable_scope(name): + i = int(inp.get_shape()[-1]) + alpha = self.make_var('alpha', shape=(i,)) + output = tf.nn.relu(inp) + tf.multiply(alpha, -tf.nn.relu(-inp)) + return output + + @layer + def max_pool(self, inp, k_h, k_w, s_h, s_w, name, padding='SAME'): + self.validate_padding(padding) + return tf.nn.max_pool(inp, + ksize=[1, k_h, k_w, 1], + strides=[1, s_h, s_w, 1], + padding=padding, + name=name) + + @layer + def fc(self, inp, num_out, name, relu=True): + with tf.variable_scope(name): + input_shape = inp.get_shape() + if input_shape.ndims == 4: + # The input is spatial. Vectorize it first. + dim = 1 + for d in input_shape[1:].as_list(): + dim *= int(d) + feed_in = tf.reshape(inp, [-1, dim]) + else: + feed_in, dim = (inp, input_shape[-1].value) + weights = self.make_var('weights', shape=[dim, num_out]) + biases = self.make_var('biases', [num_out]) + op = tf.nn.relu_layer if relu else tf.nn.xw_plus_b + fc = op(feed_in, weights, biases, name=name) + return fc + + """ + Multi dimensional softmax, + refer to https://github.com/tensorflow/tensorflow/issues/210 + compute softmax along the dimension of target + the native softmax only supports batch_size x dimension + """ + + @layer + def softmax(self, target, axis, name=None): + max_axis = tf.reduce_max(target, axis, keepdims=True) + target_exp = tf.exp(target - max_axis) + normalize = tf.reduce_sum(target_exp, axis, keepdims=True) + softmax = tf.div(target_exp, normalize, name) + return softmax + + +class PNet(Network): + def setup(self): + (self.feed('data') # pylint: disable=no-value-for-parameter, no-member + .conv(3, 3, 10, 1, 1, padding='VALID', relu=False, name='conv1') + .prelu(name='PReLU1') + .max_pool(2, 2, 2, 2, name='pool1') + .conv(3, 3, 16, 1, 1, padding='VALID', relu=False, name='conv2') + .prelu(name='PReLU2') + .conv(3, 3, 32, 1, 1, padding='VALID', relu=False, name='conv3') + .prelu(name='PReLU3') + .conv(1, 1, 2, 1, 1, relu=False, name='conv4-1') + .softmax(3, name='prob1')) + + (self.feed('PReLU3') # pylint: disable=no-value-for-parameter + .conv(1, 1, 4, 1, 1, relu=False, name='conv4-2')) + + +class RNet(Network): + def setup(self): + (self.feed('data') # pylint: disable=no-value-for-parameter, no-member + .conv(3, 3, 28, 1, 1, padding='VALID', relu=False, name='conv1') + .prelu(name='prelu1') + .max_pool(3, 3, 2, 2, name='pool1') + .conv(3, 3, 48, 1, 1, padding='VALID', relu=False, name='conv2') + .prelu(name='prelu2') + .max_pool(3, 3, 2, 2, padding='VALID', name='pool2') + .conv(2, 2, 64, 1, 1, padding='VALID', relu=False, name='conv3') + .prelu(name='prelu3') + .fc(128, relu=False, name='conv4') + .prelu(name='prelu4') + .fc(2, relu=False, name='conv5-1') + .softmax(1, name='prob1')) + + (self.feed('prelu4') # pylint: disable=no-value-for-parameter + .fc(4, relu=False, name='conv5-2')) + + +class ONet(Network): + def setup(self): + (self.feed('data') # pylint: disable=no-value-for-parameter, no-member + .conv(3, 3, 32, 1, 1, padding='VALID', relu=False, name='conv1') + .prelu(name='prelu1') + .max_pool(3, 3, 2, 2, name='pool1') + .conv(3, 3, 64, 1, 1, padding='VALID', relu=False, name='conv2') + .prelu(name='prelu2') + .max_pool(3, 3, 2, 2, padding='VALID', name='pool2') + .conv(3, 3, 64, 1, 1, padding='VALID', relu=False, name='conv3') + .prelu(name='prelu3') + .max_pool(2, 2, 2, 2, name='pool3') + .conv(2, 2, 128, 1, 1, padding='VALID', relu=False, name='conv4') + .prelu(name='prelu4') + .fc(256, relu=False, name='conv5') + .prelu(name='prelu5') + .fc(2, relu=False, name='conv6-1') + .softmax(1, name='prob1')) + + (self.feed('prelu5') # pylint: disable=no-value-for-parameter + .fc(4, relu=False, name='conv6-2')) + + (self.feed('prelu5') # pylint: disable=no-value-for-parameter + .fc(10, relu=False, name='conv6-3')) + + +def create_mtcnn(sess, model_path): + if not model_path: + model_path, _ = os.path.split(os.path.realpath(__file__)) + + with tf.variable_scope('pnet'): + data = tf.placeholder(tf.float32, (None, None, None, 3), 'input') + pnet = PNet({'data': data}) + pnet.load(os.path.join(model_path, 'weights/det1.npy'), sess) + with tf.variable_scope('rnet'): + data = tf.placeholder(tf.float32, (None, 24, 24, 3), 'input') + rnet = RNet({'data': data}) + rnet.load(os.path.join(model_path, 'weights/det2.npy'), sess) + with tf.variable_scope('onet'): + data = tf.placeholder(tf.float32, (None, 48, 48, 3), 'input') + onet = ONet({'data': data}) + onet.load(os.path.join(model_path, 'weights/det3.npy'), sess) + + pnet_fun = lambda img: sess.run(('pnet/conv4-2/BiasAdd:0', 'pnet/prob1:0'), feed_dict={'pnet/input:0': img}) + rnet_fun = lambda img: sess.run(('rnet/conv5-2/conv5-2:0', 'rnet/prob1:0'), feed_dict={'rnet/input:0': img}) + onet_fun = lambda img: sess.run(('onet/conv6-2/conv6-2:0', 'onet/conv6-3/conv6-3:0', 'onet/prob1:0'), + feed_dict={'onet/input:0': img}) + return pnet_fun, rnet_fun, onet_fun + + +def detect_face(img, minsize, pnet, rnet, onet, threshold, factor): + """Detects faces in an image, and returns bounding boxes and points for them. + img: input image + minsize: minimum faces' size + pnet, rnet, onet: caffemodel + threshold: threshold=[th1, th2, th3], th1-3 are three steps's threshold + factor: the factor used to create a scaling pyramid of face sizes to detect in the image. + """ + factor_count = 0 + total_boxes = np.empty((0, 9)) + points = np.empty(0) + h = img.shape[0] + w = img.shape[1] + minl = np.amin([h, w]) + m = 12.0 / minsize + minl = minl * m + # create scale pyramid + scales = [] + while minl >= 12: + scales += [m * np.power(factor, factor_count)] + minl = minl * factor + factor_count += 1 + + # first stage + for scale in scales: + hs = int(np.ceil(h * scale)) + ws = int(np.ceil(w * scale)) + im_data = imresample(img, (hs, ws)) + im_data = (im_data - 127.5) * 0.0078125 + img_x = np.expand_dims(im_data, 0) + img_y = np.transpose(img_x, (0, 2, 1, 3)) + out = pnet(img_y) + out0 = np.transpose(out[0], (0, 2, 1, 3)) + out1 = np.transpose(out[1], (0, 2, 1, 3)) + + boxes, _ = generateBoundingBox(out1[0, :, :, 1].copy(), out0[0, :, :, :].copy(), scale, threshold[0]) + + # inter-scale nms + pick = nms(boxes.copy(), 0.5, 'Union') + if boxes.size > 0 and pick.size > 0: + boxes = boxes[pick, :] + total_boxes = np.append(total_boxes, boxes, axis=0) + + numbox = total_boxes.shape[0] + if numbox > 0: + pick = nms(total_boxes.copy(), 0.7, 'Union') + total_boxes = total_boxes[pick, :] + regw = total_boxes[:, 2] - total_boxes[:, 0] + regh = total_boxes[:, 3] - total_boxes[:, 1] + qq1 = total_boxes[:, 0] + total_boxes[:, 5] * regw + qq2 = total_boxes[:, 1] + total_boxes[:, 6] * regh + qq3 = total_boxes[:, 2] + total_boxes[:, 7] * regw + qq4 = total_boxes[:, 3] + total_boxes[:, 8] * regh + total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, total_boxes[:, 4]])) + total_boxes = rerec(total_boxes.copy()) + total_boxes[:, 0:4] = np.fix(total_boxes[:, 0:4]).astype(np.int32) + dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad(total_boxes.copy(), w, h) + + numbox = total_boxes.shape[0] + if numbox > 0: + # second stage + tempimg = np.zeros((24, 24, 3, numbox)) + for k in range(0, numbox): + tmp = np.zeros((int(tmph[k]), int(tmpw[k]), 3)) + tmp[dy[k] - 1:edy[k], dx[k] - 1:edx[k], :] = img[y[k] - 1:ey[k], x[k] - 1:ex[k], :] + if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0: + tempimg[:, :, :, k] = imresample(tmp, (24, 24)) + else: + return np.empty() + tempimg = (tempimg - 127.5) * 0.0078125 + tempimg1 = np.transpose(tempimg, (3, 1, 0, 2)) + out = rnet(tempimg1) + out0 = np.transpose(out[0]) + out1 = np.transpose(out[1]) + score = out1[1, :] + ipass = np.where(score > threshold[1]) + total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), np.expand_dims(score[ipass].copy(), 1)]) + mv = out0[:, ipass[0]] + if total_boxes.shape[0] > 0: + pick = nms(total_boxes, 0.7, 'Union') + total_boxes = total_boxes[pick, :] + total_boxes = bbreg(total_boxes.copy(), np.transpose(mv[:, pick])) + total_boxes = rerec(total_boxes.copy()) + + numbox = total_boxes.shape[0] + if numbox > 0: + # third stage + total_boxes = np.fix(total_boxes).astype(np.int32) + dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad(total_boxes.copy(), w, h) + tempimg = np.zeros((48, 48, 3, numbox)) + for k in range(0, numbox): + tmp = np.zeros((int(tmph[k]), int(tmpw[k]), 3)) + tmp[dy[k] - 1:edy[k], dx[k] - 1:edx[k], :] = img[y[k] - 1:ey[k], x[k] - 1:ex[k], :] + if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0: + tempimg[:, :, :, k] = imresample(tmp, (48, 48)) + else: + return np.empty() + tempimg = (tempimg - 127.5) * 0.0078125 + tempimg1 = np.transpose(tempimg, (3, 1, 0, 2)) + out = onet(tempimg1) + out0 = np.transpose(out[0]) + out1 = np.transpose(out[1]) + out2 = np.transpose(out[2]) + score = out2[1, :] + points = out1 + ipass = np.where(score > threshold[2]) + points = points[:, ipass[0]] + total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), np.expand_dims(score[ipass].copy(), 1)]) + mv = out0[:, ipass[0]] + + w = total_boxes[:, 2] - total_boxes[:, 0] + 1 + h = total_boxes[:, 3] - total_boxes[:, 1] + 1 + points[0:5, :] = np.tile(w, (5, 1)) * points[0:5, :] + np.tile(total_boxes[:, 0], (5, 1)) - 1 + points[5:10, :] = np.tile(h, (5, 1)) * points[5:10, :] + np.tile(total_boxes[:, 1], (5, 1)) - 1 + if total_boxes.shape[0] > 0: + total_boxes = bbreg(total_boxes.copy(), np.transpose(mv)) + pick = nms(total_boxes.copy(), 0.7, 'Min') + total_boxes = total_boxes[pick, :] + points = points[:, pick] + + return total_boxes, points + + +def bulk_detect_face(images, detection_window_size_ratio, pnet, rnet, onet, threshold, factor): + """Detects faces in a list of images + images: list containing input images + detection_window_size_ratio: ratio of minimum face size to smallest image dimension + pnet, rnet, onet: caffemodel + threshold: threshold=[th1 th2 th3], th1-3 are three steps's threshold [0-1] + factor: the factor used to create a scaling pyramid of face sizes to detect in the image. + """ + all_scales = [None] * len(images) + images_with_boxes = [None] * len(images) + + for i in range(len(images)): + images_with_boxes[i] = {'total_boxes': np.empty((0, 9))} + + # create scale pyramid + for index, img in enumerate(images): + all_scales[index] = [] + h = img.shape[0] + w = img.shape[1] + minsize = int(detection_window_size_ratio * np.minimum(w, h)) + factor_count = 0 + minl = np.amin([h, w]) + if minsize <= 12: + minsize = 12 + + m = 12.0 / minsize + minl = minl * m + while minl >= 12: + all_scales[index].append(m * np.power(factor, factor_count)) + minl = minl * factor + factor_count += 1 + + # # # # # # # # # # # # # + # first stage - fast proposal network (pnet) to obtain face candidates + # # # # # # # # # # # # # + + images_obj_per_resolution = {} + + # TODO: use some type of rounding to number module 8 to increase probability that pyramid images will have the same resolution across input images + + for index, scales in enumerate(all_scales): + h = images[index].shape[0] + w = images[index].shape[1] + + for scale in scales: + hs = int(np.ceil(h * scale)) + ws = int(np.ceil(w * scale)) + + if (ws, hs) not in images_obj_per_resolution: + images_obj_per_resolution[(ws, hs)] = [] + + im_data = imresample(images[index], (hs, ws)) + im_data = (im_data - 127.5) * 0.0078125 + img_y = np.transpose(im_data, (1, 0, 2)) # caffe uses different dimensions ordering + images_obj_per_resolution[(ws, hs)].append({'scale': scale, 'image': img_y, 'index': index}) + + for resolution in images_obj_per_resolution: + images_per_resolution = [i['image'] for i in images_obj_per_resolution[resolution]] + outs = pnet(images_per_resolution) + + for index in range(len(outs[0])): + scale = images_obj_per_resolution[resolution][index]['scale'] + image_index = images_obj_per_resolution[resolution][index]['index'] + out0 = np.transpose(outs[0][index], (1, 0, 2)) + out1 = np.transpose(outs[1][index], (1, 0, 2)) + + boxes, _ = generateBoundingBox(out1[:, :, 1].copy(), out0[:, :, :].copy(), scale, threshold[0]) + + # inter-scale nms + pick = nms(boxes.copy(), 0.5, 'Union') + if boxes.size > 0 and pick.size > 0: + boxes = boxes[pick, :] + images_with_boxes[image_index]['total_boxes'] = np.append(images_with_boxes[image_index]['total_boxes'], + boxes, + axis=0) + + for index, image_obj in enumerate(images_with_boxes): + numbox = image_obj['total_boxes'].shape[0] + if numbox > 0: + h = images[index].shape[0] + w = images[index].shape[1] + pick = nms(image_obj['total_boxes'].copy(), 0.7, 'Union') + image_obj['total_boxes'] = image_obj['total_boxes'][pick, :] + regw = image_obj['total_boxes'][:, 2] - image_obj['total_boxes'][:, 0] + regh = image_obj['total_boxes'][:, 3] - image_obj['total_boxes'][:, 1] + qq1 = image_obj['total_boxes'][:, 0] + image_obj['total_boxes'][:, 5] * regw + qq2 = image_obj['total_boxes'][:, 1] + image_obj['total_boxes'][:, 6] * regh + qq3 = image_obj['total_boxes'][:, 2] + image_obj['total_boxes'][:, 7] * regw + qq4 = image_obj['total_boxes'][:, 3] + image_obj['total_boxes'][:, 8] * regh + image_obj['total_boxes'] = np.transpose(np.vstack([qq1, qq2, qq3, qq4, image_obj['total_boxes'][:, 4]])) + image_obj['total_boxes'] = rerec(image_obj['total_boxes'].copy()) + image_obj['total_boxes'][:, 0:4] = np.fix(image_obj['total_boxes'][:, 0:4]).astype(np.int32) + dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad(image_obj['total_boxes'].copy(), w, h) + + numbox = image_obj['total_boxes'].shape[0] + tempimg = np.zeros((24, 24, 3, numbox)) + + if numbox > 0: + for k in range(0, numbox): + tmp = np.zeros((int(tmph[k]), int(tmpw[k]), 3)) + tmp[dy[k] - 1:edy[k], dx[k] - 1:edx[k], :] = images[index][y[k] - 1:ey[k], x[k] - 1:ex[k], :] + if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0: + tempimg[:, :, :, k] = imresample(tmp, (24, 24)) + else: + return np.empty() + + tempimg = (tempimg - 127.5) * 0.0078125 + image_obj['rnet_input'] = np.transpose(tempimg, (3, 1, 0, 2)) + + # # # # # # # # # # # # # + # second stage - refinement of face candidates with rnet + # # # # # # # # # # # # # + + bulk_rnet_input = np.empty((0, 24, 24, 3)) + for index, image_obj in enumerate(images_with_boxes): + if 'rnet_input' in image_obj: + bulk_rnet_input = np.append(bulk_rnet_input, image_obj['rnet_input'], axis=0) + + out = rnet(bulk_rnet_input) + out0 = np.transpose(out[0]) + out1 = np.transpose(out[1]) + score = out1[1, :] + + i = 0 + for index, image_obj in enumerate(images_with_boxes): + if 'rnet_input' not in image_obj: + continue + + rnet_input_count = image_obj['rnet_input'].shape[0] + score_per_image = score[i:i + rnet_input_count] + out0_per_image = out0[:, i:i + rnet_input_count] + + ipass = np.where(score_per_image > threshold[1]) + image_obj['total_boxes'] = np.hstack([image_obj['total_boxes'][ipass[0], 0:4].copy(), + np.expand_dims(score_per_image[ipass].copy(), 1)]) + + mv = out0_per_image[:, ipass[0]] + + if image_obj['total_boxes'].shape[0] > 0: + h = images[index].shape[0] + w = images[index].shape[1] + pick = nms(image_obj['total_boxes'], 0.7, 'Union') + image_obj['total_boxes'] = image_obj['total_boxes'][pick, :] + image_obj['total_boxes'] = bbreg(image_obj['total_boxes'].copy(), np.transpose(mv[:, pick])) + image_obj['total_boxes'] = rerec(image_obj['total_boxes'].copy()) + + numbox = image_obj['total_boxes'].shape[0] + + if numbox > 0: + tempimg = np.zeros((48, 48, 3, numbox)) + image_obj['total_boxes'] = np.fix(image_obj['total_boxes']).astype(np.int32) + dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad(image_obj['total_boxes'].copy(), w, h) + + for k in range(0, numbox): + tmp = np.zeros((int(tmph[k]), int(tmpw[k]), 3)) + tmp[dy[k] - 1:edy[k], dx[k] - 1:edx[k], :] = images[index][y[k] - 1:ey[k], x[k] - 1:ex[k], :] + if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0: + tempimg[:, :, :, k] = imresample(tmp, (48, 48)) + else: + return np.empty() + tempimg = (tempimg - 127.5) * 0.0078125 + image_obj['onet_input'] = np.transpose(tempimg, (3, 1, 0, 2)) + + i += rnet_input_count + + # # # # # # # # # # # # # + # third stage - further refinement and facial landmarks positions with onet + # # # # # # # # # # # # # + + bulk_onet_input = np.empty((0, 48, 48, 3)) + for index, image_obj in enumerate(images_with_boxes): + if 'onet_input' in image_obj: + bulk_onet_input = np.append(bulk_onet_input, image_obj['onet_input'], axis=0) + + out = onet(bulk_onet_input) + + out0 = np.transpose(out[0]) + out1 = np.transpose(out[1]) + out2 = np.transpose(out[2]) + score = out2[1, :] + points = out1 + + i = 0 + ret = [] + for index, image_obj in enumerate(images_with_boxes): + if 'onet_input' not in image_obj: + ret.append(None) + continue + + onet_input_count = image_obj['onet_input'].shape[0] + + out0_per_image = out0[:, i:i + onet_input_count] + score_per_image = score[i:i + onet_input_count] + points_per_image = points[:, i:i + onet_input_count] + + ipass = np.where(score_per_image > threshold[2]) + points_per_image = points_per_image[:, ipass[0]] + + image_obj['total_boxes'] = np.hstack([image_obj['total_boxes'][ipass[0], 0:4].copy(), + np.expand_dims(score_per_image[ipass].copy(), 1)]) + mv = out0_per_image[:, ipass[0]] + + w = image_obj['total_boxes'][:, 2] - image_obj['total_boxes'][:, 0] + 1 + h = image_obj['total_boxes'][:, 3] - image_obj['total_boxes'][:, 1] + 1 + points_per_image[0:5, :] = np.tile(w, (5, 1)) * points_per_image[0:5, :] + np.tile( + image_obj['total_boxes'][:, 0], (5, 1)) - 1 + points_per_image[5:10, :] = np.tile(h, (5, 1)) * points_per_image[5:10, :] + np.tile( + image_obj['total_boxes'][:, 1], (5, 1)) - 1 + + if image_obj['total_boxes'].shape[0] > 0: + image_obj['total_boxes'] = bbreg(image_obj['total_boxes'].copy(), np.transpose(mv)) + pick = nms(image_obj['total_boxes'].copy(), 0.7, 'Min') + image_obj['total_boxes'] = image_obj['total_boxes'][pick, :] + points_per_image = points_per_image[:, pick] + + ret.append((image_obj['total_boxes'], points_per_image)) + else: + ret.append(None) + + i += onet_input_count + + return ret + + +# function [boundingbox] = bbreg(boundingbox,reg) +def bbreg(boundingbox, reg): + """Calibrate bounding boxes""" + if reg.shape[1] == 1: + reg = np.reshape(reg, (reg.shape[2], reg.shape[3])) + + w = boundingbox[:, 2] - boundingbox[:, 0] + 1 + h = boundingbox[:, 3] - boundingbox[:, 1] + 1 + b1 = boundingbox[:, 0] + reg[:, 0] * w + b2 = boundingbox[:, 1] + reg[:, 1] * h + b3 = boundingbox[:, 2] + reg[:, 2] * w + b4 = boundingbox[:, 3] + reg[:, 3] * h + boundingbox[:, 0:4] = np.transpose(np.vstack([b1, b2, b3, b4])) + return boundingbox + + +def generateBoundingBox(imap, reg, scale, t): + """Use heatmap to generate bounding boxes""" + stride = 2 + cellsize = 12 + + imap = np.transpose(imap) + dx1 = np.transpose(reg[:, :, 0]) + dy1 = np.transpose(reg[:, :, 1]) + dx2 = np.transpose(reg[:, :, 2]) + dy2 = np.transpose(reg[:, :, 3]) + y, x = np.where(imap >= t) + if y.shape[0] == 1: + dx1 = np.flipud(dx1) + dy1 = np.flipud(dy1) + dx2 = np.flipud(dx2) + dy2 = np.flipud(dy2) + score = imap[(y, x)] + reg = np.transpose(np.vstack([dx1[(y, x)], dy1[(y, x)], dx2[(y, x)], dy2[(y, x)]])) + if reg.size == 0: + reg = np.empty((0, 3)) + bb = np.transpose(np.vstack([y, x])) + q1 = np.fix((stride * bb + 1) / scale) + q2 = np.fix((stride * bb + cellsize - 1 + 1) / scale) + boundingbox = np.hstack([q1, q2, np.expand_dims(score, 1), reg]) + return boundingbox, reg + + +# function pick = nms(boxes,threshold,type) +def nms(boxes, threshold, method): + if boxes.size == 0: + return np.empty((0, 3)) + x1 = boxes[:, 0] + y1 = boxes[:, 1] + x2 = boxes[:, 2] + y2 = boxes[:, 3] + s = boxes[:, 4] + area = (x2 - x1 + 1) * (y2 - y1 + 1) + I = np.argsort(s) + pick = np.zeros_like(s, dtype=np.int16) + counter = 0 + while I.size > 0: + i = I[-1] + pick[counter] = i + counter += 1 + idx = I[0:-1] + xx1 = np.maximum(x1[i], x1[idx]) + yy1 = np.maximum(y1[i], y1[idx]) + xx2 = np.minimum(x2[i], x2[idx]) + yy2 = np.minimum(y2[i], y2[idx]) + w = np.maximum(0.0, xx2 - xx1 + 1) + h = np.maximum(0.0, yy2 - yy1 + 1) + inter = w * h + if method is 'Min': + o = inter / np.minimum(area[i], area[idx]) + else: + o = inter / (area[i] + area[idx] - inter) + I = I[np.where(o <= threshold)] + pick = pick[0:counter] + return pick + + +# function [dy edy dx edx y ey x ex tmpw tmph] = pad(total_boxes,w,h) +def pad(total_boxes, w, h): + """Compute the padding coordinates (pad the bounding boxes to square)""" + tmpw = (total_boxes[:, 2] - total_boxes[:, 0] + 1).astype(np.int32) + tmph = (total_boxes[:, 3] - total_boxes[:, 1] + 1).astype(np.int32) + numbox = total_boxes.shape[0] + + dx = np.ones((numbox), dtype=np.int32) + dy = np.ones((numbox), dtype=np.int32) + edx = tmpw.copy().astype(np.int32) + edy = tmph.copy().astype(np.int32) + + x = total_boxes[:, 0].copy().astype(np.int32) + y = total_boxes[:, 1].copy().astype(np.int32) + ex = total_boxes[:, 2].copy().astype(np.int32) + ey = total_boxes[:, 3].copy().astype(np.int32) + + tmp = np.where(ex > w) + edx.flat[tmp] = np.expand_dims(-ex[tmp] + w + tmpw[tmp], 1) + ex[tmp] = w + + tmp = np.where(ey > h) + edy.flat[tmp] = np.expand_dims(-ey[tmp] + h + tmph[tmp], 1) + ey[tmp] = h + + tmp = np.where(x < 1) + dx.flat[tmp] = np.expand_dims(2 - x[tmp], 1) + x[tmp] = 1 + + tmp = np.where(y < 1) + dy.flat[tmp] = np.expand_dims(2 - y[tmp], 1) + y[tmp] = 1 + + return dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph + + +# function [bboxA] = rerec(bboxA) +def rerec(bboxA): + """Convert bboxA to square.""" + h = bboxA[:, 3] - bboxA[:, 1] + w = bboxA[:, 2] - bboxA[:, 0] + l = np.maximum(w, h) + bboxA[:, 0] = bboxA[:, 0] + w * 0.5 - l * 0.5 + bboxA[:, 1] = bboxA[:, 1] + h * 0.5 - l * 0.5 + bboxA[:, 2:4] = bboxA[:, 0:2] + np.transpose(np.tile(l, (2, 1))) + return bboxA + + +def imresample(img, sz): + im_data = cv2.resize(img, (sz[1], sz[0]), interpolation=cv2.INTER_AREA) # @UndefinedVariable + return im_data + + # This method is kept for debugging purpose +# h=img.shape[0] +# w=img.shape[1] +# hs, ws = sz +# dx = float(w) / ws +# dy = float(h) / hs +# im_data = np.zeros((hs,ws,3)) +# for a1 in range(0,hs): +# for a2 in range(0,ws): +# for a3 in range(0,3): +# im_data[a1,a2,a3] = img[int(floor(a1*dy)),int(floor(a2*dx)),a3] +# return im_data diff --git a/fawkes/differentiator.py b/fawkes/differentiator.py index 98b85d6..2fd9175 100644 --- a/fawkes/differentiator.py +++ b/fawkes/differentiator.py @@ -47,7 +47,7 @@ class FawkesMaskGeneration: max_iterations=MAX_ITERATIONS, initial_const=INITIAL_CONST, intensity_range=INTENSITY_RANGE, l_threshold=L_THRESHOLD, max_val=MAX_VAL, keep_final=KEEP_FINAL, maximize=MAXIMIZE, image_shape=IMAGE_SHAPE, - verbose=0, ratio=RATIO, limit_dist=LIMIT_DIST): + verbose=0, ratio=RATIO, limit_dist=LIMIT_DIST, faces=None): assert intensity_range in {'raw', 'imagenet', 'inception', 'mnist'} @@ -69,10 +69,12 @@ class FawkesMaskGeneration: self.ratio = ratio self.limit_dist = limit_dist self.single_shape = list(image_shape) + self.faces = faces self.input_shape = tuple([self.batch_size] + self.single_shape) self.bottleneck_shape = tuple([self.batch_size] + self.single_shape) + # self.bottleneck_shape = tuple([self.batch_size, bottleneck_model_ls[0].output_shape[-1]]) # the variable we're going to optimize over self.modifier = tf.Variable(np.zeros(self.input_shape, dtype=np.float32)) @@ -149,8 +151,6 @@ class FawkesMaskGeneration: self.dist_raw, tf.zeros_like(self.dist_raw))) self.dist_sum = tf.reduce_sum(tf.where(self.mask, self.dist, tf.zeros_like(self.dist))) - # self.dist_sum = 1e-5 * tf.reduce_sum(self.dist) - # self.dist_raw_sum = self.dist_sum def resize_tensor(input_tensor, model_input_shape): if input_tensor.shape[1:] == model_input_shape or model_input_shape[1] is None: @@ -171,16 +171,14 @@ class FawkesMaskGeneration: self.bottleneck_a = bottleneck_model(cur_aimg_input) if self.MIMIC_IMG: - # cur_timg_input = resize_tensor(self.timg_input, model_input_shape) - # cur_simg_input = resize_tensor(self.simg_input, model_input_shape) cur_timg_input = self.timg_input cur_simg_input = self.simg_input self.bottleneck_t = calculate_direction(bottleneck_model, cur_timg_input, cur_simg_input) - # self.bottleneck_t = bottleneck_model(cur_timg_input) else: self.bottleneck_t = self.bottleneck_t_raw bottleneck_diff = self.bottleneck_t - self.bottleneck_a + scale_factor = tf.sqrt(tf.reduce_sum(tf.square(self.bottleneck_t), axis=1)) cur_bottlesim = tf.sqrt(tf.reduce_sum(tf.square(bottleneck_diff), axis=1)) @@ -189,7 +187,6 @@ class FawkesMaskGeneration: self.bottlesim += cur_bottlesim - # self.bottlesim_push += cur_bottlesim_push_sum self.bottlesim_sum += cur_bottlesim_sum # sum up the losses @@ -202,20 +199,13 @@ class FawkesMaskGeneration: self.loss, tf.zeros_like(self.loss))) - # self.loss_sum = self.dist_sum + tf.reduce_sum(self.bottlesim) - # import pdb - # pdb.set_trace() - # self.loss_sum = tf.reduce_sum(tf.where(self.mask, self.loss, tf.zeros_like(self.loss))) - - # Setup the Adadelta optimizer and keep track of variables - # we're creating start_vars = set(x.name for x in tf.global_variables()) self.learning_rate_holder = tf.placeholder(tf.float32, shape=[]) + optimizer = tf.train.AdadeltaOptimizer(self.learning_rate_holder) # optimizer = tf.train.AdamOptimizer(self.learning_rate_holder) - self.train = optimizer.minimize(self.loss_sum, - var_list=[self.modifier]) + self.train = optimizer.minimize(self.loss_sum, var_list=[self.modifier]) end_vars = tf.global_variables() new_vars = [x for x in end_vars if x.name not in start_vars] @@ -297,6 +287,7 @@ class FawkesMaskGeneration: LR = self.learning_rate nb_imgs = source_imgs.shape[0] mask = [True] * nb_imgs + [False] * (self.batch_size - nb_imgs) + # mask = [True] * self.batch_size mask = np.array(mask, dtype=np.bool) source_imgs = np.array(source_imgs) @@ -317,19 +308,34 @@ class FawkesMaskGeneration: timg_tanh_batch = np.zeros(self.input_shape) else: timg_tanh_batch = np.zeros(self.bottleneck_shape) + weights_batch = np.zeros(self.bottleneck_shape) simg_tanh_batch[:nb_imgs] = simg_tanh[:nb_imgs] timg_tanh_batch[:nb_imgs] = timg_tanh[:nb_imgs] weights_batch[:nb_imgs] = weights[:nb_imgs] modifier_batch = np.ones(self.input_shape) * 1e-6 - self.sess.run(self.setup, - {self.assign_timg_tanh: timg_tanh_batch, - self.assign_simg_tanh: simg_tanh_batch, - self.assign_const: CONST, - self.assign_mask: mask, - self.assign_weights: weights_batch, - self.assign_modifier: modifier_batch}) + temp_images = [] + + # set the variables so that we don't have to send them over again + if self.MIMIC_IMG: + self.sess.run(self.setup, + {self.assign_timg_tanh: timg_tanh_batch, + self.assign_simg_tanh: simg_tanh_batch, + self.assign_const: CONST, + self.assign_mask: mask, + self.assign_weights: weights_batch, + self.assign_modifier: modifier_batch}) + else: + # if directly mimicking a vector, use assign_bottleneck_t_raw + # in setup + self.sess.run(self.setup, + {self.assign_bottleneck_t_raw: timg_tanh_batch, + self.assign_simg_tanh: simg_tanh_batch, + self.assign_const: CONST, + self.assign_mask: mask, + self.assign_weights: weights_batch, + self.assign_modifier: modifier_batch}) best_bottlesim = [0] * nb_imgs if self.maximize else [np.inf] * nb_imgs best_adv = np.zeros_like(source_imgs) @@ -347,6 +353,7 @@ class FawkesMaskGeneration: dist_raw_sum, bottlesim_sum / nb_imgs)) + finished_idx = set() try: total_distance = [0] * nb_imgs @@ -369,8 +376,14 @@ class FawkesMaskGeneration: [self.dist_raw, self.bottlesim, self.aimg_input]) + + all_clear = True for e, (dist_raw, bottlesim, aimg_input) in enumerate( zip(dist_raw_list, bottlesim_list, aimg_input_list)): + + if e in finished_idx: + continue + if e >= nb_imgs: break if (bottlesim < best_bottlesim[e] and bottlesim > total_distance[e] * 0.1 and ( @@ -379,40 +392,55 @@ class FawkesMaskGeneration: best_bottlesim[e] = bottlesim best_adv[e] = aimg_input - if iteration != 0 and iteration % (self.MAX_ITERATIONS // 3) == 0: - # LR = LR / 2 + # if iteration > 20 and (dist_raw >= self.l_threshold or iteration == self.MAX_ITERATIONS - 1): + # finished_idx.add(e) + # print("{} finished at dist {}".format(e, dist_raw)) + # best_bottlesim[e] = bottlesim + # best_adv[e] = aimg_input + # + all_clear = False + + if all_clear: + break + + if iteration != 0 and iteration % (self.MAX_ITERATIONS // 2) == 0: + LR = LR / 2 print("Learning Rate: ", LR) - if iteration % (self.MAX_ITERATIONS // 10) == 0: + if iteration % (self.MAX_ITERATIONS // 5) == 0: if self.verbose == 1: - loss_sum = float(self.sess.run(self.loss_sum)) - dist_sum = float(self.sess.run(self.dist_sum)) - thresh_over = (dist_sum / - self.batch_size / - self.l_threshold * - 100) dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) bottlesim_sum = self.sess.run(self.bottlesim_sum) - print('ITER %4d: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' - % (iteration, - Decimal(loss_sum), - dist_sum, - thresh_over, - dist_raw_sum, - bottlesim_sum / nb_imgs)) + print('ITER %4d perturb: %.5f; sim: %f' + % (iteration, dist_raw_sum / nb_imgs, bottlesim_sum / nb_imgs)) + + # protected_images = aimg_input_list + # + # orginal_images = np.copy(self.faces.cropped_faces) + # cloak_perturbation = reverse_process_cloaked(protected_images) - reverse_process_cloaked( + # orginal_images) + # final_images = self.faces.merge_faces(cloak_perturbation) + # + # for p_img, img in zip(protected_images, final_images): + # dump_image(reverse_process_cloaked(p_img), + # "/home/shansixioing/fawkes/data/emily/emily_cloaked_cropped{}.png".format(iteration), + # format='png') + # + # dump_image(img, + # "/home/shansixioing/fawkes/data/emily/emily_cloaked_{}.png".format(iteration), + # format='png') + except KeyboardInterrupt: pass if self.verbose == 1: loss_sum = float(self.sess.run(self.loss_sum)) dist_sum = float(self.sess.run(self.dist_sum)) - thresh_over = (dist_sum / self.batch_size / self.l_threshold * 100) dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) bottlesim_sum = float(self.sess.run(self.bottlesim_sum)) - print('END: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' + print('END: Total loss: %.4E; perturb: %.6f (raw: %.6f); sim: %f' % (Decimal(loss_sum), dist_sum, - thresh_over, dist_raw_sum, bottlesim_sum / nb_imgs)) diff --git a/fawkes/foo.jpg.REMOVED.git-id b/fawkes/foo.jpg.REMOVED.git-id new file mode 100644 index 0000000..a4990ab --- /dev/null +++ b/fawkes/foo.jpg.REMOVED.git-id @@ -0,0 +1 @@ +837da51fc1cd7e21f6989badd07c3ccec543833e \ No newline at end of file diff --git a/fawkes/protection.py b/fawkes/protection.py index 1a44958..9df50fb 100644 --- a/fawkes/protection.py +++ b/fawkes/protection.py @@ -6,21 +6,16 @@ import sys import numpy as np from differentiator import FawkesMaskGeneration -from keras.applications.vgg16 import preprocess_input -from keras.preprocessing import image -from skimage.transform import resize -from tensorflow import set_random_seed -from utils import load_extractor, init_gpu, select_target_label, dump_image, reverse_process_cloaked +from utils import load_extractor, init_gpu, select_target_label, dump_image, reverse_process_cloaked, \ + Faces random.seed(12243) np.random.seed(122412) -set_random_seed(12242) -BATCH_SIZE = 1 -MAX_ITER = 1000 +BATCH_SIZE = 10 -def generate_cloak_images(sess, feature_extractors, image_X, target_X=None, th=0.01): +def generate_cloak_images(sess, feature_extractors, image_X, target_emb=None, th=0.01, faces=None): batch_size = BATCH_SIZE if len(image_X) > BATCH_SIZE else len(image_X) differentiator = FawkesMaskGeneration(sess, feature_extractors, @@ -29,92 +24,117 @@ def generate_cloak_images(sess, feature_extractors, image_X, target_X=None, th=0 intensity_range='imagenet', initial_const=args.sd, learning_rate=args.lr, - max_iterations=MAX_ITER, + max_iterations=args.max_step, l_threshold=th, - verbose=1, maximize=False, keep_final=False, image_shape=image_X.shape[1:]) + verbose=1, maximize=False, keep_final=False, image_shape=image_X.shape[1:], + faces=faces) - cloaked_image_X = differentiator.attack(image_X, target_X) + cloaked_image_X = differentiator.attack(image_X, target_emb) return cloaked_image_X def get_mode_config(mode): if mode == 'low': args.feature_extractor = "low_extract" + # args.th = 0.003 args.th = 0.001 elif mode == 'mid': args.feature_extractor = "mid_extract" - args.th = 0.001 + args.th = 0.004 elif mode == 'high': args.feature_extractor = "high_extract" - args.th = 0.005 + args.th = 0.004 elif mode == 'ultra': args.feature_extractor = "high_extract" - args.th = 0.007 + args.th = 0.03 elif mode == 'custom': pass else: raise Exception("mode must be one of 'low', 'mid', 'high', 'ultra', 'custom'") -def extract_faces(img): - # wait on Huiying - return preprocess_input(resize(img, (224, 224))) +def check_imgs(imgs): + if np.max(imgs) <= 1 and np.min(imgs) >= 0: + imgs = imgs * 255.0 + elif np.max(imgs) <= 255 and np.min(imgs) >= 0: + pass + else: + raise Exception("Image values ") + return imgs def fawkes(): + assert args.format in ['png', 'jpg', 'jpeg'] + if args.format == 'jpg': + args.format = 'jpeg' get_mode_config(args.mode) sess = init_gpu(args.gpu) - feature_extractors_ls = [load_extractor(args.feature_extractor)] + # feature_extractors_ls = [load_extractor(args.feature_extractor)] + # fs_names = ['mid_extract', 'high_extract'] + fs_names = [args.feature_extractor] + feature_extractors_ls = [load_extractor(name) for name in fs_names] image_paths = glob.glob(os.path.join(args.directory, "*")) image_paths = [path for path in image_paths if "_cloaked" not in path.split("/")[-1]] - orginal_images = [extract_faces(image.img_to_array(image.load_img(cur_path))) for cur_path in - image_paths] + faces = Faces(image_paths, sess) + orginal_images = faces.cropped_faces orginal_images = np.array(orginal_images) - if args.seperate_target: - target_images = [] + if args.separate_target: + target_embedding = [] for org_img in orginal_images: org_img = org_img.reshape([1] + list(org_img.shape)) - tar_img = select_target_label(org_img, feature_extractors_ls, [args.feature_extractor]) - target_images.append(tar_img) - target_images = np.concatenate(target_images) + tar_emb = select_target_label(org_img, feature_extractors_ls, fs_names) + target_embedding.append(tar_emb) + target_embedding = np.concatenate(target_embedding) else: - target_images = select_target_label(orginal_images, feature_extractors_ls, [args.feature_extractor]) + target_embedding = select_target_label(orginal_images, feature_extractors_ls, fs_names) protected_images = generate_cloak_images(sess, feature_extractors_ls, orginal_images, - target_X=target_images, th=args.th) + target_emb=target_embedding, th=args.th, faces=faces) - for p_img, path in zip(protected_images, image_paths): - p_img = reverse_process_cloaked(p_img) - file_name = "{}_cloaked.jpeg".format(".".join(path.split(".")[:-1])) - dump_image(p_img, file_name, format="JPEG") + faces.cloaked_cropped_faces = protected_images + + cloak_perturbation = reverse_process_cloaked(protected_images) - reverse_process_cloaked(orginal_images) + final_images = faces.merge_faces(cloak_perturbation) + + for p_img, cloaked_img, path in zip(final_images, protected_images, image_paths): + file_name = "{}_{}_{}_{}_cloaked.{}".format(".".join(path.split(".")[:-1]), args.mode, args.th, + args.feature_extractor, args.format) + dump_image(p_img, file_name, format=args.format) + # + # file_name = "{}_{}_{}_{}_cloaked_cropped.png".format(".".join(path.split(".")[:-1]), args.mode, args.th, + # args.feature_extractor) + # dump_image(reverse_process_cloaked(cloaked_img), file_name, format="png") def parse_arguments(argv): parser = argparse.ArgumentParser() - parser.add_argument('--directory', type=str, + parser.add_argument('--directory', '-d', type=str, help='directory that contain images for cloaking', default='imgs/') parser.add_argument('--gpu', type=str, help='GPU id', default='0') parser.add_argument('--mode', type=str, - help='cloak generation mode', default='mid') + help='cloak generation mode', default='high') parser.add_argument('--feature-extractor', type=str, help="name of the feature extractor used for optimization", - default="mid_extract") + default="high_extract") - parser.add_argument('--th', type=float, default=0.005) + parser.add_argument('--th', type=float, default=0.01) + parser.add_argument('--max-step', type=int, default=200) parser.add_argument('--sd', type=int, default=1e9) - parser.add_argument('--lr', type=float, default=1) + parser.add_argument('--lr', type=float, default=10) - parser.add_argument('--result_directory', type=str, default="../results") - parser.add_argument('--seperate_target', action='store_true') + parser.add_argument('--separate_target', action='store_true') + parser.add_argument('--format', type=str, + help="final image format", + default="jpg") return parser.parse_args(argv) diff --git a/fawkes/utils.py b/fawkes/utils.py index c0c4577..4083ac2 100644 --- a/fawkes/utils.py +++ b/fawkes/utils.py @@ -1,3 +1,5 @@ +import glob +import gzip import json import os import pickle @@ -7,12 +9,16 @@ import keras import keras.backend as K import numpy as np import tensorflow as tf +from align_face import align, aligner from keras.applications.vgg16 import preprocess_input from keras.layers import Dense, Activation from keras.models import Model from keras.preprocessing import image +from keras.utils import get_file from keras.utils import to_categorical +from skimage.transform import resize from sklearn.metrics import pairwise_distances +from PIL import Image, ExifTags def clip_img(X, preprocessing='raw'): @@ -22,6 +28,86 @@ def clip_img(X, preprocessing='raw'): return X +def load_image(path): + img = Image.open(path) + if img._getexif() is not None: + for orientation in ExifTags.TAGS.keys(): + if ExifTags.TAGS[orientation] == 'Orientation': + break + + exif = dict(img._getexif().items()) + if orientation in exif.keys(): + if exif[orientation] == 3: + img = img.rotate(180, expand=True) + elif exif[orientation] == 6: + img = img.rotate(270, expand=True) + elif exif[orientation] == 8: + img = img.rotate(90, expand=True) + else: + pass + img = img.convert('RGB') + image_array = image.img_to_array(img) + + return image_array + + +class Faces(object): + def __init__(self, image_paths, sess): + self.aligner = aligner(sess) + self.org_faces = [] + self.cropped_faces = [] + self.cropped_faces_shape = [] + self.cropped_index = [] + self.callback_idx = [] + for i, p in enumerate(image_paths): + cur_img = load_image(p) + self.org_faces.append(cur_img) + align_img = align(cur_img, self.aligner, margin=0.7) + cur_faces = align_img[0] + + cur_shapes = [f.shape[:-1] for f in cur_faces] + + cur_faces_square = [] + for img in cur_faces: + long_size = max([img.shape[1], img.shape[0]]) + base = np.zeros((long_size, long_size, 3)) + base[0:img.shape[0], 0:img.shape[1], :] = img + cur_faces_square.append(base) + + cur_index = align_img[1] + cur_faces_square = [resize(f, (224, 224)) for f in cur_faces_square] + self.cropped_faces_shape.extend(cur_shapes) + self.cropped_faces.extend(cur_faces_square) + self.cropped_index.extend(cur_index) + self.callback_idx.extend([i] * len(cur_faces_square)) + + self.cropped_faces = preprocess_input(np.array(self.cropped_faces)) + self.cloaked_cropped_faces = None + self.cloaked_faces = np.copy(self.org_faces) + + def get_faces(self): + return self.cropped_faces + + def merge_faces(self, cloaks): + # import pdb + # pdb.set_trace() + + self.cloaked_faces = np.copy(self.org_faces) + + for i in range(len(self.cropped_faces)): + cur_cloak = cloaks[i] + org_shape = self.cropped_faces_shape[i] + old_square_shape = max([org_shape[0], org_shape[1]]) + reshape_cloak = resize(cur_cloak, (old_square_shape, old_square_shape)) + reshape_cloak = reshape_cloak[0:org_shape[0], 0:org_shape[1], :] + + callback_id = self.callback_idx[i] + bb = self.cropped_index[i] + self.cloaked_faces[callback_id][bb[1]:bb[3], bb[0]:bb[2], :] += reshape_cloak + + return self.cloaked_faces + + def dump_dictionary_as_json(dict, outfile): j = json.dumps(dict) with open(outfile, "wb") as f: @@ -30,10 +116,12 @@ def dump_dictionary_as_json(dict, outfile): def fix_gpu_memory(mem_fraction=1): os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' - gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=mem_fraction) - tf_config = tf.ConfigProto(gpu_options=gpu_options) - tf_config.gpu_options.allow_growth = True - tf_config.log_device_placement = False + tf_config = None + if tf.test.is_gpu_available(): + gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=mem_fraction) + tf_config = tf.ConfigProto(gpu_options=gpu_options) + tf_config.gpu_options.allow_growth = True + tf_config.log_device_placement = False init_op = tf.global_variables_initializer() sess = tf.Session(config=tf_config) sess.run(init_op) @@ -45,7 +133,6 @@ def load_victim_model(number_classes, teacher_model=None, end2end=False): for l in teacher_model.layers: l.trainable = end2end x = teacher_model.layers[-1].output - x = Dense(number_classes)(x) x = Activation('softmax', name="act")(x) model = Model(teacher_model.input, x) @@ -141,6 +228,7 @@ def imagenet_preprocessing(x, data_format=None): return x + def imagenet_reverse_preprocessing(x, data_format=None): import keras.backend as K x = np.array(x) @@ -185,7 +273,20 @@ def build_bottleneck_model(model, cut_off): def load_extractor(name): - model = keras.models.load_model("../feature_extractors/{}.h5".format(name)) + model_dir = os.path.join(os.path.expanduser('~'), '.fawkes') + os.makedirs(model_dir, exist_ok=True) + model_file = os.path.join(model_dir, "{}.h5".format(name)) + if os.path.exists(model_file): + model = keras.models.load_model(model_file) + else: + get_file("{}.h5".format(name), "http://sandlab.cs.uchicago.edu/fawkes/files/{}.h5".format(name), + cache_dir=model_dir, cache_subdir='') + + get_file("{}_emb.p.gz".format(name), "http://sandlab.cs.uchicago.edu/fawkes/files/{}_emb.p.gz".format(name), + cache_dir=model_dir, cache_subdir='') + + model = keras.models.load_model(model_file) + if hasattr(model.layers[-1], "activation") and model.layers[-1].activation == "softmax": raise Exception( "Given extractor's last layer is softmax, need to remove the top layers to make it into a feature extractor") @@ -199,11 +300,13 @@ def load_extractor(name): return model + def get_dataset_path(dataset): - if not os.path.exists("config.json"): + model_dir = os.path.join(os.path.expanduser('~'), '.fawkes') + if not os.path.exists(os.path.join(model_dir, "config.json")): raise Exception("Please config the datasets before running protection code. See more in README and config.py.") - config = json.load(open("config.json", 'r')) + config = json.load(open(os.path.join(model_dir, "config.json"), 'r')) if dataset not in config: raise Exception( "Dataset {} does not exist, please download to data/ and add the path to this function... Abort".format( @@ -217,7 +320,8 @@ def normalize(x): def dump_image(x, filename, format="png", scale=False): - img = image.array_to_img(x, scale=scale) + # img = image.array_to_img(x, scale=scale) + img = image.array_to_img(x) img.save(filename, format) return @@ -235,9 +339,13 @@ def load_dir(path): def load_embeddings(feature_extractors_names): + model_dir = os.path.join(os.path.expanduser('~'), '.fawkes') dictionaries = [] for extractor_name in feature_extractors_names: - path2emb = pickle.load(open("../feature_extractors/embeddings/{}_emb_norm.p".format(extractor_name), "rb")) + fp = gzip.open(os.path.join(model_dir, "{}_emb.p.gz".format(extractor_name)), 'rb') + path2emb = pickle.load(fp) + fp.close() + dictionaries.append(path2emb) merge_dict = {} @@ -272,6 +380,8 @@ def calculate_dist_score(a, b, feature_extractors_ls, metric='l2'): def select_target_label(imgs, feature_extractors_ls, feature_extractors_names, metric='l2'): + model_dir = os.path.join(os.path.expanduser('~'), '.fawkes') + original_feature_x = extractor_ls_predict(feature_extractors_ls, imgs) path2emb = load_embeddings(feature_extractors_names) @@ -282,37 +392,25 @@ def select_target_label(imgs, feature_extractors_ls, feature_extractors_names, m pair_dist = pairwise_distances(original_feature_x, embs, metric) max_sum = np.min(pair_dist, axis=0) - sorted_idx = np.argsort(max_sum)[::-1] + max_id = np.argmax(max_sum) - highest_num = 0 - paired_target_X = None - final_target_class_path = None - for idx in sorted_idx[:1]: - target_class_path = paths[idx] - cur_target_X = load_dir(target_class_path) - cur_target_X = np.concatenate([cur_target_X, cur_target_X, cur_target_X]) - cur_tot_sum, cur_paired_target_X = calculate_dist_score(imgs, cur_target_X, - feature_extractors_ls, - metric=metric) - if cur_tot_sum > highest_num: - highest_num = cur_tot_sum - paired_target_X = cur_paired_target_X + image_paths = glob.glob(os.path.join(model_dir, "target_data/{}/*".format(paths[int(max_id)]))) + target_images = [image.img_to_array(image.load_img(cur_path)) for cur_path in + image_paths] + target_images = preprocess_input(np.array([resize(x, (224, 224)) for x in target_images])) - np.random.shuffle(paired_target_X) - paired_target_X = list(paired_target_X) - while len(paired_target_X) < len(imgs): - paired_target_X += paired_target_X - - paired_target_X = paired_target_X[:len(imgs)] - return np.array(paired_target_X) + target_images = list(target_images) + while len(target_images) < len(imgs): + target_images += target_images + target_images = random.sample(target_images, len(imgs)) + return np.array(target_images) class CloakData(object): def __init__(self, protect_directory=None, img_shape=(224, 224)): self.img_shape = img_shape - # self.train_data_dir, self.test_data_dir, self.number_classes, self.number_samples = get_dataset_path(dataset) # self.all_labels = sorted(list(os.listdir(self.train_data_dir))) self.protect_directory = protect_directory diff --git a/fawkes_dev/README.md b/fawkes_dev/README.md index 0e8e9cb..32dc61d 100644 --- a/fawkes_dev/README.md +++ b/fawkes_dev/README.md @@ -52,6 +52,7 @@ We shared three different feature extractors under feature_extractors/ 1. low_extract.h5: trained on WebFace dataset with DenseNet architecture. 2. mid_extract.h5: VGGFace2 dataset with DenseNet architecture. Trained with PGD adversarial training for 5 epochs. 3. high_extract.h5: WebFace dataset with DenseNet architecture. Trained with PGD adversarial training for 20 epochs. +4. high2_extract.h5: VGGFace2 dataset with DenseNet architecture. Trained with PGD adversarial training for 20 epochs. ### Citation ``` diff --git a/fawkes_dev/azure.py b/fawkes_dev/azure.py new file mode 100644 index 0000000..a0f3149 --- /dev/null +++ b/fawkes_dev/azure.py @@ -0,0 +1,399 @@ + +import http.client, urllib.request, urllib.parse, urllib.error +import json +import time + +#Face API Key and Endpoint +subscription_key = 'e127e26e4d534e2bad6fd9ca06145302' +uri_base = 'eastus.api.cognitive.microsoft.com' +# uri_base = 'https://shawn.cognitiveservices.azure.com/' + +def detect_face(image_url): + headers = { + # Request headers + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + # Request parameters + 'returnFaceId': 'true', + 'returnFaceLandmarks': 'false', + 'recognitionModel': 'recognition_01', + 'returnRecognitionModel': 'false', + 'detectionModel': 'detection_01', + }) + + body = json.dumps({ + 'url': image_url + }) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("POST", "/face/v1.0/detect?%s" % params, body, headers) + response = conn.getresponse() + data = json.loads(response.read()) + conn.close() + return data[0]["faceId"] + + +def verify_face(faceId, personGroupId, personId): + # html header + headers = { + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + # image URL + body = json.dumps({ + "faceId": faceId, + "personId": personId, + "PersonGroupId": personGroupId + }) + + # Call Face API + conn = http.client.HTTPSConnection(uri_base) + conn.request("POST", "/face/v1.0/verify?%s" % params, body, headers) + response = conn.getresponse() + data = json.loads(response.read()) + conn.close() + return data + + +def create_personGroupId(personGroupId, personGroupName): + headers = { + # Request headers + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({ + "name": personGroupName + }) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("PUT", "/face/v1.0/persongroups/{}?%s".format(personGroupId) % params, body, headers) + response = conn.getresponse() + data = response.read() + print(data) + conn.close() + + +def create_personId(personGroupId, personName): + headers = { + # Request headers + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({ + "name": personName + }) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("POST", "/face/v1.0/persongroups/{}/persons?%s".format(personGroupId) % params, body, headers) + response = conn.getresponse() + data = json.loads(response.read()) + print(data) + conn.close() + return data["personId"] + + +def add_persistedFaceId(personGroupId, personId, image_url): + headers = { + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + 'personGroupId': personGroupId, + 'personId': personId + }) + + body = json.dumps({ + 'url': image_url + }) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("POST", "/face/v1.0/persongroups/{}/persons/{}/persistedFaces?%s".format(personGroupId, personId) % params, body, headers) + response = conn.getresponse() + data = json.loads(response.read()) + print(data) + conn.close() + return data["persistedFaceId"] + + +def list_personGroupPerson(personGroupId): + headers = { + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({}) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("GET", "/face/v1.0/persongroups/{}/persons?%s".format(personGroupId) % params, body, headers) + response = conn.getresponse() + data = json.loads(response.read()) + conn.close() + for person in data: + print(person["personId"], len(person["persistedFaceIds"])) + + +def get_personGroupPerson(personGroupId, personId): + headers = { + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({}) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("GET", "/face/v1.0/persongroups/{}/persons/{}?%s".format(personGroupId, personId) % params, body, headers) + response = conn.getresponse() + data = json.loads(response.read()) + print(data) + conn.close() + + +def train_personGroup(personGroupId): + headers = { + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({}) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("POST", "/face/v1.0/persongroups/{}/train?%s".format(personGroupId) % params, body, headers) + response = conn.getresponse() + data = response.read() + print(data) + conn.close() + + +def eval(original_faceIds, personGroupId, protect_personId): + headers = { + 'Content-Type': 'application/json', + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({ + 'faceIds': original_faceIds, + 'personGroupId': personGroupId, + 'maxNumOfCandidatesReturned': 1 + }) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("POST", "/face/v1.0/identify?%s" % params, body, headers) + response = conn.getresponse() + data = json.loads(response.read()) + conn.close() + + face = data[0] + if len(face["candidates"]) and face["candidates"][0]["personId"] == protect_personId: + return True + else: + return False + + +def delete_personGroupPerson(personGroupId, personId): + headers = { + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({}) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("DELETE", "/face/v1.0/persongroups/{}/persons/{}?%s".format(personGroupId, personId) % params, body, headers) + response = conn.getresponse() + data = response.read() + print(data) + conn.close() + + +def add_protect_person(personGroupId, name): + personId = create_personId(personGroupId, name) + for idx in range(72): + cloaked_image_url = "https://super.cs.uchicago.edu/~shawn/cloaked/{}_c.png".format(idx) + add_persistedFaceId(personGroupId, personId, cloaked_image_url) + + +def add_sybil_person(personGroupId, name): + personId = create_personId(personGroupId, name) + for idx in range(82): + try: + cloaked_image_url = "https://super.cs.uchicago.edu/~shawn/sybils/{}_c.png".format(idx) + add_persistedFaceId(personGroupId, personId, cloaked_image_url) + except: + print(idx) + + +def add_other_person(personGroupId): + for idx_person in range(65): + personId = create_personId(personGroupId, str(idx_person)) + for idx_image in range(90): + try: + image_url = "https://super.cs.uchicago.edu/~shawn/train/{}/{}.png".format(idx_person, idx_image) + add_persistedFaceId(personGroupId, personId, image_url) + except: + print(idx_person, idx_image) + + +def get_trainStatus(personGroupId): + headers = { + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({}) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("GET", "/face/v1.0/persongroups/{}/training?%s".format(personGroupId) % params, body, headers) + response = conn.getresponse() + data = response.read() + print(data) + conn.close() + + +def test_original(): + personGroupId = 'pubfig' + # create_personGroupId(personGroupId, 'pubfig') + # add protect person + protect_personId = 'd3df3012-6f3f-4c1b-b86d-55e91a352e01' + #protect_personId = create_personId(personGroupId, 'Emily') + #for idx in range(50): + # image_url = "https://super.cs.uchicago.edu/~shawn/cloaked/{}_o.png".format(idx) + # add_persistedFaceId(personGroupId, protect_personId, image_url) + + # add other people + #for idx_person in range(65): + # personId = create_personId(personGroupId, str(idx_person)) + # for idx_image in range(50): + # try: + # image_url = "https://super.cs.uchicago.edu/~shawn/train/{}/{}.png".format(idx_person, idx_image) + # add_persistedFaceId(personGroupId, personId, image_url) + # except: + # print(idx_person, idx_image) + + + # train model based on personGroup + #train_personGroup(personGroupId) + #time.sleep(3) + #get_trainStatus(personGroupId) + #list_personGroupPerson(personGroupId) + + idx_range = range(50, 82) + acc = 0. + + for idx in idx_range: + original_image_url = "https://super.cs.uchicago.edu/~shawn/cloaked/{}_o.png".format(idx) + faceId = detect_face(original_image_url) + original_faceIds = [faceId] + + # verify + res = eval(original_faceIds, personGroupId, protect_personId) + if res: + acc += 1. + + acc /= len(idx_range) + print(acc) # 1.0 + + +def list_personGroups(): + headers = { + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({}) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("GET", "/face/v1.0/persongroups?%s" % params, body, headers) + response = conn.getresponse() + data = response.read() + print(data) + conn.close() + + +def delete_personGroup(personGroupId): + headers = { + 'Ocp-Apim-Subscription-Key': subscription_key, + } + + params = urllib.parse.urlencode({ + }) + + body = json.dumps({}) + + conn = http.client.HTTPSConnection(uri_base) + conn.request("DELETE", "/face/v1.0/persongroups/{}?%s".format(personGroupId) % params, body, headers) + response = conn.getresponse() + data = response.read() + print(data) + conn.close() + + + +def main(): + # delete_personGroup('cloaking') + # delete_personGroup('cloaking-emily') + # delete_personGroup('pubfig') + # list_personGroups() + # exit() + personGroupId = 'cloaking' + # create_personGroupId(personGroupId, 'cloaking') + list_personGroups() + exit() + #delete_personGroupPerson(personGroupId, '0ac606cd-24b3-440f-866a-31adf2a1b446') + #add_protect_person(personGroupId, 'Emily') + #personId = create_personId(personGroupId, 'Emily') + #add_sybil_person(personGroupId, 'sybil') + protect_personId = '6c5a71eb-f39a-4570-b3f5-72cca3ab5a6b' + #delete_personGroupPerson(personGroupId, protect_personId) + #add_protect_person(personGroupId, 'Emily') + + # train model based on personGroup + #train_personGroup(personGroupId) + get_trainStatus(personGroupId) + #add_other_person(personGroupId) + #list_personGroupPerson(personGroupId) + #delete_personGroupPerson(personGroupId, '80e32c80-bc69-416a-9dff-c8d42d7a3301') + + idx_range = range(72, 82) + original_faceIds = [] + for idx in idx_range: + original_image_url = "https://super.cs.uchicago.edu/~shawn/cloaked/{}_o.png".format(idx) + faceId = detect_face(original_image_url) + original_faceIds.append(faceId) + + # verify + eval(original_faceIds, personGroupId, protect_personId) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/fawkes_dev/config.py b/fawkes_dev/config.py index cd14f4e..97fd495 100644 --- a/fawkes_dev/config.py +++ b/fawkes_dev/config.py @@ -4,7 +4,7 @@ import os DATASETS = { "pubfig": "../data/pubfig", - "scrub": "/home/shansixioing/cloak/fawkes/data/scrub/", + "scrub": "/home/shansixioing/fawkes/data/scrub/", "vggface2": "/mnt/data/sixiongshan/data/vggface2/", "webface": "/mnt/data/sixiongshan/data/webface/", "youtubeface": "/mnt/data/sixiongshan/data/youtubeface/keras_flow_data/", @@ -32,7 +32,8 @@ def main(): "num_images": num_images} print("Successfully config {}".format(dataset)) j = json.dumps(config) - with open("config.json", "wb") as f: + model_dir = os.path.join(os.path.expanduser('~'), '.fawkes') + with open(os.path.join(model_dir, "config.json"), "wb") as f: f.write(j.encode()) diff --git a/fawkes_dev/differentiator.py b/fawkes_dev/differentiator.py deleted file mode 100644 index 2b61764..0000000 --- a/fawkes_dev/differentiator.py +++ /dev/null @@ -1,430 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# @Date : 2020-05-17 -# @Author : Shawn Shan (shansixiong@cs.uchicago.edu) -# @Link : https://www.shawnshan.com/ - -import datetime -import time -from decimal import Decimal - -import numpy as np -import tensorflow as tf -from utils import preprocess, reverse_preprocess - - -class FawkesMaskGeneration: - # if the attack is trying to mimic a target image or a neuron vector - MIMIC_IMG = True - # number of iterations to perform gradient descent - MAX_ITERATIONS = 10000 - # larger values converge faster to less accurate results - LEARNING_RATE = 1e-2 - # the initial constant c to pick as a first guess - INITIAL_CONST = 1 - # pixel intensity range - INTENSITY_RANGE = 'imagenet' - # threshold for distance - L_THRESHOLD = 0.03 - # whether keep the final result or the best result - KEEP_FINAL = False - # max_val of image - MAX_VAL = 255 - # The following variables are used by DSSIM, should keep as default - # filter size in SSIM - FILTER_SIZE = 11 - # filter sigma in SSIM - FILTER_SIGMA = 1.5 - # weights used in MS-SSIM - SCALE_WEIGHTS = None - MAXIMIZE = False - IMAGE_SHAPE = (224, 224, 3) - RATIO = 1.0 - LIMIT_DIST = False - - def __init__(self, sess, bottleneck_model_ls, mimic_img=MIMIC_IMG, - batch_size=1, learning_rate=LEARNING_RATE, - max_iterations=MAX_ITERATIONS, initial_const=INITIAL_CONST, - intensity_range=INTENSITY_RANGE, l_threshold=L_THRESHOLD, - max_val=MAX_VAL, keep_final=KEEP_FINAL, maximize=MAXIMIZE, image_shape=IMAGE_SHAPE, - verbose=0, ratio=RATIO, limit_dist=LIMIT_DIST): - - assert intensity_range in {'raw', 'imagenet', 'inception', 'mnist'} - - # constant used for tanh transformation to avoid corner cases - self.tanh_constant = 2 - 1e-6 - self.sess = sess - self.MIMIC_IMG = mimic_img - self.LEARNING_RATE = learning_rate - self.MAX_ITERATIONS = max_iterations - self.initial_const = initial_const - self.batch_size = batch_size - self.intensity_range = intensity_range - self.l_threshold = l_threshold - self.max_val = max_val - self.keep_final = keep_final - self.verbose = verbose - self.maximize = maximize - self.learning_rate = learning_rate - self.ratio = ratio - self.limit_dist = limit_dist - self.single_shape = list(image_shape) - - self.input_shape = tuple([self.batch_size] + self.single_shape) - - self.bottleneck_shape = tuple([self.batch_size] + self.single_shape) - - # the variable we're going to optimize over - self.modifier = tf.Variable(np.zeros(self.input_shape, dtype=np.float32)) - - # target image in tanh space - if self.MIMIC_IMG: - self.timg_tanh = tf.Variable(np.zeros(self.input_shape), dtype=np.float32) - else: - self.bottleneck_t_raw = tf.Variable(np.zeros(self.bottleneck_shape), dtype=np.float32) - # source image in tanh space - self.simg_tanh = tf.Variable(np.zeros(self.input_shape), dtype=np.float32) - - self.const = tf.Variable(np.ones(batch_size), dtype=np.float32) - self.mask = tf.Variable(np.ones((batch_size), dtype=np.bool)) - self.weights = tf.Variable(np.ones(self.bottleneck_shape, - dtype=np.float32)) - - # and here's what we use to assign them - self.assign_modifier = tf.placeholder(tf.float32, self.input_shape) - if self.MIMIC_IMG: - self.assign_timg_tanh = tf.placeholder( - tf.float32, self.input_shape) - else: - self.assign_bottleneck_t_raw = tf.placeholder( - tf.float32, self.bottleneck_shape) - self.assign_simg_tanh = tf.placeholder(tf.float32, self.input_shape) - self.assign_const = tf.placeholder(tf.float32, (batch_size)) - self.assign_mask = tf.placeholder(tf.bool, (batch_size)) - self.assign_weights = tf.placeholder(tf.float32, self.bottleneck_shape) - - # the resulting image, tanh'd to keep bounded from -0.5 to 0.5 - # adversarial image in raw space - self.aimg_raw = (tf.tanh(self.modifier + self.simg_tanh) / - self.tanh_constant + - 0.5) * 255.0 - # source image in raw space - self.simg_raw = (tf.tanh(self.simg_tanh) / - self.tanh_constant + - 0.5) * 255.0 - if self.MIMIC_IMG: - # target image in raw space - self.timg_raw = (tf.tanh(self.timg_tanh) / - self.tanh_constant + - 0.5) * 255.0 - - # convert source and adversarial image into input space - if self.intensity_range == 'imagenet': - mean = tf.constant(np.repeat([[[[103.939, 116.779, 123.68]]]], self.batch_size, axis=0), dtype=tf.float32, - name='img_mean') - self.aimg_input = (self.aimg_raw[..., ::-1] - mean) - self.simg_input = (self.simg_raw[..., ::-1] - mean) - if self.MIMIC_IMG: - self.timg_input = (self.timg_raw[..., ::-1] - mean) - - elif self.intensity_range == 'raw': - self.aimg_input = self.aimg_raw - self.simg_input = self.simg_raw - if self.MIMIC_IMG: - self.timg_input = self.timg_raw - - def batch_gen_DSSIM(aimg_raw_split, simg_raw_split): - msssim_split = tf.image.ssim(aimg_raw_split, simg_raw_split, max_val=255.0) - dist = (1.0 - tf.stack(msssim_split)) / 2.0 - return dist - - # raw value of DSSIM distance - self.dist_raw = batch_gen_DSSIM(self.aimg_raw, self.simg_raw) - # distance value after applying threshold - self.dist = tf.maximum(self.dist_raw - self.l_threshold, 0.0) - - self.dist_raw_sum = tf.reduce_sum( - tf.where(self.mask, - self.dist_raw, - tf.zeros_like(self.dist_raw))) - self.dist_sum = tf.reduce_sum(tf.where(self.mask, self.dist, tf.zeros_like(self.dist))) - - def resize_tensor(input_tensor, model_input_shape): - if input_tensor.shape[1:] == model_input_shape or model_input_shape[1] is None: - return input_tensor - resized_tensor = tf.image.resize(input_tensor, model_input_shape[:2]) - return resized_tensor - - def calculate_direction(bottleneck_model, cur_timg_input, cur_simg_input): - target_features = bottleneck_model(cur_timg_input) - return target_features - # target_center = tf.reduce_mean(target_features, axis=0) - # original = bottleneck_model(cur_simg_input) - # original_center = tf.reduce_mean(original, axis=0) - # direction = target_center - original_center - # final_target = original + self.ratio * direction - # return final_target - - self.bottlesim = 0.0 - self.bottlesim_sum = 0.0 - self.bottlesim_push = 0.0 - for bottleneck_model in bottleneck_model_ls: - model_input_shape = bottleneck_model.input_shape[1:] - cur_aimg_input = resize_tensor(self.aimg_input, model_input_shape) - - self.bottleneck_a = bottleneck_model(cur_aimg_input) - if self.MIMIC_IMG: - # cur_timg_input = resize_tensor(self.timg_input, model_input_shape) - # cur_simg_input = resize_tensor(self.simg_input, model_input_shape) - cur_timg_input = self.timg_input - cur_simg_input = self.simg_input - self.bottleneck_t = calculate_direction(bottleneck_model, cur_timg_input, cur_simg_input) - # self.bottleneck_t = bottleneck_model(cur_timg_input) - else: - self.bottleneck_t = self.bottleneck_t_raw - - bottleneck_diff = self.bottleneck_t - self.bottleneck_a - scale_factor = tf.sqrt(tf.reduce_sum(tf.square(self.bottleneck_t), axis=1)) - - cur_bottlesim = tf.sqrt(tf.reduce_sum(tf.square(bottleneck_diff), axis=1)) - cur_bottlesim = cur_bottlesim / scale_factor - cur_bottlesim_sum = tf.reduce_sum(cur_bottlesim) - - self.bottlesim += cur_bottlesim - - # self.bottlesim_push += cur_bottlesim_push_sum - self.bottlesim_sum += cur_bottlesim_sum - - # sum up the losses - if self.maximize: - self.loss = self.const * tf.square(self.dist) - self.bottlesim - else: - self.loss = self.const * tf.square(self.dist) + self.bottlesim - - self.loss_sum = tf.reduce_sum(tf.where(self.mask, self.loss, tf.zeros_like(self.loss))) - - # Setup the Adadelta optimizer and keep track of variables - # we're creating - start_vars = set(x.name for x in tf.global_variables()) - self.learning_rate_holder = tf.placeholder(tf.float32, shape=[]) - optimizer = tf.train.AdadeltaOptimizer(self.learning_rate_holder) - - self.train = optimizer.minimize(self.loss_sum, - var_list=[self.modifier]) - end_vars = tf.global_variables() - new_vars = [x for x in end_vars if x.name not in start_vars] - - # these are the variables to initialize when we run - self.setup = [] - self.setup.append(self.modifier.assign(self.assign_modifier)) - if self.MIMIC_IMG: - self.setup.append(self.timg_tanh.assign(self.assign_timg_tanh)) - else: - self.setup.append(self.bottleneck_t_raw.assign( - self.assign_bottleneck_t_raw)) - self.setup.append(self.simg_tanh.assign(self.assign_simg_tanh)) - self.setup.append(self.const.assign(self.assign_const)) - self.setup.append(self.mask.assign(self.assign_mask)) - self.setup.append(self.weights.assign(self.assign_weights)) - - self.init = tf.variables_initializer(var_list=[self.modifier] + new_vars) - - print('Attacker loaded') - - def preprocess_arctanh(self, imgs): - - imgs = reverse_preprocess(imgs, self.intensity_range) - imgs /= 255.0 - imgs -= 0.5 - imgs *= self.tanh_constant - tanh_imgs = np.arctanh(imgs) - - return tanh_imgs - - def clipping(self, imgs): - - imgs = reverse_preprocess(imgs, self.intensity_range) - imgs = np.clip(imgs, 0, self.max_val) - imgs = np.rint(imgs) - - imgs = preprocess(imgs, self.intensity_range) - - return imgs - - def attack(self, source_imgs, target_imgs, weights=None): - - if weights is None: - weights = np.ones([source_imgs.shape[0]] + - list(self.bottleneck_shape[1:])) - - assert weights.shape[1:] == self.bottleneck_shape[1:] - assert source_imgs.shape[1:] == self.input_shape[1:] - assert source_imgs.shape[0] == weights.shape[0] - if self.MIMIC_IMG: - assert target_imgs.shape[1:] == self.input_shape[1:] - assert source_imgs.shape[0] == target_imgs.shape[0] - else: - assert target_imgs.shape[1:] == self.bottleneck_shape[1:] - assert source_imgs.shape[0] == target_imgs.shape[0] - - start_time = time.time() - - adv_imgs = [] - print('%d batches in total' - % int(np.ceil(len(source_imgs) / self.batch_size))) - - for idx in range(0, len(source_imgs), self.batch_size): - print('processing batch %d at %s' % (idx, datetime.datetime.now())) - adv_img = self.attack_batch(source_imgs[idx:idx + self.batch_size], - target_imgs[idx:idx + self.batch_size], - weights[idx:idx + self.batch_size]) - adv_imgs.extend(adv_img) - - elapsed_time = time.time() - start_time - print('attack cost %f s' % (elapsed_time)) - - return np.array(adv_imgs) - - def attack_batch(self, source_imgs, target_imgs, weights): - - """ - Run the attack on a batch of images and labels. - """ - - LR = self.learning_rate - nb_imgs = source_imgs.shape[0] - mask = [True] * nb_imgs + [False] * (self.batch_size - nb_imgs) - mask = np.array(mask, dtype=np.bool) - - source_imgs = np.array(source_imgs) - target_imgs = np.array(target_imgs) - - # convert to tanh-space - simg_tanh = self.preprocess_arctanh(source_imgs) - if self.MIMIC_IMG: - timg_tanh = self.preprocess_arctanh(target_imgs) - else: - timg_tanh = target_imgs - - CONST = np.ones(self.batch_size) * self.initial_const - - self.sess.run(self.init) - simg_tanh_batch = np.zeros(self.input_shape) - if self.MIMIC_IMG: - timg_tanh_batch = np.zeros(self.input_shape) - else: - timg_tanh_batch = np.zeros(self.bottleneck_shape) - weights_batch = np.zeros(self.bottleneck_shape) - simg_tanh_batch[:nb_imgs] = simg_tanh[:nb_imgs] - timg_tanh_batch[:nb_imgs] = timg_tanh[:nb_imgs] - weights_batch[:nb_imgs] = weights[:nb_imgs] - modifier_batch = np.ones(self.input_shape) * 1e-6 - - # set the variables so that we don't have to send them over again - if self.MIMIC_IMG: - self.sess.run(self.setup, - {self.assign_timg_tanh: timg_tanh_batch, - self.assign_simg_tanh: simg_tanh_batch, - self.assign_const: CONST, - self.assign_mask: mask, - self.assign_weights: weights_batch, - self.assign_modifier: modifier_batch}) - else: - # if directly mimicking a vector, use assign_bottleneck_t_raw - # in setup - self.sess.run(self.setup, - {self.assign_bottleneck_t_raw: timg_tanh_batch, - self.assign_simg_tanh: simg_tanh_batch, - self.assign_const: CONST, - self.assign_mask: mask, - self.assign_weights: weights_batch, - self.assign_modifier: modifier_batch}) - - best_bottlesim = [0] * nb_imgs if self.maximize else [np.inf] * nb_imgs - best_adv = np.zeros_like(source_imgs) - - if self.verbose == 1: - loss_sum = float(self.sess.run(self.loss_sum)) - dist_sum = float(self.sess.run(self.dist_sum)) - thresh_over = (dist_sum / self.batch_size / self.l_threshold * 100) - dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) - bottlesim_sum = self.sess.run(self.bottlesim_sum) - print('START: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' - % (Decimal(loss_sum), - dist_sum, - thresh_over, - dist_raw_sum, - bottlesim_sum / nb_imgs)) - - try: - total_distance = [0] * nb_imgs - - if self.limit_dist: - dist_raw_list, bottlesim_list, aimg_input_list = self.sess.run( - [self.dist_raw, - self.bottlesim, - self.aimg_input]) - for e, (dist_raw, bottlesim, aimg_input) in enumerate( - zip(dist_raw_list, bottlesim_list, aimg_input_list)): - if e >= nb_imgs: - break - total_distance[e] = bottlesim - - for iteration in range(self.MAX_ITERATIONS): - - self.sess.run([self.train], feed_dict={self.learning_rate_holder: LR}) - - dist_raw_list, bottlesim_list, aimg_input_list = self.sess.run( - [self.dist_raw, - self.bottlesim, - self.aimg_input]) - for e, (dist_raw, bottlesim, aimg_input) in enumerate( - zip(dist_raw_list, bottlesim_list, aimg_input_list)): - if e >= nb_imgs: - break - if (bottlesim < best_bottlesim[e] and bottlesim > total_distance[e] * 0.1 and ( - not self.maximize)) or ( - bottlesim > best_bottlesim[e] and self.maximize): - best_bottlesim[e] = bottlesim - best_adv[e] = aimg_input - - if iteration != 0 and iteration % (self.MAX_ITERATIONS // 3) == 0: - LR = LR / 2 - print("Learning Rate: ", LR) - - if iteration % (self.MAX_ITERATIONS // 10) == 0: - if self.verbose == 1: - loss_sum = float(self.sess.run(self.loss_sum)) - dist_sum = float(self.sess.run(self.dist_sum)) - thresh_over = (dist_sum / - self.batch_size / - self.l_threshold * - 100) - dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) - bottlesim_sum = self.sess.run(self.bottlesim_sum) - print('ITER %4d: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' - % (iteration, - Decimal(loss_sum), - dist_sum, - thresh_over, - dist_raw_sum, - bottlesim_sum / nb_imgs)) - except KeyboardInterrupt: - pass - - if self.verbose == 1: - loss_sum = float(self.sess.run(self.loss_sum)) - dist_sum = float(self.sess.run(self.dist_sum)) - thresh_over = (dist_sum / self.batch_size / self.l_threshold * 100) - dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) - bottlesim_sum = float(self.sess.run(self.bottlesim_sum)) - print('END: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' - % (Decimal(loss_sum), - dist_sum, - thresh_over, - dist_raw_sum, - bottlesim_sum / nb_imgs)) - - best_adv = self.clipping(best_adv[:nb_imgs]) - - return best_adv diff --git a/fawkes_dev/eval_cloak.py b/fawkes_dev/eval_cloak.py index 0446449..e7d31e3 100644 --- a/fawkes_dev/eval_cloak.py +++ b/fawkes_dev/eval_cloak.py @@ -1,25 +1,18 @@ +import argparse +import os import sys -sys.path.append("/home/shansixioing/tools/") -sys.path.append("/home/shansixioing/cloak/") - -import argparse -from tensorflow import set_random_seed -from utils import init_gpu, load_extractor, load_victim_model, dump_dictionary_as_json -import os import numpy as np + +sys.path.append("/home/shansixioing/fawkes/fawkes") +from utils import extract_faces, get_dataset_path, init_gpu, load_extractor, load_victim_model + import random -import pickle -import re +import glob from keras.preprocessing import image from keras.utils import to_categorical from keras.applications.vgg16 import preprocess_input -# import locale -# -# loc = locale.getlocale() -# locale.setlocale(locale.LC_ALL, loc) - def select_samples(data_dir): all_data_path = [] @@ -27,43 +20,49 @@ def select_samples(data_dir): cls_dir = os.path.join(data_dir, cls) for data_path in os.listdir(cls_dir): all_data_path.append(os.path.join(cls_dir, data_path)) - return all_data_path -def generator_wrap(cloak_data, n_classes, test=False, validation_split=0.1): - if test: - all_data_path = select_samples(cloak_data.test_data_dir) - else: - all_data_path = select_samples(cloak_data.train_data_dir) - split = int(len(cloak_data.cloaked_protect_train_X) * (1 - validation_split)) - cloaked_train_X = cloak_data.cloaked_protect_train_X[:split] - np.random.seed(12345) +def generator_wrap(protect_images, test=False, validation_split=0.1): + train_data_dir, test_data_dir, num_classes, num_images = get_dataset_path(args.dataset) - # all_vals = list(cloak_data.path2idx.items()) + idx = 0 + path2class = {} + path2imgs_list = {} + + for target_path in sorted(glob.glob(train_data_dir + "/*")): + path2class[target_path] = idx + path2imgs_list[target_path] = glob.glob(os.path.join(target_path, "*")) + idx += 1 + if idx >= args.num_classes: + break + + path2class["protected"] = idx + + np.random.seed(12345) while True: batch_X = [] batch_Y = [] - cur_batch_path = np.random.choice(all_data_path, args.batch_size) + cur_batch_path = np.random.choice(list(path2class.keys()), args.batch_size) for p in cur_batch_path: - # p = p.encode("utf-8").decode("ascii", 'ignore') - cur_y = cloak_data.path2idx[p] - # protect class and sybil class do not need to appear in test dataset - if test and (re.search(cloak_data.protect_class, p)): + cur_y = path2class[p] + if test and p == 'protected': continue # protect class images in train dataset - elif p in cloak_data.protect_class_path: - cur_x = random.choice(cloaked_train_X) + elif p == 'protected': + cur_x = random.choice(protect_images) else: - im = image.load_img(p, target_size=cloak_data.img_shape) - im = image.img_to_array(im) - cur_x = preprocess_input(im) + cur_path = random.choice(path2imgs_list[p]) + im = image.load_img(cur_path, target_size=(224, 224)) + cur_x = image.img_to_array(im) + + cur_x = preprocess_input(cur_x) batch_X.append(cur_x) batch_Y.append(cur_y) batch_X = np.array(batch_X) - batch_Y = to_categorical(np.array(batch_Y), num_classes=n_classes) + batch_Y = to_categorical(np.array(batch_Y), num_classes=args.num_classes + 1) yield batch_X, batch_Y @@ -87,51 +86,59 @@ def eval_cloaked_test_data(cloak_data, n_classes, validation_split=0.1): def main(): init_gpu(args.gpu) - - if args.dataset == 'pubfig': - N_CLASSES = 65 - CLOAK_DIR = args.cloak_data - elif args.dataset == 'scrub': - N_CLASSES = 530 - CLOAK_DIR = args.cloak_data - else: - raise ValueError - - CLOAK_DIR = os.path.join("../results", CLOAK_DIR) - RES = pickle.load(open(os.path.join(CLOAK_DIR, "cloak_data.p"), 'rb')) + # + # if args.dataset == 'pubfig': + # N_CLASSES = 65 + # CLOAK_DIR = args.cloak_data + # elif args.dataset == 'scrub': + # N_CLASSES = 530 + # CLOAK_DIR = args.cloak_data + # else: + # raise ValueError print("Build attacker's model") - cloak_data = RES['cloak_data'] - EVAL_RES = {} - train_generator = generator_wrap(cloak_data, n_classes=N_CLASSES, + + image_paths = glob.glob(os.path.join(args.directory, "*")) + original_image_paths = sorted([path for path in image_paths if "_cloaked" not in path.split("/")[-1]]) + + protect_image_paths = sorted([path for path in image_paths if "_cloaked" in path.split("/")[-1]]) + + original_imgs = np.array([extract_faces(image.img_to_array(image.load_img(cur_path))) for cur_path in + original_image_paths[:150]]) + original_y = to_categorical([args.num_classes] * len(original_imgs), num_classes=args.num_classes + 1) + + protect_imgs = [extract_faces(image.img_to_array(image.load_img(cur_path))) for cur_path in + protect_image_paths] + + train_generator = generator_wrap(protect_imgs, validation_split=args.validation_split) - test_generator = generator_wrap(cloak_data, test=True, n_classes=N_CLASSES, + test_generator = generator_wrap(protect_imgs, test=True, validation_split=args.validation_split) - EVAL_RES['transfer_model'] = args.transfer_model - base_model = load_extractor(args.transfer_model) - model = load_victim_model(teacher_model=base_model, number_classes=N_CLASSES) + model = load_victim_model(teacher_model=base_model, number_classes=args.num_classes + 1) - original_X, original_Y = eval_uncloaked_test_data(cloak_data, N_CLASSES) - cloaked_test_X, cloaked_test_Y = eval_cloaked_test_data(cloak_data, N_CLASSES, - validation_split=args.validation_split) + # cloaked_test_X, cloaked_test_Y = eval_cloaked_test_data(cloak_data, args.num_classes, + # validation_split=args.validation_split) - try: - model.fit_generator(train_generator, steps_per_epoch=cloak_data.number_samples // 32, - validation_data=(original_X, original_Y), epochs=args.n_epochs, verbose=2, - use_multiprocessing=False, workers=1) - except KeyboardInterrupt: - pass + # try: + train_data_dir, test_data_dir, num_classes, num_images = get_dataset_path(args.dataset) + model.fit_generator(train_generator, steps_per_epoch=num_images // 32, + validation_data=(original_imgs, original_y), + epochs=args.n_epochs, + verbose=1, + use_multiprocessing=True, workers=5) + # except KeyboardInterrupt: + # pass - _, acc_original = model.evaluate(original_X, original_Y, verbose=0) + _, acc_original = model.evaluate(original_imgs, original_y, verbose=0) print("Accuracy on uncloaked/original images TEST: {:.4f}".format(acc_original)) - EVAL_RES['acc_original'] = acc_original + # EVAL_RES['acc_original'] = acc_original _, other_acc = model.evaluate_generator(test_generator, verbose=0, steps=50) print("Accuracy on other classes {:.4f}".format(other_acc)) - EVAL_RES['other_acc'] = other_acc - dump_dictionary_as_json(EVAL_RES, os.path.join(CLOAK_DIR, "eval_seed{}.json".format(args.seed_idx))) + # EVAL_RES['other_acc'] = other_acc + # dump_dictionary_as_json(EVAL_RES, os.path.join(CLOAK_DIR, "eval_seed{}.json".format(args.seed_idx))) def parse_arguments(argv): @@ -139,16 +146,21 @@ def parse_arguments(argv): parser.add_argument('--gpu', type=str, help='GPU id', default='0') + parser.add_argument('--dataset', type=str, help='name of dataset', default='scrub') - parser.add_argument('--cloak_data', type=str, + parser.add_argument('--num_classes', type=int, + help='name of dataset', default=520) + + parser.add_argument('--directory', '-d', type=str, help='name of the cloak result directory', - default='scrub_webface_dense_robust_extract_protectPatrick_Dempsey') + default='img/') + parser.add_argument('--transfer_model', type=str, - help='the feature extractor used for tracker model training. It can be the same or not same as the user\'s', default='vggface2_inception_extract') + help='the feature extractor used for tracker model training. ', default='low_extract') parser.add_argument('--batch_size', type=int, default=32) parser.add_argument('--validation_split', type=float, default=0.1) - parser.add_argument('--n_epochs', type=int, default=5) + parser.add_argument('--n_epochs', type=int, default=3) return parser.parse_args(argv) diff --git a/fawkes_dev/prepare_feature_extractor.py b/fawkes_dev/prepare_feature_extractor.py index 4867d07..5587d26 100644 --- a/fawkes_dev/prepare_feature_extractor.py +++ b/fawkes_dev/prepare_feature_extractor.py @@ -1,4 +1,5 @@ import argparse +import glob import os import pickle import random @@ -7,8 +8,9 @@ import sys import numpy as np from keras.applications.vgg16 import preprocess_input from keras.preprocessing import image -from utils import load_extractor, get_dataset_path - +sys.path.append("../fawkes") +# from utils import load_extractor +import keras def load_sample_dir(path, sample=10): x_ls = [] @@ -30,21 +32,26 @@ def normalize(x): def main(): - extractor = load_extractor(args.feature_extractor) + extractor = keras.models.load_model(args.feature_extractor) + path2emb = {} - for target_dataset in args.candidate_datasets: - target_dataset_path, _, _, _ = get_dataset_path(target_dataset) - for target_class in os.listdir(target_dataset_path): - target_class_path = os.path.join(target_dataset_path, target_class) - target_X = load_sample_dir(target_class_path) - cur_feature = extractor.predict(target_X) - cur_feature = np.mean(cur_feature, axis=0) - path2emb[target_class_path] = cur_feature + model_dir = os.path.join(os.path.expanduser('~'), '.fawkes') + for path in glob.glob(os.path.join(model_dir, "target_data/*")): + print(path) + idx = int(path.split("/")[-1]) + cur_image_paths = glob.glob(os.path.join(path, "*")) + imgs = np.array([image.img_to_array(image.load_img(p, target_size=(224, 224))) for p in cur_image_paths]) + imgs = preprocess_input(imgs) - for k, v in path2emb.items(): - path2emb[k] = normalize(v) + cur_feature = extractor.predict(imgs) + cur_feature = np.mean(cur_feature, axis=0) + path2emb[idx] = cur_feature + + model_path = os.path.join(model_dir, "{}_extract.h5".format(args.feature_extractor_name)) + emb_path = os.path.join(model_dir, "{}_emb.p".format(args.feature_extractor_name)) + extractor.save(model_path) + pickle.dump(path2emb, open(emb_path, "wb")) - pickle.dump(path2emb, open("../feature_extractors/embeddings/{}_emb.p".format(args.feature_extractor), "wb")) def parse_arguments(argv): @@ -54,8 +61,12 @@ def parse_arguments(argv): parser.add_argument('--candidate-datasets', nargs='+', help='path candidate datasets') parser.add_argument('--feature-extractor', type=str, + help="path of the feature extractor used for optimization", + default="/home/shansixioing/fawkes/feature_extractors/high2_extract.h5") + parser.add_argument('--feature-extractor-name', type=str, help="name of the feature extractor used for optimization", - default="webface_dense_robust_extract") + default="high2") + return parser.parse_args(argv) diff --git a/fawkes_dev/protection.py b/fawkes_dev/protection.py deleted file mode 100644 index cd97235..0000000 --- a/fawkes_dev/protection.py +++ /dev/null @@ -1,95 +0,0 @@ -import argparse -import os -import pickle -import random -import sys - -import numpy as np -from differentiator import FawkesMaskGeneration -from tensorflow import set_random_seed -from utils import load_extractor, CloakData, init_gpu - -random.seed(12243) -np.random.seed(122412) -set_random_seed(12242) - -NUM_IMG_PROTECTED = 400 # Number of images used to optimize the target class -BATCH_SIZE = 32 - -MAX_ITER = 1000 - - -def diff_protected_data(sess, feature_extractors_ls, image_X, number_protect, target_X=None, th=0.01): - image_X = image_X[:number_protect] - differentiator = FawkesMaskGeneration(sess, feature_extractors_ls, - batch_size=BATCH_SIZE, - mimic_img=True, - intensity_range='imagenet', - initial_const=args.sd, - learning_rate=args.lr, - max_iterations=MAX_ITER, - l_threshold=th, - verbose=1, maximize=False, keep_final=False, image_shape=image_X.shape[1:]) - - if len(target_X) < len(image_X): - target_X = np.concatenate([target_X, target_X, target_X]) - target_X = target_X[:len(image_X)] - cloaked_image_X = differentiator.attack(image_X, target_X) - return cloaked_image_X - - -def perform_defense(): - RES = {} - sess = init_gpu(args.gpu) - - FEATURE_EXTRACTORS = [args.feature_extractor] - RES_DIR = '../results/' - - RES['num_img_protected'] = NUM_IMG_PROTECTED - RES['extractors'] = FEATURE_EXTRACTORS - num_protect = NUM_IMG_PROTECTED - - print("Loading {} for optimization".format(args.feature_extractor)) - feature_extractors_ls = [load_extractor(name) for name in FEATURE_EXTRACTORS] - protect_class = args.protect_class - - cloak_data = CloakData(args.dataset, protect_class=protect_class) - RES_FILE_NAME = "{}_{}_protect{}".format(args.dataset, args.feature_extractor, cloak_data.protect_class) - RES_FILE_NAME = os.path.join(RES_DIR, RES_FILE_NAME) - print("Protect Class: ", cloak_data.protect_class) - - cloak_data.target_path, cloak_data.target_data = cloak_data.select_target_label(feature_extractors_ls, - FEATURE_EXTRACTORS) - - os.makedirs(RES_DIR, exist_ok=True) - os.makedirs(RES_FILE_NAME, exist_ok=True) - - cloak_image_X = diff_protected_data(sess, feature_extractors_ls, cloak_data.protect_train_X, - number_protect=num_protect, - target_X=cloak_data.target_data, th=args.th) - - cloak_data.cloaked_protect_train_X = cloak_image_X - RES['cloak_data'] = cloak_data - pickle.dump(RES, open(os.path.join(RES_FILE_NAME, 'cloak_data.p'), "wb")) - - -def parse_arguments(argv): - parser = argparse.ArgumentParser() - parser.add_argument('--gpu', type=str, - help='GPU id', default='0') - parser.add_argument('--dataset', type=str, - help='name of dataset', default='scrub') - parser.add_argument('--feature-extractor', type=str, - help="name of the feature extractor used for optimization", - default="webface_dense_robust_extract") - parser.add_argument('--th', type=float, default=0.007) - parser.add_argument('--sd', type=int, default=1e9) - parser.add_argument('--protect_class', type=str, default=None) - parser.add_argument('--lr', type=float, default=1) - - return parser.parse_args(argv) - - -if __name__ == '__main__': - args = parse_arguments(sys.argv[1:]) - perform_defense() diff --git a/fawkes_dev/utils.py b/fawkes_dev/utils.py deleted file mode 100644 index f3a5a31..0000000 --- a/fawkes_dev/utils.py +++ /dev/null @@ -1,373 +0,0 @@ -import json -import os -import pickle -import random - -import keras -import keras.backend as K -import numpy as np -import tensorflow as tf -from keras.applications.vgg16 import preprocess_input -from keras.layers import Dense, Activation -from keras.models import Model -from keras.preprocessing import image -from keras.utils import to_categorical -from sklearn.metrics import pairwise_distances -# from keras.utils import get_file - -def clip_img(X, preprocessing='raw'): - X = reverse_preprocess(X, preprocessing) - X = np.clip(X, 0.0, 255.0) - X = preprocess(X, preprocessing) - return X - - -def dump_dictionary_as_json(dict, outfile): - j = json.dumps(dict) - with open(outfile, "wb") as f: - f.write(j.encode()) - - -def fix_gpu_memory(mem_fraction=1): - os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' - gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=mem_fraction) - tf_config = tf.ConfigProto(gpu_options=gpu_options) - tf_config.gpu_options.allow_growth = True - tf_config.log_device_placement = False - init_op = tf.global_variables_initializer() - sess = tf.Session(config=tf_config) - sess.run(init_op) - K.set_session(sess) - return sess - - -def load_victim_model(number_classes, teacher_model=None, end2end=False): - for l in teacher_model.layers: - l.trainable = end2end - x = teacher_model.layers[-1].output - - x = Dense(number_classes)(x) - x = Activation('softmax', name="act")(x) - model = Model(teacher_model.input, x) - opt = keras.optimizers.Adadelta() - model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy']) - return model - - -def init_gpu(gpu_index, force=False): - if isinstance(gpu_index, list): - gpu_num = ','.join([str(i) for i in gpu_index]) - else: - gpu_num = str(gpu_index) - if "CUDA_VISIBLE_DEVICES" in os.environ and os.environ["CUDA_VISIBLE_DEVICES"] and not force: - print('GPU already initiated') - return - os.environ["CUDA_VISIBLE_DEVICES"] = gpu_num - sess = fix_gpu_memory() - return sess - - -def preprocess(X, method): - assert method in {'raw', 'imagenet', 'inception', 'mnist'} - - if method is 'raw': - pass - elif method is 'imagenet': - X = imagenet_preprocessing(X) - else: - raise Exception('unknown method %s' % method) - - return X - - -def reverse_preprocess(X, method): - assert method in {'raw', 'imagenet', 'inception', 'mnist'} - - if method is 'raw': - pass - elif method is 'imagenet': - X = imagenet_reverse_preprocessing(X) - else: - raise Exception('unknown method %s' % method) - - return X - - -def imagenet_preprocessing(x, data_format=None): - if data_format is None: - data_format = K.image_data_format() - assert data_format in ('channels_last', 'channels_first') - - x = np.array(x) - if data_format == 'channels_first': - # 'RGB'->'BGR' - if x.ndim == 3: - x = x[::-1, ...] - else: - x = x[:, ::-1, ...] - else: - # 'RGB'->'BGR' - x = x[..., ::-1] - - mean = [103.939, 116.779, 123.68] - std = None - - # Zero-center by mean pixel - if data_format == 'channels_first': - if x.ndim == 3: - x[0, :, :] -= mean[0] - x[1, :, :] -= mean[1] - x[2, :, :] -= mean[2] - if std is not None: - x[0, :, :] /= std[0] - x[1, :, :] /= std[1] - x[2, :, :] /= std[2] - else: - x[:, 0, :, :] -= mean[0] - x[:, 1, :, :] -= mean[1] - x[:, 2, :, :] -= mean[2] - if std is not None: - x[:, 0, :, :] /= std[0] - x[:, 1, :, :] /= std[1] - x[:, 2, :, :] /= std[2] - else: - x[..., 0] -= mean[0] - x[..., 1] -= mean[1] - x[..., 2] -= mean[2] - if std is not None: - x[..., 0] /= std[0] - x[..., 1] /= std[1] - x[..., 2] /= std[2] - - return x - - -def imagenet_reverse_preprocessing(x, data_format=None): - import keras.backend as K - x = np.array(x) - if data_format is None: - data_format = K.image_data_format() - assert data_format in ('channels_last', 'channels_first') - - if data_format == 'channels_first': - if x.ndim == 3: - # Zero-center by mean pixel - x[0, :, :] += 103.939 - x[1, :, :] += 116.779 - x[2, :, :] += 123.68 - # 'BGR'->'RGB' - x = x[::-1, :, :] - else: - x[:, 0, :, :] += 103.939 - x[:, 1, :, :] += 116.779 - x[:, 2, :, :] += 123.68 - x = x[:, ::-1, :, :] - else: - # Zero-center by mean pixel - x[..., 0] += 103.939 - x[..., 1] += 116.779 - x[..., 2] += 123.68 - # 'BGR'->'RGB' - x = x[..., ::-1] - return x - - - -def build_bottleneck_model(model, cut_off): - bottleneck_model = Model(model.input, model.get_layer(cut_off).output) - bottleneck_model.compile(loss='categorical_crossentropy', - optimizer='adam', - metrics=['accuracy']) - return bottleneck_model - - -def load_extractor(name): - model = keras.models.load_model("../feature_extractors/{}.h5".format(name)) - if hasattr(model.layers[-1], "activation") and model.layers[-1].activation == "softmax": - raise Exception( - "Given extractor's last layer is softmax, need to remove the top layers to make it into a feature extractor") - # if "extract" in name.split("/")[-1]: - # pass - # else: - # print("Convert a model to a feature extractor") - # model = build_bottleneck_model(model, model.layers[layer_idx].name) - # model.save(name + "extract") - # model = keras.models.load_model(name + "extract") - return model - - -def get_dataset_path(dataset): - if not os.path.exists("config.json"): - raise Exception("Please config the datasets before running protection code. See more in README and config.py.") - - config = json.load(open("config.json", 'r')) - if dataset not in config: - raise Exception( - "Dataset {} does not exist, please download to data/ and add the path to this function... Abort".format( - dataset)) - return config[dataset]['train_dir'], config[dataset]['test_dir'], config[dataset]['num_classes'], config[dataset][ - 'num_images'] - - -def normalize(x): - return x / np.linalg.norm(x, axis=1, keepdims=True) - - -class CloakData(object): - def __init__(self, dataset, img_shape=(224, 224), protect_class=None): - self.dataset = dataset - self.img_shape = img_shape - - self.train_data_dir, self.test_data_dir, self.number_classes, self.number_samples = get_dataset_path(dataset) - self.all_labels = sorted(list(os.listdir(self.train_data_dir))) - if protect_class: - self.protect_class = protect_class - else: - self.protect_class = random.choice(self.all_labels) - - self.sybil_class = random.choice([l for l in self.all_labels if l != self.protect_class]) - self.protect_train_X, self.protect_test_X = self.load_label_data(self.protect_class) - self.sybil_train_X, self.sybil_test_X = self.load_label_data(self.sybil_class) - - self.cloaked_protect_train_X = None - self.cloaked_sybil_train_X = None - - self.label2path_train, self.label2path_test, self.path2idx = self.build_data_mapping() - self.all_training_path = self.get_all_data_path(self.label2path_train) - self.all_test_path = self.get_all_data_path(self.label2path_test) - self.protect_class_path = self.get_class_image_files(os.path.join(self.train_data_dir, self.protect_class)) - self.sybil_class_path = self.get_class_image_files(os.path.join(self.train_data_dir, self.sybil_class)) - - print("Find {} protect images".format(len(self.protect_class_path))) - - def get_class_image_files(self, path): - return [os.path.join(path, f) for f in os.listdir(path)] - - def extractor_ls_predict(self, feature_extractors_ls, X): - feature_ls = [] - for extractor in feature_extractors_ls: - cur_features = extractor.predict(X) - feature_ls.append(cur_features) - concated_feature_ls = np.concatenate(feature_ls, axis=1) - concated_feature_ls = normalize(concated_feature_ls) - return concated_feature_ls - - def load_embeddings(self, feature_extractors_names): - dictionaries = [] - for extractor_name in feature_extractors_names: - path2emb = pickle.load(open("../feature_extractors/embeddings/{}_emb_norm.p".format(extractor_name), "rb")) - dictionaries.append(path2emb) - - merge_dict = {} - for k in dictionaries[0].keys(): - cur_emb = [dic[k] for dic in dictionaries] - merge_dict[k] = np.concatenate(cur_emb) - return merge_dict - - def select_target_label(self, feature_extractors_ls, feature_extractors_names, metric='l2'): - original_feature_x = self.extractor_ls_predict(feature_extractors_ls, self.protect_train_X) - - path2emb = self.load_embeddings(feature_extractors_names) - items = list(path2emb.items()) - paths = [p[0] for p in items] - embs = [p[1] for p in items] - embs = np.array(embs) - - pair_dist = pairwise_distances(original_feature_x, embs, metric) - max_sum = np.min(pair_dist, axis=0) - sorted_idx = np.argsort(max_sum)[::-1] - - highest_num = 0 - paired_target_X = None - final_target_class_path = None - for idx in sorted_idx[:5]: - target_class_path = paths[idx] - cur_target_X = self.load_dir(target_class_path) - cur_target_X = np.concatenate([cur_target_X, cur_target_X, cur_target_X]) - cur_tot_sum, cur_paired_target_X = self.calculate_dist_score(self.protect_train_X, cur_target_X, - feature_extractors_ls, - metric=metric) - if cur_tot_sum > highest_num: - highest_num = cur_tot_sum - paired_target_X = cur_paired_target_X - final_target_class_path = target_class_path - - np.random.shuffle(paired_target_X) - return final_target_class_path, paired_target_X - - def calculate_dist_score(self, a, b, feature_extractors_ls, metric='l2'): - features1 = self.extractor_ls_predict(feature_extractors_ls, a) - features2 = self.extractor_ls_predict(feature_extractors_ls, b) - - pair_cos = pairwise_distances(features1, features2, metric) - max_sum = np.min(pair_cos, axis=0) - max_sum_arg = np.argsort(max_sum)[::-1] - max_sum_arg = max_sum_arg[:len(a)] - max_sum = [max_sum[i] for i in max_sum_arg] - paired_target_X = [b[j] for j in max_sum_arg] - paired_target_X = np.array(paired_target_X) - return np.min(max_sum), paired_target_X - - def get_all_data_path(self, label2path): - all_paths = [] - for k, v in label2path.items(): - cur_all_paths = [os.path.join(k, cur_p) for cur_p in v] - all_paths.extend(cur_all_paths) - return all_paths - - def load_label_data(self, label): - train_label_path = os.path.join(self.train_data_dir, label) - test_label_path = os.path.join(self.test_data_dir, label) - train_X = self.load_dir(train_label_path) - test_X = self.load_dir(test_label_path) - return train_X, test_X - - def load_dir(self, path): - assert os.path.exists(path) - x_ls = [] - for file in os.listdir(path): - cur_path = os.path.join(path, file) - im = image.load_img(cur_path, target_size=self.img_shape) - im = image.img_to_array(im) - x_ls.append(im) - raw_x = np.array(x_ls) - return preprocess_input(raw_x) - - def build_data_mapping(self): - label2path_train = {} - label2path_test = {} - idx = 0 - path2idx = {} - for label_name in self.all_labels: - full_path_train = os.path.join(self.train_data_dir, label_name) - full_path_test = os.path.join(self.test_data_dir, label_name) - label2path_train[full_path_train] = list(os.listdir(full_path_train)) - label2path_test[full_path_test] = list(os.listdir(full_path_test)) - for img_file in os.listdir(full_path_train): - path2idx[os.path.join(full_path_train, img_file)] = idx - for img_file in os.listdir(full_path_test): - path2idx[os.path.join(full_path_test, img_file)] = idx - idx += 1 - return label2path_train, label2path_test, path2idx - - def generate_data_post_cloak(self, sybil=False): - assert self.cloaked_protect_train_X is not None - while True: - batch_X = [] - batch_Y = [] - cur_batch_path = random.sample(self.all_training_path, 32) - for p in cur_batch_path: - cur_y = self.path2idx[p] - if p in self.protect_class_path: - cur_x = random.choice(self.cloaked_protect_train_X) - elif sybil and (p in self.sybil_class): - cur_x = random.choice(self.cloaked_sybil_train_X) - else: - im = image.load_img(p, target_size=self.img_shape) - im = image.img_to_array(im) - cur_x = preprocess_input(im) - batch_X.append(cur_x) - batch_Y.append(cur_y) - batch_X = np.array(batch_X) - batch_Y = to_categorical(np.array(batch_Y), num_classes=self.number_classes) - yield batch_X, batch_Y