test_dnn.py 17 KB

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