Merge remote-tracking branch 'upstream/3.4' into merge-3.4
[platform/upstream/opencv.git] / modules / dnn / misc / python / test / test_dnn.py
1 #!/usr/bin/env python
2 import os
3 import cv2 as cv
4 import numpy as np
5
6 from tests_common import NewOpenCVTests, unittest
7
8 def normAssert(test, a, b, msg=None, lInf=1e-5):
9     test.assertLess(np.max(np.abs(a - b)), lInf, msg)
10
11 def inter_area(box1, box2):
12     x_min, x_max = max(box1[0], box2[0]), min(box1[2], box2[2])
13     y_min, y_max = max(box1[1], box2[1]), min(box1[3], box2[3])
14     return (x_max - x_min) * (y_max - y_min)
15
16 def area(box):
17     return (box[2] - box[0]) * (box[3] - box[1])
18
19 def box2str(box):
20     left, top = box[0], box[1]
21     width, height = box[2] - left, box[3] - top
22     return '[%f x %f from (%f, %f)]' % (width, height, left, top)
23
24 def normAssertDetections(test, refClassIds, refScores, refBoxes, testClassIds, testScores, testBoxes,
25                  confThreshold=0.0, scores_diff=1e-5, boxes_iou_diff=1e-4):
26     matchedRefBoxes = [False] * len(refBoxes)
27     errMsg = ''
28     for i in range(len(testBoxes)):
29         testScore = testScores[i]
30         if testScore < confThreshold:
31             continue
32
33         testClassId, testBox = testClassIds[i], testBoxes[i]
34         matched = False
35         for j in range(len(refBoxes)):
36             if (not matchedRefBoxes[j]) and testClassId == refClassIds[j] and \
37                abs(testScore - refScores[j]) < scores_diff:
38                 interArea = inter_area(testBox, refBoxes[j])
39                 iou = interArea / (area(testBox) + area(refBoxes[j]) - interArea)
40                 if abs(iou - 1.0) < boxes_iou_diff:
41                     matched = True
42                     matchedRefBoxes[j] = True
43         if not matched:
44             errMsg += '\nUnmatched prediction: class %d score %f box %s' % (testClassId, testScore, box2str(testBox))
45
46     for i in range(len(refBoxes)):
47         if (not matchedRefBoxes[i]) and refScores[i] > confThreshold:
48             errMsg += '\nUnmatched reference: class %d score %f box %s' % (refClassIds[i], refScores[i], box2str(refBoxes[i]))
49     if errMsg:
50         test.fail(errMsg)
51
52 def printParams(backend, target):
53     backendNames = {
54         cv.dnn.DNN_BACKEND_OPENCV: 'OCV',
55         cv.dnn.DNN_BACKEND_INFERENCE_ENGINE: 'DLIE'
56     }
57     targetNames = {
58         cv.dnn.DNN_TARGET_CPU: 'CPU',
59         cv.dnn.DNN_TARGET_OPENCL: 'OCL',
60         cv.dnn.DNN_TARGET_OPENCL_FP16: 'OCL_FP16',
61         cv.dnn.DNN_TARGET_MYRIAD: 'MYRIAD'
62     }
63     print('%s/%s' % (backendNames[backend], targetNames[target]))
64
65 testdata_required = bool(os.environ.get('OPENCV_DNN_TEST_REQUIRE_TESTDATA', False))
66
67 g_dnnBackendsAndTargets = None
68
69 class dnn_test(NewOpenCVTests):
70
71     def setUp(self):
72         super(dnn_test, self).setUp()
73
74         global g_dnnBackendsAndTargets
75         if g_dnnBackendsAndTargets is None:
76             g_dnnBackendsAndTargets = self.initBackendsAndTargets()
77         self.dnnBackendsAndTargets = g_dnnBackendsAndTargets
78
79     def initBackendsAndTargets(self):
80         self.dnnBackendsAndTargets = [
81             [cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_CPU],
82         ]
83
84         if self.checkIETarget(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_CPU):
85             self.dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_CPU])
86         if self.checkIETarget(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_MYRIAD):
87             self.dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_MYRIAD])
88
89         if cv.ocl.haveOpenCL() and cv.ocl.useOpenCL():
90             self.dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_OPENCL])
91             self.dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_OPENCL_FP16])
92             if cv.ocl_Device.getDefault().isIntel():
93                 if self.checkIETarget(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL):
94                     self.dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL])
95                 if self.checkIETarget(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL_FP16):
96                     self.dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL_FP16])
97         return self.dnnBackendsAndTargets
98
99     def find_dnn_file(self, filename, required=True):
100         if not required:
101             required = testdata_required
102         return self.find_file(filename, [os.environ.get('OPENCV_DNN_TEST_DATA_PATH', os.getcwd()),
103                                          os.environ['OPENCV_TEST_DATA_PATH']],
104                               required=required)
105
106     def checkIETarget(self, backend, target):
107         proto = self.find_dnn_file('dnn/layers/layer_convolution.prototxt')
108         model = self.find_dnn_file('dnn/layers/layer_convolution.caffemodel')
109         net = cv.dnn.readNet(proto, model)
110         net.setPreferableBackend(backend)
111         net.setPreferableTarget(target)
112         inp = np.random.standard_normal([1, 2, 10, 11]).astype(np.float32)
113         try:
114             net.setInput(inp)
115             net.forward()
116         except BaseException as e:
117             return False
118         return True
119
120     def test_getAvailableTargets(self):
121         targets = cv.dnn.getAvailableTargets(cv.dnn.DNN_BACKEND_OPENCV)
122         self.assertTrue(cv.dnn.DNN_TARGET_CPU in targets)
123
124     def test_blobFromImage(self):
125         np.random.seed(324)
126
127         width = 6
128         height = 7
129         scale = 1.0/127.5
130         mean = (10, 20, 30)
131
132         # Test arguments names.
133         img = np.random.randint(0, 255, [4, 5, 3]).astype(np.uint8)
134         blob = cv.dnn.blobFromImage(img, scale, (width, height), mean, True, False)
135         blob_args = cv.dnn.blobFromImage(img, scalefactor=scale, size=(width, height),
136                                          mean=mean, swapRB=True, crop=False)
137         normAssert(self, blob, blob_args)
138
139         # Test values.
140         target = cv.resize(img, (width, height), interpolation=cv.INTER_LINEAR)
141         target = target.astype(np.float32)
142         target = target[:,:,[2, 1, 0]]  # BGR2RGB
143         target[:,:,0] -= mean[0]
144         target[:,:,1] -= mean[1]
145         target[:,:,2] -= mean[2]
146         target *= scale
147         target = target.transpose(2, 0, 1).reshape(1, 3, height, width)  # to NCHW
148         normAssert(self, blob, target)
149
150
151     def test_model(self):
152         img_path = self.find_dnn_file("dnn/street.png")
153         weights = self.find_dnn_file("dnn/MobileNetSSD_deploy.caffemodel", required=False)
154         config = self.find_dnn_file("dnn/MobileNetSSD_deploy.prototxt", required=False)
155         if weights is None or config is None:
156             raise unittest.SkipTest("Missing DNN test files (dnn/MobileNetSSD_deploy.{prototxt/caffemodel}). Verify OPENCV_DNN_TEST_DATA_PATH configuration parameter.")
157
158         frame = cv.imread(img_path)
159         model = cv.dnn_DetectionModel(weights, config)
160         model.setInputParams(size=(300, 300), mean=(127.5, 127.5, 127.5), scale=1.0/127.5)
161
162         iouDiff = 0.05
163         confThreshold = 0.0001
164         nmsThreshold = 0
165         scoreDiff = 1e-3
166
167         classIds, confidences, boxes = model.detect(frame, confThreshold, nmsThreshold)
168
169         refClassIds = (7, 15)
170         refConfidences = (0.9998, 0.8793)
171         refBoxes = ((328, 238, 85, 102), (101, 188, 34, 138))
172
173         normAssertDetections(self, refClassIds, refConfidences, refBoxes,
174                              classIds, confidences, boxes,confThreshold, scoreDiff, iouDiff)
175
176         for box in boxes:
177             cv.rectangle(frame, box, (0, 255, 0))
178             cv.rectangle(frame, np.array(box), (0, 255, 0))
179             cv.rectangle(frame, tuple(box), (0, 255, 0))
180             cv.rectangle(frame, list(box), (0, 255, 0))
181
182
183     def test_classification_model(self):
184         img_path = self.find_dnn_file("dnn/googlenet_0.png")
185         weights = self.find_dnn_file("dnn/squeezenet_v1.1.caffemodel", required=False)
186         config = self.find_dnn_file("dnn/squeezenet_v1.1.prototxt")
187         ref = np.load(self.find_dnn_file("dnn/squeezenet_v1.1_prob.npy"))
188         if weights is None or config is None:
189             raise unittest.SkipTest("Missing DNN test files (dnn/squeezenet_v1.1.{prototxt/caffemodel}). Verify OPENCV_DNN_TEST_DATA_PATH configuration parameter.")
190
191         frame = cv.imread(img_path)
192         model = cv.dnn_ClassificationModel(config, weights)
193         model.setInputSize(227, 227)
194         model.setInputCrop(True)
195
196         out = model.predict(frame)
197         normAssert(self, out, ref)
198
199
200     def test_face_detection(self):
201         proto = self.find_dnn_file('dnn/opencv_face_detector.prototxt')
202         model = self.find_dnn_file('dnn/opencv_face_detector.caffemodel', required=False)
203         if proto is None or model is None:
204             raise unittest.SkipTest("Missing DNN test files (dnn/opencv_face_detector.{prototxt/caffemodel}). Verify OPENCV_DNN_TEST_DATA_PATH configuration parameter.")
205
206         img = self.get_sample('gpu/lbpcascade/er.png')
207         blob = cv.dnn.blobFromImage(img, mean=(104, 177, 123), swapRB=False, crop=False)
208
209         ref = [[0, 1, 0.99520785, 0.80997437, 0.16379407, 0.87996572, 0.26685631],
210                [0, 1, 0.9934696,  0.2831718,  0.50738752, 0.345781,   0.5985168],
211                [0, 1, 0.99096733, 0.13629119, 0.24892329, 0.19756334, 0.3310290],
212                [0, 1, 0.98977017, 0.23901358, 0.09084064, 0.29902688, 0.1769477],
213                [0, 1, 0.97203469, 0.67965847, 0.06876482, 0.73999709, 0.1513494],
214                [0, 1, 0.95097077, 0.51901293, 0.45863652, 0.5777427,  0.5347801]]
215
216         print('\n')
217         for backend, target in self.dnnBackendsAndTargets:
218             printParams(backend, target)
219
220             net = cv.dnn.readNet(proto, model)
221             net.setPreferableBackend(backend)
222             net.setPreferableTarget(target)
223             net.setInput(blob)
224             out = net.forward().reshape(-1, 7)
225
226             scoresDiff = 4e-3 if target in [cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD] else 1e-5
227             iouDiff = 2e-2 if target in [cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD] else 1e-4
228
229             ref = np.array(ref, np.float32)
230             refClassIds, testClassIds = ref[:, 1], out[:, 1]
231             refScores, testScores = ref[:, 2], out[:, 2]
232             refBoxes, testBoxes = ref[:, 3:], out[:, 3:]
233
234             normAssertDetections(self, refClassIds, refScores, refBoxes, testClassIds,
235                                  testScores, testBoxes, 0.5, scoresDiff, iouDiff)
236
237     def test_async(self):
238         timeout = 10*1000*10**6  # in nanoseconds (10 sec)
239         proto = self.find_dnn_file('dnn/layers/layer_convolution.prototxt')
240         model = self.find_dnn_file('dnn/layers/layer_convolution.caffemodel')
241         if proto is None or model is None:
242             raise unittest.SkipTest("Missing DNN test files (dnn/layers/layer_convolution.{prototxt/caffemodel}). Verify OPENCV_DNN_TEST_DATA_PATH configuration parameter.")
243
244         print('\n')
245         for backend, target in self.dnnBackendsAndTargets:
246             if backend != cv.dnn.DNN_BACKEND_INFERENCE_ENGINE:
247                 continue
248
249             printParams(backend, target)
250
251             netSync = cv.dnn.readNet(proto, model)
252             netSync.setPreferableBackend(backend)
253             netSync.setPreferableTarget(target)
254
255             netAsync = cv.dnn.readNet(proto, model)
256             netAsync.setPreferableBackend(backend)
257             netAsync.setPreferableTarget(target)
258
259             # Generate inputs
260             numInputs = 10
261             inputs = []
262             for _ in range(numInputs):
263                 inputs.append(np.random.standard_normal([2, 6, 75, 113]).astype(np.float32))
264
265             # Run synchronously
266             refs = []
267             for i in range(numInputs):
268                 netSync.setInput(inputs[i])
269                 refs.append(netSync.forward())
270
271             # Run asynchronously. To make test more robust, process inputs in the reversed order.
272             outs = []
273             for i in reversed(range(numInputs)):
274                 netAsync.setInput(inputs[i])
275                 outs.insert(0, netAsync.forwardAsync())
276
277             for i in reversed(range(numInputs)):
278                 ret, result = outs[i].get(timeoutNs=float(timeout))
279                 self.assertTrue(ret)
280                 normAssert(self, refs[i], result, 'Index: %d' % i, 1e-10)
281
282     def test_nms(self):
283         confs = (1, 1)
284         rects = ((0, 0, 0.4, 0.4), (0, 0, 0.2, 0.4)) # 0.5 overlap
285
286         self.assertTrue(all(cv.dnn.NMSBoxes(rects, confs, 0, 0.6).ravel() == (0, 1)))
287
288     def test_custom_layer(self):
289         class CropLayer(object):
290             def __init__(self, params, blobs):
291                 self.xstart = 0
292                 self.xend = 0
293                 self.ystart = 0
294                 self.yend = 0
295             # Our layer receives two inputs. We need to crop the first input blob
296             # to match a shape of the second one (keeping batch size and number of channels)
297             def getMemoryShapes(self, inputs):
298                 inputShape, targetShape = inputs[0], inputs[1]
299                 batchSize, numChannels = inputShape[0], inputShape[1]
300                 height, width = targetShape[2], targetShape[3]
301                 self.ystart = (inputShape[2] - targetShape[2]) // 2
302                 self.xstart = (inputShape[3] - targetShape[3]) // 2
303                 self.yend = self.ystart + height
304                 self.xend = self.xstart + width
305                 return [[batchSize, numChannels, height, width]]
306             def forward(self, inputs):
307                 return [inputs[0][:,:,self.ystart:self.yend,self.xstart:self.xend]]
308
309         cv.dnn_registerLayer('CropCaffe', CropLayer)
310         proto = '''
311         name: "TestCrop"
312         input: "input"
313         input_shape
314         {
315             dim: 1
316             dim: 2
317             dim: 5
318             dim: 5
319         }
320         input: "roi"
321         input_shape
322         {
323             dim: 1
324             dim: 2
325             dim: 3
326             dim: 3
327         }
328         layer {
329           name: "Crop"
330           type: "CropCaffe"
331           bottom: "input"
332           bottom: "roi"
333           top: "Crop"
334         }'''
335
336         net = cv.dnn.readNetFromCaffe(bytearray(proto.encode()))
337         for backend, target in self.dnnBackendsAndTargets:
338             if backend != cv.dnn.DNN_BACKEND_OPENCV:
339                 continue
340
341             printParams(backend, target)
342
343             net.setPreferableBackend(backend)
344             net.setPreferableTarget(target)
345             src_shape = [1, 2, 5, 5]
346             dst_shape = [1, 2, 3, 3]
347             inp = np.arange(0, np.prod(src_shape), dtype=np.float32).reshape(src_shape)
348             roi = np.empty(dst_shape, dtype=np.float32)
349             net.setInput(inp, "input")
350             net.setInput(roi, "roi")
351             out = net.forward()
352             ref = inp[:, :, 1:4, 1:4]
353             normAssert(self, out, ref)
354
355         cv.dnn_unregisterLayer('CropCaffe')
356
357 if __name__ == '__main__':
358     NewOpenCVTests.bootstrap()