aliabd's picture
aliabd HF staff
Update app.py
287400c
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 = "<p style='display: flex; flex-grow: 1; align-items: center; justify-content: center; padding: 2rem 1rem; font-size: 1.5rem; line-height: 2rem; font-weight: 400;'>{}</p>"
pre_image = """
<p style='display: flex; flex-grow: 1; align-items: center; justify-content: center; padding: 2rem 1rem; font-size: 1.5rem; line-height: 2rem; font-weight: 400;'>{}</p>
<p style='display: flex; flex-grow: 1; align-items: center; justify-content: center; padding: 2rem 1rem; font-size: 1.5rem; line-height: 2rem; font-weight: 400;'>{}</p>
<div style="text-align:center;">
<img style="display: inline-block; margin-left: auto; margin-right: auto;" src="best.png"/>
<img style="display: inline-block; margin-left: auto; margin-right: auto;" src="images/dog.png"/>
</div>
"""
def get_hash(img):
if isinstance(img, str):
img = Image.open(img)
img = img.convert('RGB')
image = img.resize([360, 360])
arr = np.array(image).astype(np.float32) / 255.0
arr = arr * 2.0 - 1.0
arr = arr.transpose(2, 0, 1).reshape([1, 3, 360, 360])
# Run model
inputs = {session.get_inputs()[0].name: arr}
outs = session.run(None, inputs)
# Convert model output to hex hash
hash_output = seed1.dot(outs[0].flatten())
hash_bits = ''.join(['1' if it >= 0 else '0' for it in hash_output])
hash_hex = '{:0{}x}'.format(int(hash_bits, 2), len(hash_bits) // 4)
return hash_hex
cache = {
"732777d208dff6dd3268cb5a59a34eabe31910abfb06f308": (pre_text.format("732777d208dff6dd3268cb5a"), pre_text.format("59a34eabe31910abfb06f308"), ["cached/example1.png", "images/dog.png"]),
"32dac883f7b91bbf45a4829635f7238ba05c404756bb33ee": (
pre_text.format("32dac883f7b91bbf45a48296"), pre_text.format("35f7238ba05c404756bb33ee"),
["cached/example2.png", "images/apple.png"]),
"f16d358106da998227b323f2a73e6ec2303af3d801f9133a": (
pre_text.format("f16d358106da998227b323f2"), pre_text.format("a73e6ec2303af3d801f9133a"),
["cached/example3.png", "images/iphone.png"])
}
def inference(first_img, second_img):
first_hash_hex = get_hash(first_img.name)
second_hash_hex = get_hash(second_img.name)
if first_hash_hex + second_hash_hex in cache:
return cache[first_hash_hex + second_hash_hex]
generated_image, arr = generate_image(first_img, second_hash_hex)
if generated_image is None:
return pre_text.format(first_hash_hex), pre_text.format("Could not generate a collision.. :("), None
generated_image.save(first_img.name, format="PNG")
new_hash = get_hash(generated_image)
if new_hash != second_hash_hex:
return pre_text.format(first_hash_hex), pre_text.format("Could not generate a collision.. :("), None
return pre_text.format(first_hash_hex), pre_text.format(new_hash), [first_img.name, second_img.name]
title = "Generate a Neural Hash Collision"
description = "Apple's NeuralHash, a perceptual hashing method for images based on neural networks, has been criticized heavily by researchers. You can use this demo to generate a hash collision of any two images. Upload your own (or click one of the examples to load them), and an adverserial image will be created from the first one to match the hash of the second. Note: In some cases the generation times out (we set a limit of 1000 iterations). The examples are cached, but submitting your own images should take about a minute. Read more at the links below."
article = "<p style='text-align: center'><a href='https://www.apple.com/child-safety/pdf/CSAM_Detection_Technical_Summary.pdf'>CSAM Detection Technical Summary</a> | <a href='https://github.com/AsuharietYgvar/AppleNeuralHash2ONNX'>AppleNeuralHash2ONNX Github Repo</a> | <a href='https://github.com/anishathalye/neural-hash-collider'>Neural Hash Collider Repo</a> | <a href='https://github.com/AsuharietYgvar/AppleNeuralHash2ONNX/issues/1'>Working Collision example images from github issue</a></p> "
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)