Skip to content

Commit

Permalink
Merge pull request #11 from GOOD-I-DEER/feat/face-detection-node
Browse files Browse the repository at this point in the history
Feat/face detection node
  • Loading branch information
chickennight authored Sep 21, 2023
2 parents 540bd25 + 079faf9 commit da4c163
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 26 deletions.
79 changes: 79 additions & 0 deletions GOOD-I-DEER/nodes/object-detection-node/face-detection.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script type="text/javascript">
RED.nodes.registerType("face-detection", {
category: "GOOD I DEER",
color: "#1AE5BE",
defaults: {
name: { value: "" },
returnType: { value: "information", validate: function(v) {
const returnType = Number(v);

if(returnType <= 1) {
$("#absolutePathDir-form").css('display', 'none');
this.outputs = 1;
if(returnType == 0) {
this._def.outputLabels = ["Information"];
} else if(returnType == 1) {
this._def.outputLabels = ["Image Buffer"];
}
} else if (returnType == 2){
$("#absolutePathDir-form").css('display', 'block');
this.outputs = 0;
this._def.outputLabels = undefined;
}
return true;
} },
threshold: {value: 0.5, validate: function(v) {
let threshold = Number(v);

if(threshold < 0 || threshold >= 1){
$("#threshold-warning").css('display', 'block');
}else{
$("#threshold-warning").css('display', 'none');
}
return threshold >= 0 && threshold < 1;
}},
absolutePathDir: { value: "", validate: function(v) {
if(v)
return true;
return false;
} }
},
inputs: 1,
outputs: 1,
paletteLabel: "Good Face Detection",
icon: "font-awesome/fa-smile-o",
label: function () {
return this.name || "Good Face Detection";
},
inputLabels: ["Image Buffer"],
outputLabels: undefined
});
</script>

<script type="text/html" data-template-name="face-detection">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name" />
</div>
<div class="form-row">
<label for="node-input-returnType"><i class="fa fa-file"></i> Return Type</label>
<select type="text" id="node-input-returnType">
<option value=0>Inforamation</option>
<option value=1>Image Buffer</option>
<option value=2>Image File</option>
</select>
</div>
<div class="form-row">
<label for="node-input-threshold"><i class="fa fa-sort"></i> Threshold</label>
<input type="number" id="node-input-threshold" step="0.1" />
<p id="threshold-warning" style="color: red; display: none; margin-left: 105px;" >0 ≤ threshold < 1</p>
</div>
<div class="form-row" id="absolutePathDir-form">
<label for="node-input-absolutePathDir"><i class="fa fa-folder"></i> Absolute Path Dir</label>
<input type="text" id="node-input-absolutePathDir" />
</div>
</script>

<script type="text/html" data-help-name="face-detection">
<p>face-detection node using yolov8</p>
</script>
211 changes: 211 additions & 0 deletions GOOD-I-DEER/nodes/object-detection-node/face-detection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
module.exports = function (RED) {
function yolov8NodeFace(config) {
RED.nodes.createNode(this, config);
const ort = require("onnxruntime-node");
const sharp = require("sharp");
const fs = require("fs");

const node = this;
const returnType = Number(config.returnType);
const saveDir = config.absolutePathDir;
let bufferFromImage;

node.on("input", async function (msg) {
const img = sharp(msg.payload);
bufferFromImage = msg.payload;
if (returnType <= 1) {
if (returnType === 0) {
msg.payload = await detect_face_on_image_informations(img);
} else if (returnType === 1) {
msg.payload = await detect_face_on_image_imageBuffer(img);
}
node.send(msg);
} else if (returnType === 2) {
// 저장 위치 존재하는지 검사해야함!!!!!!!!!!!!
if (fs.existsSync(saveDir)) {
await detect_face_on_image_save(img);
} else {
node.error("folder dosen't exists");
}
}
});

async function detect_face_on_image_informations(img) {
const [input, img_width, img_height] = await prepare_input(img);
const output = await run_model(input);
const boxes = process_output(output, img_width, img_height);
return get_image_informations(boxes);
}

async function detect_face_on_image_imageBuffer(img) {
const boxes = await detect_face_on_image_informations(img);
return get_image_buffers(boxes);
}

async function detect_face_on_image_save(img) {
const boxes = await detect_face_on_image_informations(img);
save_images(boxes);
}

async function prepare_input(img) {
const md = await img.metadata();

const [img_width, img_height] = [md.width, md.height];
const pixels = await img
.removeAlpha()
.resize({ width: 640, height: 640, fit: "fill" })
.raw()
.toBuffer();
const red = [],
green = [],
blue = [];
for (let index = 0; index < pixels.length; index += 3) {
red.push(pixels[index] / 255.0);
green.push(pixels[index + 1] / 255.0);
blue.push(pixels[index + 2] / 255.0);
}
const input = [...red, ...green, ...blue];
return [input, img_width, img_height];
}

async function run_model(input) {
const model = await ort.InferenceSession.create(
`${__dirname}/model/yolov8n-face.onnx`
);
input = new ort.Tensor(Float32Array.from(input), [1, 3, 640, 640]);
const outputs = await model.run({ images: input });
return outputs["output0"].data;
}

function process_output(output, img_width, img_height) {
let boxes = [];
for (let index = 0; index < 8400; index++) {
const [class_id, prob] = [...Array(1).keys()] // 수정 필요
.map((col) => [col, output[8400 * (col + 4) + index]])
.reduce((accum, item) => (item[1] > accum[1] ? item : accum), [0, 0]);
if (prob < config.threshold) {
// 확률이 threshold이하면 무시
continue;
}
const label = "face";
const xc = output[index];
const yc = output[8400 + index];
const w = output[2 * 8400 + index];
const h = output[3 * 8400 + index];
const x1 = ((xc - w / 2) / 640) * img_width;
const y1 = ((yc - h / 2) / 640) * img_height;
const x2 = ((xc + w / 2) / 640) * img_width;
const y2 = ((yc + h / 2) / 640) * img_height;
boxes.push([x1, y1, x2, y2, label, prob]);
}

boxes = boxes.sort((box1, box2) => box2[5] - box1[5]);
const result = [];
while (boxes.length > 0) {
result.push(boxes[0]);
boxes = boxes.filter((box) => iou(boxes[0], box) < 0.7);
}
return result;
}

function get_image_informations(boxes) {
const result = [];
boxes.forEach((box) => {
const info = {
label: box[4],
x: box[0],
y: box[1],
w: box[2] - box[0],
h: box[3] - box[1],
prob: box[5],
};
result.push(info);
});
return result;
}

async function get_image_buffers(boxes) {
const result = [];
await Promise.all(
boxes.map(async (box) => {
const buffer = await makeBuffer(box);
result.push(buffer);
})
);
return result;
}

function save_images(boxes) {
let faceCount = 1;
const today = new Date();
const dateformat =
"/" +
today.getFullYear() +
(today.getMonth() + 1 < 10
? "0" + (today.getMonth() + 1)
: today.getMonth() + 1) +
(today.getDate() < 10 ? "0" + today.getDate() : today.getDate()) +
(today.getHours() < 10 ? "0" + today.getHours() : today.getHours()) +
(today.getMinutes() < 10
? "0" + today.getMinutes()
: today.getMinutes()) +
(today.getSeconds() < 10
? "0" + today.getSeconds()
: today.getSeconds());
boxes.forEach(async (box) => {
const outputImage =
saveDir + dateformat + "_" + "face" + faceCount++ + ".png";
sharp(bufferFromImage)
.extract({
width: parseInt(box.w),
height: parseInt(box.h),
left: parseInt(box.x),
top: parseInt(box.y),
})
.toFile(outputImage)
.then(() => node.trace("Image cropped and saved in saveDir"))
.catch(() =>
node.error("An error occured, when image cropped and saved")
);
});
}

async function makeBuffer(box) {
const buffer = await sharp(bufferFromImage)
.extract({
width: parseInt(box.w),
height: parseInt(box.h),
left: parseInt(box.x),
top: parseInt(box.y),
})
.toFormat("png")
.toBuffer();

return buffer;
}

function iou(box1, box2) {
return intersection(box1, box2) / union(box1, box2);
}

function union(box1, box2) {
const [box1_x1, box1_y1, box1_x2, box1_y2] = box1;
const [box2_x1, box2_y1, box2_x2, box2_y2] = box2;
const box1_area = (box1_x2 - box1_x1) * (box1_y2 - box1_y1);
const box2_area = (box2_x2 - box2_x1) * (box2_y2 - box2_y1);
return box1_area + box2_area - intersection(box1, box2);
}

function intersection(box1, box2) {
const [box1_x1, box1_y1, box1_x2, box1_y2] = box1;
const [box2_x1, box2_y1, box2_x2, box2_y2] = box2;
const x1 = Math.max(box1_x1, box2_x1);
const y1 = Math.max(box1_y1, box2_y1);
const x2 = Math.min(box1_x2, box2_x2);
const y2 = Math.min(box1_y2, box2_y2);
return (x2 - x1) * (y2 - y1);
}
}

RED.nodes.registerType("face-detection", yolov8NodeFace);
};
Binary file not shown.
32 changes: 12 additions & 20 deletions GOOD-I-DEER/nodes/object-detection-node/object-detection.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<script type="text/javascript">
RED.nodes.registerType("object-detection", {
category: "Samsung AutomationStudio",
color: "#a6bbcf",
category: "GOOD I DEER",
color: "#1AE5BE",
defaults: {
name: { value: "" },
model: { value: "YoloV8n"},
threshold: {value: 0.5, validate: function(v){
let threshold = Number($("#node-input-threshold").val());
let threshold = Number(v);

if(threshold < 0 || threshold >= 1){
$("#threshold-warning").css('display', 'block');
Expand All @@ -18,25 +18,13 @@
},
inputs: 1,
outputs: 1,
paletteLabel: "Good Object Detection",
inputLabels: ["Image Buffer"],
outputLabels: ["Information"],
icon: "font-awesome/fa-object-group",
label: function () {
return this.name || "object-detection";
return this.name || "Good Object Detection";
},
oneditprepare: function () {
$("#node-input-model").typedInput({
types:[
{
value: "model",
options: [
{ value: "yolov8n", label: "YoloV8n"},
{ value: "yolov8s", label: "YoloV8s"},
{ value: "yolov8m", label: "YoloV8m"}
]
}
]
}
)
}
});
</script>

Expand All @@ -47,7 +35,11 @@
</div>
<div class="form-row">
<label for="node-input-model"><i class="fa fa-wrench"></i> Model</label>
<input type="text" id="node-input-model">
<select type="text" id="node-input-model">
<option value="yolov8n">YoloV8n</option>
<option value="yolov8s">YoloV8s</option>
<option value="yolov8m">YoloV8m</option>
</select>
</div>
<div class="form-row">
<label for="node-input-threshold"><i class="fa fa-sort"></i> Threshold</label>
Expand Down
Loading

0 comments on commit da4c163

Please sign in to comment.