/worker.nude.js
JavaScript | 358 lines | 225 code | 73 blank | 60 comment | 72 complexity | 9b9cc7329e5c27147c1337b5b03f198d MD5 | raw file
- /*
- * Nude.js - Nudity detection with Javascript and HTMLCanvas
- *
- * Author: Patrick Wied ( http://www.patrick-wied.at )
- * Version: 0.1 (2010-11-21)
- * License: MIT License
- */
- var skinRegions = [],
- skinMap = [],
- canvas = {};
- onmessage = function(event){
- canvas.width = event.data[1];
- canvas.height = event.data[2];
- scanImage(event.data[0]);
- };
- Array.prototype.remove = function(index) {
- var rest = this.slice(index + 1);
- this.length = index;
- return this.push.apply(this, rest);
- };
- function scanImage(imageData){
- var detectedRegions = [],
- mergeRegions = [],
- width = canvas.width,
- lastFrom = -1,
- lastTo = -1;
-
- var addMerge = function(from, to){
- lastFrom = from;
- lastTo = to;
- var len = mergeRegions.length,
- fromIndex = -1,
- toIndex = -1;
-
-
- while(len--){
-
- var region = mergeRegions[len],
- rlen = region.length;
-
- while(rlen--){
-
- if(region[rlen] == from){
- fromIndex = len;
- }
-
- if(region[rlen] == to){
- toIndex = len;
- }
-
- }
-
- }
- if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex){
- return;
- }
-
- if(fromIndex == -1 && toIndex == -1){
- mergeRegions.push([from, to]);
-
- return;
- }
- if(fromIndex != -1 && toIndex == -1){
- mergeRegions[fromIndex].push(to);
- return;
- }
- if(fromIndex == -1 && toIndex != -1){
- mergeRegions[toIndex].push(from);
- return;
- }
- if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex){
- mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]);
- mergeRegions.remove(toIndex);
- return;
- }
- };
- // iterate the image from the top left to the bottom right
- var length = imageData.length,
- width = canvas.width;
- for(var i = 0, u = 1; i < length; i+=4, u++){
-
- var r = imageData[i],
- g = imageData[i+1],
- b = imageData[i+2],
- x = (u>width)?((u%width)-1):u,
- y = (u>width)?(Math.ceil(u/width)-1):1;
-
- if(classifySkin(r, g, b)){ //
- skinMap.push({"id": u, "skin": true, "region": 0, "x": x, "y": y, "checked": false});
-
- var region = -1,
- checkIndexes = [u-2, (u-width)-2, u-width-1, (u-width)],
- checker = false;
-
- for(var o = 0; o < 4; o++){
- var index = checkIndexes[o];
- if(skinMap[index] && skinMap[index].skin){
- if(skinMap[index].region!=region && region!=-1 && lastFrom!=region && lastTo!=skinMap[index].region){
- addMerge(region, skinMap[index].region);
- }
- region = skinMap[index].region;
- checker = true;
- }
- }
- if(!checker){
- skinMap[u-1].region = detectedRegions.length;
- detectedRegions.push([skinMap[u-1]]);
- continue;
- }else{
-
- if(region > -1){
-
- if(!detectedRegions[region]){
- detectedRegions[region] = [];
- }
- skinMap[u-1].region = region;
- detectedRegions[region].push(skinMap[u-1]);
- }
- }
-
- }else{
- skinMap.push({"id": u, "skin": false, "region": 0, "x": x, "y": y, "checked": false});
- }
- }
- merge(detectedRegions, mergeRegions);
- analyseRegions();
- };
- // function for merging detected regions
- function merge(detectedRegions, mergeRegions){
- var length = mergeRegions.length,
- detRegions = [];
- // merging detected regions
- while(length--){
-
- var region = mergeRegions[length],
- rlen = region.length;
- if(!detRegions[length])
- detRegions[length] = [];
- while(rlen--){
- var index = region[rlen];
- detRegions[length] = detRegions[length].concat(detectedRegions[index]);
- detectedRegions[index] = [];
- }
- }
- // push the rest of the regions to the detRegions array
- // (regions without merging)
- var l = detectedRegions.length;
- while(l--){
- if(detectedRegions[l].length > 0){
- detRegions.push(detectedRegions[l]);
- }
- }
- // clean up
- clearRegions(detRegions);
- };
- // clean up function
- // only pushes regions which are bigger than a specific amount to the final result
- function clearRegions(detectedRegions){
- var length = detectedRegions.length;
- for(var i=0; i < length; i++){
- if(detectedRegions[i].length > 30){
- skinRegions.push(detectedRegions[i]);
- }
- }
- };
- function analyseRegions(){
- // sort the detected regions by size
- var length = skinRegions.length,
- totalPixels = canvas.width * canvas.height,
- totalSkin = 0;
- // if there are less than 3 regions
- if(length < 3){
- postMessage(false);
- return;
- }
- // sort the skinRegions with bubble sort algorithm
- (function(){
- var sorted = false;
- while(!sorted){
- sorted = true;
- for(var i = 0; i < length-1; i++){
- if(skinRegions[i].length < skinRegions[i+1].length){
- sorted = false;
- var temp = skinRegions[i];
- skinRegions[i] = skinRegions[i+1];
- skinRegions[i+1] = temp;
- }
- }
- }
- })();
- // count total skin pixels
- while(length--){
- totalSkin += skinRegions[length].length;
- }
- // check if there are more than 15% skin pixel in the image
- if((totalSkin/totalPixels)*100 < 15){
- // if the percentage lower than 15, it's not nude!
- //console.log("it's not nude :) - total skin percent is "+((totalSkin/totalPixels)*100)+"% ");
- postMessage(false);
- return;
- }
- // check if the largest skin region is less than 35% of the total skin count
- // AND if the second largest region is less than 30% of the total skin count
- // AND if the third largest region is less than 30% of the total skin count
- if((skinRegions[0].length/totalSkin)*100 < 35
- && (skinRegions[1].length/totalSkin)*100 < 30
- && (skinRegions[2].length/totalSkin)*100 < 30){
- // the image is not nude.
- //console.log("it's not nude :) - less than 35%,30%,30% skin in the biggest areas :" + ((skinRegions[0].length/totalSkin)*100) + "%, " + ((skinRegions[1].length/totalSkin)*100)+"%, "+((skinRegions[2].length/totalSkin)*100)+"%");
- postMessage(false);
- return;
-
- }
- // check if the number of skin pixels in the largest region is less than 45% of the total skin count
- if((skinRegions[0].length/totalSkin)*100 < 45){
- // it's not nude
- //console.log("it's not nude :) - the biggest region contains less than 45%: "+((skinRegions[0].length/totalSkin)*100)+"%");
- postMessage(false);
- return;
- }
- // TODO:
- // build the bounding polygon by the regions edge values:
- // Identify the leftmost, the uppermost, the rightmost, and the lowermost skin pixels of the three largest skin regions.
- // Use these points as the corner points of a bounding polygon.
- // TODO:
- // check if the total skin count is less than 30% of the total number of pixels
- // AND the number of skin pixels within the bounding polygon is less than 55% of the size of the polygon
- // if this condition is true, it's not nude.
- // TODO: include bounding polygon functionality
- // if there are more than 60 skin regions and the average intensity within the polygon is less than 0.25
- // the image is not nude
- if(skinRegions.length > 60){
- //console.log("it's not nude :) - more than 60 skin regions");
- postMessage(false);
- return;
- }
- // otherwise it is nude
- postMessage(true);
-
- };
- function classifySkin(r, g, b){
- // A Survey on Pixel-Based Skin Color Detection Techniques
- var rgbClassifier = ((r>95) && (g>40 && g <100) && (b>20) && ((Math.max(r,g,b) - Math.min(r,g,b)) > 15) && (Math.abs(r-g)>15) && (r > g) && (r > b)),
- nurgb = toNormalizedRgb(r, g, b),
- nr = nurgb[0],
- ng = nurgb[1],
- nb = nurgb[2],
- normRgbClassifier = (((nr/ng)>1.185) && (((r*b)/(Math.pow(r+g+b,2))) > 0.107) && (((r*g)/(Math.pow(r+g+b,2))) > 0.112)),
- //hsv = toHsv(r, g, b),
- //h = hsv[0]*100,
- //s = hsv[1],
- //hsvClassifier = (h < 50 && h > 0 && s > 0.23 && s < 0.68);
- hsv = toHsvTest(r, g, b),
- h = hsv[0],
- s = hsv[1],
- hsvClassifier = (h > 0 && h < 35 && s > 0.23 && s < 0.68);
- /*
- * ycc doesnt work
-
- ycc = toYcc(r, g, b),
- y = ycc[0],
- cb = ycc[1],
- cr = ycc[2],
- yccClassifier = ((y > 80) && (cb > 77 && cb < 127) && (cr > 133 && cr < 173));
- */
-
- return (rgbClassifier || normRgbClassifier || hsvClassifier); //
- };
- function toYcc(r, g, b){
- r/=255,g/=255,b/=255;
- var y = 0.299*r + 0.587*g + 0.114*b,
- cr = r - y,
- cb = b - y;
-
- return [y, cr, cb];
- };
- function toHsv(r, g, b){
- return [
- // hue
- Math.acos((0.5*((r-g)+(r-b)))/(Math.sqrt((Math.pow((r-g),2)+((r-b)*(g-b)))))),
- // saturation
- 1-(3*((Math.min(r,g,b))/(r+g+b))),
- // value
- (1/3)*(r+g+b)
- ];
- };
- function toHsvTest(r, g, b){
- var h = 0,
- mx = Math.max(r, g, b),
- mn = Math.min(r, g, b),
- dif = mx - mn;
-
- if(mx == r){
- h = (g - b)/dif;
- }else if(mx == g){
- h = 2+((g - r)/dif)
- }else{
- h = 4+((r - g)/dif);
- }
- h = h*60;
- if(h < 0){
- h = h+360;
- }
-
- return [h, 1-(3*((Math.min(r,g,b))/(r+g+b))),(1/3)*(r+g+b)] ;
-
- };
- function toNormalizedRgb(r, g, b){
- var sum = r+g+b;
- return [(r/sum), (g/sum), (b/sum)];
- };