更新API
This commit is contained in:
1 parent
16769a8726
commit
ece1e8065c
16 files changed
+161
-1
No files matched your search
@@ -12,3 +12,4 @@ run/
|
|||||||
*.un~
|
*.un~
|
||||||
typings/
|
typings/
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
|
api-service.zip
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const nsfw = require('nsfwjs');
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const tf = require('@tensorflow/tfjs-node');
|
||||||
|
|
||||||
|
class AppBootHook {
|
||||||
|
constructor(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
configWillLoad() {
|
||||||
|
// console.log('config: ', this.app.config.uploadHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
async didLoad() {
|
||||||
|
console.log('nsfwModel 加载中···');
|
||||||
|
this.app.nsfwModel = await nsfw.load('file://./app/model/', { type: 'graph' });
|
||||||
|
// this.app.nsfwModel = await tf.loadGraphModel('file://./app/model/model.json');
|
||||||
|
console.log('nsfwModel 已经加州完毕!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// async willReady() {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// async didReady() {
|
||||||
|
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AppBootHook;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Controller = require('egg').Controller;
|
||||||
|
// const fs = require('fs');
|
||||||
|
const respFormat = require('../util/respFormat');
|
||||||
|
|
||||||
|
class CheckImageController extends Controller {
|
||||||
|
async check() {
|
||||||
|
const { ctx } = this;
|
||||||
|
console.log(ctx.request.body);
|
||||||
|
console.log('got %d files', ctx.request.files.length);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const filesResult = [];
|
||||||
|
// 遍历处理多个文件
|
||||||
|
for (const file of ctx.request.files) {
|
||||||
|
console.log('field: ' + file.fieldname);
|
||||||
|
console.log('filename: ' + file.filename);
|
||||||
|
console.log('encoding: ' + file.encoding);
|
||||||
|
console.log('mime: ' + file.mime);
|
||||||
|
console.log('tmp filepath: ' + file.filepath);
|
||||||
|
const contentCheck = await ctx.service.indecentContentCheck.contentCheck(file);
|
||||||
|
console.log('contentCheck检测结果: ', contentCheck);
|
||||||
|
const item = {
|
||||||
|
check: contentCheck,
|
||||||
|
filename: file.filename,
|
||||||
|
};
|
||||||
|
filesResult.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = respFormat({ data: filesResult });
|
||||||
|
await ctx.cleanupRequestFiles();
|
||||||
|
} finally {
|
||||||
|
// 需要删除临时文件
|
||||||
|
await ctx.cleanupRequestFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CheckImageController;
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"format": "graph-model", "generatedBy": "2.1.0", "convertedBy": "TensorFlow.js Converter v1.5.2", "userDefinedMetadata": {"signature": {"inputs": {"input:0": {"name": "input:0", "dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "-1"}, {"size": "224"}, {"size": "224"}, {"size": "3"}]}}}, "outputs": {"Identity:0": {"name": "Identity:0", "dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "-1"}, {"size": "5"}]}}}}}, "modelTopology": {"node": [{"name": "input", "op": "Placeholder", "attr": {"shape": {"shape": {"dim": [{"size": "-1"}, {"size": "224"}, {"size": "224"}, {"size": "3"}]}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/Conv/Conv2D_weights", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "3"}, {"size": "48"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv_14/expand/Conv2D_bn_offset", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1344"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/Conv/Conv2D_bn_offset", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "48"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv/depthwise/depthwise_weights", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "48"}, {"size": "1"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv/depthwise/depthwise_bn_offset", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "48"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv/project/Conv2D_weights", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "48"}, {"size": "24"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv/project/Conv2D_bn_offset", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "24"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv_1/expand/Conv2D_weights", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "1"}, {"size": "1"}, {"size": "24"}, {"size": "144"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv_14/depthwise/depthwise_weights", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "1344"}, {"size": "1"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv_1/expand/Conv2D_bn_offset", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv_1/depthwise/depthwise_weights", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "3"}, {"size": "3"}, {"size": "144"}, {"size": "1"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/StatefulPartitionedCall/predict/MobilenetV2/expanded_conv_1/depthwise/depthwise_bn_offset", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "144"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "StatefulPartitionedCall/sequential/keras_layer/StatefulPartitionedCall/StatefulPartitionedCall/Line truncated
|
||||||
@@ -8,4 +8,5 @@ module.exports = app => {
|
|||||||
// router.get('/', controller.home.index);
|
// router.get('/', controller.home.index);
|
||||||
|
|
||||||
require('./router/music')(app);
|
require('./router/music')(app);
|
||||||
|
require('./router/nsfw')(app);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = app => {
|
||||||
|
const { router, controller } = app;
|
||||||
|
// router.get(neteasecloud, controller.music.neteasecloud);
|
||||||
|
|
||||||
|
router.post('/nsfw/images/check', controller.checkImage.check);
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Service = require('egg').Service;
|
||||||
|
const tf = require('@tensorflow/tfjs-node');
|
||||||
|
// const jpeg = require('jpeg-js');
|
||||||
|
const { Image } = require('image-js');
|
||||||
|
const contentBoundary = require('../util/contentBoundary');
|
||||||
|
|
||||||
|
const convert = async img => {
|
||||||
|
// const image = await jpeg.decode(img, true);
|
||||||
|
const image = await Image.load(img.filepath);
|
||||||
|
// console.log('jpeg.decode:', image);
|
||||||
|
const numChannels = 3;
|
||||||
|
const numPixels = image.width * image.height;
|
||||||
|
const values = new Int32Array(numPixels * numChannels);
|
||||||
|
|
||||||
|
for (let i = 0; i < numPixels; i++) {
|
||||||
|
for (let c = 0; c < numChannels; ++c) { values[i * numChannels + c] = image.data[i * 4 + c]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return tf.tensor3d(values, [ image.height, image.width, numChannels ], 'int32');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ContentCheckService extends Service {
|
||||||
|
async contentCheck(img) {
|
||||||
|
try {
|
||||||
|
const image = await convert(img);
|
||||||
|
const predictions = await this.app.nsfwModel.classify(image);
|
||||||
|
console.log('Predictions: ', predictions);
|
||||||
|
const isSafeContent = contentBoundary(predictions);
|
||||||
|
return isSafeContent;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ContentCheckService;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const safeContent = [ 'Drawing', 'Neutral' ];
|
||||||
|
|
||||||
|
const contentBoundary = predictions => {
|
||||||
|
let safeProbability = 0;
|
||||||
|
for (let index = 0; index < predictions.length; index++) {
|
||||||
|
const item = predictions[index];
|
||||||
|
const className = item.className;
|
||||||
|
const probability = item.probability;
|
||||||
|
if (safeContent.includes(className)) {
|
||||||
|
safeProbability += probability;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return safeProbability > 0.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = contentBoundary;
|
||||||
@@ -23,6 +23,22 @@ module.exports = appInfo => {
|
|||||||
// myAppName: 'egg',
|
// myAppName: 'egg',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config.multipart = {
|
||||||
|
mode: 'file',
|
||||||
|
fieldNameSize: 100,
|
||||||
|
fieldSize: '100kb',
|
||||||
|
fields: 9,
|
||||||
|
fileSize: '10mb',
|
||||||
|
files: 9,
|
||||||
|
whitelist: [ '.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp' ],
|
||||||
|
};
|
||||||
|
|
||||||
|
config.security = {
|
||||||
|
csrf: {
|
||||||
|
enable: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
config.onerror = {
|
config.onerror = {
|
||||||
all(error, ctx) {
|
all(error, ctx) {
|
||||||
ctx.body = { errcode: 500, desc: error, result: false, data: null };
|
ctx.body = { errcode: 500, desc: error, result: false, data: null };
|
||||||
|
|||||||
+5
-1
@@ -8,7 +8,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"egg": "^2.15.1",
|
"egg": "^2.15.1",
|
||||||
"egg-scripts": "^2.11.0"
|
"egg-scripts": "^2.11.0",
|
||||||
|
"@tensorflow/tfjs": "^2.7.0",
|
||||||
|
"@tensorflow/tfjs-node": "^2.7.0",
|
||||||
|
"image-js": "^0.30.3",
|
||||||
|
"nsfwjs": "^2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autod": "^3.0.1",
|
"autod": "^3.0.1",
|
||||||
|
|||||||
Reference in new issue
Block a user