Pārlūkot izejas kodu

Use OpenCV instead of Clarifai for face recognition

denikson 6 gadi atpakaļ
vecāks
revīzija
af5883ab22
6 mainītis faili ar 6753 papildinājumiem un 101 dzēšanām
  1. 6693 0
      commands/animu.xml
  2. 57 78
      commands/facemorph.js
  3. 0 6
      commands/image_statistics.js
  4. 0 11
      db.js
  5. 3 3
      package.json
  6. 0 3
      util.js

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6693 - 0
commands/animu.xml


+ 57 - 78
commands/facemorph.js

@@ -1,98 +1,77 @@
-const Clarifai = require("clarifai");
-const ClarifaiTOKEN = require("../clarifai_keys.js");
 const db = require("../db.js");
 const util = require("../util.js");
 const Jimp = require("jimp");
 const client = require("../client.js");
-const schedule = require("node-schedule");
+const cv = require("opencv4nodejs");
+const path = require("path");
+const request = require("request-promise-native");
 
 const EMOTE_GUILD = "505333548694241281";
-const clarifaiApp = new Clarifai.App({
-    apiKey: ClarifaiTOKEN
-});
-const MAX_REQUESTS_PER_MONTH = 5000;
 
-let quotaRecomputeJob = null;
-let dailyResetJob = null;
+const animeCascade = new cv.CascadeClassifier(path.resolve(__dirname, "./animu.xml"));
+const faceCascade = new cv.CascadeClassifier(cv.HAAR_FRONTALFACE_ALT2);
 
-function recomputeQuotas() {
-    console.log(`Recomputing quotas on ${new Date().toISOString()}!`);
-    db.set("faceEditInfo.quotaRecomputeDate", "").write();
 
-    let channelPredictProbability = db.get("faceEditChannels").value();
-    let channelStatistics = db.get("faceEditStatistics").value();
-
-    let totalRandomPredicts = 0;
-
-    for (const channel in channelStatistics) {
-        if (channelStatistics.hasOwnProperty(channel) && channelPredictProbability.hasOwnProperty(channel)) {
-            totalRandomPredicts += channelPredictProbability[channel] * channelStatistics[channel];
-            channelStatistics[channel] = 0;
-        }
-    }
-
-    let dailyQuota = Math.floor((MAX_REQUESTS_PER_MONTH - totalRandomPredicts) / 30.0);
-
-    db.set("faceEditInfo.customRequestsPerDay", dailyQuota).write();
-    db.set("faceEditStatistics", channelStatistics).write();
-
-    quotaRecomputeJob.cancel();
-}
-
-function resetDailyQuota() {
-    console.log("Resetting today image request count!");
-    db.set("faceEditInfo.todayRequests", 0).write();
-}
-
-function setupJobs() {
-    let recomputeJobDate = db.get("faceEditInfo.quotaRecomputeDate").value();
-    if (recomputeJobDate)
-        quotaRecomputeJob = schedule.scheduleJob(new Date(recomputeJobDate), recomputeQuotas);
-
-    let rule = new schedule.RecurrenceRule();
-    rule.hour = 0;
-    rule.minute = 0;
-    dailyResetJob = schedule.scheduleJob(rule, resetDailyQuota);
-
-    checkQuotaJobs();
+function intersects(r1, r2) {
+    return (r1.x <= r2.x + r2.width && r1.x + r1.width >= r2.x)
+        && (r1.y <= r2.y + r2.height && r1.y + r1.height >= r2.y);
 }
-setupJobs();
 
-function checkQuotaJobs() {
-    let jobDateText = db.get("faceEditInfo.quotaRecomputeDate").value();
-    if (jobDateText)
-        return;
+async function processFaceSwap(message, attachmentUrl, failMessage, successMessage) {
+    let data = await request(attachmentUrl, {encoding: null});
 
-    let jobDate = new Date();
-    jobDate.setMonth(jobDate.getMonth() + 1);
-    db.set("faceEditInfo.quotaRecomputeDate", jobDate.toISOString()).write();
-    quotaRecomputeJob.reschedule(jobDate, recomputeQuotas);
-}
+    let im = await cv.imdecodeAsync(data, cv.IMREAD_COLOR);
 
-async function processFaceSwap(message, attachmentUrl, failMessage, successMessage) {
-    let result = await clarifaiApp.models.predict(Clarifai.FACE_DETECT_MODEL, attachmentUrl);
+    let gray = await im.cvtColorAsync(cv.COLOR_BGR2GRAY);
+    let normGray = await gray.equalizeHistAsync();
 
-    checkQuotaJobs();
+    let animeFaces = await animeCascade.detectMultiScaleAsync(normGray, 1.1, 5, 0, new cv.Size(24, 24));
+    let normalFaces = await faceCascade.detectMultiScaleAsync(gray);
 
-    if (result.outputs[0].data.regions === undefined || result.outputs[0].data.regions.length == 0) {
+    if(animeFaces.objects.length == 0 && normalFaces.objects.length == 0) {
         if (failMessage)
             message.channel.send(failMessage);
         return;
     }
+    
+    let faces = [...normalFaces.objects, ...animeFaces.objects];
+
+    let normalCount = normalFaces.objects.length;
+    let animeCount = animeFaces.objects.length;
+
+    for (let i = 0; i < normalCount; i++) {
+        const rNormal = faces[i];
+        
+        if(animeCount == 0)
+            break;
+
+        for (let j = normalCount; j < faces.length; j++) {
+            const rAnime = faces[j];
+            
+            if(intersects(rAnime, rNormal)) {
+                let animeA = rAnime.width * rAnime.height;
+                let faceA = rNormal.width * rNormal.height;
+
+                if(animeA > faceA){
+                    faces.splice(i, 1);
+                    normalCount--;
+                    i--;
+                    break;
+                } else {
+                    faces.splice(j, 1);
+                    animeCount--;
+                    j--;
+                }
+            }
+        }
+    }
 
-    let image = await Jimp.read(attachmentUrl);
-    let w = image.getWidth();
-    let h = image.getHeight();
-
+    let jimpImage = await Jimp.read(data);
     let emojiKeys = [...client.guilds.get(EMOTE_GUILD).emojis.filter(e => !e.animated).keys()];
 
-    for (let region of result.outputs[0].data.regions) {
-        let bb = region.region_info.bounding_box;
-        let bw = (bb.right_col - bb.left_col) * w;
-        let bh = (bb.bottom_row - bb.top_row) * h;
-
-        let dx = (bb.right_col + bb.left_col) * w / 2;
-        let dy = (bb.bottom_row + bb.top_row) * h / 2;
+    for (const rect of faces) {
+        let dx = rect.x + rect.width / 2;
+        let dy = rect.y + rect.height / 2;
 
         let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
         let emoji = client.emojis.get(emojiKey);
@@ -101,17 +80,17 @@ async function processFaceSwap(message, attachmentUrl, failMessage, successMessa
         let ew = emojiImage.getWidth();
         let eh = emojiImage.getHeight();
 
-        const CONSTANT_SCALE = 1.5;
-        let scaleFactor = Math.max(bw, bh) / Math.min(ew, eh) * CONSTANT_SCALE;
+        const CONSTANT_SCALE = 1.1;
+        let scaleFactor = Math.max(rect.width, rect.height) / Math.min(ew, eh) * CONSTANT_SCALE;
         ew *= scaleFactor;
         eh *= scaleFactor;
 
         emojiImage = emojiImage.scale(scaleFactor);
-        image = image.composite(emojiImage, dx - ew / 2, dy - eh / 2);
+        jimpImage = jimpImage.composite(emojiImage, dx - ew / 2, dy - eh / 2);
     }
 
-    image.quality(90);
-    let buffer = await image.getBufferAsync(Jimp.MIME_JPEG);
+    jimpImage.quality(90);
+    let buffer = await jimpImage.getBufferAsync(Jimp.MIME_JPEG);
 
     let messageContents = successMessage || `I noticed a face in the image. I think this looks better ${client.emojis.get("505076258753740810").toString()}`;
 

+ 0 - 6
commands/image_statistics.js

@@ -1,7 +1,6 @@
 const fs = require("fs");
 const util = require("../util.js");
 const path = require("path");
-const db = require("../db.js");
 
 const statsFilePath = path.resolve(path.dirname(module.filename), "../imagestats.csv");
 const statsFile = fs.openSync(statsFilePath, "a");
@@ -12,11 +11,6 @@ const onMessage = msg => {
     if(imagesCount > 0) {
         let now = new Date();
         fs.writeSync(statsFile, `${now.getUTCFullYear()}-${now.getUTCMonth()+1}-${now.getUTCDate()} ${now.getUTCHours()}:${now.getUTCMinutes()};${imagesCount};${msg.channel.name}\n`);
-
-        if(db.get("faceEditStatistics").has(msg.channel.id).value()) {
-            let val = db.get("faceEditStatistics").get(msg.channel.id).value();
-            db.get("faceEditStatistics").set(msg.channel.id, val + 1).write();
-        }
     }
 
     return false;

+ 0 - 11
db.js

@@ -77,17 +77,6 @@ db.defaults({
         "297117726546198528": 0.1,
         "401837400109875216": 0.1
     },
-    faceEditStatistics: {
-        "459622760839118848": 0,
-        "297109482905796608": 0,
-        "297117726546198528": 0,
-        "401837400109875216": 0
-    },
-    faceEditInfo: {
-        quotaRecomputeDate: "",
-        customRequestsPerDay: 0,
-        todayRequests: 0
-    },
     quotes: []
 }).write();
 

+ 3 - 3
package.json

@@ -20,13 +20,13 @@
   "author": "Geoffrey Horsington <geoffrey.hoooooorse@gmail.com>",
   "license": "MIT",
   "dependencies": {
-    "clarifai": "^2.9.0",
     "discord.js": "^11.4.2",
     "interval-promise": "^1.2.0",
-    "is-image": "^2.0.0",
     "jimp": "^0.5.4",
     "lowdb": "^1.0.0",
-    "node-schedule": "^1.3.0",
+    "opencv4nodejs": "^4.9.0",
+    "request": "^2.88.0",
+    "request-promise-native": "^1.0.5",
     "rss-parser": "^3.4.3",
     "turndown": "^5.0.1",
     "uws": "^99.0.0"

+ 0 - 3
util.js

@@ -4,10 +4,7 @@ const VALID_EXTENSIONS = new Set([
     "png",
     "jpg",
     "jpeg",
-    "tiff",
-    "tif",
     "bmp",
-    "webp"
 ]);
 
 function isValidImage(fileName) {