Skip to content

Commit eab556e

Browse files
committed
OpenCV face detection network in TensorFlow
1 parent 53305d4 commit eab556e

File tree

4 files changed

+592
-2
lines changed

4 files changed

+592
-2
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# This script is used to estimate an accuracy of different face detection models.
2+
# COCO evaluation tool is used to compute an accuracy metrics (Average Precision).
3+
# Script works with different face detection datasets.
4+
import os
5+
import json
6+
from fnmatch import fnmatch
7+
from math import pi
8+
import cv2 as cv
9+
import argparse
10+
import os
11+
import sys
12+
from pycocotools.coco import COCO
13+
from pycocotools.cocoeval import COCOeval
14+
15+
parser = argparse.ArgumentParser(
16+
description='Evaluate OpenCV face detection algorithms '
17+
'using COCO evaluation tool, http://cocodataset.org/#detections-eval')
18+
parser.add_argument('--proto', help='Path to .prototxt of Caffe model or .pbtxt of TensorFlow graph')
19+
parser.add_argument('--model', help='Path to .caffemodel trained in Caffe or .pb from TensorFlow')
20+
parser.add_argument('--caffe', help='Indicate that tested model is from Caffe. Otherwise model from TensorFlow is expected.', action='store_true')
21+
parser.add_argument('--cascade', help='Optional path to trained Haar cascade as '
22+
'an additional model for evaluation')
23+
parser.add_argument('--ann', help='Path to text file with ground truth annotations')
24+
parser.add_argument('--pics', help='Path to images root directory')
25+
parser.add_argument('--fddb', help='Evaluate FDDB dataset, http://vis-www.cs.umass.edu/fddb/', action='store_true')
26+
parser.add_argument('--wider', help='Evaluate WIDER FACE dataset, http://mmlab.ie.cuhk.edu.hk/projects/WIDERFace/', action='store_true')
27+
args = parser.parse_args()
28+
29+
dataset = {}
30+
dataset['images'] = []
31+
dataset['categories'] = [{ 'id': 0, 'name': 'face' }]
32+
dataset['annotations'] = []
33+
34+
def ellipse2Rect(params):
35+
rad_x = params[0]
36+
rad_y = params[1]
37+
angle = params[2] * 180.0 / pi
38+
center_x = params[3]
39+
center_y = params[4]
40+
pts = cv.ellipse2Poly((int(center_x), int(center_y)), (int(rad_x), int(rad_y)),
41+
int(angle), 0, 360, 10)
42+
rect = cv.boundingRect(pts)
43+
left = rect[0]
44+
top = rect[1]
45+
right = rect[0] + rect[2]
46+
bottom = rect[1] + rect[3]
47+
return left, top, right, bottom
48+
49+
def addImage(imagePath):
50+
assert('images' in dataset)
51+
imageId = len(dataset['images'])
52+
dataset['images'].append({
53+
'id': int(imageId),
54+
'file_name': imagePath
55+
})
56+
return imageId
57+
58+
def addBBox(imageId, left, top, width, height):
59+
assert('annotations' in dataset)
60+
dataset['annotations'].append({
61+
'id': len(dataset['annotations']),
62+
'image_id': int(imageId),
63+
'category_id': 0, # Face
64+
'bbox': [int(left), int(top), int(width), int(height)],
65+
'iscrowd': 0,
66+
'area': float(width * height)
67+
})
68+
69+
def addDetection(detections, imageId, left, top, width, height, score):
70+
detections.append({
71+
'image_id': int(imageId),
72+
'category_id': 0, # Face
73+
'bbox': [int(left), int(top), int(width), int(height)],
74+
'score': float(score)
75+
})
76+
77+
78+
def fddb_dataset(annotations, images):
79+
for d in os.listdir(annotations):
80+
if fnmatch(d, 'FDDB-fold-*-ellipseList.txt'):
81+
with open(os.path.join(annotations, d), 'rt') as f:
82+
lines = [line.rstrip('\n') for line in f]
83+
lineId = 0
84+
while lineId < len(lines):
85+
# Image
86+
imgPath = lines[lineId]
87+
lineId += 1
88+
imageId = addImage(os.path.join(images, imgPath) + '.jpg')
89+
90+
img = cv.imread(os.path.join(images, imgPath) + '.jpg')
91+
92+
# Faces
93+
numFaces = int(lines[lineId])
94+
lineId += 1
95+
for i in range(numFaces):
96+
params = [float(v) for v in lines[lineId].split()]
97+
lineId += 1
98+
left, top, right, bottom = ellipse2Rect(params)
99+
addBBox(imageId, left, top, width=right - left + 1,
100+
height=bottom - top + 1)
101+
102+
103+
def wider_dataset(annotations, images):
104+
with open(annotations, 'rt') as f:
105+
lines = [line.rstrip('\n') for line in f]
106+
lineId = 0
107+
while lineId < len(lines):
108+
# Image
109+
imgPath = lines[lineId]
110+
lineId += 1
111+
imageId = addImage(os.path.join(images, imgPath))
112+
113+
# Faces
114+
numFaces = int(lines[lineId])
115+
lineId += 1
116+
for i in range(numFaces):
117+
params = [int(v) for v in lines[lineId].split()]
118+
lineId += 1
119+
left, top, width, height = params[0], params[1], params[2], params[3]
120+
addBBox(imageId, left, top, width, height)
121+
122+
def evaluate():
123+
cocoGt = COCO('annotations.json')
124+
cocoDt = cocoGt.loadRes('detections.json')
125+
cocoEval = COCOeval(cocoGt, cocoDt, 'bbox')
126+
cocoEval.evaluate()
127+
cocoEval.accumulate()
128+
cocoEval.summarize()
129+
130+
131+
### Convert to COCO annotations format #########################################
132+
assert(args.fddb or args.wider)
133+
if args.fddb:
134+
fddb_dataset(args.ann, args.pics)
135+
elif args.wider:
136+
wider_dataset(args.ann, args.pics)
137+
138+
with open('annotations.json', 'wt') as f:
139+
json.dump(dataset, f)
140+
141+
### Obtain detections ##########################################################
142+
detections = []
143+
if args.proto and args.model:
144+
if args.caffe:
145+
net = cv.dnn.readNetFromCaffe(args.proto, args.model)
146+
else:
147+
net = cv.dnn.readNetFromTensorflow(args.model, args.proto)
148+
149+
def detect(img, imageId):
150+
imgWidth = img.shape[1]
151+
imgHeight = img.shape[0]
152+
net.setInput(cv.dnn.blobFromImage(img, 1.0, (300, 300), (104., 177., 123.), False, False))
153+
out = net.forward()
154+
155+
for i in range(out.shape[2]):
156+
confidence = out[0, 0, i, 2]
157+
left = int(out[0, 0, i, 3] * img.shape[1])
158+
top = int(out[0, 0, i, 4] * img.shape[0])
159+
right = int(out[0, 0, i, 5] * img.shape[1])
160+
bottom = int(out[0, 0, i, 6] * img.shape[0])
161+
addDetection(detections, imageId, left, top, width=right - left + 1,
162+
height=bottom - top + 1, score=confidence)
163+
164+
elif args.cascade:
165+
cascade = cv.CascadeClassifier(args.cascade)
166+
167+
def detect(img, imageId):
168+
srcImgGray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
169+
faces = cascade.detectMultiScale(srcImgGray)
170+
171+
for rect in faces:
172+
left, top, width, height = rect[0], rect[1], rect[2], rect[3]
173+
addDetection(detections, imageId, left, top, width, height, score=1.0)
174+
175+
for i in range(len(dataset['images'])):
176+
sys.stdout.write('\r%d / %d' % (i + 1, len(dataset['images'])))
177+
sys.stdout.flush()
178+
179+
img = cv.imread(dataset['images'][i]['file_name'])
180+
imageId = int(dataset['images'][i]['id'])
181+
182+
detect(img, imageId)
183+
184+
with open('detections.json', 'wt') as f:
185+
json.dump(detections, f)
186+
187+
evaluate()
188+
189+
190+
def rm(f):
191+
if os.path.exists(f):
192+
os.remove(f)
193+
194+
rm('annotations.json')
195+
rm('detections.json')

0 commit comments

Comments
 (0)