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 python distort_image_ops.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21import time 22 23import numpy as np 24from six.moves import xrange # pylint: disable=redefined-builtin 25 26from tensorflow.contrib.image.python.ops import distort_image_ops 27from tensorflow.core.protobuf import config_pb2 28from tensorflow.python.client import session 29from tensorflow.python.framework import constant_op 30from tensorflow.python.framework import dtypes 31from tensorflow.python.framework import ops 32from tensorflow.python.framework import test_util 33from tensorflow.python.ops import control_flow_ops 34from tensorflow.python.ops import random_ops 35from tensorflow.python.ops import variables 36from tensorflow.python.platform import googletest 37from tensorflow.python.platform import test 38 39 40# TODO(huangyp): also measure the differences between AdjustHsvInYiq and 41# AdjustHsv in core. 42class AdjustHueInYiqTest(test_util.TensorFlowTestCase): 43 44 def _adjust_hue_in_yiq_np(self, x_np, delta_h): 45 """Rotate hue in YIQ space. 46 47 Mathematically we first convert rgb color to yiq space, rotate the hue 48 degrees, and then convert back to rgb. 49 50 Args: 51 x_np: input x with last dimension = 3. 52 delta_h: degree of hue rotation, in radians. 53 54 Returns: 55 Adjusted y with the same shape as x_np. 56 """ 57 self.assertEqual(x_np.shape[-1], 3) 58 x_v = x_np.reshape([-1, 3]) 59 y_v = np.ndarray(x_v.shape, dtype=x_v.dtype) 60 u = np.cos(delta_h) 61 w = np.sin(delta_h) 62 # Projection matrix from RGB to YIQ. Numbers from wikipedia 63 # https://en.wikipedia.org/wiki/YIQ 64 tyiq = np.array([[0.299, 0.587, 0.114], [0.596, -0.274, -0.322], 65 [0.211, -0.523, 0.312]]) 66 y_v = np.dot(x_v, tyiq.T) 67 # Hue rotation matrix in YIQ space. 68 hue_rotation = np.array([[1.0, 0.0, 0.0], [0.0, u, -w], [0.0, w, u]]) 69 y_v = np.dot(y_v, hue_rotation.T) 70 # Projecting back to RGB space. 71 y_v = np.dot(y_v, np.linalg.inv(tyiq).T) 72 return y_v.reshape(x_np.shape) 73 74 def _adjust_hue_in_yiq_tf(self, x_np, delta_h): 75 with self.test_session(use_gpu=True): 76 x = constant_op.constant(x_np) 77 y = distort_image_ops.adjust_hsv_in_yiq(x, delta_h, 1, 1) 78 y_tf = y.eval() 79 return y_tf 80 81 def test_adjust_random_hue_in_yiq(self): 82 x_shapes = [ 83 [2, 2, 3], 84 [4, 2, 3], 85 [2, 4, 3], 86 [2, 5, 3], 87 [1000, 1, 3], 88 ] 89 test_styles = [ 90 'all_random', 91 'rg_same', 92 'rb_same', 93 'gb_same', 94 'rgb_same', 95 ] 96 for x_shape in x_shapes: 97 for test_style in test_styles: 98 x_np = np.random.rand(*x_shape) * 255. 99 delta_h = (np.random.rand() * 2.0 - 1.0) * np.pi 100 if test_style == 'all_random': 101 pass 102 elif test_style == 'rg_same': 103 x_np[..., 1] = x_np[..., 0] 104 elif test_style == 'rb_same': 105 x_np[..., 2] = x_np[..., 0] 106 elif test_style == 'gb_same': 107 x_np[..., 2] = x_np[..., 1] 108 elif test_style == 'rgb_same': 109 x_np[..., 1] = x_np[..., 0] 110 x_np[..., 2] = x_np[..., 0] 111 else: 112 raise AssertionError('Invalid test style: %s' % (test_style)) 113 y_np = self._adjust_hue_in_yiq_np(x_np, delta_h) 114 y_tf = self._adjust_hue_in_yiq_tf(x_np, delta_h) 115 self.assertAllClose(y_tf, y_np, rtol=2e-4, atol=1e-4) 116 117 def test_invalid_shapes(self): 118 x_np = np.random.rand(2, 3) * 255. 119 delta_h = np.random.rand() * 2.0 - 1.0 120 with self.assertRaisesRegexp(ValueError, 'Shape must be at least rank 3'): 121 self._adjust_hue_in_yiq_tf(x_np, delta_h) 122 x_np = np.random.rand(4, 2, 4) * 255. 123 delta_h = np.random.rand() * 2.0 - 1.0 124 with self.assertRaisesOpError('input must have 3 channels but instead has ' 125 '4 channels'): 126 self._adjust_hue_in_yiq_tf(x_np, delta_h) 127 128 129class AdjustValueInYiqTest(test_util.TensorFlowTestCase): 130 131 def _adjust_value_in_yiq_np(self, x_np, scale): 132 return x_np * scale 133 134 def _adjust_value_in_yiq_tf(self, x_np, scale): 135 with self.test_session(use_gpu=True): 136 x = constant_op.constant(x_np) 137 y = distort_image_ops.adjust_hsv_in_yiq(x, 0, 1, scale) 138 y_tf = y.eval() 139 return y_tf 140 141 def test_adjust_random_value_in_yiq(self): 142 x_shapes = [ 143 [2, 2, 3], 144 [4, 2, 3], 145 [2, 4, 3], 146 [2, 5, 3], 147 [1000, 1, 3], 148 ] 149 test_styles = [ 150 'all_random', 151 'rg_same', 152 'rb_same', 153 'gb_same', 154 'rgb_same', 155 ] 156 for x_shape in x_shapes: 157 for test_style in test_styles: 158 x_np = np.random.rand(*x_shape) * 255. 159 scale = np.random.rand() * 2.0 - 1.0 160 if test_style == 'all_random': 161 pass 162 elif test_style == 'rg_same': 163 x_np[..., 1] = x_np[..., 0] 164 elif test_style == 'rb_same': 165 x_np[..., 2] = x_np[..., 0] 166 elif test_style == 'gb_same': 167 x_np[..., 2] = x_np[..., 1] 168 elif test_style == 'rgb_same': 169 x_np[..., 1] = x_np[..., 0] 170 x_np[..., 2] = x_np[..., 0] 171 else: 172 raise AssertionError('Invalid test style: %s' % (test_style)) 173 y_np = self._adjust_value_in_yiq_np(x_np, scale) 174 y_tf = self._adjust_value_in_yiq_tf(x_np, scale) 175 self.assertAllClose(y_tf, y_np, rtol=2e-4, atol=1e-4) 176 177 def test_invalid_shapes(self): 178 x_np = np.random.rand(2, 3) * 255. 179 scale = np.random.rand() * 2.0 - 1.0 180 with self.assertRaisesRegexp(ValueError, 'Shape must be at least rank 3'): 181 self._adjust_value_in_yiq_tf(x_np, scale) 182 x_np = np.random.rand(4, 2, 4) * 255. 183 scale = np.random.rand() * 2.0 - 1.0 184 with self.assertRaisesOpError('input must have 3 channels but instead has ' 185 '4 channels'): 186 self._adjust_value_in_yiq_tf(x_np, scale) 187 188 189class AdjustSaturationInYiqTest(test_util.TensorFlowTestCase): 190 191 def _adjust_saturation_in_yiq_tf(self, x_np, scale): 192 with self.test_session(use_gpu=True): 193 x = constant_op.constant(x_np) 194 y = distort_image_ops.adjust_hsv_in_yiq(x, 0, scale, 1) 195 y_tf = y.eval() 196 return y_tf 197 198 def _adjust_saturation_in_yiq_np(self, x_np, scale): 199 """Adjust saturation using linear interpolation.""" 200 rgb_weights = np.array([0.299, 0.587, 0.114]) 201 gray = np.sum(x_np * rgb_weights, axis=-1, keepdims=True) 202 y_v = x_np * scale + gray * (1 - scale) 203 return y_v 204 205 def test_adjust_random_saturation_in_yiq(self): 206 x_shapes = [ 207 [2, 2, 3], 208 [4, 2, 3], 209 [2, 4, 3], 210 [2, 5, 3], 211 [1000, 1, 3], 212 ] 213 test_styles = [ 214 'all_random', 215 'rg_same', 216 'rb_same', 217 'gb_same', 218 'rgb_same', 219 ] 220 with self.cached_session(): 221 for x_shape in x_shapes: 222 for test_style in test_styles: 223 x_np = np.random.rand(*x_shape) * 255. 224 scale = np.random.rand() * 2.0 - 1.0 225 if test_style == 'all_random': 226 pass 227 elif test_style == 'rg_same': 228 x_np[..., 1] = x_np[..., 0] 229 elif test_style == 'rb_same': 230 x_np[..., 2] = x_np[..., 0] 231 elif test_style == 'gb_same': 232 x_np[..., 2] = x_np[..., 1] 233 elif test_style == 'rgb_same': 234 x_np[..., 1] = x_np[..., 0] 235 x_np[..., 2] = x_np[..., 0] 236 else: 237 raise AssertionError('Invalid test style: %s' % (test_style)) 238 y_baseline = self._adjust_saturation_in_yiq_np(x_np, scale) 239 y_tf = self._adjust_saturation_in_yiq_tf(x_np, scale) 240 self.assertAllClose(y_tf, y_baseline, rtol=2e-4, atol=1e-4) 241 242 def test_invalid_shapes(self): 243 x_np = np.random.rand(2, 3) * 255. 244 scale = np.random.rand() * 2.0 - 1.0 245 with self.assertRaisesRegexp(ValueError, 'Shape must be at least rank 3'): 246 self._adjust_saturation_in_yiq_tf(x_np, scale) 247 x_np = np.random.rand(4, 2, 4) * 255. 248 scale = np.random.rand() * 2.0 - 1.0 249 with self.assertRaisesOpError('input must have 3 channels but instead has ' 250 '4 channels'): 251 self._adjust_saturation_in_yiq_tf(x_np, scale) 252 253 254class AdjustHueInYiqBenchmark(test.Benchmark): 255 256 def _benchmark_adjust_hue_in_yiq(self, device, cpu_count): 257 image_shape = [299, 299, 3] 258 warmup_rounds = 100 259 benchmark_rounds = 1000 260 config = config_pb2.ConfigProto() 261 if cpu_count is not None: 262 config.inter_op_parallelism_threads = 1 263 config.intra_op_parallelism_threads = cpu_count 264 with session.Session('', graph=ops.Graph(), config=config) as sess: 265 with ops.device(device): 266 inputs = variables.Variable( 267 random_ops.random_uniform(image_shape, dtype=dtypes.float32) * 255, 268 trainable=False, 269 dtype=dtypes.float32) 270 delta = constant_op.constant(0.1, dtype=dtypes.float32) 271 outputs = distort_image_ops.adjust_hsv_in_yiq(inputs, delta, 1, 1) 272 run_op = control_flow_ops.group(outputs) 273 sess.run(variables.global_variables_initializer()) 274 for i in xrange(warmup_rounds + benchmark_rounds): 275 if i == warmup_rounds: 276 start = time.time() 277 sess.run(run_op) 278 end = time.time() 279 step_time = (end - start) / benchmark_rounds 280 tag = device + '_%s' % (cpu_count if cpu_count is not None else 'all') 281 print('benchmarkadjust_hue_in_yiq_299_299_3_%s step_time: %.2f us' % 282 (tag, step_time * 1e6)) 283 self.report_benchmark( 284 name='benchmarkadjust_hue_in_yiq_299_299_3_%s' % (tag), 285 iters=benchmark_rounds, 286 wall_time=step_time) 287 288 def benchmark_adjust_hue_in_yiqCpu1(self): 289 self._benchmark_adjust_hue_in_yiq('/cpu:0', 1) 290 291 def benchmark_adjust_hue_in_yiqCpuAll(self): 292 self._benchmark_adjust_hue_in_yiq('/cpu:0', None) 293 294 def benchmark_adjust_hue_in_yiq_gpu_all(self): 295 self._benchmark_adjust_hue_in_yiq(test.gpu_device_name(), None) 296 297 298class AdjustSaturationInYiqBenchmark(test.Benchmark): 299 300 def _benchmark_adjust_saturation_in_yiq(self, device, cpu_count): 301 image_shape = [299, 299, 3] 302 warmup_rounds = 100 303 benchmark_rounds = 1000 304 config = config_pb2.ConfigProto() 305 if cpu_count is not None: 306 config.inter_op_parallelism_threads = 1 307 config.intra_op_parallelism_threads = cpu_count 308 with session.Session('', graph=ops.Graph(), config=config) as sess: 309 with ops.device(device): 310 inputs = variables.Variable( 311 random_ops.random_uniform(image_shape, dtype=dtypes.float32) * 255, 312 trainable=False, 313 dtype=dtypes.float32) 314 scale = constant_op.constant(0.1, dtype=dtypes.float32) 315 outputs = distort_image_ops.adjust_hsv_in_yiq(inputs, 0, scale, 1) 316 run_op = control_flow_ops.group(outputs) 317 sess.run(variables.global_variables_initializer()) 318 for _ in xrange(warmup_rounds): 319 sess.run(run_op) 320 start = time.time() 321 for _ in xrange(benchmark_rounds): 322 sess.run(run_op) 323 end = time.time() 324 step_time = (end - start) / benchmark_rounds 325 tag = '%s' % (cpu_count) if cpu_count is not None else '_all' 326 print('benchmarkAdjustSaturationInYiq_299_299_3_cpu%s step_time: %.2f us' % 327 (tag, step_time * 1e6)) 328 self.report_benchmark( 329 name='benchmarkAdjustSaturationInYiq_299_299_3_cpu%s' % (tag), 330 iters=benchmark_rounds, 331 wall_time=step_time) 332 333 def benchmark_adjust_saturation_in_yiq_cpu1(self): 334 self._benchmark_adjust_saturation_in_yiq('/cpu:0', 1) 335 336 def benchmark_adjust_saturation_in_yiq_cpu_all(self): 337 self._benchmark_adjust_saturation_in_yiq('/cpu:0', None) 338 339 def benchmark_adjust_saturation_in_yiq_gpu_all(self): 340 self._benchmark_adjust_saturation_in_yiq(test.gpu_device_name(), None) 341 342 343if __name__ == '__main__': 344 googletest.main() 345