Skip to content

Commit

Permalink
完成基本demo
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniellChiang committed Feb 27, 2020
1 parent 9360ffa commit b4425bc
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 1 deletion.
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,55 @@
# mask-detection-web-demo
# face-mask-detection-web-demo
# 人脸口罩检测网页demo
Face mask deteciton demo, by tensorflow.js

## 原理
本demo是在浏览器运行的人脸口罩检测网页demo,介绍如何将深度学习的人脸口罩检测模型部署到浏览器里面。
关于人脸口罩检测的PyTorch、TensorFlow、Caffe、Keras、MXNet版本,可以进入相应Github仓库
[FaceMaskDetection](https://github.com/AIZOOTech/FaceMaskDetection)
关于项目介绍,可以阅读一下两篇文章:
[AIZOO开源人脸口罩检测数据+模型+代码+在线网页体验,通通都开源了](https://mp.weixin.qq.com/s/22U_v6IQ9PBHslI-65v_0Q)
[人脸口罩检测现开源PyTorch、TensorFlow、MXNet等全部五大主流深度学习框架模型和代码
](https://mp.weixin.qq.com/s?__biz=MzIyMDY2MTUyNg==&mid=2247483779&idx=1&sn=b9ac5af31adf1dfdc3c87eb1c74836a5&exportkey=AX%2FANiIY8CWWMPQrHKh6A5E%3D&pass_ticket=aaNfWJGBgSum6CY5pvFqx0IIfljPPkeX%2BdMtPEl3zo5hQfPnYR5mEUlayz06kNKG)


深度学习模型,可以借助TensorFlow.js库,运行在浏览器里。首先,需要使用`tensorflowjs_converter`将tensorflow的`graph model`或者keras的`layer model`转换为TensorFlow.js支持的模型。
该工具可以通过`pip install tensorflowjs`安装。

如果使用Keras模型转的操作如下:
```
tensorflowjs_convert --input_format keras --output_format tfjs_layers_model /path/to/keras/hdf5/model /path/to/output/folder
```
模型会生成`model.json`文件和一个或者多个`bin`文件,其中前者保存模型的拓扑,后者保存模型的权重。
使用JavaScript,需要在html中先引入`tfjs.min.js`库,然后加载模型
```
<script src="js/tfjs.min.js"></script>
```
`detection.js`中,加载模型
```
model = await tf.loadLayersModel('./tfjs-models/model.json');
```
置于anchor生成、输出解码、nms与使用python版本并无太大差异,大家可以查看`detection.js`中三个相关的函数,一目了然。

## 运行方法
在当前目录下打开终端,只需要建立一个最小web server即可。
对于使用python的用户
```
// python3用户
python -m http.server
// python2用户
python -m SimpleHTTPServer
```
如果你使用Node.js
```
npm install serve -g //安装serve
serve // this will open a mini web serve
// 您也可以使用http-serve
npm install http-server -g
http-server
```

## 效果
您可以点击网页中的上传图片按钮,或者拖拽图片到网页区域,然后模型会自动进行检测并画框。
![页面效果图](/images/result.png)
80 changes: 80 additions & 0 deletions css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@

html, body {
margin: 0;
padding: 0;
font-family: Apercu;

}

.title {
text-align: center
}

#tips {
color:rosybrown
}

#container {
background: #c79ef812;
/* height: 500px; */
padding: 50px;
font-size: 14px;
border-radius: 33px;
text-align: center;
}

#text-container {
height: 100px;
width: 100%;
text-align: center;
}

#instruction {
margin-top: 20px;
}

.result-color {
color: #A159FC;
}

.green-color {
color: #0bcf82;
}

.highlight {
opacity: 0.4;
background-color: rgba(161,89,252, 0.2) !important;
}

.center {
display: block;
margin-left: auto;
margin-right: auto;
}

#fileUploader {
opacity: 0;
}

#uploader-btn {
background: #faebd700;
margin-top: 1em;
color: #0bcf81;
border: solid 1px #0bcf81;
padding: .6em 2em;
line-height: 2;
font-size: 12px;
font-family: Apercu;
cursor: pointer;
border-radius: 10px;
}

#uploader-btn:hover {
color: white;
background-color: #0bcf82;
border: solid 1px #0bcf82;
-webkit-transition: all 0.3s ease;
-moz-transition: all 0.3s ease;
-o-transition: all 0.3s ease;
transition: all 0.3s ease;
}
Binary file added images/aizoocom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/demo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!--
Copyright (c) 2018 ml5
This software is released under the MIT License.
https://opensource.org/licenses/MIT
-->

<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="js/tfjs.min.js"></script>
</head>

<body onresize="windowResized()">
<a href="https://aizoo.com"><img src=images/aizoocom.png height="30px"></a>
<h1 class='title'>借助TensorFlow.js</h1>
<h1 class='title'>让人脸口罩识别跑在您本地浏览器里面</h1>
<h5 class='title' id='tips'> 注意:部分手机拍摄的图片,读取出来是旋转90°,需要旋转正,为避免本demo过于复杂,本demo不对这种case进行处理</h3>
<div id="container">
<img src="images/demo.jpg" id="image" class="center">
<div id="text-container">
<p id="instruction" class="green-color">
<button id="uploader-btn" onClick="clickUploader()">打开本地图片</button>
<p class="green-color "> 或者将图片拖到这里 📂, 然后下方会显示结果</p>

<p id="warning" class="result-color"></p>
<p class="result-color">
检测结果如下:
</p>
<input name="imgFile" type="file" id="fileUploader" accept="image/*" onChange="handleFiles()">
</div>
<canvas id="canvas"></canvas>
</div>
<script src="js/detection.js"></script>
<script src="js/index.js"></script>
</body>
</html>
130 changes: 130 additions & 0 deletions js/detection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// decode the output according to anchors
function decodeBBox(anchors, rawOutput, variances=[0.1,0.1,0.2,0.2]) {
const [anchorXmin, anchorYmin, anchorXmax, anchorYmax] = tf.split(anchors, [1,1,1,1], -1);
const anchorCX = tf.div(tf.add(anchorXmin, anchorXmax), 2);
const anchorCY = tf.div(tf.add(anchorYmin, anchorYmax), 2);

const anchorW = tf.sub(anchorXmax, anchorXmin);
const anchorH = tf.sub(anchorYmax, anchorYmin);

const rawOutputScale = tf.mul(rawOutput, tf.tensor(variances));
const [rawOutputCX, rawOutputCY, rawOutputW, rawOutputH] = tf.split(rawOutputScale, [1,1,1,1], -1);
const predictCX = tf.add(tf.mul(rawOutputCX, anchorW), anchorCX);
const predictCY = tf.add(tf.mul(rawOutputCY,anchorH), anchorCY);
const predictW = tf.mul(tf.exp(rawOutputW), anchorW);
const predictH = tf.mul(tf.exp(rawOutputH), anchorH);
const predictXmin = tf.sub(predictCX, tf.div(predictW, 2));
const predictYmin = tf.sub(predictCY, tf.div(predictH, 2));
const predictXmax = tf.add(predictCX, tf.div(predictW, 2));
const predictYmax = tf.add(predictCY, tf.div(predictH, 2));
// eslint-disable-next-line
const predictBBox = tf.concat([predictYmin, predictXmin, predictYmax, predictXmax],-1);
return predictBBox
}

// generate anchors
function anchorGenerator(featureMapSizes, anchorSizes, anchorRatios) {
let anchorBBoxes = [];
// eslint-disable-next-line
featureMapSizes.map((featureSize, idx) =>{
const cx = tf.div(tf.add(tf.linspace(0, featureSize[0] - 1, featureSize[0]) , 0.5) , featureSize[0]);
const cy = tf.div(tf.add(tf.linspace(0, featureSize[1] - 1, featureSize[1]) , 0.5) , featureSize[1]);
const cxGrid = tf.matMul(tf.ones([featureSize[1], 1]), cx.reshape([1,featureSize[0]]));
const cyGrid = tf.matMul(cy.reshape([featureSize[1], 1]), tf.ones([1, featureSize[0]]));
// eslint-disable-next-line
const cxGridExpend = tf.expandDims(cxGrid, -1);
// eslint-disable-next-line
const cyGridExpend = tf.expandDims(cyGrid, -1);
// eslint-disable-next-line
const center = tf.concat([cxGridExpend, cyGridExpend], -1);
const numAnchors = anchorSizes[idx].length + anchorRatios[idx].length -1;
const centerTiled = tf.tile(center, [1, 1, 2*numAnchors]);
// eslint-disable-next-line
let anchorWidthHeights = [];

// eslint-disable-next-line
for (const scale of anchorSizes[idx]) {
const ratio = anchorRatios[idx][0];
const width = scale * Math.sqrt(ratio);
const height = scale / Math.sqrt(ratio);

const halfWidth = width / 2;
const halfHeight = height / 2;
anchorWidthHeights.push(-halfWidth, -halfHeight, halfWidth, halfHeight);
// width = tf.mul(scale, tf.sqrt(ratio));
// height = tf.div(scale, tf.sqrt(ratio));

// halfWidth = tf.div(width, 2);
// halfHeight = tf.div(height, 2);
// anchorWidthHeights.push(tf.neg(halfWidth), tf.neg(halfWidth), halfWidth, halfHeight);
}

// eslint-disable-next-line
for ( const ratio of anchorRatios[idx].slice(1)) {
const scale = anchorSizes[idx][0];
const width = scale * Math.sqrt(ratio);
const height = scale / Math.sqrt(ratio);
const halfWidth = width / 2;
const halfHeight = height / 2;
anchorWidthHeights.push(-halfWidth, -halfHeight, halfWidth, halfHeight);
}
const bboxCoord = tf.add(centerTiled , tf.tensor(anchorWidthHeights));
const bboxCoordReshape = bboxCoord.reshape([-1, 4]);
anchorBBoxes.push(bboxCoordReshape);
})
// eslint-disable-next-line
anchorBBoxes = tf.concat(anchorBBoxes, 0);
return anchorBBoxes;
}

// nms function
function nonMaxSuppression(bboxes, confidences, confThresh, iouThresh, width, height, maxOutputSize=100) {
const bboxMaxFlag = tf.argMax(confidences, -1);
const bboxConf = tf.max(confidences, -1);
const keepIndices = tf.image.nonMaxSuppression(bboxes, bboxConf, maxOutputSize, iouThresh, confThresh);
// eslint-disable-next-line
let results = []
const keepIndicesData = keepIndices.dataSync();
const bboxConfData = bboxConf.dataSync();
const bboxMaxFlagData = bboxMaxFlag.dataSync();
const bboxesData = bboxes.dataSync();
// eslint-disable-next-line
keepIndicesData.map((idx) => {
const xmin = Math.round(Math.max(bboxesData[4*idx + 1] * width, 0));
const ymin = Math.round(Math.max(bboxesData[4*idx + 0] * height, 0));
const xmax = Math.round(Math.min(bboxesData[4*idx+3] * width, width))
const ymax = Math.round(Math.min(bboxesData[4*idx + 2] * height, height));
results.push([[xmin, ymin, xmax, ymax],
bboxMaxFlagData[idx], bboxConfData[idx]])
});
return results;
}


async function loadModel() {
model = await tf.loadLayersModel('./tfjs-models/model.json');
return model;
}

async function detect(imgToPredict) {
const detectionResults = tf.tidy(() => {
// eslint-disable-next-line
const width = imgToPredict.width;
// eslint-disable-next-line
const height = imgToPredict.height;
let img = tf.browser.fromPixels(imgToPredict);
img = tf.image.resizeBilinear(img, [260, 260]);
img = img.expandDims(0).toFloat().div(tf.scalar(255));
const [rawBBoxes, rawConfidences] = model.predict(img);
const bboxes = decodeBBox(anchors, tf.squeeze(rawBBoxes));
const Results = nonMaxSuppression(bboxes, tf.squeeze(rawConfidences), 0.5, 0.5, width, height );
return Results;
})
return detectionResults;
}

featureMapSizes = [[33, 33], [17, 17], [9, 9], [5, 5], [3,3]];
anchorSizes = [[0.04, 0.056], [0.08, 0.11], [0.16, 0.22], [0.32, 0.45], [0.64, 0.72]];
anchorRatios = [[1, 0.62, 0.42], [1, 0.62, 0.42], [1, 0.62, 0.42], [1, 0.62, 0.42], [1, 0.62, 0.42]];

let anchors = anchorGenerator(featureMapSizes, anchorSizes, anchorRatios);
Loading

0 comments on commit b4425bc

Please sign in to comment.