2 * Nude.js - Nudity detection with Javascript and HTMLCanvas
4 * Author: Patrick Wied ( http://www.patrick-wied.at )
5 * Version: 0.1 (2010-11-21)
12 onmessage = function(event
){
13 canvas
.width
= event
.data
[1];
14 canvas
.height
= event
.data
[2];
15 scanImage(event
.data
[0]);
20 Array
.prototype.remove = function(index
) {
21 var rest
= this.slice(index
+ 1);
23 return this.push
.apply(this, rest
);
26 function scanImage(imageData
){
28 var detectedRegions
= [],
35 var addMerge = function(from, to
){
38 var len
= mergeRegions
.length
,
45 var region
= mergeRegions
[len
],
50 if(region
[rlen
] == from){
54 if(region
[rlen
] == to
){
62 if(fromIndex
!= -1 && toIndex
!= -1 && fromIndex
== toIndex
){
66 if(fromIndex
== -1 && toIndex
== -1){
68 mergeRegions
.push([from, to
]);
72 if(fromIndex
!= -1 && toIndex
== -1){
74 mergeRegions
[fromIndex
].push(to
);
77 if(fromIndex
== -1 && toIndex
!= -1){
78 mergeRegions
[toIndex
].push(from);
81 if(fromIndex
!= -1 && toIndex
!= -1 && fromIndex
!= toIndex
){
82 mergeRegions
[fromIndex
] = mergeRegions
[fromIndex
].concat(mergeRegions
[toIndex
]);
83 mergeRegions
.remove(toIndex
);
89 // iterate the image from the top left to the bottom right
90 var length
= imageData
.length
,
93 for(var i
= 0, u
= 1; i
< length
; i
+=4, u
++){
98 x
= (u
>width
)?((u
%width
)-1):u
,
99 y
= (u
>width
)?(Math
.ceil(u
/width
)-1):1;
101 if(classifySkin(r
, g
, b
)){ //
102 skinMap
.push({"id": u
, "skin": true, "region": 0, "x": x
, "y": y
, "checked": false});
105 checkIndexes
= [u
-2, (u
-width
)-2, u
-width
-1, (u
-width
)],
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
);
114 region
= skinMap
[index
].region
;
120 skinMap
[u
-1].region
= detectedRegions
.length
;
121 detectedRegions
.push([skinMap
[u
-1]]);
127 if(!detectedRegions
[region
]){
128 detectedRegions
[region
] = [];
131 skinMap
[u
-1].region
= region
;
132 detectedRegions
[region
].push(skinMap
[u
-1]);
138 skinMap
.push({"id": u
, "skin": false, "region": 0, "x": x
, "y": y
, "checked": false});
143 merge(detectedRegions
, mergeRegions
);
147 // function for merging detected regions
148 function merge(detectedRegions
, mergeRegions
){
150 var length
= mergeRegions
.length
,
154 // merging detected regions
157 var region
= mergeRegions
[length
],
158 rlen
= region
.length
;
160 if(!detRegions
[length
])
161 detRegions
[length
] = [];
164 var index
= region
[rlen
];
165 detRegions
[length
] = detRegions
[length
].concat(detectedRegions
[index
]);
166 detectedRegions
[index
] = [];
171 // push the rest of the regions to the detRegions array
172 // (regions without merging)
173 var l
= detectedRegions
.length
;
175 if(detectedRegions
[l
].length
> 0){
176 detRegions
.push(detectedRegions
[l
]);
181 clearRegions(detRegions
);
186 // only pushes regions which are bigger than a specific amount to the final result
187 function clearRegions(detectedRegions
){
189 var length
= detectedRegions
.length
;
191 for(var i
=0; i
< length
; i
++){
192 if(detectedRegions
[i
].length
> 30){
193 skinRegions
.push(detectedRegions
[i
]);
199 function analyseRegions(){
201 // sort the detected regions by size
202 var length
= skinRegions
.length
,
203 totalPixels
= canvas
.width
* canvas
.height
,
206 // if there are less than 3 regions
212 // sort the skinRegions with bubble sort algorithm
217 for(var i
= 0; i
< length
-1; i
++){
218 if(skinRegions
[i
].length
< skinRegions
[i
+1].length
){
220 var temp
= skinRegions
[i
];
221 skinRegions
[i
] = skinRegions
[i
+1];
222 skinRegions
[i
+1] = temp
;
228 // count total skin pixels
230 totalSkin
+= skinRegions
[length
].length
;
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)+"% ");
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)+"%");
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){
258 //console.log("it's not nude :) - the biggest region contains less than 45%: "+((skinRegions[0].length/totalSkin)*100)+"%");
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.
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.
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");
283 // otherwise it is nude
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
),
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),
298 //hsvClassifier = (h < 50 && h > 0 && s > 0.23 && s < 0.68);
299 hsv
= toHsvTest(r
, g
, b
),
302 hsvClassifier
= (h
> 0 && h
< 35 && s
> 0.23 && s
< 0.68);
306 ycc = toYcc(r, g, b),
310 yccClassifier = ((y > 80) && (cb > 77 && cb < 127) && (cr > 133 && cr < 173));
313 return (rgbClassifier
|| normRgbClassifier
|| hsvClassifier
); //
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
,
324 function toHsv(r
, g
, b
){
327 Math
.acos((0.5*((r
-g
)+(r
-b
)))/(Math
.sqrt((Math
.pow((r
-g
),2)+((r
-b
)*(g
-b
)))))),
329 1-(3*((Math
.min(r
,g
,b
))/(r
+g
+b
))),
334 function toHsvTest(r
, g
, b
){
336 mx
= Math
.max(r
, g
, b
),
337 mn
= Math
.min(r
, g
, b
),
352 return [h
, 1-(3*((Math
.min(r
,g
,b
))/(r+g+b))),(1/3)*(r
+g
+b
)] ;
355 function toNormalizedRgb(r
, g
, b
){
357 return [(r
/sum), (g/sum), (b
/sum
)];