Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added iPhone/iPad support. Optimized the code structure. Removed usel…

…ess features
  • Loading branch information...
commit ed3aa40fcee2f0b80471fff6b4c212edb1da9b95 1 parent 22e9282
Patrick Wied authored
1  compressed/noworker.nude.min.js
... ... @@ -0,0 +1 @@
  1 +(function(){Array.prototype.remove=function(b){var c=this.slice(b+1);this.length=b;return this.push.apply(this,c)};var a=(function(){var e=null,o=null,i=[],l=null,s=null,j=function(){e=document.createElement("canvas");e.style.display="none";var t=document.getElementsByTagName("body")[0];t.appendChild(e);o=e.getContext("2d")},f=function(t){s=document.getElementById(t);e.width=s.width;e.height=s.height;i=[],l=null;o.drawImage(s,0,0)},p=function(){var I=o.getImageData(0,0,e.width,e.height),P=I.data,K=[],z=[],R=[],M=e.width,D=-1,J=-1;var A=function(V,U){D=V;J=U;var u=R.length,x=-1,S=-1;while(u--){var y=R[u],T=y.length;while(T--){if(y[T]==V){x=u}if(y[T]==U){S=u}}}if(x!=-1&&S!=-1&&x==S){return}if(x==-1&&S==-1){R.push([V,U]);return}if(x!=-1&&S==-1){R[x].push(U);return}if(x==-1&&S!=-1){R[S].push(V);return}if(x!=-1&&S!=-1&&x!=S){R[x]=R[x].concat(R[S]);R.remove(S);return}};var w=P.length,M=e.width;for(var N=0,G=1;N<w;N+=4,G++){var H=P[N],O=P[N+1],Q=P[N+2],F=(G>M)?((G%M)-1):G,E=(G>M)?(Math.ceil(G/M)-1):1;if(k(H,O,Q)){K.push({id:G,skin:true,region:0,x:F,y:E,checked:false});var v=-1,B=[G-2,(G-M)-2,G-M-1,(G-M)],t=false;for(var L=0;L<4;L++){var C=B[L];if(K[C]&&K[C].skin){if(K[C].region!=v&&v!=-1&&D!=v&&J!=K[C].region){A(v,K[C].region)}v=K[C].region;t=true}}if(!t){K[G-1].region=z.length;z.push([K[G-1]]);continue}else{if(v>-1){if(!z[v]){z[v]=[]}K[G-1].region=v;z[v].push(K[G-1])}}}else{K.push({id:G,skin:false,region:0,x:F,y:E,checked:false})}}g(z,R);n()},g=function(v,y){var w=y.length,A=[];while(w--){var x=y[w],z=x.length;if(!A[w]){A[w]=[]}while(z--){var u=x[z];A[w]=A[w].concat(v[u]);v[u]=[]}}var t=v.length;while(t--){if(v[t].length>0){A.push(v[t])}}q(A)},q=function(t){var v=t.length;for(var u=0;u<v;u++){if(t[u].length>30){i.push(t[u])}}},n=function(){var v=i.length,t=e.width*e.height,u=0;if(v<3){b(false);return}(function(){var x=false;while(!x){x=true;for(var y=0;y<v-1;y++){if(i[y].length<i[y+1].length){x=false;var w=i[y];i[y]=i[y+1];i[y+1]=w}}}})();while(v--){u+=i[v].length}if((u/t)*100<15){b(false);return}if((i[0].length/u)*100<35&&(i[1].length/u)*100<30&&(i[2].length/u)*100<30){b(false);return}if((i[0].length/u)*100<45){b(false);return}if(i.length>60){b(false);return}b(true)},b=function(t){if(l){l(t)}else{if(t){console.log("the picture contains nudity")}}},h=function(){var v=i.length;for(var y=0;y<v;y++){var A=i[y],x=A.length,w=Math.ceil(Math.random()*255),B=Math.ceil(Math.random()*255),C=Math.ceil(Math.random()*255);for(var u=0;u<x;u++){var t=o.getImageData(A[u].x,A[u].y,1,1),z=t.data;z[0]=w;z[1]=B;z[2]=C;t.data=z;o.putImageData(t,A[u].x,A[u].y)}}},k=function(t,z,D){var A=((t>95)&&(z>40&&z<100)&&(D>20)&&((Math.max(t,z,D)-Math.min(t,z,D))>15)&&(Math.abs(t-z)>15)&&(t>z)&&(t>D)),B=d(t,z,D),E=B[0],u=B[1],y=B[2],C=(((E/u)>1.185)&&(((t*D)/(Math.pow(t+z+D,2)))>0.107)&&(((t*z)/(Math.pow(t+z+D,2)))>0.112)),x=c(t,z,D),w=x[0],F=x[1],v=(w>0&&w<35&&F>0.23&&F<0.68);return(A||C||v)},r=function(x,w,u){x/=255,w/=255,u/=255;var z=0.299*x+0.587*w+0.114*u,v=x-z,t=u-z;return[z,v,t]},m=function(v,u,t){return[Math.acos((0.5*((v-u)+(v-t)))/(Math.sqrt((Math.pow((v-u),2)+((v-t)*(u-t)))))),1-(3*((Math.min(v,u,t))/(v+u+t))),(1/3)*(v+u+t)]},c=function(x,w,t){var v=0,z=Math.max(x,w,t),y=Math.min(x,w,t),u=z-y;if(z==x){v=(w-t)/u}else{if(z==w){v=2+((w-x)/u)}else{v=4+((x-w)/u)}}v=v*60;if(v<0){v=v+360}return[v,1-(3*((Math.min(x,w,t))/(x+w+t))),(1/3)*(x+w+t)]},d=function(w,v,t){var u=w+v+t;return[(w/u),(v/u),(t/u)]};return{init:function(){j()},load:function(t){f(t)},scan:function(t){if(arguments.length>0&&typeof(arguments[0])=="function"){l=t}p()}}})();window.nude=a;a.init()})();
1  compressed/nude.min.js
... ... @@ -0,0 +1 @@
  1 +(function(){var a=(function(){var e=null,c=null,g=null,d=function(){e=document.createElement("canvas");e.style.display="none";var i=document.getElementsByTagName("body")[0];i.appendChild(e);c=e.getContext("2d")},b=function(j){var i=document.getElementById(j);e.width=i.width;e.height=i.height;g=null;c.drawImage(i,0,0)},h=function(){var k=c.getImageData(0,0,e.width,e.height),l=k.data;var i=new Worker("worker.nude.js"),j=[l,e.width,e.height];i.postMessage(j);i.onmessage=function(m){f(m.data)}},f=function(i){if(g){g(i)}else{if(i){console.log("the picture contains nudity")}}};return{init:function(){d();if(!!!window.Worker){document.write(unescape("%3Cscript src='noworker.nude.js' type='text/javascript'%3E%3C/script%3E"))}},load:function(i){b(i)},scan:function(i){if(arguments.length>0&&typeof(arguments[0])=="function"){g=i}h()}}})();if(!window.nude){window.nude=a}a.init()})();
1  compressed/worker.nude.min.js
... ... @@ -0,0 +1 @@
  1 +var skinRegions=[],skinMap=[],canvas={};onmessage=function(a){canvas.width=a.data[1];canvas.height=a.data[2];scanImage(a.data[0])};Array.prototype.remove=function(a){var b=this.slice(a+1);this.length=a;return this.push.apply(this,b)};function scanImage(z){var e=[],B=[],t=canvas.width,k=-1,q=-1;var f=function(x,u){k=x;q=u;var b=B.length,g=-1,o=-1;while(b--){var i=B[b],r=i.length;while(r--){if(i[r]==x){g=b}if(i[r]==u){o=b}}}if(g!=-1&&o!=-1&&g==o){return}if(g==-1&&o==-1){B.push([x,u]);return}if(g!=-1&&o==-1){B[g].push(u);return}if(g==-1&&o!=-1){B[o].push(x);return}if(g!=-1&&o!=-1&&g!=o){B[g]=B[g].concat(B[o]);B.remove(o);return}};var d=z.length,t=canvas.width;for(var v=0,n=1;v<d;v+=4,n++){var p=z[v],w=z[v+1],A=z[v+2],m=(n>t)?((n%t)-1):n,l=(n>t)?(Math.ceil(n/t)-1):1;if(classifySkin(p,w,A)){skinMap.push({id:n,skin:true,region:0,x:m,y:l,checked:false});var c=-1,h=[n-2,(n-t)-2,n-t-1,(n-t)],a=false;for(var s=0;s<4;s++){var j=h[s];if(skinMap[j]&&skinMap[j].skin){if(skinMap[j].region!=c&&c!=-1&&k!=c&&q!=skinMap[j].region){f(c,skinMap[j].region)}c=skinMap[j].region;a=true}}if(!a){skinMap[n-1].region=e.length;e.push([skinMap[n-1]]);continue}else{if(c>-1){if(!e[c]){e[c]=[]}skinMap[n-1].region=c;e[c].push(skinMap[n-1])}}}else{skinMap.push({id:n,skin:false,region:0,x:m,y:l,checked:false})}}merge(e,B);analyseRegions()}function merge(c,f){var d=f.length,h=[];while(d--){var e=f[d],g=e.length;if(!h[d]){h[d]=[]}while(g--){var b=e[g];h[d]=h[d].concat(c[b]);c[b]=[]}}var a=c.length;while(a--){if(c[a].length>0){h.push(c[a])}}clearRegions(h)}function clearRegions(a){var c=a.length;for(var b=0;b<c;b++){if(a[b].length>30){skinRegions.push(a[b])}}}function analyseRegions(){var c=skinRegions.length,a=canvas.width*canvas.height,b=0;if(c<3){postMessage(false);return}(function(){var e=false;while(!e){e=true;for(var f=0;f<c-1;f++){if(skinRegions[f].length<skinRegions[f+1].length){e=false;var d=skinRegions[f];skinRegions[f]=skinRegions[f+1];skinRegions[f+1]=d}}}})();while(c--){b+=skinRegions[c].length}if((b/a)*100<15){postMessage(false);return}if((skinRegions[0].length/b)*100<35&&(skinRegions[1].length/b)*100<30&&(skinRegions[2].length/b)*100<30){postMessage(false);return}if((skinRegions[0].length/b)*100<45){postMessage(false);return}if(skinRegions.length>60){postMessage(false);return}postMessage(true)}function classifySkin(a,j,n){var k=((a>95)&&(j>40&&j<100)&&(n>20)&&((Math.max(a,j,n)-Math.min(a,j,n))>15)&&(Math.abs(a-j)>15)&&(a>j)&&(a>n)),l=toNormalizedRgb(a,j,n),o=l[0],c=l[1],i=l[2],m=(((o/c)>1.185)&&(((a*n)/(Math.pow(a+j+n,2)))>0.107)&&(((a*j)/(Math.pow(a+j+n,2)))>0.112)),f=toHsvTest(a,j,n),e=f[0],p=f[1],d=(e>0&&e<35&&p>0.23&&p<0.68);return(k||m||d)}function toYcc(f,e,c){f/=255,e/=255,c/=255;var h=0.299*f+0.587*e+0.114*c,d=f-h,a=c-h;return[h,d,a]}function toHsv(d,c,a){return[Math.acos((0.5*((d-c)+(d-a)))/(Math.sqrt((Math.pow((d-c),2)+((d-a)*(c-a)))))),1-(3*((Math.min(d,c,a))/(d+c+a))),(1/3)*(d+c+a)]}function toHsvTest(f,e,a){var d=0,j=Math.max(f,e,a),i=Math.min(f,e,a),c=j-i;if(j==f){d=(e-a)/c}else{if(j==e){d=2+((e-f)/c)}else{d=4+((f-e)/c)}}d=d*60;if(d<0){d=d+360}return[d,1-(3*((Math.min(f,e,a))/(f+e+a))),(1/3)*(f+e+a)]}function toNormalizedRgb(e,d,a){var c=e+d+a;return[(e/c),(d/c),(a/c)]};
443 noworker.nude.js
... ... @@ -0,0 +1,443 @@
  1 +/*
  2 + * Nude.js - Nudity detection with Javascript and HTMLCanvas
  3 + *
  4 + * Author: Patrick Wied ( http://www.patrick-wied.at )
  5 + * Version: 0.1 (2010-11-21)
  6 + * License: MIT License
  7 + */
  8 +(function(){
  9 + Array.prototype.remove = function(index) {
  10 + var rest = this.slice(index + 1);
  11 + this.length = index;
  12 + return this.push.apply(this, rest);
  13 + };
  14 +
  15 + var nude = (function(){
  16 + // private var definition
  17 + var canvas = null,
  18 + ctx = null,
  19 + skinRegions = [],
  20 + resultFn = null,
  21 + img = null,
  22 + // private functions
  23 + initCanvas = function(){
  24 + canvas = document.createElement("canvas");
  25 + // the canvas should not be visible
  26 + canvas.style.display = "none";
  27 + var b = document.getElementsByTagName("body")[0];
  28 + b.appendChild(canvas);
  29 + ctx = canvas.getContext("2d");
  30 + },
  31 + loadImage = function(id){
  32 +
  33 + img = document.getElementById(id);
  34 + canvas.width = img.width;
  35 + canvas.height = img.height;
  36 + // reset the arrays
  37 + skinRegions = [],
  38 + resultFn = null;
  39 + // draw the image into the canvas element
  40 + ctx.drawImage(img, 0, 0);
  41 +
  42 +
  43 + },
  44 + scanImage = function(){
  45 + // get the image data
  46 + var image = ctx.getImageData(0, 0, canvas.width, canvas.height),
  47 + imageData = image.data,
  48 + skinMap = [],
  49 + detectedRegions = [],
  50 + mergeRegions = [],
  51 + width = canvas.width,
  52 + lastFrom = -1,
  53 + lastTo = -1;
  54 +
  55 +
  56 + var addMerge = function(from, to){
  57 + lastFrom = from;
  58 + lastTo = to;
  59 + var len = mergeRegions.length,
  60 + fromIndex = -1,
  61 + toIndex = -1;
  62 +
  63 +
  64 + while(len--){
  65 +
  66 + var region = mergeRegions[len],
  67 + rlen = region.length;
  68 +
  69 + while(rlen--){
  70 +
  71 + if(region[rlen] == from){
  72 + fromIndex = len;
  73 + }
  74 +
  75 + if(region[rlen] == to){
  76 + toIndex = len;
  77 + }
  78 +
  79 + }
  80 +
  81 + }
  82 +
  83 + if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex){
  84 + return;
  85 + }
  86 +
  87 + if(fromIndex == -1 && toIndex == -1){
  88 +
  89 + mergeRegions.push([from, to]);
  90 +
  91 + return;
  92 + }
  93 + if(fromIndex != -1 && toIndex == -1){
  94 +
  95 + mergeRegions[fromIndex].push(to);
  96 + return;
  97 + }
  98 + if(fromIndex == -1 && toIndex != -1){
  99 + mergeRegions[toIndex].push(from);
  100 + return;
  101 + }
  102 +
  103 + if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex){
  104 + mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]);
  105 + mergeRegions.remove(toIndex);
  106 + return;
  107 + }
  108 +
  109 + };
  110 +
  111 + // iterate the image from the top left to the bottom right
  112 + var length = imageData.length,
  113 + width = canvas.width;
  114 +
  115 + for(var i = 0, u = 1; i < length; i+=4, u++){
  116 +
  117 + var r = imageData[i],
  118 + g = imageData[i+1],
  119 + b = imageData[i+2],
  120 + x = (u>width)?((u%width)-1):u,
  121 + y = (u>width)?(Math.ceil(u/width)-1):1;
  122 +
  123 + if(classifySkin(r, g, b)){ //
  124 + skinMap.push({"id": u, "skin": true, "region": 0, "x": x, "y": y, "checked": false});
  125 +
  126 + var region = -1,
  127 + checkIndexes = [u-2, (u-width)-2, u-width-1, (u-width)],
  128 + checker = false;
  129 +
  130 + for(var o = 0; o < 4; o++){
  131 + var index = checkIndexes[o];
  132 + if(skinMap[index] && skinMap[index].skin){
  133 + if(skinMap[index].region!=region && region!=-1 && lastFrom!=region && lastTo!=skinMap[index].region){
  134 + addMerge(region, skinMap[index].region);
  135 + }
  136 + region = skinMap[index].region;
  137 + checker = true;
  138 + }
  139 + }
  140 +
  141 + if(!checker){
  142 + skinMap[u-1].region = detectedRegions.length;
  143 + detectedRegions.push([skinMap[u-1]]);
  144 + continue;
  145 + }else{
  146 +
  147 + if(region > -1){
  148 +
  149 + if(!detectedRegions[region]){
  150 + detectedRegions[region] = [];
  151 + }
  152 +
  153 + skinMap[u-1].region = region;
  154 + detectedRegions[region].push(skinMap[u-1]);
  155 +
  156 + }
  157 + }
  158 +
  159 + }else{
  160 + skinMap.push({"id": u, "skin": false, "region": 0, "x": x, "y": y, "checked": false});
  161 + }
  162 +
  163 + }
  164 +
  165 + merge(detectedRegions, mergeRegions);
  166 + analyseRegions();
  167 + },
  168 + // function for merging detected regions
  169 + merge = function(detectedRegions, mergeRegions){
  170 +
  171 + var length = mergeRegions.length,
  172 + detRegions = [];
  173 +
  174 +
  175 + // merging detected regions
  176 + while(length--){
  177 +
  178 + var region = mergeRegions[length],
  179 + rlen = region.length;
  180 +
  181 + if(!detRegions[length])
  182 + detRegions[length] = [];
  183 +
  184 + while(rlen--){
  185 + var index = region[rlen];
  186 + detRegions[length] = detRegions[length].concat(detectedRegions[index]);
  187 + detectedRegions[index] = [];
  188 + }
  189 +
  190 + }
  191 +
  192 + // push the rest of the regions to the detRegions array
  193 + // (regions without merging)
  194 + var l = detectedRegions.length;
  195 + while(l--){
  196 + if(detectedRegions[l].length > 0){
  197 + detRegions.push(detectedRegions[l]);
  198 + }
  199 + }
  200 +
  201 + // clean up
  202 + clearRegions(detRegions);
  203 +
  204 + },
  205 + // clean up function
  206 + // only pushes regions which are bigger than a specific amount to the final result
  207 + clearRegions = function(detectedRegions){
  208 +
  209 + var length = detectedRegions.length;
  210 +
  211 + for(var i=0; i < length; i++){
  212 + if(detectedRegions[i].length > 30){
  213 + skinRegions.push(detectedRegions[i]);
  214 + }
  215 + }
  216 +
  217 + },
  218 + analyseRegions = function(){
  219 +
  220 + // sort the detected regions by size
  221 + var length = skinRegions.length,
  222 + totalPixels = canvas.width * canvas.height,
  223 + totalSkin = 0;
  224 +
  225 + // if there are less than 3 regions
  226 + if(length < 3){
  227 + resultHandler(false);
  228 + return;
  229 + }
  230 +
  231 + // sort the skinRegions with bubble sort algorithm
  232 + (function(){
  233 + var sorted = false;
  234 + while(!sorted){
  235 + sorted = true;
  236 + for(var i = 0; i < length-1; i++){
  237 + if(skinRegions[i].length < skinRegions[i+1].length){
  238 + sorted = false;
  239 + var temp = skinRegions[i];
  240 + skinRegions[i] = skinRegions[i+1];
  241 + skinRegions[i+1] = temp;
  242 + }
  243 + }
  244 + }
  245 + })();
  246 +
  247 + // count total skin pixels
  248 + while(length--){
  249 + totalSkin += skinRegions[length].length;
  250 + }
  251 +
  252 + // check if there are more than 15% skin pixel in the image
  253 + if((totalSkin/totalPixels)*100 < 15){
  254 + // if the percentage lower than 15, it's not nude!
  255 + //console.log("it's not nude :) - total skin percent is "+((totalSkin/totalPixels)*100)+"% ");
  256 + resultHandler(false);
  257 + return;
  258 + }
  259 +
  260 +
  261 + // check if the largest skin region is less than 35% of the total skin count
  262 + // AND if the second largest region is less than 30% of the total skin count
  263 + // AND if the third largest region is less than 30% of the total skin count
  264 + if((skinRegions[0].length/totalSkin)*100 < 35
  265 + && (skinRegions[1].length/totalSkin)*100 < 30
  266 + && (skinRegions[2].length/totalSkin)*100 < 30){
  267 + // the image is not nude.
  268 + //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)+"%");
  269 + resultHandler(false);
  270 + return;
  271 +
  272 + }
  273 +
  274 + // check if the number of skin pixels in the largest region is less than 45% of the total skin count
  275 + if((skinRegions[0].length/totalSkin)*100 < 45){
  276 + // it's not nude
  277 + //console.log("it's not nude :) - the biggest region contains less than 45%: "+((skinRegions[0].length/totalSkin)*100)+"%");
  278 + resultHandler(false);
  279 + return;
  280 + }
  281 +
  282 + // TODO:
  283 + // build the bounding polygon by the regions edge values:
  284 + // Identify the leftmost, the uppermost, the rightmost, and the lowermost skin pixels of the three largest skin regions.
  285 + // Use these points as the corner points of a bounding polygon.
  286 +
  287 + // TODO:
  288 + // check if the total skin count is less than 30% of the total number of pixels
  289 + // AND the number of skin pixels within the bounding polygon is less than 55% of the size of the polygon
  290 + // if this condition is true, it's not nude.
  291 +
  292 + // TODO: include bounding polygon functionality
  293 + // if there are more than 60 skin regions and the average intensity within the polygon is less than 0.25
  294 + // the image is not nude
  295 + if(skinRegions.length > 60){
  296 + //console.log("it's not nude :) - more than 60 skin regions");
  297 + resultHandler(false);
  298 + return;
  299 + }
  300 +
  301 +
  302 + // otherwise it is nude
  303 + resultHandler(true);
  304 +
  305 + },
  306 + // the result handler will be executed when the analysing process is done
  307 + // the result contains true (it is nude) or false (it is not nude)
  308 + // if the user passed an result function to the scan function, the result function will be executed
  309 + // otherwise the default resulthandling executes
  310 + resultHandler = function(result){
  311 +
  312 + if(resultFn){
  313 + resultFn(result);
  314 + }else{
  315 + if(result)
  316 + console.log("the picture contains nudity");
  317 + }
  318 +
  319 + },
  320 + // colorizeRegions function is for testdevelopment only
  321 + // the detected skinRegions will be painted in random colors (one color per region)
  322 + colorizeRegions = function(){
  323 +
  324 + var length = skinRegions.length;
  325 + for(var i = 0; i < length; i++){
  326 +
  327 + var region = skinRegions[i],
  328 + regionLength = region.length,
  329 + randR = Math.ceil(Math.random()*255),
  330 + randG = Math.ceil(Math.random()*255),
  331 + rangB = Math.ceil(Math.random()*255);
  332 +
  333 + for(var o = 0; o < regionLength; o++){
  334 +
  335 + var pixel = ctx.getImageData(region[o].x, region[o].y, 1,1),
  336 + pdata = pixel.data;
  337 +
  338 + pdata[0] = randR;
  339 + pdata[1] = randG;
  340 + pdata[2] = rangB;
  341 +
  342 + pixel.data = pdata;
  343 +
  344 + ctx.putImageData(pixel, region[o].x, region[o].y);
  345 +
  346 + }
  347 +
  348 + }
  349 +
  350 + },
  351 + classifySkin = function(r, g, b){
  352 + // A Survey on Pixel-Based Skin Color Detection Techniques
  353 + 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)),
  354 + nurgb = toNormalizedRgb(r, g, b),
  355 + nr = nurgb[0],
  356 + ng = nurgb[1],
  357 + nb = nurgb[2],
  358 + 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)),
  359 + //hsv = toHsv(r, g, b),
  360 + //h = hsv[0]*100,
  361 + //s = hsv[1],
  362 + //hsvClassifier = (h < 50 && h > 0 && s > 0.23 && s < 0.68);
  363 + hsv = toHsvTest(r, g, b),
  364 + h = hsv[0],
  365 + s = hsv[1],
  366 + hsvClassifier = (h > 0 && h < 35 && s > 0.23 && s < 0.68);
  367 + /*
  368 + * ycc doesnt work
  369 +
  370 + ycc = toYcc(r, g, b),
  371 + y = ycc[0],
  372 + cb = ycc[1],
  373 + cr = ycc[2],
  374 + yccClassifier = ((y > 80) && (cb > 77 && cb < 127) && (cr > 133 && cr < 173));
  375 + */
  376 +
  377 + return (rgbClassifier || normRgbClassifier || hsvClassifier); //
  378 + },
  379 + toYcc = function(r, g, b){
  380 + r/=255,g/=255,b/=255;
  381 + var y = 0.299*r + 0.587*g + 0.114*b,
  382 + cr = r - y,
  383 + cb = b - y;
  384 +
  385 + return [y, cr, cb];
  386 + },
  387 + toHsv = function(r, g, b){
  388 + return [
  389 + // hue
  390 + Math.acos((0.5*((r-g)+(r-b)))/(Math.sqrt((Math.pow((r-g),2)+((r-b)*(g-b)))))),
  391 + // saturation
  392 + 1-(3*((Math.min(r,g,b))/(r+g+b))),
  393 + // value
  394 + (1/3)*(r+g+b)
  395 + ];
  396 + },
  397 + toHsvTest = function(r, g, b){
  398 + var h = 0,
  399 + mx = Math.max(r, g, b),
  400 + mn = Math.min(r, g, b),
  401 + dif = mx - mn;
  402 +
  403 + if(mx == r){
  404 + h = (g - b)/dif;
  405 + }else if(mx == g){
  406 + h = 2+((g - r)/dif)
  407 + }else{
  408 + h = 4+((r - g)/dif);
  409 + }
  410 + h = h*60;
  411 + if(h < 0){
  412 + h = h+360;
  413 + }
  414 +
  415 + return [h, 1-(3*((Math.min(r,g,b))/(r+g+b))),(1/3)*(r+g+b)] ;
  416 +
  417 + },
  418 + toNormalizedRgb = function(r, g, b){
  419 + var sum = r+g+b;
  420 + return [(r/sum), (g/sum), (b/sum)];
  421 + };
  422 +
  423 + // public interface
  424 + return {
  425 + init: function(){
  426 + initCanvas();
  427 + },
  428 + load: function(id){
  429 + loadImage(id);
  430 + },
  431 + scan: function(fn){
  432 + if(arguments.length>0 && typeof(arguments[0]) == "function"){
  433 + resultFn = fn;
  434 + }
  435 + scanImage();
  436 + }
  437 + };
  438 + })();
  439 + // register nude at window object
  440 + window.nude = nude;
  441 + nude.init();
  442 +})();
  443 +
BIN  tests/images/1.jpg
BIN  tests/images/2.jpg
BIN  tests/images/3.jpg
BIN  tests/images/4.jpg
43 tests/index.html
... ... @@ -0,0 +1,43 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE html
  3 + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  4 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  5 +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  6 + <head>
  7 + <title>Nudity Detection with HTMLCanvas and Javascript</title>
  8 + <link rel="stylesheet" type="text/css" href="style.css" />
  9 + </head>
  10 + <body>
  11 + <div id="wrapper">
  12 + <h1>Demo pictures</h1>
  13 + <div class="entry">
  14 + <img id="testImage" src="images/1.jpg" width="500" height="375" alt="" />
  15 + <div class="btn" onclick="nude.load('testImage');nude.scan(function(result){ if(!result) document.getElementById('result1').innerHTML='No nude!'; });">Scan Image</div>
  16 + <div style="float:right;margin-top:-20px;" id="result1"></div>
  17 + </div>
  18 + <div class="entry">
  19 + <img id="testImage2" src="images/2.jpg" width="500" height="375" alt="" />
  20 + <div class="btn" onclick="nude.load('testImage2');nude.scan(function(result){ if(!result) document.getElementById('result2').innerHTML='No nude!!'; });">Scan Image</div>
  21 + <div style="float:right;margin-top:-20px;" id="result2"></div>
  22 + </div>
  23 + <div class="entry">
  24 + <img id="testImage3" src="images/3.jpg" width="500" height="375" alt="" />
  25 + <div class="btn" onclick="nude.load('testImage3');nude.scan(function(result){ if(!result) document.getElementById('result3').innerHTML='No nude!!'; });">Scan Image</div>
  26 + <div style="float:right;margin-top:-20px;" id="result3"></div>
  27 + </div>
  28 + <div class="entry">
  29 + <img id="testImage4" src="images/4.jpg" width="500" height="375" alt="" />
  30 + <div class="btn" onclick="nude.load('testImage4');nude.scan(function(result){ if(!result){ document.getElementById('result4').innerHTML='No nude!!'; }else{ document.getElementById('result4').innerHTML='Nude!!'; } });">Scan Image</div>
  31 + <div style="float:right;margin-top:-20px;" id="result4"></div>
  32 + </div>
  33 +
  34 + </div>
  35 + <!-- please download excanvas if you use ie -->
  36 + <!--[if IE]>
  37 + <script type="text/javascript" src="excanvas.js"></script>
  38 + <![endif]-->
  39 + <script type="text/javascript" src="nude.min.js"></script>
  40 + <script type="text/javascript" src="test.nude.js"></script>
  41 +
  42 + </body>
  43 +</html>
1  tests/noworker.nude.min.js
... ... @@ -0,0 +1 @@
  1 +(function(){Array.prototype.remove=function(b){var c=this.slice(b+1);this.length=b;return this.push.apply(this,c)};var a=(function(){var e=null,o=null,i=[],l=null,s=null,j=function(){e=document.createElement("canvas");e.style.display="none";var t=document.getElementsByTagName("body")[0];t.appendChild(e);o=e.getContext("2d")},f=function(t){s=document.getElementById(t);e.width=s.width;e.height=s.height;i=[],l=null;o.drawImage(s,0,0)},p=function(){var I=o.getImageData(0,0,e.width,e.height),P=I.data,K=[],z=[],R=[],M=e.width,D=-1,J=-1;var A=function(V,U){D=V;J=U;var u=R.length,x=-1,S=-1;while(u--){var y=R[u],T=y.length;while(T--){if(y[T]==V){x=u}if(y[T]==U){S=u}}}if(x!=-1&&S!=-1&&x==S){return}if(x==-1&&S==-1){R.push([V,U]);return}if(x!=-1&&S==-1){R[x].push(U);return}if(x==-1&&S!=-1){R[S].push(V);return}if(x!=-1&&S!=-1&&x!=S){R[x]=R[x].concat(R[S]);R.remove(S);return}};var w=P.length,M=e.width;for(var N=0,G=1;N<w;N+=4,G++){var H=P[N],O=P[N+1],Q=P[N+2],F=(G>M)?((G%M)-1):G,E=(G>M)?(Math.ceil(G/M)-1):1;if(k(H,O,Q)){K.push({id:G,skin:true,region:0,x:F,y:E,checked:false});var v=-1,B=[G-2,(G-M)-2,G-M-1,(G-M)],t=false;for(var L=0;L<4;L++){var C=B[L];if(K[C]&&K[C].skin){if(K[C].region!=v&&v!=-1&&D!=v&&J!=K[C].region){A(v,K[C].region)}v=K[C].region;t=true}}if(!t){K[G-1].region=z.length;z.push([K[G-1]]);continue}else{if(v>-1){if(!z[v]){z[v]=[]}K[G-1].region=v;z[v].push(K[G-1])}}}else{K.push({id:G,skin:false,region:0,x:F,y:E,checked:false})}}g(z,R);n()},g=function(v,y){var w=y.length,A=[];while(w--){var x=y[w],z=x.length;if(!A[w]){A[w]=[]}while(z--){var u=x[z];A[w]=A[w].concat(v[u]);v[u]=[]}}var t=v.length;while(t--){if(v[t].length>0){A.push(v[t])}}q(A)},q=function(t){var v=t.length;for(var u=0;u<v;u++){if(t[u].length>30){i.push(t[u])}}},n=function(){var v=i.length,t=e.width*e.height,u=0;if(v<3){b(false);return}(function(){var x=false;while(!x){x=true;for(var y=0;y<v-1;y++){if(i[y].length<i[y+1].length){x=false;var w=i[y];i[y]=i[y+1];i[y+1]=w}}}})();while(v--){u+=i[v].length}if((u/t)*100<15){b(false);return}if((i[0].length/u)*100<35&&(i[1].length/u)*100<30&&(i[2].length/u)*100<30){b(false);return}if((i[0].length/u)*100<45){b(false);return}if(i.length>60){b(false);return}b(true)},b=function(t){if(l){l(t)}else{if(t){console.log("the picture contains nudity")}}},h=function(){var v=i.length;for(var y=0;y<v;y++){var A=i[y],x=A.length,w=Math.ceil(Math.random()*255),B=Math.ceil(Math.random()*255),C=Math.ceil(Math.random()*255);for(var u=0;u<x;u++){var t=o.getImageData(A[u].x,A[u].y,1,1),z=t.data;z[0]=w;z[1]=B;z[2]=C;t.data=z;o.putImageData(t,A[u].x,A[u].y)}}},k=function(t,z,D){var A=((t>95)&&(z>40&&z<100)&&(D>20)&&((Math.max(t,z,D)-Math.min(t,z,D))>15)&&(Math.abs(t-z)>15)&&(t>z)&&(t>D)),B=d(t,z,D),E=B[0],u=B[1],y=B[2],C=(((E/u)>1.185)&&(((t*D)/(Math.pow(t+z+D,2)))>0.107)&&(((t*z)/(Math.pow(t+z+D,2)))>0.112)),x=c(t,z,D),w=x[0],F=x[1],v=(w>0&&w<35&&F>0.23&&F<0.68);return(A||C||v)},r=function(x,w,u){x/=255,w/=255,u/=255;var z=0.299*x+0.587*w+0.114*u,v=x-z,t=u-z;return[z,v,t]},m=function(v,u,t){return[Math.acos((0.5*((v-u)+(v-t)))/(Math.sqrt((Math.pow((v-u),2)+((v-t)*(u-t)))))),1-(3*((Math.min(v,u,t))/(v+u+t))),(1/3)*(v+u+t)]},c=function(x,w,t){var v=0,z=Math.max(x,w,t),y=Math.min(x,w,t),u=z-y;if(z==x){v=(w-t)/u}else{if(z==w){v=2+((w-x)/u)}else{v=4+((x-w)/u)}}v=v*60;if(v<0){v=v+360}return[v,1-(3*((Math.min(x,w,t))/(x+w+t))),(1/3)*(x+w+t)]},d=function(w,v,t){var u=w+v+t;return[(w/u),(v/u),(t/u)]};return{init:function(){j()},load:function(t){f(t)},scan:function(t){if(arguments.length>0&&typeof(arguments[0])=="function"){l=t}p()}}})();window.nude=a;a.init()})();
1  tests/nude.min.js
... ... @@ -0,0 +1 @@
  1 +(function(){var a=(function(){var e=null,c=null,g=null,d=function(){e=document.createElement("canvas");e.style.display="none";var i=document.getElementsByTagName("body")[0];i.appendChild(e);c=e.getContext("2d")},b=function(j){var i=document.getElementById(j);e.width=i.width;e.height=i.height;g=null;c.drawImage(i,0,0)},h=function(){var k=c.getImageData(0,0,e.width,e.height),l=k.data;var i=new Worker("worker.nude.js"),j=[l,e.width,e.height];i.postMessage(j);i.onmessage=function(m){f(m.data)}},f=function(i){if(g){g(i)}else{if(i){console.log("the picture contains nudity")}}};return{init:function(){d();if(!!!window.Worker){document.write(unescape("%3Cscript src='noworker.nude.js' type='text/javascript'%3E%3C/script%3E"))}},load:function(i){b(i)},scan:function(i){if(arguments.length>0&&typeof(arguments[0])=="function"){g=i}h()}}})();if(!window.nude){window.nude=a}a.init()})();
72 tests/style.css
... ... @@ -0,0 +1,72 @@
  1 +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td {
  2 + margin:0;
  3 + padding:0;
  4 +}
  5 +table {
  6 + border-collapse:collapse;
  7 + border-spacing:0;
  8 +}
  9 +fieldset,img {
  10 + border:0;
  11 +}
  12 +address,caption,cite,code,dfn,em,strong,th,var {
  13 + font-style:normal;
  14 + font-weight:normal;
  15 +}
  16 +ol,ul {
  17 + list-style:none;
  18 +}
  19 +caption,th {
  20 + text-align:left;
  21 +}
  22 +h1,h2,h3,h4,h5,h6 {
  23 + font-size:100%;
  24 + font-weight:normal;
  25 +}
  26 +q:before,q:after {
  27 + content:'';
  28 +}
  29 +abbr,acronym { border:0;
  30 +}
  31 +/* end style reset */
  32 +
  33 +body{
  34 + font-family:Arial;
  35 +}
  36 +.btn {
  37 + margin-top:10px;
  38 + padding:10px;
  39 + padding-left:20px;
  40 + padding-right:20px;
  41 + color:white;
  42 + -moz-border-radius:20px;
  43 + -webkit-border-radius:20px;
  44 + background-color:#df0019;
  45 + width:100px;
  46 + cursor:pointer;
  47 + text-align:center;
  48 +}
  49 +
  50 +.btn:hover{
  51 + background-color:#de3f51;
  52 +}
  53 +
  54 +.entry{
  55 + width:510px;
  56 + padding-bottom:20px;
  57 + padding-top:20px;
  58 +}
  59 +
  60 +#wrapper{
  61 + position:relative;
  62 + width:500px;
  63 + margin:auto;
  64 + background:#e1e0e0;
  65 + padding:10px;
  66 +}
  67 +
  68 +h1 {
  69 + margin-top:20px;
  70 + font-size:36px;
  71 + text-decoration:underline;
  72 +}
44 tests/test.nude.js
... ... @@ -0,0 +1,44 @@
  1 +window.onload = function(){
  2 +
  3 +
  4 + var images = [
  5 + {"id":"testImage", "expected":false}, {"id":"testImage2", "expected":false}, {"id":"testImage3", "expected":false},
  6 + {"id":"testImage4", "expected":true}
  7 + ];
  8 +
  9 + var matches = [];
  10 + var startTime = new Date().getTime();
  11 +
  12 +
  13 + (function testImage(i){
  14 +
  15 + var image = images[i];
  16 +
  17 + nude.load(image.id);
  18 + nude.scan(function(result){
  19 + if(result == image.expected){
  20 + matches.push(image);
  21 + }
  22 + if(i != images.length-1){
  23 + testImage(i+1);
  24 + }else{
  25 + var endTime = new Date().getTime();
  26 + console.log("Test complete:");
  27 + console.log("Test duration: "+(endTime-startTime)+"ms");
  28 + console.log("Checked "+images.length + " images for nudity.");
  29 + console.log(matches.length + " / "+ images.length + " images returned the expected result");
  30 + if(matches.length != images.length){
  31 + console.log("Detection algorithm checked successfully for nudity at the following images: ");
  32 + console.log(matches);
  33 + }
  34 + }
  35 + });
  36 +
  37 + })(0);
  38 +
  39 +
  40 +
  41 +
  42 +
  43 +
  44 +}
1  tests/worker.nude.min.js
... ... @@ -0,0 +1 @@
  1 +var skinRegions=[],skinMap=[],canvas={};onmessage=function(a){canvas.width=a.data[1];canvas.height=a.data[2];scanImage(a.data[0])};Array.prototype.remove=function(a){var b=this.slice(a+1);this.length=a;return this.push.apply(this,b)};function scanImage(z){var e=[],B=[],t=canvas.width,k=-1,q=-1;var f=function(x,u){k=x;q=u;var b=B.length,g=-1,o=-1;while(b--){var i=B[b],r=i.length;while(r--){if(i[r]==x){g=b}if(i[r]==u){o=b}}}if(g!=-1&&o!=-1&&g==o){return}if(g==-1&&o==-1){B.push([x,u]);return}if(g!=-1&&o==-1){B[g].push(u);return}if(g==-1&&o!=-1){B[o].push(x);return}if(g!=-1&&o!=-1&&g!=o){B[g]=B[g].concat(B[o]);B.remove(o);return}};var d=z.length,t=canvas.width;for(var v=0,n=1;v<d;v+=4,n++){var p=z[v],w=z[v+1],A=z[v+2],m=(n>t)?((n%t)-1):n,l=(n>t)?(Math.ceil(n/t)-1):1;if(classifySkin(p,w,A)){skinMap.push({id:n,skin:true,region:0,x:m,y:l,checked:false});var c=-1,h=[n-2,(n-t)-2,n-t-1,(n-t)],a=false;for(var s=0;s<4;s++){var j=h[s];if(skinMap[j]&&skinMap[j].skin){if(skinMap[j].region!=c&&c!=-1&&k!=c&&q!=skinMap[j].region){f(c,skinMap[j].region)}c=skinMap[j].region;a=true}}if(!a){skinMap[n-1].region=e.length;e.push([skinMap[n-1]]);continue}else{if(c>-1){if(!e[c]){e[c]=[]}skinMap[n-1].region=c;e[c].push(skinMap[n-1])}}}else{skinMap.push({id:n,skin:false,region:0,x:m,y:l,checked:false})}}merge(e,B);analyseRegions()}function merge(c,f){var d=f.length,h=[];while(d--){var e=f[d],g=e.length;if(!h[d]){h[d]=[]}while(g--){var b=e[g];h[d]=h[d].concat(c[b]);c[b]=[]}}var a=c.length;while(a--){if(c[a].length>0){h.push(c[a])}}clearRegions(h)}function clearRegions(a){var c=a.length;for(var b=0;b<c;b++){if(a[b].length>30){skinRegions.push(a[b])}}}function analyseRegions(){var c=skinRegions.length,a=canvas.width*canvas.height,b=0;if(c<3){postMessage(false);return}(function(){var e=false;while(!e){e=true;for(var f=0;f<c-1;f++){if(skinRegions[f].length<skinRegions[f+1].length){e=false;var d=skinRegions[f];skinRegions[f]=skinRegions[f+1];skinRegions[f+1]=d}}}})();while(c--){b+=skinRegions[c].length}if((b/a)*100<15){postMessage(false);return}if((skinRegions[0].length/b)*100<35&&(skinRegions[1].length/b)*100<30&&(skinRegions[2].length/b)*100<30){postMessage(false);return}if((skinRegions[0].length/b)*100<45){postMessage(false);return}if(skinRegions.length>60){postMessage(false);return}postMessage(true)}function classifySkin(a,j,n){var k=((a>95)&&(j>40&&j<100)&&(n>20)&&((Math.max(a,j,n)-Math.min(a,j,n))>15)&&(Math.abs(a-j)>15)&&(a>j)&&(a>n)),l=toNormalizedRgb(a,j,n),o=l[0],c=l[1],i=l[2],m=(((o/c)>1.185)&&(((a*n)/(Math.pow(a+j+n,2)))>0.107)&&(((a*j)/(Math.pow(a+j+n,2)))>0.112)),f=toHsvTest(a,j,n),e=f[0],p=f[1],d=(e>0&&e<35&&p>0.23&&p<0.68);return(k||m||d)}function toYcc(f,e,c){f/=255,e/=255,c/=255;var h=0.299*f+0.587*e+0.114*c,d=f-h,a=c-h;return[h,d,a]}function toHsv(d,c,a){return[Math.acos((0.5*((d-c)+(d-a)))/(Math.sqrt((Math.pow((d-c),2)+((d-a)*(c-a)))))),1-(3*((Math.min(d,c,a))/(d+c+a))),(1/3)*(d+c+a)]}function toHsvTest(f,e,a){var d=0,j=Math.max(f,e,a),i=Math.min(f,e,a),c=j-i;if(j==f){d=(e-a)/c}else{if(j==e){d=2+((e-f)/c)}else{d=4+((f-e)/c)}}d=d*60;if(d<0){d=d+360}return[d,1-(3*((Math.min(f,e,a))/(f+e+a))),(1/3)*(f+e+a)]}function toNormalizedRgb(e,d,a){var c=e+d+a;return[(e/c),(d/c),(a/c)]};
358 worker.nude.js
... ... @@ -0,0 +1,358 @@
  1 +/*
  2 + * Nude.js - Nudity detection with Javascript and HTMLCanvas
  3 + *
  4 + * Author: Patrick Wied ( http://www.patrick-wied.at )
  5 + * Version: 0.1 (2010-11-21)
  6 + * License: MIT License
  7 + */
  8 +var skinRegions = [],
  9 +skinMap = [],
  10 +canvas = {};
  11 +
  12 +onmessage = function(event){
  13 + canvas.width = event.data[1];
  14 + canvas.height = event.data[2];
  15 + scanImage(event.data[0]);
  16 +};
  17 +
  18 +
  19 +
  20 +Array.prototype.remove = function(index) {
  21 + var rest = this.slice(index + 1);
  22 + this.length = index;
  23 + return this.push.apply(this, rest);
  24 +};
  25 +
  26 +function scanImage(imageData){
  27 +
  28 +var detectedRegions = [],
  29 +mergeRegions = [],
  30 +width = canvas.width,
  31 +lastFrom = -1,
  32 +lastTo = -1;
  33 +
  34 +
  35 +var addMerge = function(from, to){
  36 + lastFrom = from;
  37 + lastTo = to;
  38 + var len = mergeRegions.length,
  39 + fromIndex = -1,
  40 + toIndex = -1;
  41 +
  42 +
  43 + while(len--){
  44 +
  45 + var region = mergeRegions[len],
  46 + rlen = region.length;
  47 +
  48 + while(rlen--){
  49 +
  50 + if(region[rlen] == from){
  51 + fromIndex = len;
  52 + }
  53 +
  54 + if(region[rlen] == to){
  55 + toIndex = len;
  56 + }
  57 +
  58 + }
  59 +
  60 + }
  61 +
  62 + if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex){
  63 + return;
  64 + }
  65 +
  66 + if(fromIndex == -1 && toIndex == -1){
  67 +
  68 + mergeRegions.push([from, to]);
  69 +
  70 + return;
  71 + }
  72 + if(fromIndex != -1 && toIndex == -1){
  73 +
  74 + mergeRegions[fromIndex].push(to);
  75 + return;
  76 + }
  77 + if(fromIndex == -1 && toIndex != -1){
  78 + mergeRegions[toIndex].push(from);
  79 + return;
  80 + }
  81 + if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex){
  82 + mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]);
  83 + mergeRegions.remove(toIndex);
  84 + return;
  85 + }
  86 +
  87 +};
  88 +
  89 +// iterate the image from the top left to the bottom right
  90 +var length = imageData.length,
  91 +width = canvas.width;
  92 +
  93 +for(var i = 0, u = 1; i < length; i+=4, u++){
  94 +
  95 + var r = imageData[i],
  96 + g = imageData[i+1],
  97 + b = imageData[i+2],
  98 + x = (u>width)?((u%width)-1):u,
  99 + y = (u>width)?(Math.ceil(u/width)-1):1;
  100 +
  101 + if(classifySkin(r, g, b)){ //
  102 + skinMap.push({"id": u, "skin": true, "region": 0, "x": x, "y": y, "checked": false});
  103 +
  104 + var region = -1,
  105 + checkIndexes = [u-2, (u-width)-2, u-width-1, (u-width)],
  106 + checker = false;
  107 +
  108 + for(var o = 0; o < 4; o++){
  109 + var index = checkIndexes[o];
  110 + if(skinMap[index] && skinMap[index].skin){
  111 + if(skinMap[index].region!=region && region!=-1 && lastFrom!=region && lastTo!=skinMap[index].region){
  112 + addMerge(region, skinMap[index].region);
  113 + }
  114 + region = skinMap[index].region;
  115 + checker = true;
  116 + }
  117 + }
  118 +
  119 + if(!checker){
  120 + skinMap[u-1].region = detectedRegions.length;
  121 + detectedRegions.push([skinMap[u-1]]);
  122 + continue;
  123 + }else{
  124 +
  125 + if(region > -1){
  126 +
  127 + if(!detectedRegions[region]){
  128 + detectedRegions[region] = [];
  129 + }
  130 +
  131 + skinMap[u-1].region = region;
  132 + detectedRegions[region].push(skinMap[u-1]);
  133 +
  134 + }
  135 + }
  136 +
  137 + }else{
  138 + skinMap.push({"id": u, "skin": false, "region": 0, "x": x, "y": y, "checked": false});
  139 + }
  140 +
  141 +}
  142 +
  143 +merge(detectedRegions, mergeRegions);
  144 +analyseRegions();
  145 +};
  146 +
  147 +// function for merging detected regions
  148 +function merge(detectedRegions, mergeRegions){
  149 +
  150 +var length = mergeRegions.length,
  151 +detRegions = [];
  152 +
  153 +
  154 +// merging detected regions
  155 +while(length--){
  156 +
  157 + var region = mergeRegions[length],
  158 + rlen = region.length;
  159 +
  160 + if(!detRegions[length])
  161 + detRegions[length] = [];
  162 +
  163 + while(rlen--){
  164 + var index = region[rlen];
  165 + detRegions[length] = detRegions[length].concat(detectedRegions[index]);
  166 + detectedRegions[index] = [];
  167 + }
  168 +
  169 +}
  170 +
  171 +// push the rest of the regions to the detRegions array
  172 +// (regions without merging)
  173 +var l = detectedRegions.length;
  174 +while(l--){
  175 + if(detectedRegions[l].length > 0){
  176 + detRegions.push(detectedRegions[l]);
  177 + }
  178 +}
  179 +
  180 +// clean up
  181 +clearRegions(detRegions);
  182 +
  183 +};
  184 +
  185 +// clean up function
  186 +// only pushes regions which are bigger than a specific amount to the final result
  187 +function clearRegions(detectedRegions){
  188 +
  189 +var length = detectedRegions.length;
  190 +
  191 +for(var i=0; i < length; i++){
  192 + if(detectedRegions[i].length > 30){
  193 + skinRegions.push(detectedRegions[i]);
  194 + }
  195 +}
  196 +
  197 +};
  198 +
  199 +function analyseRegions(){
  200 +
  201 +// sort the detected regions by size
  202 +var length = skinRegions.length,
  203 +totalPixels = canvas.width * canvas.height,
  204 +totalSkin = 0;
  205 +
  206 +// if there are less than 3 regions
  207 +if(length < 3){
  208 + postMessage(false);
  209 + return;
  210 +}
  211 +
  212 +// sort the skinRegions with bubble sort algorithm
  213 +(function(){
  214 + var sorted = false;
  215 + while(!sorted){
  216 + sorted = true;
  217 + for(var i = 0; i < length-1; i++){
  218 + if(skinRegions[i].length < skinRegions[i+1].length){
  219 + sorted = false;
  220 + var temp = skinRegions[i];
  221 + skinRegions[i] = skinRegions[i+1];
  222 + skinRegions[i+1] = temp;
  223 + }
  224 + }
  225 + }
  226 +})();
  227 +
  228 +// count total skin pixels
  229 +while(length--){
  230 + totalSkin += skinRegions[length].length;
  231 +}
  232 +
  233 +// check if there are more than 15% skin pixel in the image
  234 +if((totalSkin/totalPixels)*100 < 15){
  235 + // if the percentage lower than 15, it's not nude!
  236 + //console.log("it's not nude :) - total skin percent is "+((totalSkin/totalPixels)*100)+"% ");
  237 + postMessage(false);
  238 + return;
  239 +}
  240 +
  241 +
  242 +// check if the largest skin region is less than 35% of the total skin count
  243 +// AND if the second largest region is less than 30% of the total skin count
  244 +// AND if the third largest region is less than 30% of the total skin count
  245 +if((skinRegions[0].length/totalSkin)*100 < 35
  246 + && (skinRegions[1].length/totalSkin)*100 < 30
  247 + && (skinRegions[2].length/totalSkin)*100 < 30){
  248 + // the image is not nude.
  249 + //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)+"%");
  250 + postMessage(false);
  251 + return;
  252 +
  253 +}
  254 +
  255 +// check if the number of skin pixels in the largest region is less than 45% of the total skin count
  256 +if((skinRegions[0].length/totalSkin)*100 < 45){
  257 + // it's not nude
  258 + //console.log("it's not nude :) - the biggest region contains less than 45%: "+((skinRegions[0].length/totalSkin)*100)+"%");
  259 + postMessage(false);
  260 + return;
  261 +}
  262 +
  263 +// TODO:
  264 +// build the bounding polygon by the regions edge values:
  265 +// Identify the leftmost, the uppermost, the rightmost, and the lowermost skin pixels of the three largest skin regions.
  266 +// Use these points as the corner points of a bounding polygon.
  267 +
  268 +// TODO:
  269 +// check if the total skin count is less than 30% of the total number of pixels
  270 +// AND the number of skin pixels within the bounding polygon is less than 55% of the size of the polygon
  271 +// if this condition is true, it's not nude.
  272 +
  273 +// TODO: include bounding polygon functionality
  274 +// if there are more than 60 skin regions and the average intensity within the polygon is less than 0.25
  275 +// the image is not nude
  276 +if(skinRegions.length > 60){
  277 + //console.log("it's not nude :) - more than 60 skin regions");
  278 + postMessage(false);
  279 + return;
  280 +}
  281 +
  282 +
  283 +// otherwise it is nude
  284 +postMessage(true);
  285 +
  286 +};
  287 +function classifySkin(r, g, b){
  288 + // A Survey on Pixel-Based Skin Color Detection Techniques
  289 + 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)),
  290 + nurgb = toNormalizedRgb(r, g, b),
  291 + nr = nurgb[0],
  292 + ng = nurgb[1],
  293 + nb = nurgb[2],
  294 + 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)),
  295 + //hsv = toHsv(r, g, b),
  296 + //h = hsv[0]*100,
  297 + //s = hsv[1],
  298 + //hsvClassifier = (h < 50 && h > 0 && s > 0.23 && s < 0.68);
  299 + hsv = toHsvTest(r, g, b),
  300 + h = hsv[0],
  301 + s = hsv[1],
  302 + hsvClassifier = (h > 0 && h < 35 && s > 0.23 && s < 0.68);
  303 + /*
  304 + * ycc doesnt work
  305 +
  306 + ycc = toYcc(r, g, b),
  307 + y = ycc[0],
  308 + cb = ycc[1],
  309 + cr = ycc[2],
  310 + yccClassifier = ((y > 80) && (cb > 77 && cb < 127) && (cr > 133 && cr < 173));
  311 + */
  312 +
  313 + return (rgbClassifier || normRgbClassifier || hsvClassifier); //
  314 +};
  315 +function toYcc(r, g, b){
  316 + r/=255,g/=255,b/=255;
  317 + var y = 0.299*r + 0.587*g + 0.114*b,
  318 + cr = r - y,
  319 + cb = b - y;
  320 +
  321 + return [y, cr, cb];
  322 +};
  323 +
  324 +function toHsv(r, g, b){
  325 + return [
  326 + // hue
  327 + Math.acos((0.5*((r-g)+(r-b)))/(Math.sqrt((Math.pow((r-g),2)+((r-b)*(g-b)))))),
  328 + // saturation
  329 + 1-(3*((Math.min(r,g,b))/(r+g+b))),
  330 + // value
  331 + (1/3)*(r+g+b)
  332 + ];
  333 +};
  334 +function toHsvTest(r, g, b){
  335 + var h = 0,
  336 + mx = Math.max(r, g, b),
  337 + mn = Math.min(r, g, b),
  338 + dif = mx - mn;
  339 +
  340 + if(mx == r){
  341 + h = (g - b)/dif;
  342 + }else if(mx == g){
  343 + h = 2+((g - r)/dif)
  344 + }else{
  345 + h = 4+((r - g)/dif);
  346 + }
  347 + h = h*60;
  348 + if(h < 0){
  349 + h = h+360;
  350 + }
  351 +
  352 + return [h, 1-(3*((Math.min(r,g,b))/(r+g+b))),(1/3)*(r+g+b)] ;
  353 +
  354 +};
  355 +function toNormalizedRgb(r, g, b){
  356 + var sum = r+g+b;
  357 + return [(r/sum), (g/sum), (b/sum)];
  358 +};

0 comments on commit ed3aa40

Please sign in to comment.
Something went wrong with that request. Please try again.