import sys import onnxruntime import numpy as np from PIL import Image import gradio as gr import torch import os import random # os.system('wget https://www.dropbox.com/s/ggf6ok63u7hywhc/neuralhash_128x96_seed1.dat') # os.system('wget https://www.dropbox.com/s/1jug4wtevz1rol0/model.onnx') # Copyright (c) 2021 Anish Athalye. Released under the MIT license. import tensorflow as tf from scipy.ndimage.filters import gaussian_filter import time from util import * DEFAULT_MODEL_PATH = 'model.onnx' DEFAULT_SEED_PATH = 'neuralhash_128x96_seed1.dat' DEFAULT_TARGET_HASH = '59a34eabe31910abfb06f308' DEFAULT_ITERATIONS = 1000 DEFAULT_SAVE_ITERATIONS = 0 DEFAULT_LR = 2.0 DEFAULT_COMBINED_THRESHOLD = 2 DEFAULT_K = 10.0 DEFAULT_CLIP_RANGE = 0.1 DEFAULT_W_L2 = 2e-3 DEFAULT_W_TV = 1e-4 DEFAULT_W_HASH = 0.8 DEFAULT_BLUR = 0 tf.compat.v1.disable_eager_execution() model = load_model(DEFAULT_MODEL_PATH) image = model.tensor_dict['image'] logits = model.tensor_dict['leaf/logits'] seed = load_seed(DEFAULT_SEED_PATH) def generate_image(first_img, second_hash_hex): with model.graph.as_default(): with tf.compat.v1.Session() as sess: sess.run(tf.compat.v1.global_variables_initializer()) target = hash_from_hex(second_hash_hex) original = load_image(first_img.name) h = hash_from_hex(second_hash_hex) proj = tf.reshape(tf.linalg.matmul(seed, tf.reshape(logits, (128, 1))), (96,)) # proj is in R^96; it's interpreted as a 96-bit hash by mapping # entries < 0 to the bit '0', and entries >= 0 to the bit '1' normalized, _ = tf.linalg.normalize(proj) hash_output = tf.sigmoid(normalized * DEFAULT_K) # now, hash_output has entries in (0, 1); it's interpreted by # mapping entries < 0.5 to the bit '0' and entries >= 0.5 to the # bit '1' # we clip hash_output to (clip_range, 1-clip_range); this seems to # improve the search (we don't "waste" perturbation tweaking # "strong" bits); the sigmoid already does this to some degree, but # this seems to help hash_output = tf.clip_by_value(hash_output, DEFAULT_CLIP_RANGE, 1.0 - DEFAULT_CLIP_RANGE) - 0.5 hash_output = hash_output * (0.5 / (0.5 - DEFAULT_CLIP_RANGE)) hash_output = hash_output + 0.5 # hash loss: how far away we are from the target hash hash_loss = tf.math.reduce_sum(tf.math.squared_difference(hash_output, h)) perturbation = image - original # image loss: how big / noticeable is the perturbation? img_loss = DEFAULT_W_L2 * tf.nn.l2_loss(perturbation) + DEFAULT_W_TV * tf.image.total_variation(perturbation)[0] # combined loss: try to minimize both at once combined_loss = DEFAULT_W_HASH * hash_loss + (1 - DEFAULT_W_HASH) * img_loss # gradients of all the losses g_hash_loss, = tf.gradients(hash_loss, image) g_img_loss, = tf.gradients(img_loss, image) g_combined_loss, = tf.gradients(combined_loss, image) # perform attack x = original best = (float('inf'), 0) # (distance, image quality loss) dist = float('inf') for i in range(DEFAULT_ITERATIONS): # we do an alternating projections style attack here; if we # haven't found a colliding image yet, only optimize for that; # if we have a colliding image, then minimize the size of the # perturbation; if we're close, then do both at once if dist == 0: loss_name, loss, g = 'image', img_loss, g_img_loss elif best[0] == 0 and dist <= DEFAULT_COMBINED_THRESHOLD: loss_name, loss, g = 'combined', combined_loss, g_combined_loss else: loss_name, loss, g = 'hash', hash_loss, g_hash_loss # compute loss values and gradient xq = quantize(x) # take derivatives wrt the quantized version of the image hash_output_v, img_loss_v, loss_v, g_v = sess.run([hash_output, img_loss, loss, g], feed_dict={image: xq}) dist = np.sum((hash_output_v >= 0.5) != (h >= 0.5)) # if it's better than any image found so far, save it score = (dist, img_loss_v) if score < best: best = score if dist == 0: # if True: arr = x.reshape([3, 360, 360]).transpose(1, 2, 0) arr = (arr + 1.0) * (255.0 / 2.0) arr = arr.astype(np.uint8) im = Image.fromarray(arr) return im, arr # gradient descent step g_v_norm = g_v / np.linalg.norm(g_v) x = x - DEFAULT_LR * g_v_norm if DEFAULT_BLUR > 0: x = blur_perturbation(original, x, DEFAULT_BLUR) x = x.clip(-1, 1) # print('iteration: {}/{}, best: ({}, {:.3f}), hash: {}, distance: {}, loss: {:.3f} ({})'.format( # i+1, # DEFAULT_ITERATIONS, # best[0], # best[1], # hash_to_hex(hash_output_v), # dist, # loss_v, # loss_name # )) return None, None def quantize(x): x = (x + 1.0) * (255.0 / 2.0) x = x.astype(np.uint8).astype(np.float32) x = x / (255.0 / 2.0) - 1.0 return x def blur_perturbation(original, x, sigma): perturbation = x - original perturbation = gaussian_filter_by_channel(perturbation, sigma=sigma) return original + perturbation def gaussian_filter_by_channel(x, sigma): return np.stack([gaussian_filter(x[0,ch,:,:], sigma) for ch in range(x.shape[1])])[np.newaxis] # Load ONNX model session = onnxruntime.InferenceSession('model.onnx') # Load output hash matrix seed1 = open('neuralhash_128x96_seed1.dat', 'rb').read()[128:] seed1 = np.frombuffer(seed1, dtype=np.float32) seed1 = seed1.reshape([96, 128]) pre_text = "
{}
" pre_image = """{}
{}
CSAM Detection Technical Summary | AppleNeuralHash2ONNX Github Repo | Neural Hash Collider Repo | Working Collision example images from github issue
" examples = [ ["images/cat.png", "images/dog.png"], ["images/lena.png", "images/apple.png"], ["images/stevejobs.png", "images/iphone.png"], ] inputs = [gr.inputs.Image(type="file", label="First Image"), gr.inputs.Image(type="file", label="Second Image")] outputs = [gr.outputs.HTML(label="Initial Hash of First Image.."), gr.outputs.HTML(label="Generated a collision at.."), gr.outputs.Carousel(components=[gr.outputs.Image(type="file"), gr.outputs.Image(type="file")], label="Images at this hash..")] gr.Interface( inference, inputs, outputs, title=title, description=description, article=article, examples=examples, allow_flagging=False, theme="huggingface", capture_session=True ).launch(share=True)