Skip to content
Newer
Older
100644 319 lines (305 sloc) 12.7 KB
b78451d @azwan082 add files
authored Sep 7, 2011
1 <?php
2
3 class NudityFilter {
4
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
5 var $file, // full path of image file
6 $filename, // image file name
7 $img_w, // image width
8 $img_h, // image height
9 $last_from, // previous `from` region number
10 $last_to, // previous `to` region number
11 $pixel_map, // array of skin pixel holding region number
12 $merge_regions, // array of pixel number which are merged in a region
13 $detected_regions, // array of skin pixel for arranging region number
14 $skin_regions, // array of skin regions, store number of pixels in a region
15 $error, // last error message
16 $log; // array of log message
b78451d @azwan082 add files
authored Sep 7, 2011
17
18 /**
19 * @return bool True if it is nude picture
20 */
21 function check($file) {
22 $this->file = $file;
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
23 $this->reset_var();
b78451d @azwan082 add files
authored Sep 7, 2011
24 // get image info
25 $start = microtime(true);
26 $img_info = getimagesize($this->file);
27 if ($img_info === false) {
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
28 $this->error = $this->filename .' is not an image file';
b78451d @azwan082 add files
authored Sep 7, 2011
29 return false;
30 }
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
31 $this->img_w = $img_info[0];
32 $this->img_h = $img_info[1];
b78451d @azwan082 add files
authored Sep 7, 2011
33 switch ($img_info[2]) {
34 case IMAGETYPE_GIF:
35 $img_type = 'gif';
36 $img = imagecreatefromgif($this->file);
37 break;
38 case IMAGETYPE_JPEG:
39 $img_type = 'jpg';
40 $img = imagecreatefromjpeg($this->file);
41 break;
42 case IMAGETYPE_PNG:
43 $img_type = 'png';
44 $img = imagecreatefrompng($this->file);
45 break;
46 default:
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
47 $this->error = 'Unsupported image type ('. $this->filename . ')';
b78451d @azwan082 add files
authored Sep 7, 2011
48 return false;
49 }
50 if ($img === false) {
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
51 $this->error = 'Failed to read image file ('. $this->filename .')';
b78451d @azwan082 add files
authored Sep 7, 2011
52 return false;
53 }
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
54 $this->log[] = 'Finish reading image file in '.number_format(microtime(true) - $start, 4). ' secs';
b78451d @azwan082 add files
authored Sep 7, 2011
55 // iterate image from top left to bottom right
56 $x = 0;
57 $y = 0;
58 $i = 0;
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
59 while ($y < $this->img_h) {
60 while ($x < $this->img_w) {
b78451d @azwan082 add files
authored Sep 7, 2011
61 $rgb = imagecolorat($img, $x, $y);
62 $r = ($rgb >> 16) & 0xFF;
63 $g = ($rgb >> 8) & 0xFF;
64 $b = $rgb & 0xFF;
65 $skin_px = false;
66 if ($this->classify_skin($r, $g, $b)) {
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
67 $this->pixel_map[$i] = 0; // pixel_map stores `region` value of the skin pixel
b78451d @azwan082 add files
authored Sep 7, 2011
68 $region = -1;
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
69 // check neighbouring pixels for skin pixel, if one of them is skin pixel, all subsequent pixels will be labelled as skin pixel
70 // left, above left, above, above right pixel relative to current pixel (order of checking is important)
71 $check_pixels = array($i-1, ($i-$this->img_w)-1, $i-$this->img_w, ($i-$this->img_w)+1);
b78451d @azwan082 add files
authored Sep 7, 2011
72 foreach ($check_pixels as $cpx) {
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
73 if (isset($this->pixel_map[$cpx])) {
74 if ($this->pixel_map[$cpx] != $region && $region != -1 && $this->last_from != $region && $this->last_to != $this->pixel_map[$cpx]) {
75 $this->add_merge_region($region, $this->pixel_map[$cpx]);
b78451d @azwan082 add files
authored Sep 7, 2011
76 }
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
77 $region = $this->pixel_map[$cpx];
b78451d @azwan082 add files
authored Sep 7, 2011
78 $skin_px = true;
79 }
80 }
81 if ($skin_px) {
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
82 // one of neighbouring pixel is skin pixel, so need to update current pixel region
83 // to that neighbour pixel region number
b78451d @azwan082 add files
authored Sep 7, 2011
84 if ($region > -1) {
85 if (!isset($this->detected_regions[$region])) {
86 $this->detected_regions[$region] = array();
87 }
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
88 $this->pixel_map[$i] = $region;
b78451d @azwan082 add files
authored Sep 7, 2011
89 $this->detected_regions[$region][] = $this->pixel_map[$i];
90 }
91 } else {
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
92 // append new region number to this pixel
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
93 $this->pixel_map[$i] = count($this->detected_regions);
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
94 $this->detected_regions[] = array($this->pixel_map[$i]);
b78451d @azwan082 add files
authored Sep 7, 2011
95 }
96 }
97 $x++;
98 $i++;
99 }
100 $x = 0;
101 $y++;
102 }
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
103 $this->log[] = 'Finish reading all pixels in '.number_format(microtime(true) - $start, 4). ' secs';
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
104 $this->merge_and_clear();
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
105 $this->log[] = 'Finish merge_and_clear() in '.number_format(microtime(true) - $start, 4). ' secs';
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
106 $result = $this->analyze_regions();
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
107 $this->log[] = 'Process completed in '.number_format(microtime(true) - $start, 4). ' secs';
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
108 return $result;
b78451d @azwan082 add files
authored Sep 7, 2011
109 }
110
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
111 /**
112 * Reset class variables for current checking process
113 */
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
114 private function reset_var() {
115 $this->filename = basename($this->file);
116 $this->last_from = -1;
117 $this->last_to = -1;
118 $this->pixel_map = array();
119 $this->merge_regions = array();
120 $this->detected_regions = array();
121 $this->log = array();
122 $this->error = '';
123 }
124
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
125 /**
126 * Determine if current pixel is a skin pixel
127 * @param int $r `red` color component
128 * @param int $g `green` color component
129 * @param int $b `blue` color component
130 * @return bool True if this pixel is a skin pixel
131 */
b78451d @azwan082 add files
authored Sep 7, 2011
132 private function classify_skin($r, $g, $b) {
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
133 // A Survey on Pixel-Based Skin Color Detection Techniques
b78451d @azwan082 add files
authored Sep 7, 2011
134 $rgb_classifier = (($r>95) && ($g>40 && $g <100) && ($b>20) && ((max($r,$g,$b) - min($r,$g,$b)) > 15) && (abs($r-$g)>15) && ($r > $g) && ($r > $b));
135 // normalize rgb
136 $sum = $r+$g+$b;
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
137 $nr = $this->div($r,$sum);
138 $ng = $this->div($g,$sum);
139 $norm_rgb_classifier = (($this->div($nr,$ng)>1.185) && ($this->div(($r*$b),(pow($r+$g+$b,2))) > 0.107) && ($this->div(($r*$g),(pow($r+$g+$b,2))) > 0.112));
b78451d @azwan082 add files
authored Sep 7, 2011
140 // to hsv
141 list($h, $s) = $this->to_hsv($r, $g, $b);
142 $hsv_classifier = ($h > 0 && $h < 35 && $s > 0.23 && $s < 0.68);
143 return ($rgb_classifier || $norm_rgb_classifier || $hsv_classifier);
144 }
145
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
146 /**
147 * Convert RGB value to HSV
148 * @param int $r `red` color component
149 * @param int $g `green` color component
150 * @param int $b `blue` color component
151 * @return array HSV component
152 */
b78451d @azwan082 add files
authored Sep 7, 2011
153 private function to_hsv($r, $g, $b) {
154 $h = 0;
155 $mx = max($r, $g, $b);
156 $mn = min($r, $g, $b);
157 $df = $mx - $mn;
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
158 if ($mx == $r) {
159 $h = $this->div(($g - $b),$df);
160 }
161 else if ($mx == $g) {
162 $h = 2+$this->div(($g - $r),$df);
163 }
164 else {
165 $h = 4+$this->div(($r - $g),$df);
b78451d @azwan082 add files
authored Sep 7, 2011
166 }
167 $h = $h * 60;
168 if ($h < 0) {
169 $h = $h+360;
170 }
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
171 return array( $h, 1-(3*$this->div((min($r,$g,$b)),($r+$g+$b))), (1/3)*($r+$g+$b) );
b78451d @azwan082 add files
authored Sep 7, 2011
172 }
173
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
174 /**
175 * when iterating from top left pixel to bottom right, some early pixels marked as skin pixel and some don't,
176 * if skin pixel are not continuous, each skin pixels will be marked as new region (and the region number will increase),
177 * but even if skin pixels that have only one pixel gap, will be treated as two different region
178 * so add_merge_region() will merge skin & non-skin pixels that are near to each other and combine under one region
179 * @param int $from
180 * @param int $to
181 * @return null
182 */
183 private function add_merge_region($from, $to) {
b78451d @azwan082 add files
authored Sep 7, 2011
184 $this->last_from = $from;
185 $this->last_to = $to;
186 $from_idx = -1;
187 $to_idx = -1;
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
188 foreach ($this->merge_regions as $k => $mr) {
189 if (in_array($from, $mr)) {
b78451d @azwan082 add files
authored Sep 7, 2011
190 $from_idx = $k;
191 }
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
192 if (in_array($to, $mr)) {
b78451d @azwan082 add files
authored Sep 7, 2011
193 $to_idx = $k;
194 }
195 }
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
196 // cannot merge same region (in same $this->merge_regions[$k])
b78451d @azwan082 add files
authored Sep 7, 2011
197 if ($from_idx != -1 && $to_idx != -1 && $from_idx == $to_idx) {
198 return;
199 }
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
200 // no element inside $this->merge_regions
b78451d @azwan082 add files
authored Sep 7, 2011
201 if ($from_idx == -1 && $to_idx == -1) {
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
202 $this->merge_regions[] = array($from, $to); // add new element (array element) to $this->merge_regions array
b78451d @azwan082 add files
authored Sep 7, 2011
203 return;
204 }
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
205 // $from exists in $this->merge_regions
b78451d @azwan082 add files
authored Sep 7, 2011
206 if ($from_idx != -1 && $to_idx == -1) {
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
207 $this->merge_regions[$from_idx][] = $to; // add new element to an array element (identified by $from_idx) inside $this->merge_regions array
b78451d @azwan082 add files
authored Sep 7, 2011
208 return;
209 }
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
210 // $to exists in $this->merge_regions
b78451d @azwan082 add files
authored Sep 7, 2011
211 if ($from_idx == -1 && $to_idx != -1) {
212 $this->merge_regions[$to_idx][] = $from;
213 return;
214 }
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
215 // both $to and $from exists, merge them into $from, then empty $this->merge_regions[$to_idx]
b78451d @azwan082 add files
authored Sep 7, 2011
216 if ($from_idx != -1 && $to_idx != -1 && $from_idx != $to_idx) {
217 $this->merge_regions[$from_idx] = array_merge($this->merge_regions[$from_idx], $this->merge_regions[$to_idx]);
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
218 unset($this->merge_regions[$to_idx]);
b78451d @azwan082 add files
authored Sep 7, 2011
219 return;
220 }
221 }
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
222
f300735 @azwan082 add more comments, update readme
authored Sep 7, 2011
223 /**
224 * Get merge_regions pixel data, get only regions of certain size and store to skin_regions
225 * data in skin_regions will be used to determine if picture contains nudity or not
226 */
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
227 private function merge_and_clear() {
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
228 $det_regions = array();
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
229 $this->skin_regions = array();
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
230 foreach ($this->merge_regions as $i => $mr) {
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
231 if (!isset($det_regions[$i])) {
232 $det_regions[$i] = array();
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
233 }
234 foreach ($mr as $m) {
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
235 if (!empty($this->detected_regions[$m])) {
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
236 $det_regions[$i] = array_merge($det_regions[$i], $this->detected_regions[$m]);
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
237 }
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
238 unset($this->detected_regions[$m]);
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
239 }
240 }
241 if (!empty($this->detected_regions)) {
242 foreach ($this->detected_regions as $dr) {
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
243 $det_regions[] = $dr;
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
244 }
245 }
246 // only pushes regions which are bigger than a specific amount to the final result
fb811b5 @azwan082 clean up code, add comments, update example code
authored Sep 7, 2011
247 foreach ($det_regions as $dt) {
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
248 $count_dt = count($dt);
249 if ($count_dt > 30) {
250 $this->skin_regions[] = $count_dt;
9400a3a @azwan082 implement up to clear() func
authored Sep 7, 2011
251 }
252 }
253 }
b78451d @azwan082 add files
authored Sep 7, 2011
254
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
255 /**
256 * analyze skin_regions based on criteria on the research paper
257 * @return <type>
258 */
259 private function analyze_regions() {
260 // if there are less than 3 regions
261 if (count($this->skin_regions) < 3) {
262 return false;
263 }
264 // sort the detected regions by size
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
265 rsort($this->skin_regions);
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
266 $total_pixel = $this->img_w * $this->img_h;
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
267 $total_skin = array_sum($this->skin_regions);
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
268 // check if there are more than 15% skin pixel in the image
269 if (($total_skin/$total_pixel)*100 < 15) {
270 return false;
271 }
272 // check if the largest skin region is less than 35% of the total skin count
273 // AND if the second largest region is less than 30% of the total skin count
274 // AND if the third largest region is less than 30% of the total skin count
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
275 if (($this->skin_regions[0]/$total_skin)*100 < 35
276 && ($this->skin_regions[1]/$total_skin)*100 < 30
277 && ($this->skin_regions[2]/$total_skin)*100 < 30) {
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
278 return false;
279 }
280 // check if the number of skin pixels in the largest region is less than 45% of the total skin count
83bfb4e @azwan082 optimize array, only set region data, improve speed, example script r…
authored Sep 7, 2011
281 if (($this->skin_regions[0]/$total_skin)*100 < 45) {
f9f0306 @azwan082 finish implement all func
authored Sep 7, 2011
282 return false;
283 }
284 // @todo:
285 // build the bounding polygon by the regions edge values:
286 // Identify the leftmost, the uppermost, the rightmost, and the lowermost skin pixels of the three largest skin regions.
287 // Use these points as the corner points of a bounding polygon.
288
289 // @todo:
290 // check if the total skin count is less than 30% of the total number of pixels
291 // AND the number of skin pixels within the bounding polygon is less than 55% of the size of the polygon
292 // if this condition is true, it's not nude.
293
294 // @todo: include bounding polygon functionality
295
296 // if there are more than 60 skin regions
297 // @todo: the average intensity within the polygon is less than 0.25
298 if (count($this->skin_regions) > 60){
299 return false;
300 }
301 return true;
302 }
303
304 /**
305 * Safe division operation, check for division by zero
306 * @param int|float $a
307 * @param int|float $b
308 * @return int|float
309 */
310 private function div($a, $b) {
311 if ($b == 0) {
312 return 0;
313 } else {
314 return $a / $b;
315 }
316 }
317 }
b78451d @azwan082 add files
authored Sep 7, 2011
318 ?>
Something went wrong with that request. Please try again.