From d7a25eb292ef8b45a2019e14b23cdc633f3fd513 Mon Sep 17 00:00:00 2001 From: Shawn-Shan Date: Thu, 2 Jul 2020 12:32:05 -0500 Subject: [PATCH] cli Former-commit-id: c3687684a19c18309f97b69f8161af7a31fe0fb8 [formerly b68719d5e14a54377fafbea9f2c7c9b996bea583] Former-commit-id: f17d9cbb79833f56450e0518d978603997037e94 --- dist/fawkes-0.0.1-py3-none-any.whl | Bin 13487 -> 0 bytes dist/fawkes-0.0.1.tar.gz | Bin 13106 -> 0 bytes fawkes/__init__.py | 24 ++ fawkes/__main__.py | 4 + fawkes/__pycache__/align_face.cpython-36.pyc | Bin 2303 -> 2298 bytes fawkes/__pycache__/detect_face.cpython-36.pyc | Bin 22110 -> 22110 bytes .../__pycache__/differentiator.cpython-36.pyc | Bin 9154 -> 9162 bytes fawkes/__pycache__/utils.cpython-36.pyc | Bin 18326 -> 13083 bytes fawkes/align_face.py | 7 +- fawkes/detect_face.py | 1 - fawkes/differentiator.py | 2 +- fawkes/protection.py | 140 ++++---- fawkes/utils.py | 337 +++++++++--------- 13 files changed, 282 insertions(+), 233 deletions(-) delete mode 100644 dist/fawkes-0.0.1-py3-none-any.whl delete mode 100644 dist/fawkes-0.0.1.tar.gz create mode 100644 fawkes/__main__.py diff --git a/dist/fawkes-0.0.1-py3-none-any.whl b/dist/fawkes-0.0.1-py3-none-any.whl deleted file mode 100644 index a45d1ca22686a47adc867e3ab2c991d4c462db80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13487 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 k7Im~O8y~y+>(<`3wUnFe%i+SBbRj9NW zyc(veZl_ue3pL1!G|w0LJ_L&&do9m0XV#c*nXB;8bqJd8aC;mOYFubh?G@939=)WG znY4~U2wU(F^m&U@8c2`Ig|$c4W*4n1SjmvR=PyJB6|Y1IB`?K@)`X`qXd;`pynAJ3 zoIji#jB=$r)QGw==z#8-Z@drJ%y0hOJPnCl%Ip2fkkDG9CJ{4jfc)QGB`$#jUC?!O$}BSe+U cqpu8+ce5!TPSkz6^o)dbKnf2+A${ro2k_x?Hvj+t delta 629 zcmZ8d&x_PB7|oaTN2Z-Fj6Vk5+1r3v4+|o$2%@sC2!dYLQwmM?a|TYlXReL_fy zAd2fd@X`g82l^9Sdg|F?OZJZOs$!(34+GRCMHvusZ6@5lH*E_=G9i3=doj)QkJE0_HtPn zN3~oa9~wJy=Z~}9X)aaI{%cJTI0kB<-_jwh>(BH)%=9gG4$f%BE=KQbDuMxN(1-e> zU$Y^+d-Z{xr|?Y&-p1`NK38@k)j>M2?No>~Md12=?XMu$V^B7b__%R$u{&?GP<^bZ z$kK{_?lmyb+#iOgY~i$}RlVVV?Ee?9+YZpzXZaee>YIG)$z{A7Q)NTE7YA)upr`#xwWmtpcBvFlO<=m&UpP$Tk-s@v#{f zu;?p%t9C(>mL(S&4VzSIanQ|a*MrWhZTA{%j@nf}Dr`lQGEVtmQ1@V;PloOUj(9N^ e@VF1#BMFZie2nKI{scMW3@^nd-XW-OGV%``Fkc%0 delta 283 zcmWNLJxBs^7>DoozcaXwOGPTWgv*Kq?E`daj2nVLD$YKZXy|LIK|~4!2Z@#jhj%$R zsKyErJ%g?D|ygz#H8k^gK5rr9)|g7{7FZWtwhI<4~|>g3MyQhH)NFfdf`#vj5Dc# d>^&5h(y|wPv1<^LOwI~JqtxmrWSU)@_y^A~Ug-b; diff --git a/fawkes/__pycache__/differentiator.cpython-36.pyc b/fawkes/__pycache__/differentiator.cpython-36.pyc index fc557807a1b347bb0b7051c62c08d8beb692714b..0b8b35fb7ea23d16a7bdfb71f2dd54598db4f613 100644 GIT binary patch delta 3584 zcmZ`+TWlN06`k2#zLpe4QGALbMZIj(4_UJOh;7xf6DGD3*NGh`b~dtTio2pn@#W#l zR)l33fOY~Dbs~Fyn)Igu+5!RcQ^ZBT`hd}&<}XD*iY-u}2pYicM^L0e0RxSD?=B^) zj>E;Cow@hUojWsk&t2X-`RPe{x~E4z;rnL(-=}ZOrw@y&RB=~5il-_oG9@R8>QJ3G zi0bSJidUsClZ@*=f#2B-HCe z!X4V}{X$4gyKV_EKDE_$LvLw@x|*~hZOHjUA$%IfSu>Ar>Pb0b`|WDRKr;?^0_X4G zEZd+Zu2c5Itr~t=9H(L45npV(qeS(D2v|;l#U8L8!a2$`LDWaOy_Oe&WxF=XC)iAa zjawC#g>`}b3GAjIvVE$odR1RhEK8_|x?iWw0jW^M0@uJ144d7u>s+}|27!k~{E1050Kdb;KY zX$o8L#QQ{yv=XqWXW%{zcNXOx3N!ifXj+4|ZlA8=%K72ckTFy~1J0j=d$35=q?Iwx!`M?D(n`NW7O9$2hiiA=ev=b4_nmS@dJAw7_1Dy61gs8<`u{>cC0yRgHLLtF?w2tI(~ z*EiR{`sU%o+4~RS$F`w-8=vcWmj?Midz8RpzMwTthrZINXr{MR<8%JO0o)12{c4_- z@(UmqY7Nt|rm=cc;dlJUlZvBIuQhd3TBz46rc}*0mrd7otyEmn!QxB*N3iqn_s%91 zS2e#{s;*R71RcsuQ&$RFuDMcG_*cExz^WYhX?w(!np$O%WzeR7p|0x{*qg#~u3A^M zO0LobS}avdg?yR6s#?Cr;wT+NNFyW>5&(+7u)=bA2q9OhHCA+%Ld_8WeV@9$du(GU zu~NmI1EZ3j%VA8-F93+kC-QT_lzrEx@AJ!sv zU~2|IaphS-&)1eRBAbW4DM1jhCn&-#=_bTxZkfRb(;BP}$J)$whyI=WLhoiDIk)^u zg-WSm?~??6GlJ!wU4iyv>1w*sv{9-Rzx?E{p`buT{;$ZXEP7!l5C#wqAsj{6j&KBF zCx9vGrK-j*V^b{EAZ-d-sbY$7%4~wSqKD}`|5Y^Q$f5x^qx+)PS{^OPnb-AvVfjCN zG&b+Yw*p_=XW(w$`{cb?nEHyOtZ=>w_Cl?9xy2I zNC2#%npy0=PalMyT=oJ2UxBu7izH=9f(FvMkU)YS!dem{t^O@mz(Ft$>DThpE)PRD ztdFQ3ebnd!9u6A)z|LXAksynX2v8X%`WR4LWSze8kBwk~!AW@>3eL|r9F@00YdhRK zieiV(iLF>G-WphaO3&y!jhM~g9hxPV$L54q0`!BeBoJ;pVK|l@;OIv$5#Zyfk(eRt z!qqn~l6AVes_z1ayK&e$y}Z;K+MH~URnsO6sYF%zmOyc}_P#tZ3{_~QSMPi)lsD&u z_Lf)@Mr7FuwQX!&yy`aWz(h7_1=edM;$)eCRKh;t0y4ppNvaPjl?2Ds z1K%dNUfasQX(h0`Fm5AL?tb@)ZytCr_z1P}oV%wj+NTZMfA=D}NNN#@=zEvtb%`CJ z1h3afO}K#6GO_8nC*c>XphhVXWMIs7zpF=o>a2nxxgclH= zML3Co^lr9+SQgn!*hg|V9gCHEUT3eO;5>p=6V74l0s@i|6A&&UKp7zHMTD0T&H|Vo zt+oQR1q(ODH6Y3&YIY;MhHwdO`d-l9Sg7Zj%6~m1PcMKdGiVw=NNb94=Dgyn zE!cdG)Wwb?%<>0AXI}B@TD1WxC{Rtszo0esoLvQ*rK;k#`g!%*qs1s*eF;8BmU8uL z8e^M_QhbY0b(%|>nk%WR{H=7h{rHkL%cbJUS1P$e1FDS%U~f0DQL?=2;2s#A1u?s0MtqB*_Q-v1-Se$`Pwj_>JRhJhfjK~jIs`8 z7e6ra!9)&s)yg$I40aU(7qn%On{|ab`F}?aR)cM_-z`HNBg_ew86R810R;fXrP}|s ttW;4PkPwYG?4m5&pMj8O*RAhH)3KtLDouu!lv#xqoqcOs{*%#>{{fXSR;&O3 delta 3491 zcmb_fTWlN06`k2#zE%`TQ52sdC0ddl#pJ#K__AF>cy{c4hyfGsOz;+ZKWBtajio$Y$@1kE9Zd2Z& z3%((+<0s31OTXPVhb>a}lqg_0Mmq3Yh;(h1pn4LdJfcn{NJ(&*s2MBA%|tl~-Vyjs zslIDnW)v(UyVcKk=%xq6OxKcqTy%>|2ta1~sK_S8KNnoHR=$B$bQgxse3_^6ne z*_?V3*eQ5Ym{bS-w_`Hvl)cfK-oH$BbH|Rw_kWqd3wU1>dwqBvPKYMh_4HGIn}LtTaF9{(+dW zGW-*FYIy8BWSOcNb?l1Q8e4!%*Ba-t=Y2ZPf9{FV9sG};oid|vMVT9bi{AYr+;4u& zo90FD2Q<#N`OeYr@^AUxO``(~0^}syWt>+U8jGN51iAwZ^&~M-7t&wYGeNQ;OB&QleI@MJRcd%?)G2^KLSbs8w4o*{p$J`U*5TMGIA#=e^%h zn650rSfSRiU27Vv8#ccdI11Rp3w}MAfwx_DB>eXhI#UO{g%rv!G)Cxwk zYV*&77r{#l{ji<2B}1z$vz_QO+&62ru4;S|r=%&UL zJScqqX@$Ydl2>kz(T7^38r=~%w9AfKsZRq0b)y4O{uXY8Zrpm5XKPF0Qo?% z$qEY0UT}7cjU$ugzZ_C`^p0~oTAbVFEr64577Dw;Wc(R`c>E&&YB=L{Q(1_JNs(U) zHwQ#Fb<>dO75tR{BmA>75=L;ce;8Yj0oWde6-}jfJ}0te>`Rb>k@ukq_o8=Awr-*w zZZNIE>P5{k3caD9@<8PJ!rkCbT&Y;m8{G{OA#P4^!m}z)>_od8(u=MQy;l0$d$%HC zfx7t@(We$L3VR$Og>VRA4k3qd7-1KHEtz^%V;VL^y=K}{QPV572$zb@@^}x%LcJhc9+3U#`HvD8+TT-A zzFiSglTERl=(bw{XeTWSk`n~)l@gMK<3}DgiY(LRKWlLdtPJaK>rix}Fm3#0I1g zcYYn~9D!;R>@8eDTl-De`aw(T;7Yo5V2c*mO&G9w z`ZxPd@&9!CX`lA3S^H@};n z#6J9=(mVUW+u0Wiu4gaB(0{+6kj9J^R9%(}UoOBuXa)J{^ega}|9)Dh1AHtqLS?=$ zGeuAF7c;x~U((t3tfRnOh5r{STLuF4O)DTb+x}xDIHLATWvSy8`NX#QR%Hasf z5_Z3Wa0=lWgl7?+LpX`>G=ih;Z4j4bwupUHcH6aFsVgQshlaBVPC+<>trrnckQml! zlz+lbBjgcY0I+>ptqH0HuK~8W20Gb9W*R|3cm=>7dRn`@R9BdKvIYg{AZs>E)&L@x z=}PH0(bGb3v`skmwY;ac)D`U~(R>`?82`iADLylv9N$VcKVY7R_m%U7`bCYgt(5tt z@df4nJhy7!eWJ})A$Strri|Y~gSesY&?F5BGL=Ocs*Ycfgs>2xerN}v{`hIc`G$Bd zdzde0Cyr8yj=~^;jsr%i7u+%e6fbZM)36wTfngc|uW=fpZU_NZ2!SX8C-9H5r2*%> zoqgNQAKUi!^a?K7xe2&a>{SHJv@iQOcCm~y7k5qUZ-?4spI7#JSssaFh!wT?NZ5Q9BvTH8AZ?;OpFHji&r J2LI_q=->CFO&S0I diff --git a/fawkes/__pycache__/utils.cpython-36.pyc b/fawkes/__pycache__/utils.cpython-36.pyc index c1e14d5b9f257f7dc017ec7677fe1a608e20133d..fba5d6fabab5f589cc1a256a5bf29f82cbaade7b 100644 GIT binary patch delta 4179 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 literal 18326 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 0: det = bounding_boxes[:, 0:4] diff --git a/fawkes/detect_face.py b/fawkes/detect_face.py index 54c67a2..dff56c0 100644 --- a/fawkes/detect_face.py +++ b/fawkes/detect_face.py @@ -29,7 +29,6 @@ from __future__ import print_function import os -# from math import floor import cv2 import numpy as np import tensorflow as tf diff --git a/fawkes/differentiator.py b/fawkes/differentiator.py index 2fd9175..98a46e0 100644 --- a/fawkes/differentiator.py +++ b/fawkes/differentiator.py @@ -10,7 +10,7 @@ from decimal import Decimal import numpy as np import tensorflow as tf -from utils import preprocess, reverse_preprocess +from .utils import preprocess, reverse_preprocess class FawkesMaskGeneration: diff --git a/fawkes/protection.py b/fawkes/protection.py index 9df50fb..da04387 100644 --- a/fawkes/protection.py +++ b/fawkes/protection.py @@ -1,3 +1,7 @@ +# from __future__ import absolute_import +# from __future__ import division +# from __future__ import print_function + import argparse import glob import os @@ -5,26 +9,28 @@ import random import sys import numpy as np -from differentiator import FawkesMaskGeneration -from utils import load_extractor, init_gpu, select_target_label, dump_image, reverse_process_cloaked, \ + +from .differentiator import FawkesMaskGeneration +from .utils import load_extractor, init_gpu, select_target_label, dump_image, reverse_process_cloaked, \ Faces random.seed(12243) np.random.seed(122412) -BATCH_SIZE = 10 +BATCH_SIZE = 32 -def generate_cloak_images(sess, feature_extractors, image_X, target_emb=None, th=0.01, faces=None): +def generate_cloak_images(sess, feature_extractors, image_X, target_emb=None, th=0.01, faces=None, sd=1e9, lr=2, + max_step=500): batch_size = BATCH_SIZE if len(image_X) > BATCH_SIZE else len(image_X) differentiator = FawkesMaskGeneration(sess, feature_extractors, batch_size=batch_size, mimic_img=True, intensity_range='imagenet', - initial_const=args.sd, - learning_rate=args.lr, - max_iterations=args.max_step, + initial_const=sd, + learning_rate=lr, + max_iterations=max_step, l_threshold=th, verbose=1, maximize=False, keep_final=False, image_shape=image_X.shape[1:], faces=faces) @@ -33,26 +39,6 @@ def generate_cloak_images(sess, feature_extractors, image_X, target_emb=None, th 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.004 - elif mode == 'high': - args.feature_extractor = "high_extract" - args.th = 0.004 - elif mode == 'ultra': - args.feature_extractor = "high_extract" - args.th = 0.03 - elif mode == 'custom': - pass - else: - raise Exception("mode must be one of 'low', 'mid', 'high', 'ultra', 'custom'") - - def check_imgs(imgs): if np.max(imgs) <= 1 and np.min(imgs) >= 0: imgs = imgs * 255.0 @@ -63,20 +49,72 @@ def check_imgs(imgs): return imgs -def fawkes(): +def main(*argv): + if not argv: + argv = list(sys.argv) + + # attach SIGPIPE handler to properly handle broken pipe + try: # sigpipe not available under windows. just ignore in this case + import signal + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + except Exception as e: + pass + + parser = argparse.ArgumentParser() + 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='high') + parser.add_argument('--feature-extractor', type=str, + help="name of the feature extractor used for optimization", + default="high_extract") + + parser.add_argument('--th', type=float, default=0.01) + parser.add_argument('--max-step', type=int, default=500) + parser.add_argument('--sd', type=int, default=1e9) + parser.add_argument('--lr', type=float, default=2) + + parser.add_argument('--separate_target', action='store_true') + + parser.add_argument('--format', type=str, + help="final image format", + default="jpg") + args = parser.parse_args(argv[1:]) + + if args.mode == 'low': + args.feature_extractor = "high_extract" + args.th = 0.003 + elif args.mode == 'mid': + args.feature_extractor = "high_extract" + args.th = 0.005 + elif args.mode == 'high': + args.feature_extractor = "high_extract" + args.th = 0.007 + elif args.mode == 'ultra': + args.feature_extractor = "high_extract" + args.th = 0.01 + elif args.mode == 'custom': + pass + else: + raise Exception("mode must be one of 'low', 'mid', 'high', 'ultra', 'custom'") + 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)] - # 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]] + if not image_paths: + print("No images in the directory") + exit(1) faces = Faces(image_paths, sess) @@ -94,7 +132,8 @@ def fawkes(): target_embedding = select_target_label(orginal_images, feature_extractors_ls, fs_names) protected_images = generate_cloak_images(sess, feature_extractors_ls, orginal_images, - target_emb=target_embedding, th=args.th, faces=faces) + target_emb=target_embedding, th=args.th, faces=faces, sd=args.sd, + lr=args.lr, max_step=args.max_step) faces.cloaked_cropped_faces = protected_images @@ -102,42 +141,9 @@ def fawkes(): 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) + file_name = "{}_{}_{}_cloaked.{}".format(".".join(path.split(".")[:-1]), args.mode, args.th, 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', '-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='high') - parser.add_argument('--feature-extractor', type=str, - help="name of the feature extractor used for optimization", - default="high_extract") - - 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=10) - - 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) if __name__ == '__main__': - args = parse_arguments(sys.argv[1:]) - fawkes() + main(*sys.argv) diff --git a/fawkes/utils.py b/fawkes/utils.py index 4083ac2..6f8f590 100644 --- a/fawkes/utils.py +++ b/fawkes/utils.py @@ -4,21 +4,26 @@ import json import os import pickle import random +import sys +stderr = sys.stderr +sys.stderr = open(os.devnull, 'w') import keras + +sys.stderr = stderr 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 PIL import Image, ExifTags +# 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 + +from .align_face import align, aligner def clip_img(X, preprocessing='raw'): @@ -81,7 +86,14 @@ class Faces(object): 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)) + if not self.cropped_faces: + print("No faces detected") + exit(1) + + self.cropped_faces = np.array(self.cropped_faces) + + self.cropped_faces = preprocess(self.cropped_faces, 'imagenet') + self.cloaked_cropped_faces = None self.cloaked_faces = np.copy(self.org_faces) @@ -89,8 +101,6 @@ class Faces(object): return self.cropped_faces def merge_faces(self, cloaks): - # import pdb - # pdb.set_trace() self.cloaked_faces = np.copy(self.org_faces) @@ -300,7 +310,6 @@ def load_extractor(name): return model - def get_dataset_path(dataset): model_dir = os.path.join(os.path.expanduser('~'), '.fawkes') if not os.path.exists(os.path.join(model_dir, "config.json")): @@ -335,7 +344,7 @@ def load_dir(path): im = image.img_to_array(im) x_ls.append(im) raw_x = np.array(x_ls) - return preprocess_input(raw_x) + return preprocess(raw_x, 'imagenet') def load_embeddings(feature_extractors_names): @@ -394,10 +403,19 @@ def select_target_label(imgs, feature_extractors_ls, feature_extractors_names, m max_sum = np.min(pair_dist, axis=0) max_id = np.argmax(max_sum) - image_paths = glob.glob(os.path.join(model_dir, "target_data/{}/*".format(paths[int(max_id)]))) + target_data_id = paths[int(max_id)] + image_dir = os.path.join(model_dir, "target_data/{}/*".format(target_data_id)) + if not os.path.exists(image_dir): + get_file("{}.h5".format(name), "http://sandlab.cs.uchicago.edu/fawkes/files/target_images".format(name), + cache_dir=model_dir, cache_subdir='') + + image_paths = glob.glob(image_dir) + 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])) + + target_images = np.array([resize(x, (224, 224)) for x in target_images]) + target_images = preprocess(target_images, 'imagenet') target_images = list(target_images) while len(target_images) < len(imgs): @@ -406,152 +424,151 @@ def select_target_label(imgs, feature_extractors_ls, feature_extractors_names, m 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 - - self.protect_X = self.load_label_data(self.protect_directory) - - self.cloaked_protect_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)) - - 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 +# 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 +# +# self.protect_X = self.load_label_data(self.protect_directory) +# +# self.cloaked_protect_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)) +# +# 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