You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
18 KiB

  1. # Copyright 2017 The TensorFlow Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # ==============================================================================
  15. """Tests for object_detection.utils.visualization_utils."""
  16. import logging
  17. import os
  18. import numpy as np
  19. import PIL.Image as Image
  20. import tensorflow as tf
  21. from object_detection.core import standard_fields as fields
  22. from object_detection.utils import visualization_utils
  23. _TESTDATA_PATH = 'object_detection/test_images'
  24. class VisualizationUtilsTest(tf.test.TestCase):
  25. def test_get_prime_multiplier_for_color_randomness(self):
  26. # Show that default multipler is not 1 and does not divide the total number
  27. # of standard colors.
  28. multiplier = visualization_utils._get_multiplier_for_color_randomness()
  29. self.assertNotEqual(
  30. 0, multiplier % len(visualization_utils.STANDARD_COLORS))
  31. self.assertNotEqual(1, multiplier)
  32. # Show that with 34 colors, the closest prime number to 34/10 that
  33. # satisfies the constraints is 5.
  34. visualization_utils.STANDARD_COLORS = [
  35. 'color_{}'.format(str(i)) for i in range(34)
  36. ]
  37. multiplier = visualization_utils._get_multiplier_for_color_randomness()
  38. self.assertEqual(5, multiplier)
  39. # Show that with 110 colors, the closest prime number to 110/10 that
  40. # satisfies the constraints is 13 (since 11 equally divides 110).
  41. visualization_utils.STANDARD_COLORS = [
  42. 'color_{}'.format(str(i)) for i in range(110)
  43. ]
  44. multiplier = visualization_utils._get_multiplier_for_color_randomness()
  45. self.assertEqual(13, multiplier)
  46. def create_colorful_test_image(self):
  47. """This function creates an image that can be used to test vis functions.
  48. It makes an image composed of four colored rectangles.
  49. Returns:
  50. colorful test numpy array image.
  51. """
  52. ch255 = np.full([100, 200, 1], 255, dtype=np.uint8)
  53. ch128 = np.full([100, 200, 1], 128, dtype=np.uint8)
  54. ch0 = np.full([100, 200, 1], 0, dtype=np.uint8)
  55. imr = np.concatenate((ch255, ch128, ch128), axis=2)
  56. img = np.concatenate((ch255, ch255, ch0), axis=2)
  57. imb = np.concatenate((ch255, ch0, ch255), axis=2)
  58. imw = np.concatenate((ch128, ch128, ch128), axis=2)
  59. imu = np.concatenate((imr, img), axis=1)
  60. imd = np.concatenate((imb, imw), axis=1)
  61. image = np.concatenate((imu, imd), axis=0)
  62. return image
  63. def create_test_image_with_five_channels(self):
  64. return np.full([100, 200, 5], 255, dtype=np.uint8)
  65. def create_test_grayscale_image(self):
  66. return np.full([100, 200, 1], 255, dtype=np.uint8)
  67. def test_draw_bounding_box_on_image(self):
  68. test_image = self.create_colorful_test_image()
  69. test_image = Image.fromarray(test_image)
  70. width_original, height_original = test_image.size
  71. ymin = 0.25
  72. ymax = 0.75
  73. xmin = 0.4
  74. xmax = 0.6
  75. visualization_utils.draw_bounding_box_on_image(test_image, ymin, xmin, ymax,
  76. xmax)
  77. width_final, height_final = test_image.size
  78. self.assertEqual(width_original, width_final)
  79. self.assertEqual(height_original, height_final)
  80. def test_draw_bounding_box_on_image_array(self):
  81. test_image = self.create_colorful_test_image()
  82. width_original = test_image.shape[0]
  83. height_original = test_image.shape[1]
  84. ymin = 0.25
  85. ymax = 0.75
  86. xmin = 0.4
  87. xmax = 0.6
  88. visualization_utils.draw_bounding_box_on_image_array(
  89. test_image, ymin, xmin, ymax, xmax)
  90. width_final = test_image.shape[0]
  91. height_final = test_image.shape[1]
  92. self.assertEqual(width_original, width_final)
  93. self.assertEqual(height_original, height_final)
  94. def test_draw_bounding_boxes_on_image(self):
  95. test_image = self.create_colorful_test_image()
  96. test_image = Image.fromarray(test_image)
  97. width_original, height_original = test_image.size
  98. boxes = np.array([[0.25, 0.75, 0.4, 0.6],
  99. [0.1, 0.1, 0.9, 0.9]])
  100. visualization_utils.draw_bounding_boxes_on_image(test_image, boxes)
  101. width_final, height_final = test_image.size
  102. self.assertEqual(width_original, width_final)
  103. self.assertEqual(height_original, height_final)
  104. def test_draw_bounding_boxes_on_image_array(self):
  105. test_image = self.create_colorful_test_image()
  106. width_original = test_image.shape[0]
  107. height_original = test_image.shape[1]
  108. boxes = np.array([[0.25, 0.75, 0.4, 0.6],
  109. [0.1, 0.1, 0.9, 0.9]])
  110. visualization_utils.draw_bounding_boxes_on_image_array(test_image, boxes)
  111. width_final = test_image.shape[0]
  112. height_final = test_image.shape[1]
  113. self.assertEqual(width_original, width_final)
  114. self.assertEqual(height_original, height_final)
  115. def test_draw_bounding_boxes_on_image_tensors(self):
  116. """Tests that bounding box utility produces reasonable results."""
  117. category_index = {1: {'id': 1, 'name': 'dog'}, 2: {'id': 2, 'name': 'cat'}}
  118. fname = os.path.join(_TESTDATA_PATH, 'image1.jpg')
  119. image_np = np.array(Image.open(fname))
  120. images_np = np.stack((image_np, image_np), axis=0)
  121. original_image_shape = [[636, 512], [636, 512]]
  122. with tf.Graph().as_default():
  123. images_tensor = tf.constant(value=images_np, dtype=tf.uint8)
  124. image_shape = tf.constant(original_image_shape, dtype=tf.int32)
  125. boxes = tf.constant([[[0.4, 0.25, 0.75, 0.75], [0.5, 0.3, 0.6, 0.9]],
  126. [[0.25, 0.25, 0.75, 0.75], [0.1, 0.3, 0.6, 1.0]]])
  127. classes = tf.constant([[1, 1], [1, 2]], dtype=tf.int64)
  128. scores = tf.constant([[0.8, 0.1], [0.6, 0.5]])
  129. images_with_boxes = (
  130. visualization_utils.draw_bounding_boxes_on_image_tensors(
  131. images_tensor,
  132. boxes,
  133. classes,
  134. scores,
  135. category_index,
  136. original_image_spatial_shape=image_shape,
  137. true_image_shape=image_shape,
  138. min_score_thresh=0.2))
  139. with self.test_session() as sess:
  140. sess.run(tf.global_variables_initializer())
  141. # Write output images for visualization.
  142. images_with_boxes_np = sess.run(images_with_boxes)
  143. self.assertEqual(images_np.shape[0], images_with_boxes_np.shape[0])
  144. self.assertEqual(images_np.shape[3], images_with_boxes_np.shape[3])
  145. self.assertEqual(
  146. tuple(original_image_shape[0]), images_with_boxes_np.shape[1:3])
  147. for i in range(images_with_boxes_np.shape[0]):
  148. img_name = 'image_' + str(i) + '.png'
  149. output_file = os.path.join(self.get_temp_dir(), img_name)
  150. logging.info('Writing output image %d to %s', i, output_file)
  151. image_pil = Image.fromarray(images_with_boxes_np[i, ...])
  152. image_pil.save(output_file)
  153. def test_draw_bounding_boxes_on_image_tensors_with_track_ids(self):
  154. """Tests that bounding box utility produces reasonable results."""
  155. category_index = {1: {'id': 1, 'name': 'dog'}, 2: {'id': 2, 'name': 'cat'}}
  156. fname = os.path.join(_TESTDATA_PATH, 'image1.jpg')
  157. image_np = np.array(Image.open(fname))
  158. images_np = np.stack((image_np, image_np), axis=0)
  159. original_image_shape = [[636, 512], [636, 512]]
  160. with tf.Graph().as_default():
  161. images_tensor = tf.constant(value=images_np, dtype=tf.uint8)
  162. image_shape = tf.constant(original_image_shape, dtype=tf.int32)
  163. boxes = tf.constant([[[0.4, 0.25, 0.75, 0.75],
  164. [0.5, 0.3, 0.7, 0.9],
  165. [0.7, 0.5, 0.8, 0.9]],
  166. [[0.41, 0.25, 0.75, 0.75],
  167. [0.51, 0.3, 0.7, 0.9],
  168. [0.75, 0.5, 0.8, 0.9]]])
  169. classes = tf.constant([[1, 1, 2], [1, 1, 2]], dtype=tf.int64)
  170. scores = tf.constant([[0.8, 0.5, 0.7], [0.6, 0.5, 0.8]])
  171. track_ids = tf.constant([[3, 9, 7], [3, 9, 144]], dtype=tf.int32)
  172. images_with_boxes = (
  173. visualization_utils.draw_bounding_boxes_on_image_tensors(
  174. images_tensor,
  175. boxes,
  176. classes,
  177. scores,
  178. category_index,
  179. original_image_spatial_shape=image_shape,
  180. true_image_shape=image_shape,
  181. track_ids=track_ids,
  182. min_score_thresh=0.2))
  183. with self.test_session() as sess:
  184. sess.run(tf.global_variables_initializer())
  185. # Write output images for visualization.
  186. images_with_boxes_np = sess.run(images_with_boxes)
  187. self.assertEqual(images_np.shape[0], images_with_boxes_np.shape[0])
  188. self.assertEqual(images_np.shape[3], images_with_boxes_np.shape[3])
  189. self.assertEqual(
  190. tuple(original_image_shape[0]), images_with_boxes_np.shape[1:3])
  191. for i in range(images_with_boxes_np.shape[0]):
  192. img_name = 'image_with_track_ids_' + str(i) + '.png'
  193. output_file = os.path.join(self.get_temp_dir(), img_name)
  194. logging.info('Writing output image %d to %s', i, output_file)
  195. image_pil = Image.fromarray(images_with_boxes_np[i, ...])
  196. image_pil.save(output_file)
  197. def test_draw_bounding_boxes_on_image_tensors_with_additional_channels(self):
  198. """Tests the case where input image tensor has more than 3 channels."""
  199. category_index = {1: {'id': 1, 'name': 'dog'}}
  200. image_np = self.create_test_image_with_five_channels()
  201. images_np = np.stack((image_np, image_np), axis=0)
  202. with tf.Graph().as_default():
  203. images_tensor = tf.constant(value=images_np, dtype=tf.uint8)
  204. boxes = tf.constant(0, dtype=tf.float32, shape=[2, 0, 4])
  205. classes = tf.constant(0, dtype=tf.int64, shape=[2, 0])
  206. scores = tf.constant(0, dtype=tf.float32, shape=[2, 0])
  207. images_with_boxes = (
  208. visualization_utils.draw_bounding_boxes_on_image_tensors(
  209. images_tensor,
  210. boxes,
  211. classes,
  212. scores,
  213. category_index,
  214. min_score_thresh=0.2))
  215. with self.test_session() as sess:
  216. sess.run(tf.global_variables_initializer())
  217. final_images_np = sess.run(images_with_boxes)
  218. self.assertEqual((2, 100, 200, 3), final_images_np.shape)
  219. def test_draw_bounding_boxes_on_image_tensors_grayscale(self):
  220. """Tests the case where input image tensor has one channel."""
  221. category_index = {1: {'id': 1, 'name': 'dog'}}
  222. image_np = self.create_test_grayscale_image()
  223. images_np = np.stack((image_np, image_np), axis=0)
  224. with tf.Graph().as_default():
  225. images_tensor = tf.constant(value=images_np, dtype=tf.uint8)
  226. image_shape = tf.constant([[100, 200], [100, 200]], dtype=tf.int32)
  227. boxes = tf.constant(0, dtype=tf.float32, shape=[2, 0, 4])
  228. classes = tf.constant(0, dtype=tf.int64, shape=[2, 0])
  229. scores = tf.constant(0, dtype=tf.float32, shape=[2, 0])
  230. images_with_boxes = (
  231. visualization_utils.draw_bounding_boxes_on_image_tensors(
  232. images_tensor,
  233. boxes,
  234. classes,
  235. scores,
  236. category_index,
  237. original_image_spatial_shape=image_shape,
  238. true_image_shape=image_shape,
  239. min_score_thresh=0.2))
  240. with self.test_session() as sess:
  241. sess.run(tf.global_variables_initializer())
  242. final_images_np = sess.run(images_with_boxes)
  243. self.assertEqual((2, 100, 200, 3), final_images_np.shape)
  244. def test_draw_keypoints_on_image(self):
  245. test_image = self.create_colorful_test_image()
  246. test_image = Image.fromarray(test_image)
  247. width_original, height_original = test_image.size
  248. keypoints = [[0.25, 0.75], [0.4, 0.6], [0.1, 0.1], [0.9, 0.9]]
  249. visualization_utils.draw_keypoints_on_image(test_image, keypoints)
  250. width_final, height_final = test_image.size
  251. self.assertEqual(width_original, width_final)
  252. self.assertEqual(height_original, height_final)
  253. def test_draw_keypoints_on_image_array(self):
  254. test_image = self.create_colorful_test_image()
  255. width_original = test_image.shape[0]
  256. height_original = test_image.shape[1]
  257. keypoints = [[0.25, 0.75], [0.4, 0.6], [0.1, 0.1], [0.9, 0.9]]
  258. visualization_utils.draw_keypoints_on_image_array(test_image, keypoints)
  259. width_final = test_image.shape[0]
  260. height_final = test_image.shape[1]
  261. self.assertEqual(width_original, width_final)
  262. self.assertEqual(height_original, height_final)
  263. def test_draw_mask_on_image_array(self):
  264. test_image = np.asarray([[[0, 0, 0], [0, 0, 0]],
  265. [[0, 0, 0], [0, 0, 0]]], dtype=np.uint8)
  266. mask = np.asarray([[0, 1],
  267. [1, 1]], dtype=np.uint8)
  268. expected_result = np.asarray([[[0, 0, 0], [0, 0, 127]],
  269. [[0, 0, 127], [0, 0, 127]]], dtype=np.uint8)
  270. visualization_utils.draw_mask_on_image_array(test_image, mask,
  271. color='Blue', alpha=.5)
  272. self.assertAllEqual(test_image, expected_result)
  273. def test_add_cdf_image_summary(self):
  274. values = [0.1, 0.2, 0.3, 0.4, 0.42, 0.44, 0.46, 0.48, 0.50]
  275. visualization_utils.add_cdf_image_summary(values, 'PositiveAnchorLoss')
  276. cdf_image_summary = tf.get_collection(key=tf.GraphKeys.SUMMARIES)[0]
  277. with self.test_session():
  278. cdf_image_summary.eval()
  279. def test_add_hist_image_summary(self):
  280. values = [0.1, 0.2, 0.3, 0.4, 0.42, 0.44, 0.46, 0.48, 0.50]
  281. bins = [0.01 * i for i in range(101)]
  282. visualization_utils.add_hist_image_summary(values, bins,
  283. 'ScoresDistribution')
  284. hist_image_summary = tf.get_collection(key=tf.GraphKeys.SUMMARIES)[0]
  285. with self.test_session():
  286. hist_image_summary.eval()
  287. def test_eval_metric_ops(self):
  288. category_index = {1: {'id': 1, 'name': 'dog'}, 2: {'id': 2, 'name': 'cat'}}
  289. max_examples_to_draw = 4
  290. metric_op_base = 'Detections_Left_Groundtruth_Right'
  291. eval_metric_ops = visualization_utils.VisualizeSingleFrameDetections(
  292. category_index,
  293. max_examples_to_draw=max_examples_to_draw,
  294. summary_name_prefix=metric_op_base)
  295. original_image = tf.placeholder(tf.uint8, [4, None, None, 3])
  296. original_image_spatial_shape = tf.placeholder(tf.int32, [4, 2])
  297. true_image_shape = tf.placeholder(tf.int32, [4, 3])
  298. detection_boxes = tf.random_uniform([4, 20, 4],
  299. minval=0.0,
  300. maxval=1.0,
  301. dtype=tf.float32)
  302. detection_classes = tf.random_uniform([4, 20],
  303. minval=1,
  304. maxval=3,
  305. dtype=tf.int64)
  306. detection_scores = tf.random_uniform([4, 20],
  307. minval=0.,
  308. maxval=1.,
  309. dtype=tf.float32)
  310. groundtruth_boxes = tf.random_uniform([4, 8, 4],
  311. minval=0.0,
  312. maxval=1.0,
  313. dtype=tf.float32)
  314. groundtruth_classes = tf.random_uniform([4, 8],
  315. minval=1,
  316. maxval=3,
  317. dtype=tf.int64)
  318. eval_dict = {
  319. fields.DetectionResultFields.detection_boxes:
  320. detection_boxes,
  321. fields.DetectionResultFields.detection_classes:
  322. detection_classes,
  323. fields.DetectionResultFields.detection_scores:
  324. detection_scores,
  325. fields.InputDataFields.original_image:
  326. original_image,
  327. fields.InputDataFields.original_image_spatial_shape: (
  328. original_image_spatial_shape),
  329. fields.InputDataFields.true_image_shape: (true_image_shape),
  330. fields.InputDataFields.groundtruth_boxes:
  331. groundtruth_boxes,
  332. fields.InputDataFields.groundtruth_classes:
  333. groundtruth_classes
  334. }
  335. metric_ops = eval_metric_ops.get_estimator_eval_metric_ops(eval_dict)
  336. _, update_op = metric_ops[metric_ops.keys()[0]]
  337. with self.test_session() as sess:
  338. sess.run(tf.global_variables_initializer())
  339. value_ops = {}
  340. for key, (value_op, _) in metric_ops.iteritems():
  341. value_ops[key] = value_op
  342. # First run enough update steps to surpass `max_examples_to_draw`.
  343. for i in range(max_examples_to_draw):
  344. # Use a unique image shape on each eval image.
  345. sess.run(
  346. update_op,
  347. feed_dict={
  348. original_image:
  349. np.random.randint(
  350. low=0,
  351. high=256,
  352. size=(4, 6 + i, 7 + i, 3),
  353. dtype=np.uint8),
  354. original_image_spatial_shape: [[6 + i, 7 + i], [6 + i, 7 + i],
  355. [6 + i, 7 + i], [6 + i, 7 + i]],
  356. true_image_shape: [[6 + i, 7 + i, 3], [6 + i, 7 + i, 3],
  357. [6 + i, 7 + i, 3], [6 + i, 7 + i, 3]]
  358. })
  359. value_ops_out = sess.run(value_ops)
  360. for key, value_op in value_ops_out.iteritems():
  361. self.assertNotEqual('', value_op)
  362. # Now run fewer update steps than `max_examples_to_draw`. A single value
  363. # op will be the empty string, since not enough image summaries can be
  364. # produced.
  365. for i in range(max_examples_to_draw - 1):
  366. # Use a unique image shape on each eval image.
  367. sess.run(
  368. update_op,
  369. feed_dict={
  370. original_image:
  371. np.random.randint(
  372. low=0,
  373. high=256,
  374. size=(4, 6 + i, 7 + i, 3),
  375. dtype=np.uint8),
  376. original_image_spatial_shape: [[6 + i, 7 + i], [6 + i, 7 + i],
  377. [6 + i, 7 + i], [6 + i, 7 + i]],
  378. true_image_shape: [[6 + i, 7 + i, 3], [6 + i, 7 + i, 3],
  379. [6 + i, 7 + i, 3], [6 + i, 7 + i, 3]]
  380. })
  381. value_ops_out = sess.run(value_ops)
  382. self.assertEqual(
  383. '',
  384. value_ops_out[metric_op_base + '/' + str(max_examples_to_draw - 1)])
  385. if __name__ == '__main__':
  386. tf.test.main()