def hough_peaks(hough_bins, thetas, minval=0, invalidate_distance=0): """Finds the peak lines in Hough space. Args: hough_bins: Hough bins returned by `hough_lines`. thetas: Angles; argument given to `hough_lines`. minval: Minimum vote count for a Hough bin to be considered. int or float. invalidate_distance: When selecting a line `(rho, theta)`, invalidate all lines with the same theta and `+- invalidate_distance` from `rho`. int32. Caveat: this should only be used if all theta values are similar. If thetas cover a wide range, this will invalidate lines that might not even intersect. Returns: Tensor of peak rho indices (int32). Tensor of peak theta values (float32). """ thetas = tf.convert_to_tensor(thetas) bin_score_dtype = thetas.dtype # floating point score derived from hough_bins minval = tf.convert_to_tensor(minval) if minval.dtype.is_floating: minval = tf.ceil(minval) invalidate_distance = tf.convert_to_tensor( invalidate_distance, dtype=tf.int32) # Choose the theta with the highest bin value for each rho. selected_theta_ind = tf.argmax(hough_bins, axis=0) # Take the Hough bin value for each rho and the selected theta. hough_bins = tf.gather_nd(hough_bins, tf.stack( [ tf.cast(selected_theta_ind, tf.int32), tf.range(tf.shape(hough_bins)[1]) ], axis=1)) # hough_bins are integers. Subtract a penalty (< 1) for lines that are not # horizontal or vertical, so that we break ties in favor of the more # horizontal or vertical line. infinitesimal = tf.constant(1e-10, bin_score_dtype) # Decrease minval so we don't discard bins that are penalized, if they # originally equalled minval. minval = tf.cast(minval, bin_score_dtype) - infinitesimal selected_thetas = tf.gather(thetas, selected_theta_ind) # min(|sin(t)|, |cos(t)|) is 0 for horizontal and vertical angles, and between # 0 and 1 otherwise. penalty = tf.multiply( tf.minimum( tf.abs(tf.sin(selected_thetas)), tf.abs(tf.cos(selected_thetas))), infinitesimal) bin_score = tf.cast(hough_bins, bin_score_dtype) - penalty # Find the peaks in the 1D hough_bins array. peak_rhos = segments.peaks( bin_score, minval=minval, invalidate_distance=invalidate_distance) # Get the actual angles for each selected peak. peak_thetas = tf.gather(thetas, tf.gather(selected_theta_ind, peak_rhos)) return peak_rhos, peak_thetas
def test_peaks_invalidate_distance(self): values = tf.constant([0, 0, 10, 0, 5, 3, 2, 1, 2, 3, 8, 8, 7, 8]) with self.test_session(): self.assertAllEqual( segments.peaks(values, invalidate_distance=0).eval(), [2, 4, 10, 13]) self.assertAllEqual( segments.peaks(values, invalidate_distance=1).eval(), [2, 4, 10, 13]) self.assertAllEqual( segments.peaks(values, invalidate_distance=2).eval(), [2, 10, 13]) self.assertAllEqual( segments.peaks(values, invalidate_distance=3).eval(), [2, 10]) self.assertAllEqual( segments.peaks(values, invalidate_distance=4).eval(), [2, 10]) self.assertAllEqual( segments.peaks(values, invalidate_distance=7).eval(), [2, 10]) self.assertAllEqual( segments.peaks(values, invalidate_distance=8).eval(), [2, 13]) self.assertAllEqual( segments.peaks(values, invalidate_distance=99).eval(), [2])
def test_peaks_one_segment(self): values = tf.constant([0, 0, 0, 0, 3, 0, 0, 0, 0]) with self.test_session(): self.assertAllEqual(segments.peaks(values).eval(), [4])
def test_peaks_array_filled_with_same_value(self): for value in (0, 42, 4.2): arr = tf.fill([100], value) with self.test_session(): self.assertEmpty(segments.peaks(arr).eval())
def test_peaks_empty(self): with self.test_session(): self.assertAllEqual(segments.peaks([]).eval(), [])
def test_peaks(self): values = tf.constant([5, 3, 1, 1, 0, 1, 2, 3, 3, 3, 2, 3, 4, 1, 2]) with self.test_session(): self.assertAllEqual(segments.peaks(values).eval(), [0, 8, 12, 14]) self.assertAllEqual(segments.peaks(values, minval=3).eval(), [0, 8, 12])
def _estimate_staffline_distance(columns, lengths): """Estimates the staffline distances of a music score. Args: columns: 1D array. The column indices of each vertical run. lengths: 1D array. The length of each consecutive vertical run. Returns: A 1D tensor of possible staffline distances in the image. """ with tf.name_scope('estimate_staffline_distance'): run_pair_lengths = lengths[:-1] + lengths[1:] keep_pair = tf.equal(columns[:-1], columns[1:]) staffline_distance_histogram = tf.bincount( tf.boolean_mask(run_pair_lengths, keep_pair), # minlength required to avoid errors on a fully white image. minlength=_MAX_STAFFLINE_DISTANCE_THICKNESS_VALUE, maxlength=_MAX_STAFFLINE_DISTANCE_THICKNESS_VALUE) peaks = segments.peaks( staffline_distance_histogram, minval=_MIN_STAFFLINE_DISTANCE_SCORE, invalidate_distance=_STAFFLINE_DISTANCE_INVALIDATE_DISTANCE) def do_filter_peaks(): """Process the peaks if they are non-empty. Returns: The filtered peaks. Peaks below the cutoff when compared to the highest peak are removed. If the peaks are invalid, then an empty list is returned. """ histogram_size = tf.shape(staffline_distance_histogram)[0] peak_values = tf.to_float( tf.gather(staffline_distance_histogram, peaks)) max_value = tf.reduce_max(peak_values) allowed_peaks = tf.greater_equal( peak_values, max_value * tf.constant(_PEAK_CUTOFF)) # Check if there are too many detected staffline distances, and we should # return an empty list. allowed_peaks &= tf.less_equal( tf.reduce_sum(tf.to_int32(allowed_peaks)), _MAX_ALLOWED_UNIQUE_STAFFLINE_DISTANCES) # Check if any values sufficiently far away from the peaks are too high. # This means the peaks are not sharp enough and we should return an empty # list. far_from_peak = tf.greater( tf.reduce_min( tf.abs(tf.range(histogram_size)[None, :] - peaks[:, None]), axis=0), _STAFFLINE_DISTANCE_INVALIDATE_DISTANCE) allowed_peaks &= tf.less( tf.to_float( tf.reduce_max( tf.boolean_mask(staffline_distance_histogram, far_from_peak))), max_value * tf.constant(_PEAK_CUTOFF)) return tf.boolean_mask(peaks, allowed_peaks) return tf.cond(tf.greater(tf.shape(peaks)[0], 0), do_filter_peaks, lambda: tf.identity(peaks))