diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..5c98b42
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
\ No newline at end of file
diff --git a/.idea/flask-open-nsfw.iml b/.idea/flask-open-nsfw.iml
new file mode 100644
index 0000000..efe4eef
--- /dev/null
+++ b/.idea/flask-open-nsfw.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..a3ffc2d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..d1dfae3
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/other.xml b/.idea/other.xml
new file mode 100644
index 0000000..640fd80
--- /dev/null
+++ b/.idea/other.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..16777b2
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1557308447629
+
+
+ 1557308447629
+
+
+
+
+ 1557713329813
+
+
+
+ 1557713329813
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$PROJECT_DIR$/main.py
+ 28
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 5290a9d..5d8c690 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
-# NSFW-Python
-修改自雅虎开源项目open_nsfw
+####
+
+- 修改自:https://github.com/yahoo/open_nsfw
\ No newline at end of file
diff --git a/data/open_nsfw-weights.npy b/data/open_nsfw-weights.npy
new file mode 100644
index 0000000..fd83911
Binary files /dev/null and b/data/open_nsfw-weights.npy differ
diff --git a/images/image1.png b/images/image1.png
new file mode 100644
index 0000000..81261ac
Binary files /dev/null and b/images/image1.png differ
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..9a04b43
--- /dev/null
+++ b/main.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+import sys
+import argparse
+import tensorflow as tf
+import io
+from model import OpenNsfwModel, InputType
+import flask
+from PIL import Image
+import numpy as np
+import skimage
+import skimage.io
+import os
+os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
+import os
+os.environ["CUDA_VISIBLE_DEVICES"]="-1"
+
+model_weights_path = 'data/open_nsfw-weights.npy'
+model = OpenNsfwModel()
+
+VGG_MEAN = [104, 117, 123]
+
+img_width, img_height = 224, 224
+
+app = flask.Flask(__name__)
+
+
+# 将RGB按照BGR重新组装,然后对每一个RGB对应的值减去一定阈值
+def prepare_image(image):
+ H, W, _ = image.shape
+ h, w = (img_width, img_height)
+
+ h_off = max((H - h) // 2, 0)
+ w_off = max((W - w) // 2, 0)
+ image = image[h_off:h_off + h, w_off:w_off + w, :]
+
+ image = image[:, :, :: -1]
+
+ image = image.astype(np.float32, copy=False)
+ image = image * 255.0
+ image = image-np.array(VGG_MEAN, dtype=np.float32)
+
+ image = np.expand_dims(image, axis=0)
+ return image
+
+# 使用TFLite文件检测
+def getResultFromFilePathByTFLite(path):
+ model_path = "./model/nsfw.tflite"
+ interpreter = tf.lite.Interpreter(model_path=model_path)
+ interpreter.allocate_tensors()
+
+ # Get input and output tensors.
+ input_details = interpreter.get_input_details()
+ # print(str(input_details))
+ output_details = interpreter.get_output_details()
+ # print(str(output_details))
+
+ im = Image.open(path)
+ # im = Image.open(r"./images/image1.png")
+ if im.mode != "RGB":
+ im = im.convert('RGB')
+ imr = im.resize((256, 256), resample=Image.BILINEAR)
+ fh_im = io.BytesIO()
+ imr.save(fh_im, format='JPEG')
+ fh_im.seek(0)
+
+ image = (skimage.img_as_float(skimage.io.imread(fh_im, as_grey=False))
+ .astype(np.float32))
+
+ # 填装数据
+ final = prepare_image(image)
+ interpreter.set_tensor(input_details[0]['index'], final)
+
+ # 调用模型
+ interpreter.invoke()
+ output_data = interpreter.get_tensor(output_details[0]['index'])
+
+ # 出来的结果去掉没用的维度
+ result = np.squeeze(output_data)
+ print('TFLite-->>result:{},path:{}'.format(result, path))
+ print(
+ "==========================================================================================================")
+ print("")
+ print("")
+
+def getResultFromFilePathByPyModle(path):
+ # print("numpy-version:" + np.__version__)
+ # print("tensorflow-version:" + tf.__version__)
+ im = Image.open(path)
+
+ if im.mode != "RGB":
+ im = im.convert('RGB')
+
+ # print("图片reSize:256*256")
+ imr = im.resize((256, 256), resample=Image.BILINEAR)
+
+ fh_im = io.BytesIO()
+ imr.save(fh_im, format='JPEG')
+ fh_im.seek(0)
+
+ image = (skimage.img_as_float(skimage.io.imread(fh_im, as_grey=False))
+ .astype(np.float32))
+
+ final = prepare_image(image)
+
+ tf.reset_default_graph()
+ with tf.Session() as sess:
+ input_type = InputType[InputType.TENSOR.name.upper()]
+ model.build(weights_path=model_weights_path, input_type=input_type)
+ sess.run(tf.global_variables_initializer())
+
+ predictions = sess.run(model.predictions, feed_dict={model.input: final})
+ # print("\tSFW score:\t{}\n\tNSFW score:\t{}".format(*predictions[0]))
+ print(
+ "==========================================================================================================")
+ print('Python-->>result:{},path:{}'.format(predictions[0], path))
+
+
+def getResultListFromDir():
+ list = os.listdir("/Users/zhaowenwen/Downloads/testImages")
+ for i in range(0, len(list)):
+ if (list[i] != ".DS_Store" and list[i] != ".localized"):
+ getResultFromFilePathByPyModle(os.path.join("/Users/zhaowenwen/Downloads/testImages", list[i]))
+ getResultFromFilePathByTFLite(os.path.join("/Users/zhaowenwen/Downloads/testImages", list[i]))
+
+
+# 代码生成tflite文件
+def createTfliteFile():
+ in_path = "./model/frozen_nsfw.pb"
+ out_path = "./model/nsfw.tflite"
+
+ # 模型输入节点
+ input_tensor_name = ["input"]
+ input_tensor_shape = {"input":[1, 224,224,3]}
+ # 模型输出节点
+ classes_tensor_name = ["predictions"]
+
+ converter = tf.lite.TFLiteConverter.from_frozen_graph(in_path,
+ input_tensor_name, classes_tensor_name,
+ input_shapes = input_tensor_shape)
+ # converter.post_training_quantize = True
+ tflite_model = converter.convert()
+
+ with open(out_path, "wb") as f:
+ f.write(tflite_model)
+
+#生成.pb .index .meta .ckpt.data文件
+# freeze_graph --input_graph=/Users/jason/nsfw/flask-open-nsfw/model/nsfw-graph.pb --input_checkpoint=/Users/jason/nsfw/flask-open-nsfw/model/nsfw_model.ckpt --input_binary=true --output_graph=/Users/jason/nsfw/flask-open-nsfw/model/frozen_nsfw.pb --output_node_names=predictions
+
+#命令行生成tflite文件(tonserflow1.3前可用)
+#toco --graph_def_file=/Users/jason/nsfw/flask-open-nsfw/model/frozen_nsfw.pb --input_format=TENSORFLOW_GRAPHDEF --output_format=TFLITE --output_file=/Users/jason/nsfw/flask-open-nsfw/model/nsfw.tflite --inference_type=FLOAT --input_type=FLOAT --input_arrays=input --output_arrays=predictions input_shapes=1,224,224,3
+
+
+#
+# toco \
+# --graph_def_file=/Users/jason/nsfw/flask-open-nsfw/model/frozen_nsfw.pb \
+# --output_file=/Users/jason/nsfw/flask-open-nsfw/model/aaaa.lite \
+# --input_format=TENSORFLOW_GRAPHDEF \
+# --output_format=TFLITE \
+# --input_shape=1,224,224,3 \
+# --input_array=input \
+# --output_array=predictions \
+# --inference_type=FLOAT \
+# --input_data_type=FLOAT
+
+
+if __name__ == "__main__":
+ #检测加载Downloads下所有文件,逐个输出检测结果
+ getResultListFromDir()
+ #生成TFLite文件
+ # createTfliteFile()
+ # print('tensorflowVersion:',tf.__version__)
+ # print('npVersion:',np.__version__)
\ No newline at end of file
diff --git a/model.py b/model.py
new file mode 100644
index 0000000..b92736b
--- /dev/null
+++ b/model.py
@@ -0,0 +1,252 @@
+import math
+import numpy as np
+import tensorflow as tf
+from enum import Enum, unique
+
+
+@unique
+class InputType(Enum):
+ TENSOR = 1
+ BASE64_JPEG = 2
+
+
+class OpenNsfwModel:
+ """Tensorflow implementation of Yahoo's Open NSFW Model
+
+ Original implementation:
+ https://github.com/yahoo/open_nsfw
+
+ Weights have been converted using caffe-tensorflow:
+ https://github.com/ethereon/caffe-tensorflow
+ """
+
+ def __init__(self):
+ self.weights = {}
+ self.bn_epsilon = 1e-5 # Default used by Caffe
+
+ def build(self, weights_path="open_nsfw-weights.npy",
+ input_type=InputType.TENSOR):
+
+ self.weights = np.load(weights_path, encoding="latin1",allow_pickle=True).item()
+ self.input_tensor = None
+
+ if input_type == InputType.TENSOR:
+ self.input = tf.placeholder(tf.float32,
+ shape=[None, 224, 224, 3],
+ name="input")
+ self.input_tensor = self.input
+ elif input_type == InputType.BASE64_JPEG:
+ from image_utils import load_base64_tensor
+
+ self.input = tf.placeholder(tf.string, shape=(None,), name="input")
+ self.input_tensor = load_base64_tensor(self.input)
+ else:
+ raise ValueError("invalid input type '{}'".format(input_type))
+
+ x = self.input_tensor
+
+ x = tf.pad(x, [[0, 0], [3, 3], [3, 3], [0, 0]], 'CONSTANT')
+ x = self.__conv2d("conv_1", x, filter_depth=64,
+ kernel_size=7, stride=2, padding='valid')
+
+ x = self.__batch_norm("bn_1", x)
+ x = tf.nn.relu(x)
+
+ x = tf.layers.max_pooling2d(x, pool_size=3, strides=2, padding='same')
+
+ x = self.__conv_block(stage=0, block=0, inputs=x,
+ filter_depths=[32, 32, 128],
+ kernel_size=3, stride=1)
+
+ x = self.__identity_block(stage=0, block=1, inputs=x,
+ filter_depths=[32, 32, 128], kernel_size=3)
+ x = self.__identity_block(stage=0, block=2, inputs=x,
+ filter_depths=[32, 32, 128], kernel_size=3)
+
+ x = self.__conv_block(stage=1, block=0, inputs=x,
+ filter_depths=[64, 64, 256],
+ kernel_size=3, stride=2)
+ x = self.__identity_block(stage=1, block=1, inputs=x,
+ filter_depths=[64, 64, 256], kernel_size=3)
+ x = self.__identity_block(stage=1, block=2, inputs=x,
+ filter_depths=[64, 64, 256], kernel_size=3)
+ x = self.__identity_block(stage=1, block=3, inputs=x,
+ filter_depths=[64, 64, 256], kernel_size=3)
+
+ x = self.__conv_block(stage=2, block=0, inputs=x,
+ filter_depths=[128, 128, 512],
+ kernel_size=3, stride=2)
+ x = self.__identity_block(stage=2, block=1, inputs=x,
+ filter_depths=[128, 128, 512], kernel_size=3)
+ x = self.__identity_block(stage=2, block=2, inputs=x,
+ filter_depths=[128, 128, 512], kernel_size=3)
+ x = self.__identity_block(stage=2, block=3, inputs=x,
+ filter_depths=[128, 128, 512], kernel_size=3)
+ x = self.__identity_block(stage=2, block=4, inputs=x,
+ filter_depths=[128, 128, 512], kernel_size=3)
+ x = self.__identity_block(stage=2, block=5, inputs=x,
+ filter_depths=[128, 128, 512], kernel_size=3)
+
+ x = self.__conv_block(stage=3, block=0, inputs=x,
+ filter_depths=[256, 256, 1024], kernel_size=3,
+ stride=2)
+ x = self.__identity_block(stage=3, block=1, inputs=x,
+ filter_depths=[256, 256, 1024],
+ kernel_size=3)
+ x = self.__identity_block(stage=3, block=2, inputs=x,
+ filter_depths=[256, 256, 1024],
+ kernel_size=3)
+
+ x = tf.layers.average_pooling2d(x, pool_size=7, strides=1,
+ padding="valid", name="pool")
+
+ x = tf.reshape(x, shape=(-1, 1024))
+
+ self.logits = self.__fully_connected(name="fc_nsfw",
+ inputs=x, num_outputs=2)
+ self.predictions = tf.nn.softmax(self.logits, name="predictions")
+
+ """Get weights for layer with given name
+ """
+ def __get_weights(self, layer_name, field_name):
+ if not layer_name in self.weights:
+ raise ValueError("No weights for layer named '{}' found"
+ .format(layer_name))
+
+ w = self.weights[layer_name]
+ if not field_name in w:
+ raise (ValueError("No entry for field '{}' in layer named '{}'"
+ .format(field_name, layer_name)))
+
+ return w[field_name]
+
+ """Layer creation and weight initialization
+ """
+ def __fully_connected(self, name, inputs, num_outputs):
+ return tf.layers.dense(
+ inputs=inputs, units=num_outputs, name=name,
+ kernel_initializer=tf.constant_initializer(
+ self.__get_weights(name, "weights"), dtype=tf.float32),
+ bias_initializer=tf.constant_initializer(
+ self.__get_weights(name, "biases"), dtype=tf.float32))
+
+ def __conv2d(self, name, inputs, filter_depth, kernel_size, stride=1,
+ padding="same", trainable=False):
+
+ if padding.lower() == 'same' and kernel_size > 1:
+ if kernel_size > 1:
+ oh = inputs.get_shape().as_list()[1]
+ h = inputs.get_shape().as_list()[1]
+
+ p = int(math.floor(((oh - 1) * stride + kernel_size - h)//2))
+
+ inputs = tf.pad(inputs,
+ [[0, 0], [p, p], [p, p], [0, 0]],
+ 'CONSTANT')
+ else:
+ raise Exception('unsupported kernel size for padding: "{}"'
+ .format(kernel_size))
+
+ return tf.layers.conv2d(
+ inputs, filter_depth,
+ kernel_size=(kernel_size, kernel_size),
+ strides=(stride, stride), padding='valid',
+ activation=None, trainable=trainable, name=name,
+ kernel_initializer=tf.constant_initializer(
+ self.__get_weights(name, "weights"), dtype=tf.float32),
+ bias_initializer=tf.constant_initializer(
+ self.__get_weights(name, "biases"), dtype=tf.float32))
+
+ def __batch_norm(self, name, inputs, training=False):
+ return tf.layers.batch_normalization(
+ inputs, training=training, epsilon=self.bn_epsilon,
+ gamma_initializer=tf.constant_initializer(
+ self.__get_weights(name, "scale"), dtype=tf.float32),
+ beta_initializer=tf.constant_initializer(
+ self.__get_weights(name, "offset"), dtype=tf.float32),
+ moving_mean_initializer=tf.constant_initializer(
+ self.__get_weights(name, "mean"), dtype=tf.float32),
+ moving_variance_initializer=tf.constant_initializer(
+ self.__get_weights(name, "variance"), dtype=tf.float32),
+ name=name)
+
+ """ResNet blocks
+ """
+ def __conv_block(self, stage, block, inputs, filter_depths,
+ kernel_size=3, stride=2):
+ filter_depth1, filter_depth2, filter_depth3 = filter_depths
+
+ conv_name_base = "conv_stage{}_block{}_branch".format(stage, block)
+ bn_name_base = "bn_stage{}_block{}_branch".format(stage, block)
+ shortcut_name_post = "_stage{}_block{}_proj_shortcut" \
+ .format(stage, block)
+
+ shortcut = self.__conv2d(
+ name="conv{}".format(shortcut_name_post), stride=stride,
+ inputs=inputs, filter_depth=filter_depth3, kernel_size=1,
+ padding="same"
+ )
+
+ shortcut = self.__batch_norm("bn{}".format(shortcut_name_post),
+ shortcut)
+
+ x = self.__conv2d(
+ name="{}2a".format(conv_name_base),
+ inputs=inputs, filter_depth=filter_depth1, kernel_size=1,
+ stride=stride, padding="same",
+ )
+ x = self.__batch_norm("{}2a".format(bn_name_base), x)
+ x = tf.nn.relu(x)
+
+ x = self.__conv2d(
+ name="{}2b".format(conv_name_base),
+ inputs=x, filter_depth=filter_depth2, kernel_size=kernel_size,
+ padding="same", stride=1
+ )
+ x = self.__batch_norm("{}2b".format(bn_name_base), x)
+ x = tf.nn.relu(x)
+
+ x = self.__conv2d(
+ name="{}2c".format(conv_name_base),
+ inputs=x, filter_depth=filter_depth3, kernel_size=1,
+ padding="same", stride=1
+ )
+ x = self.__batch_norm("{}2c".format(bn_name_base), x)
+
+ x = tf.add(x, shortcut)
+
+ return tf.nn.relu(x)
+
+ def __identity_block(self, stage, block, inputs,
+ filter_depths, kernel_size):
+ filter_depth1, filter_depth2, filter_depth3 = filter_depths
+ conv_name_base = "conv_stage{}_block{}_branch".format(stage, block)
+ bn_name_base = "bn_stage{}_block{}_branch".format(stage, block)
+
+ x = self.__conv2d(
+ name="{}2a".format(conv_name_base),
+ inputs=inputs, filter_depth=filter_depth1, kernel_size=1,
+ stride=1, padding="same",
+ )
+
+ x = self.__batch_norm("{}2a".format(bn_name_base), x)
+ x = tf.nn.relu(x)
+
+ x = self.__conv2d(
+ name="{}2b".format(conv_name_base),
+ inputs=x, filter_depth=filter_depth2, kernel_size=kernel_size,
+ padding="same", stride=1
+ )
+ x = self.__batch_norm("{}2b".format(bn_name_base), x)
+ x = tf.nn.relu(x)
+
+ x = self.__conv2d(
+ name="{}2c".format(conv_name_base),
+ inputs=x, filter_depth=filter_depth3, kernel_size=1,
+ padding="same", stride=1
+ )
+ x = self.__batch_norm("{}2c".format(bn_name_base), x)
+
+ x = tf.add(x, inputs)
+
+ return tf.nn.relu(x)
diff --git a/model/checkpoint b/model/checkpoint
new file mode 100644
index 0000000..621e67f
--- /dev/null
+++ b/model/checkpoint
@@ -0,0 +1,2 @@
+model_checkpoint_path: "nsfw_model.ckpt"
+all_model_checkpoint_paths: "nsfw_model.ckpt"
diff --git a/model/frozen_nsfw.pb b/model/frozen_nsfw.pb
new file mode 100644
index 0000000..8e7147e
Binary files /dev/null and b/model/frozen_nsfw.pb differ
diff --git a/model/nsfw-graph.pb b/model/nsfw-graph.pb
new file mode 100644
index 0000000..4cefd43
Binary files /dev/null and b/model/nsfw-graph.pb differ
diff --git a/model/nsfw.tflite b/model/nsfw.tflite
new file mode 100644
index 0000000..a13a741
Binary files /dev/null and b/model/nsfw.tflite differ
diff --git a/model/nsfw_model.ckpt.data-00000-of-00001 b/model/nsfw_model.ckpt.data-00000-of-00001
new file mode 100644
index 0000000..e86b1ed
Binary files /dev/null and b/model/nsfw_model.ckpt.data-00000-of-00001 differ
diff --git a/model/nsfw_model.ckpt.index b/model/nsfw_model.ckpt.index
new file mode 100644
index 0000000..7b1ebc4
Binary files /dev/null and b/model/nsfw_model.ckpt.index differ
diff --git a/model/nsfw_model.ckpt.meta b/model/nsfw_model.ckpt.meta
new file mode 100644
index 0000000..a16ad4c
Binary files /dev/null and b/model/nsfw_model.ckpt.meta differ