PageRenderTime 64ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/worker.nude.js

http://github.com/pa7/nude.js
JavaScript | 358 lines | 225 code | 73 blank | 60 comment | 72 complexity | 9b9cc7329e5c27147c1337b5b03f198d MD5 | raw file
  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. onmessage = function(event){
  12. canvas.width = event.data[1];
  13. canvas.height = event.data[2];
  14. scanImage(event.data[0]);
  15. };
  16. Array.prototype.remove = function(index) {
  17. var rest = this.slice(index + 1);
  18. this.length = index;
  19. return this.push.apply(this, rest);
  20. };
  21. function scanImage(imageData){
  22. var detectedRegions = [],
  23. mergeRegions = [],
  24. width = canvas.width,
  25. lastFrom = -1,
  26. lastTo = -1;
  27. var addMerge = function(from, to){
  28. lastFrom = from;
  29. lastTo = to;
  30. var len = mergeRegions.length,
  31. fromIndex = -1,
  32. toIndex = -1;
  33. while(len--){
  34. var region = mergeRegions[len],
  35. rlen = region.length;
  36. while(rlen--){
  37. if(region[rlen] == from){
  38. fromIndex = len;
  39. }
  40. if(region[rlen] == to){
  41. toIndex = len;
  42. }
  43. }
  44. }
  45. if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex){
  46. return;
  47. }
  48. if(fromIndex == -1 && toIndex == -1){
  49. mergeRegions.push([from, to]);
  50. return;
  51. }
  52. if(fromIndex != -1 && toIndex == -1){
  53. mergeRegions[fromIndex].push(to);
  54. return;
  55. }
  56. if(fromIndex == -1 && toIndex != -1){
  57. mergeRegions[toIndex].push(from);
  58. return;
  59. }
  60. if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex){
  61. mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]);
  62. mergeRegions.remove(toIndex);
  63. return;
  64. }
  65. };
  66. // iterate the image from the top left to the bottom right
  67. var length = imageData.length,
  68. width = canvas.width;
  69. for(var i = 0, u = 1; i < length; i+=4, u++){
  70. var r = imageData[i],
  71. g = imageData[i+1],
  72. b = imageData[i+2],
  73. x = (u>width)?((u%width)-1):u,
  74. y = (u>width)?(Math.ceil(u/width)-1):1;
  75. if(classifySkin(r, g, b)){ //
  76. skinMap.push({"id": u, "skin": true, "region": 0, "x": x, "y": y, "checked": false});
  77. var region = -1,
  78. checkIndexes = [u-2, (u-width)-2, u-width-1, (u-width)],
  79. checker = false;
  80. for(var o = 0; o < 4; o++){
  81. var index = checkIndexes[o];
  82. if(skinMap[index] && skinMap[index].skin){
  83. if(skinMap[index].region!=region && region!=-1 && lastFrom!=region && lastTo!=skinMap[index].region){
  84. addMerge(region, skinMap[index].region);
  85. }
  86. region = skinMap[index].region;
  87. checker = true;
  88. }
  89. }
  90. if(!checker){
  91. skinMap[u-1].region = detectedRegions.length;
  92. detectedRegions.push([skinMap[u-1]]);
  93. continue;
  94. }else{
  95. if(region > -1){
  96. if(!detectedRegions[region]){
  97. detectedRegions[region] = [];
  98. }
  99. skinMap[u-1].region = region;
  100. detectedRegions[region].push(skinMap[u-1]);
  101. }
  102. }
  103. }else{
  104. skinMap.push({"id": u, "skin": false, "region": 0, "x": x, "y": y, "checked": false});
  105. }
  106. }
  107. merge(detectedRegions, mergeRegions);
  108. analyseRegions();
  109. };
  110. // function for merging detected regions
  111. function merge(detectedRegions, mergeRegions){
  112. var length = mergeRegions.length,
  113. detRegions = [];
  114. // merging detected regions
  115. while(length--){
  116. var region = mergeRegions[length],
  117. rlen = region.length;
  118. if(!detRegions[length])
  119. detRegions[length] = [];
  120. while(rlen--){
  121. var index = region[rlen];
  122. detRegions[length] = detRegions[length].concat(detectedRegions[index]);
  123. detectedRegions[index] = [];
  124. }
  125. }
  126. // push the rest of the regions to the detRegions array
  127. // (regions without merging)
  128. var l = detectedRegions.length;
  129. while(l--){
  130. if(detectedRegions[l].length > 0){
  131. detRegions.push(detectedRegions[l]);
  132. }
  133. }
  134. // clean up
  135. clearRegions(detRegions);
  136. };
  137. // clean up function
  138. // only pushes regions which are bigger than a specific amount to the final result
  139. function clearRegions(detectedRegions){
  140. var length = detectedRegions.length;
  141. for(var i=0; i < length; i++){
  142. if(detectedRegions[i].length > 30){
  143. skinRegions.push(detectedRegions[i]);
  144. }
  145. }
  146. };
  147. function analyseRegions(){
  148. // sort the detected regions by size
  149. var length = skinRegions.length,
  150. totalPixels = canvas.width * canvas.height,
  151. totalSkin = 0;
  152. // if there are less than 3 regions
  153. if(length < 3){
  154. postMessage(false);
  155. return;
  156. }
  157. // sort the skinRegions with bubble sort algorithm
  158. (function(){
  159. var sorted = false;
  160. while(!sorted){
  161. sorted = true;
  162. for(var i = 0; i < length-1; i++){
  163. if(skinRegions[i].length < skinRegions[i+1].length){
  164. sorted = false;
  165. var temp = skinRegions[i];
  166. skinRegions[i] = skinRegions[i+1];
  167. skinRegions[i+1] = temp;
  168. }
  169. }
  170. }
  171. })();
  172. // count total skin pixels
  173. while(length--){
  174. totalSkin += skinRegions[length].length;
  175. }
  176. // check if there are more than 15% skin pixel in the image
  177. if((totalSkin/totalPixels)*100 < 15){
  178. // if the percentage lower than 15, it's not nude!
  179. //console.log("it's not nude :) - total skin percent is "+((totalSkin/totalPixels)*100)+"% ");
  180. postMessage(false);
  181. return;
  182. }
  183. // check if the largest skin region is less than 35% of the total skin count
  184. // AND if the second largest region is less than 30% of the total skin count
  185. // AND if the third largest region is less than 30% of the total skin count
  186. if((skinRegions[0].length/totalSkin)*100 < 35
  187. && (skinRegions[1].length/totalSkin)*100 < 30
  188. && (skinRegions[2].length/totalSkin)*100 < 30){
  189. // the image is not nude.
  190. //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)+"%");
  191. postMessage(false);
  192. return;
  193. }
  194. // check if the number of skin pixels in the largest region is less than 45% of the total skin count
  195. if((skinRegions[0].length/totalSkin)*100 < 45){
  196. // it's not nude
  197. //console.log("it's not nude :) - the biggest region contains less than 45%: "+((skinRegions[0].length/totalSkin)*100)+"%");
  198. postMessage(false);
  199. return;
  200. }
  201. // TODO:
  202. // build the bounding polygon by the regions edge values:
  203. // Identify the leftmost, the uppermost, the rightmost, and the lowermost skin pixels of the three largest skin regions.
  204. // Use these points as the corner points of a bounding polygon.
  205. // TODO:
  206. // check if the total skin count is less than 30% of the total number of pixels
  207. // AND the number of skin pixels within the bounding polygon is less than 55% of the size of the polygon
  208. // if this condition is true, it's not nude.
  209. // TODO: include bounding polygon functionality
  210. // if there are more than 60 skin regions and the average intensity within the polygon is less than 0.25
  211. // the image is not nude
  212. if(skinRegions.length > 60){
  213. //console.log("it's not nude :) - more than 60 skin regions");
  214. postMessage(false);
  215. return;
  216. }
  217. // otherwise it is nude
  218. postMessage(true);
  219. };
  220. function classifySkin(r, g, b){
  221. // A Survey on Pixel-Based Skin Color Detection Techniques
  222. 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)),
  223. nurgb = toNormalizedRgb(r, g, b),
  224. nr = nurgb[0],
  225. ng = nurgb[1],
  226. nb = nurgb[2],
  227. 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)),
  228. //hsv = toHsv(r, g, b),
  229. //h = hsv[0]*100,
  230. //s = hsv[1],
  231. //hsvClassifier = (h < 50 && h > 0 && s > 0.23 && s < 0.68);
  232. hsv = toHsvTest(r, g, b),
  233. h = hsv[0],
  234. s = hsv[1],
  235. hsvClassifier = (h > 0 && h < 35 && s > 0.23 && s < 0.68);
  236. /*
  237. * ycc doesnt work
  238. ycc = toYcc(r, g, b),
  239. y = ycc[0],
  240. cb = ycc[1],
  241. cr = ycc[2],
  242. yccClassifier = ((y > 80) && (cb > 77 && cb < 127) && (cr > 133 && cr < 173));
  243. */
  244. return (rgbClassifier || normRgbClassifier || hsvClassifier); //
  245. };
  246. function toYcc(r, g, b){
  247. r/=255,g/=255,b/=255;
  248. var y = 0.299*r + 0.587*g + 0.114*b,
  249. cr = r - y,
  250. cb = b - y;
  251. return [y, cr, cb];
  252. };
  253. function toHsv(r, g, b){
  254. return [
  255. // hue
  256. Math.acos((0.5*((r-g)+(r-b)))/(Math.sqrt((Math.pow((r-g),2)+((r-b)*(g-b)))))),
  257. // saturation
  258. 1-(3*((Math.min(r,g,b))/(r+g+b))),
  259. // value
  260. (1/3)*(r+g+b)
  261. ];
  262. };
  263. function toHsvTest(r, g, b){
  264. var h = 0,
  265. mx = Math.max(r, g, b),
  266. mn = Math.min(r, g, b),
  267. dif = mx - mn;
  268. if(mx == r){
  269. h = (g - b)/dif;
  270. }else if(mx == g){
  271. h = 2+((g - r)/dif)
  272. }else{
  273. h = 4+((r - g)/dif);
  274. }
  275. h = h*60;
  276. if(h < 0){
  277. h = h+360;
  278. }
  279. return [h, 1-(3*((Math.min(r,g,b))/(r+g+b))),(1/3)*(r+g+b)] ;
  280. };
  281. function toNormalizedRgb(r, g, b){
  282. var sum = r+g+b;
  283. return [(r/sum), (g/sum), (b/sum)];
  284. };