├── .idea
├── .gitignore
├── flask-open-nsfw.iml
├── misc.xml
├── modules.xml
├── other.xml
├── vcs.xml
└── workspace.xml
├── README.md
├── data
└── open_nsfw-weights.npy
├── images
└── image1.png
├── main.py
├── model.py
└── model
├── checkpoint
├── frozen_nsfw.pb
├── nsfw-graph.pb
├── nsfw.tflite
├── nsfw_model.ckpt.data-00000-of-00001
├── nsfw_model.ckpt.index
└── nsfw_model.ckpt.meta
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/flask-open-nsfw.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/other.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | 1557308447629
149 |
150 |
151 | 1557308447629
152 |
153 |
154 |
155 |
156 | 1557713329813
157 |
158 |
159 |
160 | 1557713329813
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | file://$PROJECT_DIR$/main.py
216 | 28
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ####
2 |
3 | - 修改自:https://github.com/yahoo/open_nsfw
4 | - 3.6
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/data/open_nsfw-weights.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/data/open_nsfw-weights.npy
--------------------------------------------------------------------------------
/images/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/images/image1.png
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import sys
3 | import argparse
4 | import tensorflow as tf
5 | import io
6 | from model import OpenNsfwModel, InputType
7 | import flask
8 | from PIL import Image
9 | import numpy as np
10 | import skimage
11 | import skimage.io
12 | import os
13 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
14 | import os
15 | os.environ["CUDA_VISIBLE_DEVICES"]="-1"
16 |
17 | model_weights_path = 'data/open_nsfw-weights.npy'
18 | model = OpenNsfwModel()
19 |
20 | VGG_MEAN = [104, 117, 123]
21 |
22 | img_width, img_height = 224, 224
23 |
24 | app = flask.Flask(__name__)
25 |
26 |
27 | # 将RGB按照BGR重新组装,然后对每一个RGB对应的值减去一定阈值
28 | def prepare_image(image):
29 | H, W, _ = image.shape
30 | h, w = (img_width, img_height)
31 |
32 | h_off = max((H - h) // 2, 0)
33 | w_off = max((W - w) // 2, 0)
34 | image = image[h_off:h_off + h, w_off:w_off + w, :]
35 |
36 | image = image[:, :, :: -1]
37 |
38 | image = image.astype(np.float32, copy=False)
39 | image = image * 255.0
40 | image = image-np.array(VGG_MEAN, dtype=np.float32)
41 |
42 | image = np.expand_dims(image, axis=0)
43 | return image
44 |
45 | # 使用TFLite文件检测
46 | def getResultFromFilePathByTFLite(path):
47 | model_path = "./model/nsfw.tflite"
48 | interpreter = tf.lite.Interpreter(model_path=model_path)
49 | interpreter.allocate_tensors()
50 |
51 | # Get input and output tensors.
52 | input_details = interpreter.get_input_details()
53 | # print(str(input_details))
54 | output_details = interpreter.get_output_details()
55 | # print(str(output_details))
56 |
57 | im = Image.open(path)
58 | # im = Image.open(r"./images/image1.png")
59 | if im.mode != "RGB":
60 | im = im.convert('RGB')
61 | imr = im.resize((256, 256), resample=Image.BILINEAR)
62 | fh_im = io.BytesIO()
63 | imr.save(fh_im, format='JPEG')
64 | fh_im.seek(0)
65 |
66 | image = (skimage.img_as_float(skimage.io.imread(fh_im, as_grey=False))
67 | .astype(np.float32))
68 |
69 | # 填装数据
70 | final = prepare_image(image)
71 | interpreter.set_tensor(input_details[0]['index'], final)
72 |
73 | # 调用模型
74 | interpreter.invoke()
75 | output_data = interpreter.get_tensor(output_details[0]['index'])
76 |
77 | # 出来的结果去掉没用的维度
78 | result = np.squeeze(output_data)
79 | print('TFLite-->>result:{},path:{}'.format(result, path))
80 | print(
81 | "==========================================================================================================")
82 | print("")
83 | print("")
84 |
85 | def getResultFromFilePathByPyModle(path):
86 | # print("numpy-version:" + np.__version__)
87 | # print("tensorflow-version:" + tf.__version__)
88 | im = Image.open(path)
89 |
90 | if im.mode != "RGB":
91 | im = im.convert('RGB')
92 |
93 | # print("图片reSize:256*256")
94 | imr = im.resize((256, 256), resample=Image.BILINEAR)
95 |
96 | fh_im = io.BytesIO()
97 | imr.save(fh_im, format='JPEG')
98 | fh_im.seek(0)
99 |
100 | image = (skimage.img_as_float(skimage.io.imread(fh_im, as_grey=False))
101 | .astype(np.float32))
102 |
103 | final = prepare_image(image)
104 |
105 | tf.reset_default_graph()
106 | with tf.Session() as sess:
107 | input_type = InputType[InputType.TENSOR.name.upper()]
108 | model.build(weights_path=model_weights_path, input_type=input_type)
109 | sess.run(tf.global_variables_initializer())
110 |
111 | predictions = sess.run(model.predictions, feed_dict={model.input: final})
112 | # print("\tSFW score:\t{}\n\tNSFW score:\t{}".format(*predictions[0]))
113 | print(
114 | "==========================================================================================================")
115 | print('Python-->>result:{},path:{}'.format(predictions[0], path))
116 |
117 |
118 | def getResultListFromDir():
119 | list = os.listdir("/Users/zhaowenwen/Downloads/testImages")
120 | for i in range(0, len(list)):
121 | if (list[i] != ".DS_Store" and list[i] != ".localized"):
122 | getResultFromFilePathByPyModle(os.path.join("/Users/zhaowenwen/Downloads/testImages", list[i]))
123 | getResultFromFilePathByTFLite(os.path.join("/Users/zhaowenwen/Downloads/testImages", list[i]))
124 |
125 |
126 | # 代码生成tflite文件
127 | def createTfliteFile():
128 | in_path = "./model/frozen_nsfw.pb"
129 | out_path = "./model/nsfw.tflite"
130 |
131 | # 模型输入节点
132 | input_tensor_name = ["input"]
133 | input_tensor_shape = {"input":[1, 224,224,3]}
134 | # 模型输出节点
135 | classes_tensor_name = ["predictions"]
136 |
137 | converter = tf.lite.TFLiteConverter.from_frozen_graph(in_path,
138 | input_tensor_name, classes_tensor_name,
139 | input_shapes = input_tensor_shape)
140 | # converter.post_training_quantize = True
141 | tflite_model = converter.convert()
142 |
143 | with open(out_path, "wb") as f:
144 | f.write(tflite_model)
145 |
146 | #生成.pb .index .meta .ckpt.data文件
147 | # 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
148 |
149 | #命令行生成tflite文件(tonserflow1.3前可用)
150 | #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
151 |
152 |
153 | #
154 | # toco \
155 | # --graph_def_file=/Users/jason/nsfw/flask-open-nsfw/model/frozen_nsfw.pb \
156 | # --output_file=/Users/jason/nsfw/flask-open-nsfw/model/aaaa.lite \
157 | # --input_format=TENSORFLOW_GRAPHDEF \
158 | # --output_format=TFLITE \
159 | # --input_shape=1,224,224,3 \
160 | # --input_array=input \
161 | # --output_array=predictions \
162 | # --inference_type=FLOAT \
163 | # --input_data_type=FLOAT
164 |
165 |
166 | if __name__ == "__main__":
167 | #检测加载Downloads下所有文件,逐个输出检测结果
168 | getResultListFromDir()
169 | #生成TFLite文件
170 | # createTfliteFile()
171 | # print('tensorflowVersion:',tf.__version__)
172 | # print('npVersion:',np.__version__)
--------------------------------------------------------------------------------
/model.py:
--------------------------------------------------------------------------------
1 | import math
2 | import numpy as np
3 | import tensorflow as tf
4 | from enum import Enum, unique
5 |
6 |
7 | @unique
8 | class InputType(Enum):
9 | TENSOR = 1
10 | BASE64_JPEG = 2
11 |
12 |
13 | class OpenNsfwModel:
14 | """Tensorflow implementation of Yahoo's Open NSFW Model
15 |
16 | Original implementation:
17 | https://github.com/yahoo/open_nsfw
18 |
19 | Weights have been converted using caffe-tensorflow:
20 | https://github.com/ethereon/caffe-tensorflow
21 | """
22 |
23 | def __init__(self):
24 | self.weights = {}
25 | self.bn_epsilon = 1e-5 # Default used by Caffe
26 |
27 | def build(self, weights_path="open_nsfw-weights.npy",
28 | input_type=InputType.TENSOR):
29 |
30 | self.weights = np.load(weights_path, encoding="latin1",allow_pickle=True).item()
31 | self.input_tensor = None
32 |
33 | if input_type == InputType.TENSOR:
34 | self.input = tf.placeholder(tf.float32,
35 | shape=[None, 224, 224, 3],
36 | name="input")
37 | self.input_tensor = self.input
38 | elif input_type == InputType.BASE64_JPEG:
39 | from image_utils import load_base64_tensor
40 |
41 | self.input = tf.placeholder(tf.string, shape=(None,), name="input")
42 | self.input_tensor = load_base64_tensor(self.input)
43 | else:
44 | raise ValueError("invalid input type '{}'".format(input_type))
45 |
46 | x = self.input_tensor
47 |
48 | x = tf.pad(x, [[0, 0], [3, 3], [3, 3], [0, 0]], 'CONSTANT')
49 | x = self.__conv2d("conv_1", x, filter_depth=64,
50 | kernel_size=7, stride=2, padding='valid')
51 |
52 | x = self.__batch_norm("bn_1", x)
53 | x = tf.nn.relu(x)
54 |
55 | x = tf.layers.max_pooling2d(x, pool_size=3, strides=2, padding='same')
56 |
57 | x = self.__conv_block(stage=0, block=0, inputs=x,
58 | filter_depths=[32, 32, 128],
59 | kernel_size=3, stride=1)
60 |
61 | x = self.__identity_block(stage=0, block=1, inputs=x,
62 | filter_depths=[32, 32, 128], kernel_size=3)
63 | x = self.__identity_block(stage=0, block=2, inputs=x,
64 | filter_depths=[32, 32, 128], kernel_size=3)
65 |
66 | x = self.__conv_block(stage=1, block=0, inputs=x,
67 | filter_depths=[64, 64, 256],
68 | kernel_size=3, stride=2)
69 | x = self.__identity_block(stage=1, block=1, inputs=x,
70 | filter_depths=[64, 64, 256], kernel_size=3)
71 | x = self.__identity_block(stage=1, block=2, inputs=x,
72 | filter_depths=[64, 64, 256], kernel_size=3)
73 | x = self.__identity_block(stage=1, block=3, inputs=x,
74 | filter_depths=[64, 64, 256], kernel_size=3)
75 |
76 | x = self.__conv_block(stage=2, block=0, inputs=x,
77 | filter_depths=[128, 128, 512],
78 | kernel_size=3, stride=2)
79 | x = self.__identity_block(stage=2, block=1, inputs=x,
80 | filter_depths=[128, 128, 512], kernel_size=3)
81 | x = self.__identity_block(stage=2, block=2, inputs=x,
82 | filter_depths=[128, 128, 512], kernel_size=3)
83 | x = self.__identity_block(stage=2, block=3, inputs=x,
84 | filter_depths=[128, 128, 512], kernel_size=3)
85 | x = self.__identity_block(stage=2, block=4, inputs=x,
86 | filter_depths=[128, 128, 512], kernel_size=3)
87 | x = self.__identity_block(stage=2, block=5, inputs=x,
88 | filter_depths=[128, 128, 512], kernel_size=3)
89 |
90 | x = self.__conv_block(stage=3, block=0, inputs=x,
91 | filter_depths=[256, 256, 1024], kernel_size=3,
92 | stride=2)
93 | x = self.__identity_block(stage=3, block=1, inputs=x,
94 | filter_depths=[256, 256, 1024],
95 | kernel_size=3)
96 | x = self.__identity_block(stage=3, block=2, inputs=x,
97 | filter_depths=[256, 256, 1024],
98 | kernel_size=3)
99 |
100 | x = tf.layers.average_pooling2d(x, pool_size=7, strides=1,
101 | padding="valid", name="pool")
102 |
103 | x = tf.reshape(x, shape=(-1, 1024))
104 |
105 | self.logits = self.__fully_connected(name="fc_nsfw",
106 | inputs=x, num_outputs=2)
107 | self.predictions = tf.nn.softmax(self.logits, name="predictions")
108 |
109 | """Get weights for layer with given name
110 | """
111 | def __get_weights(self, layer_name, field_name):
112 | if not layer_name in self.weights:
113 | raise ValueError("No weights for layer named '{}' found"
114 | .format(layer_name))
115 |
116 | w = self.weights[layer_name]
117 | if not field_name in w:
118 | raise (ValueError("No entry for field '{}' in layer named '{}'"
119 | .format(field_name, layer_name)))
120 |
121 | return w[field_name]
122 |
123 | """Layer creation and weight initialization
124 | """
125 | def __fully_connected(self, name, inputs, num_outputs):
126 | return tf.layers.dense(
127 | inputs=inputs, units=num_outputs, name=name,
128 | kernel_initializer=tf.constant_initializer(
129 | self.__get_weights(name, "weights"), dtype=tf.float32),
130 | bias_initializer=tf.constant_initializer(
131 | self.__get_weights(name, "biases"), dtype=tf.float32))
132 |
133 | def __conv2d(self, name, inputs, filter_depth, kernel_size, stride=1,
134 | padding="same", trainable=False):
135 |
136 | if padding.lower() == 'same' and kernel_size > 1:
137 | if kernel_size > 1:
138 | oh = inputs.get_shape().as_list()[1]
139 | h = inputs.get_shape().as_list()[1]
140 |
141 | p = int(math.floor(((oh - 1) * stride + kernel_size - h)//2))
142 |
143 | inputs = tf.pad(inputs,
144 | [[0, 0], [p, p], [p, p], [0, 0]],
145 | 'CONSTANT')
146 | else:
147 | raise Exception('unsupported kernel size for padding: "{}"'
148 | .format(kernel_size))
149 |
150 | return tf.layers.conv2d(
151 | inputs, filter_depth,
152 | kernel_size=(kernel_size, kernel_size),
153 | strides=(stride, stride), padding='valid',
154 | activation=None, trainable=trainable, name=name,
155 | kernel_initializer=tf.constant_initializer(
156 | self.__get_weights(name, "weights"), dtype=tf.float32),
157 | bias_initializer=tf.constant_initializer(
158 | self.__get_weights(name, "biases"), dtype=tf.float32))
159 |
160 | def __batch_norm(self, name, inputs, training=False):
161 | return tf.layers.batch_normalization(
162 | inputs, training=training, epsilon=self.bn_epsilon,
163 | gamma_initializer=tf.constant_initializer(
164 | self.__get_weights(name, "scale"), dtype=tf.float32),
165 | beta_initializer=tf.constant_initializer(
166 | self.__get_weights(name, "offset"), dtype=tf.float32),
167 | moving_mean_initializer=tf.constant_initializer(
168 | self.__get_weights(name, "mean"), dtype=tf.float32),
169 | moving_variance_initializer=tf.constant_initializer(
170 | self.__get_weights(name, "variance"), dtype=tf.float32),
171 | name=name)
172 |
173 | """ResNet blocks
174 | """
175 | def __conv_block(self, stage, block, inputs, filter_depths,
176 | kernel_size=3, stride=2):
177 | filter_depth1, filter_depth2, filter_depth3 = filter_depths
178 |
179 | conv_name_base = "conv_stage{}_block{}_branch".format(stage, block)
180 | bn_name_base = "bn_stage{}_block{}_branch".format(stage, block)
181 | shortcut_name_post = "_stage{}_block{}_proj_shortcut" \
182 | .format(stage, block)
183 |
184 | shortcut = self.__conv2d(
185 | name="conv{}".format(shortcut_name_post), stride=stride,
186 | inputs=inputs, filter_depth=filter_depth3, kernel_size=1,
187 | padding="same"
188 | )
189 |
190 | shortcut = self.__batch_norm("bn{}".format(shortcut_name_post),
191 | shortcut)
192 |
193 | x = self.__conv2d(
194 | name="{}2a".format(conv_name_base),
195 | inputs=inputs, filter_depth=filter_depth1, kernel_size=1,
196 | stride=stride, padding="same",
197 | )
198 | x = self.__batch_norm("{}2a".format(bn_name_base), x)
199 | x = tf.nn.relu(x)
200 |
201 | x = self.__conv2d(
202 | name="{}2b".format(conv_name_base),
203 | inputs=x, filter_depth=filter_depth2, kernel_size=kernel_size,
204 | padding="same", stride=1
205 | )
206 | x = self.__batch_norm("{}2b".format(bn_name_base), x)
207 | x = tf.nn.relu(x)
208 |
209 | x = self.__conv2d(
210 | name="{}2c".format(conv_name_base),
211 | inputs=x, filter_depth=filter_depth3, kernel_size=1,
212 | padding="same", stride=1
213 | )
214 | x = self.__batch_norm("{}2c".format(bn_name_base), x)
215 |
216 | x = tf.add(x, shortcut)
217 |
218 | return tf.nn.relu(x)
219 |
220 | def __identity_block(self, stage, block, inputs,
221 | filter_depths, kernel_size):
222 | filter_depth1, filter_depth2, filter_depth3 = filter_depths
223 | conv_name_base = "conv_stage{}_block{}_branch".format(stage, block)
224 | bn_name_base = "bn_stage{}_block{}_branch".format(stage, block)
225 |
226 | x = self.__conv2d(
227 | name="{}2a".format(conv_name_base),
228 | inputs=inputs, filter_depth=filter_depth1, kernel_size=1,
229 | stride=1, padding="same",
230 | )
231 |
232 | x = self.__batch_norm("{}2a".format(bn_name_base), x)
233 | x = tf.nn.relu(x)
234 |
235 | x = self.__conv2d(
236 | name="{}2b".format(conv_name_base),
237 | inputs=x, filter_depth=filter_depth2, kernel_size=kernel_size,
238 | padding="same", stride=1
239 | )
240 | x = self.__batch_norm("{}2b".format(bn_name_base), x)
241 | x = tf.nn.relu(x)
242 |
243 | x = self.__conv2d(
244 | name="{}2c".format(conv_name_base),
245 | inputs=x, filter_depth=filter_depth3, kernel_size=1,
246 | padding="same", stride=1
247 | )
248 | x = self.__batch_norm("{}2c".format(bn_name_base), x)
249 |
250 | x = tf.add(x, inputs)
251 |
252 | return tf.nn.relu(x)
253 |
--------------------------------------------------------------------------------
/model/checkpoint:
--------------------------------------------------------------------------------
1 | model_checkpoint_path: "nsfw_model.ckpt"
2 | all_model_checkpoint_paths: "nsfw_model.ckpt"
3 |
--------------------------------------------------------------------------------
/model/frozen_nsfw.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/model/frozen_nsfw.pb
--------------------------------------------------------------------------------
/model/nsfw-graph.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/model/nsfw-graph.pb
--------------------------------------------------------------------------------
/model/nsfw.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/model/nsfw.tflite
--------------------------------------------------------------------------------
/model/nsfw_model.ckpt.data-00000-of-00001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/model/nsfw_model.ckpt.data-00000-of-00001
--------------------------------------------------------------------------------
/model/nsfw_model.ckpt.index:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/model/nsfw_model.ckpt.index
--------------------------------------------------------------------------------
/model/nsfw_model.ckpt.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devzwy/NSFW-Python/9721812813833c0d76d1915a2740339017ac5fe9/model/nsfw_model.ckpt.meta
--------------------------------------------------------------------------------