1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 import cv
35
36 import os
37 import subprocess
38 import random
39 import tempfile
40 import unittest
41
42 import pyvision as pv
43
44 from pyvision.analysis.face import EyesFile
45
46 from pyvision.analysis.FaceAnalysis.FaceDetectionTest import FaceDetectionTest
47 import pickle
48
49 import time
50
51
56
57
58 DEFAULT_CASCADE=os.path.join(pv.__path__[0],"config","haarcascade_frontalface_alt.xml")
59 OPENCV_CASCADE=os.path.join(pv.__path__[0],"config","haarcascade_frontalface_alt.xml")
60 CELEB1_CASCADE=os.path.join(pv.__path__[0],"config","facedetector_celebdb1.xml")
61 CELEB2_CASCADE=os.path.join(pv.__path__[0],"config","facedetector_celebdb2.xml")
62 FULLBODY_CASCADE=os.path.join(pv.__path__[0],"config","haarcascade_fullbody.xml")
63 UPPERBODY_CASCADE=os.path.join(pv.__path__[0],"config","haarcascade_upperbody.xml")
64 LOWERBODY_CASCADE=os.path.join(pv.__path__[0],"config","haarcascade_lowerbody.xml")
65 UPPERBODY_MCS_CASCADE=os.path.join(pv.__path__[0],"config","haarcascade_mcs_upperbody.xml")
66
67 DEFAULT_NEGATIVE=os.path.join(pv.__path__[0],"data","nonface")
68
69
70
71
72 AVE_LEFT_EYE = pv.Point(0.300655,0.381525,0.000000)
73 AVE_RIGHT_EYE = pv.Point(0.708847,0.379736,0.000000)
74
76 ''' This class is a wrapper around the OpenCV cascade detectior. '''
77
78 - def __init__(self, cascade_name=DEFAULT_CASCADE,orig_size=None,min_size=(60,60), image_scale=1.3, haar_scale=1.2, min_neighbors=2, haar_flags=0):
79 ''' Init the detector and create the cascade classifier '''
80
81 self.cascade_name = cascade_name
82 self.min_size = min_size
83 self.image_scale = image_scale
84 self.haar_scale = haar_scale
85 self.min_neighbors = min_neighbors
86 self.haar_flags = haar_flags
87
88 if cascade_name != None:
89 if not os.path.isfile(cascade_name):
90 raise CascadeNotFound("Could not find file: "+cascade_name)
91
92 if orig_size == None:
93 orig_size = (1,1)
94 else:
95 orig_size = (orig_size[0],orig_size[1])
96
97 self.cascade_data = open(cascade_name).read()
98 self.cascade = cv.Load( cascade_name )
99 self.storage = cv.CreateMemStorage(0)
100 self.trained = True
101
102
104 ''' This function is the same as detect. '''
105 return self.detect(im)
106
108 ''' Function required to save and load the state from pickel. '''
109 state = {}
110
111 for key,value in self.__dict__.iteritems():
112 if key in ['cascade','storage']:
113 continue
114
115 state[key] = value
116
117 return state
118
120 ''' Function required to save and load the state from pickel. '''
121
122
123 for key,value in state.iteritems():
124 self.__dict__[key] = value
125
126
127 filename = tempfile.mktemp()
128 open(filename,'w').write(self.cascade_data)
129 self.cascade = cv.Load( filename )
130 self.storage = cv.CreateMemStorage(0)
131 os.remove(filename)
132
134 ''' Resize an image by a scale or a size. Internal use only.'''
135 if scale != None and type(scale) in (int,float):
136 size = (int(image.width*scale),int(image.height*scale))
137 elif size != None and type(size) in [list,tuple]:
138 size = (int(size[0]),int(size[1]))
139 else:
140 pass
141 depth = image.depth
142 channels = image.nChannels
143 resized = cv.CreateImage( (size[0],size[1]), depth, channels )
144 cv.Resize( image, resized, cv.CV_INTER_LINEAR )
145 return resized
146
148 ''' Runs the cascade classifer on an image. '''
149 image = im.asOpenCV()
150
151 min_size = (self.min_size[0],self.min_size[1])
152
153
154 if image.nChannels == 3:
155 gray = cv.CreateImage( (image.width,image.height), image.depth, 1 )
156 cv.CvtColor( image, gray, cv.CV_BGR2GRAY );
157 image = gray
158
159
160 image = self._resizeImage(image,self.image_scale)
161
162
163 cv.EqualizeHist( image, image )
164
165
166 faces = cv.HaarDetectObjects( image, self.cascade, self.storage,
167 self.haar_scale, self.min_neighbors, self.haar_flags, min_size );
168
169
170 result = []
171 for r in faces:
172 rect = pv.Rect(r[0][0]/self.image_scale, r[0][1]/self.image_scale, r[0][2]/self.image_scale, r[0][3]/self.image_scale)
173 result.append(rect)
174
175 return result
176
177 -def trainHaarClassifier(pos_rects,
178 neg_images,
179 tile_size=(20,20),
180 nneg=2000,
181 nstages=20,
182 mem = 1500,
183 maxtreesplits = 0,
184 mode='BASIC',
185 minhitrate=0.9990,
186 maxfalsealarm=0.50,
187 max_run_time=72*3600,
188 verbose=False,
189 createsamples='/usr/local/bin/opencv-createsamples',
190 haartraining='/usr/local/bin/opencv-haartraining',
191 ):
192 '''
193 Train the detector.
194 '''
195
196 training_dir = tempfile.mktemp()
197 os.makedirs(training_dir, 0700)
198
199 random_name = "haar_"
200 for _ in range(8):
201 random_name += random.choice('abcdefghijklmnopqrstuvwxyz')
202 cascade_name = random_name+"_cascade"
203 pos_name = random_name+"_pos.txt"
204 pos_vec_name = random_name+"_pos.vec"
205 neg_name = random_name+"_neg.txt"
206
207
208 pos_filename = os.path.join(training_dir,pos_name)
209 pos_file = open(pos_filename,'w')
210 num_pos = 0
211 for im_name,rects in pos_rects:
212 num_pos += len(rects)
213 if len(rects) > 0: pos_file.write("%s %d "%(im_name,len(rects)))
214 for rect in rects:
215 pos_file.write("%d %d %d %d "%(rect.x,rect.y,rect.w,rect.h))
216 num_pos += 1
217 pos_file.write("\n")
218 pos_file.close()
219
220
221 neg_filename = os.path.join(training_dir,neg_name)
222 neg_file = open(neg_filename,'w')
223 for im_name in neg_images:
224 neg_file.write("%s\n"%im_name)
225 neg_file.close()
226
227
228 proc = subprocess.Popen(
229 (createsamples,
230 '-info',pos_name,
231 '-vec',pos_vec_name,
232 '-num',str(num_pos),
233 '-w',str(tile_size[0]),
234 '-h',str(tile_size[1]),
235 ),
236 cwd=training_dir
237 ,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.STDOUT
238 )
239
240 proc.wait()
241 if verbose:
242 print proc.stdout.read()
243
244
245 success = False
246
247 start_time = time.time()
248
249 if verbose:
250 proc = subprocess.Popen(
251 (haartraining,
252 '-data',cascade_name,
253 '-vec',pos_vec_name,
254 '-bg',neg_name,
255 '-nstages',str(nstages),
256 '-mode','ALL',
257 '-npos',str(num_pos),
258 '-nneg',str(nneg),
259 '-mem',str(mem),
260 '-w',str(tile_size[0]),
261 '-h',str(tile_size[1]),
262 '-maxtreesplits',str(maxtreesplits),
263 '-minhitrate',str(minhitrate),
264 '-maxfalsealarm',str(maxfalsealarm),
265 '-mode',mode,
266 ),
267 cwd=training_dir
268
269 )
270 else:
271 proc = subprocess.Popen(
272 (haartraining,
273 '-data',cascade_name,
274 '-vec',pos_vec_name,
275 '-bg',neg_name,
276 '-nstages',str(nstages),
277 '-mode','ALL',
278 '-npos',str(num_pos),
279 '-nneg',str(nneg),
280 '-mem',str(mem),
281 '-w',str(tile_size[0]),
282 '-h',str(tile_size[1]),
283 '-maxtreesplits',str(maxtreesplits),
284 '-minhitrate',str(minhitrate),
285 '-maxfalsealarm',str(maxfalsealarm),
286 '-mode',mode,
287 ),
288 cwd=training_dir
289 ,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.STDOUT
290 )
291
292 while True:
293 if proc.poll() != None:
294 break
295 if time.time() - start_time > max_run_time:
296 print "Haar Training time exceeded. Killing process..."
297 os.kill(proc.pid,6)
298 proc.wait()
299 break
300
301
302
303 time.sleep(1)
304
305 if proc.returncode == 0:
306 if verbose: print "Cascade successful."
307 success = True
308
309
310 else:
311 print "Problem with return code:",proc.returncode
312
313
314
315
316
317 detector = None
318 if success:
319 detector = CascadeDetector(os.path.join(training_dir,cascade_name+'.xml'))
320 else:
321
322 print "Cascade Failure. Could not create classifier."
323
324
325 os.system("rm -rf %s"%training_dir)
326
327 time.sleep(5)
328 return detector
329
330
331
332 SCRAPS_FACE_DATA = os.path.join(pv.__path__[0],"data","csuScrapShots")
333 NONFACE_DATA = os.path.join(pv.__path__[0],"data","NonFace")
334 BAD_CASCADE=os.path.join(pv.__path__[0],"config","not_there.xml")
335
336
338 ''' Unit tests for the Cascade Detector '''
339
340
342 '''
343 If the cascade file does not exist, opencv can crash without warning.
344 This makes sure a test is run to make sure the cascade is there.
345 '''
346
347 self.assertRaises(CascadeNotFound,CascadeDetector,BAD_CASCADE)
348
365
366
380
393
407
408
410
411 positives = []
412 self.eyes = EyesFile(os.path.join(SCRAPS_FACE_DATA,"coords.txt"))
413 n = len(self.eyes.files())
414 for filename in self.eyes.files()[:n/2]:
415 img = pv.Image(os.path.join(SCRAPS_FACE_DATA, filename + ".pgm"))
416 faces = self.eyes.getFaces(img.filename)
417 positives.append([os.path.join(SCRAPS_FACE_DATA,img.filename),faces])
418
419 neg_files = []
420 for im_name in os.listdir(NONFACE_DATA):
421 if im_name[-4:] != ".jpg": continue
422 neg_files.append(os.path.join(NONFACE_DATA,im_name))
423
424 fd = trainHaarClassifier(positives,neg_files,nstages=6,maxtreesplits=0,max_run_time=300)
425 fdt = FaceDetectionTest(name='scraps')
426
427 self.eyes = EyesFile(os.path.join(SCRAPS_FACE_DATA,"coords.txt"))
428 for filename in self.eyes.files():
429 img = pv.Image(os.path.join(SCRAPS_FACE_DATA, filename + ".pgm"))
430 rects = fd(img)
431 truth = self.eyes.getFaces(img.filename)
432 fdt.addSample(truth,rects,im=img)
433
434 self.assertAlmostEqual( fdt.pos_rate , 0.9942196531791907 )
435