def scale_channel(im, c): """Scale the data in the channel to implement equalize.""" im = tf.cast(im[:, :, c], tf.int32) # Compute the histogram of the image channel. histo = tf.histogram_fixed_width(im, [0, 255], nbins=256) # For the purposes of computing the step, filter out the nonzeros. nonzero = tf.where(tf.not_equal(histo, 0)) nonzero_histo = tf.reshape(tf.gather(histo, nonzero), [-1]) step = (tf.reduce_sum(nonzero_histo) - nonzero_histo[-1]) // 255 def build_lut(histo, step): # Compute the cumulative sum, shifting by step // 2 # and then normalization by step. lut = (tf.cumsum(histo) + (step // 2)) // step # Shift lut, prepending with 0. lut = tf.concat([[0], lut[:-1]], 0) # Clip the counts to be in range. This is done # in the C code for image.point. return tf.clip_by_value(lut, 0, 255) # If step is zero, return the original image. Otherwise, build # lut from the full histogram and step and then index from it. result = tf.cond(tf.equal(step, 0), lambda: im, lambda: tf.gather(build_lut(histo, step), im)) return tf.cast(result, tf.uint8)
def contrast(image: tf.Tensor, factor: float) -> tf.Tensor: """Equivalent of PIL Contrast.""" degenerate = tf.image.rgb_to_grayscale(image) # Cast before calling tf.histogram. degenerate = tf.cast(degenerate, tf.int32) # Compute the grayscale histogram, then compute the mean pixel value, # and create a constant image size of that value. Use that as the # blending degenerate target of the original image. hist = tf.histogram_fixed_width(degenerate, [0, 255], nbins=256) mean = tf.reduce_sum(tf.cast(hist, tf.float32)) / 256.0 degenerate = tf.ones_like(degenerate, dtype=tf.float32) * mean degenerate = tf.clip_by_value(degenerate, 0.0, 255.0) degenerate = tf.image.grayscale_to_rgb(tf.cast(degenerate, tf.uint8)) return blend(degenerate, image, factor)
def histogram(self, x, value_range=None, nbins=None, name=None): """Return histogram of values. Given the tensor `values`, this operation returns a rank 1 histogram counting the number of entries in `values` that fell into every bin. The bins are equal width and determined by the arguments `value_range` and `nbins`. Args: x: 1D numeric `Tensor` of items to count. value_range: Shape [2] `Tensor`. `new_values <= value_range[0]` will be mapped to `hist[0]`, `values >= value_range[1]` will be mapped to `hist[-1]`. Must be same dtype as `x`. nbins: Scalar `int32 Tensor`. Number of histogram bins. name: Python `str` name prefixed to Ops created by this class. Returns: counts: 1D `Tensor` of counts, i.e., `counts[i] = sum{ edges[i-1] <= values[j] < edges[i] : j }`. edges: 1D `Tensor` characterizing intervals used for counting. """ with tf.name_scope(name or 'histogram'): x = tf.convert_to_tensor(value=x, name='x') if value_range is None: value_range = [ tf.reduce_min(input_tensor=x), 1 + tf.reduce_max(input_tensor=x) ] value_range = tf.convert_to_tensor(value=value_range, name='value_range') lo = value_range[0] hi = value_range[1] if nbins is None: nbins = tf.cast(hi - lo, dtype=tf.int32) delta = (hi - lo) / tf.cast( nbins, dtype=dtype_util.base_dtype(value_range.dtype)) edges = tf.range(start=lo, limit=hi, delta=delta, dtype=dtype_util.base_dtype(x.dtype)) counts = tf.histogram_fixed_width(x, value_range=value_range, nbins=nbins) return counts, edges