Skip to content

Commit

Permalink
Implemented thresholding with different types for binarization in the…
Browse files Browse the repository at this point in the history
… imageproc library. Added enum `ThresholdType` to represent the threshold operation corresponding to OpenCV threshold types. Updated functions `threshold` and `threshold_mut` to take `ThresholdType` as a parameter for specifying the type of thresholding operation. Added tests and benchmarks for the new functionality.
  • Loading branch information
Dr.Guo committed Apr 28, 2024
1 parent b6d08eb commit 6a100b4
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 45 deletions.
126 changes: 107 additions & 19 deletions src/contrast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,21 @@ pub fn otsu_level(image: &GrayImage) -> u8 {
best_threshold
}

/// type of the threshold operation, corresponding to opencv threshold type
/// https://docs.opencv.org/4.x/d7/d1b/group__imgproc__misc.html#gaa9e58d2860d4afa658ef70a9b1115576
pub enum ThresholdType {
/// `dst(x,y) = maxval if src(x,y) > thresh else 0`
ThreshBinary,
/// `dst(x,y) = 0 if src(x,y) > thresh else maxval`
ThreshBinaryInv,
/// `dst(x,y) = thresh if src(x,y) > thresh else src(x,y)`
ThreshTrunc,
/// `dst(x,y) = src(x,y) if src(x,y) > thresh else 0`
ThreshToZero,
/// `dst(x,y) = 0 if src(x,y) > thresh else src(x,y)`
ThreshToZeroInv,
}

/// Returns a binarized image from an input 8bpp grayscale image
/// obtained by applying the given threshold. Pixels with intensity
/// equal to the threshold are assigned to the background.
Expand All @@ -110,21 +125,42 @@ pub fn otsu_level(image: &GrayImage) -> u8 {
/// # extern crate imageproc;
/// # fn main() {
/// use imageproc::contrast::threshold;
/// use imageproc::contrast::ThresholdType;
///
/// let image = gray_image!(
/// 10, 80, 20;
/// 50, 90, 70);
///
/// let thresholded = gray_image!(
/// let binary_threshold = gray_image!(
/// 0, 255, 0;
/// 0, 255, 255);
/// let binary_threshold_inv = gray_image!(
/// 255, 0, 255;
/// 255, 0, 0);
/// let trunc_threshold = gray_image!(
/// 10, 50, 20;
/// 50, 50, 50);
/// let to_zero_threshold = gray_image!(
/// 10, 0, 20;
/// 50, 0, 0);
/// let to_zero_inv_threshold = gray_image!(
/// 0, 80, 0;
/// 0, 90, 70);
///
/// assert_pixels_eq!(threshold(&image, 50), thresholded);
/// let binary_thresholded = threshold(&image, 50, ThresholdType::ThreshBinary);
/// let binary_thresholded_inv = threshold(&image, 50, ThresholdType::ThreshBinaryInv);
/// let trunc_thresholded = threshold(&image, 50, ThresholdType::ThreshTrunc);
/// let to_zero_thresholded = threshold(&image, 50, ThresholdType::ThreshToZero);
/// let to_zero_inv_thresholded = threshold(&image, 50, ThresholdType::ThreshToZeroInv);
/// assert_pixels_eq!(binary_threshold, binary_thresholded);
/// assert_pixels_eq!(binary_threshold_inv, binary_thresholded_inv);
/// assert_pixels_eq!(trunc_threshold, trunc_thresholded);
/// assert_pixels_eq!(to_zero_threshold, to_zero_thresholded);
/// assert_pixels_eq!(to_zero_inv_threshold, to_zero_inv_thresholded);
/// # }
/// ```
pub fn threshold(image: &GrayImage, thresh: u8) -> GrayImage {
pub fn threshold(image: &GrayImage, thresh: u8, threshold_type: ThresholdType) -> GrayImage {
let mut out = image.clone();
threshold_mut(&mut out, thresh);
threshold_mut(&mut out, thresh, threshold_type);
out
}

Expand All @@ -139,23 +175,75 @@ pub fn threshold(image: &GrayImage, thresh: u8) -> GrayImage {
/// # extern crate imageproc;
/// # fn main() {
/// use imageproc::contrast::threshold_mut;
/// use imageproc::contrast::ThresholdType;
///
/// let mut image = gray_image!(
/// 10, 80, 20;
/// 50, 90, 70);
///
/// let thresholded = gray_image!(
/// let binary_threshold = gray_image!(
/// 0, 255, 0;
/// 0, 255, 255);
/// let binary_threshold_inv = gray_image!(
/// 255, 0, 255;
/// 255, 0, 0);
/// let trunc_threshold = gray_image!(
/// 10, 50, 20;
/// 50, 50, 50);
/// let to_zero_threshold = gray_image!(
/// 10, 0, 20;
/// 50, 0, 0);
/// let to_zero_inv_threshold = gray_image!(
/// 0, 80, 0;
/// 0, 90, 70);
///
/// let mut binary_thresholded=image.clone();
/// let mut binary_thresholded_inv=image.clone();
/// let mut trunc_thresholded=image.clone();
/// let mut to_zero_thresholded=image.clone();
/// let mut to_zero_inv_thresholded=image.clone();
/// threshold_mut(&mut binary_thresholded, 50, ThresholdType::ThreshBinary);
/// threshold_mut(&mut binary_thresholded_inv, 50, ThresholdType::ThreshBinaryInv);
/// threshold_mut(&mut trunc_thresholded, 50, ThresholdType::ThreshTrunc);
/// threshold_mut(&mut to_zero_thresholded, 50, ThresholdType::ThreshToZero);
/// threshold_mut(&mut to_zero_inv_thresholded, 50, ThresholdType::ThreshToZeroInv);
///
/// threshold_mut(&mut image, 50);
///
/// assert_pixels_eq!(image, thresholded);
///
/// assert_pixels_eq!(binary_threshold, binary_thresholded);
/// assert_pixels_eq!(binary_threshold_inv, binary_thresholded_inv);
/// assert_pixels_eq!(trunc_threshold, trunc_thresholded);
/// assert_pixels_eq!(to_zero_threshold, to_zero_thresholded);
/// assert_pixels_eq!(to_zero_inv_threshold, to_zero_inv_thresholded);
/// # }
/// ```
pub fn threshold_mut(image: &mut GrayImage, thresh: u8) {
for p in image.iter_mut() {
*p = if *p <= thresh { 0 } else { 255 };
pub fn threshold_mut(image: &mut GrayImage, thresh: u8, threshold_type: ThresholdType) {
match threshold_type {
ThresholdType::ThreshBinary => {
for p in image.iter_mut() {
*p = if *p > thresh { 255 } else { 0 };
}
}
ThresholdType::ThreshBinaryInv => {
for p in image.iter_mut() {
*p = if *p > thresh { 0 } else { 255 };
}
}
ThresholdType::ThreshTrunc => {
for p in image.iter_mut() {
*p = if *p > thresh { thresh } else { *p };
}
}
ThresholdType::ThreshToZero => {
for p in image.iter_mut() {
*p = if *p > thresh { 0 } else { *p };
}
}
ThresholdType::ThreshToZeroInv => {
for p in image.iter_mut() {
*p = if *p > thresh { *p } else { 0 };
}
}
}
}

Expand All @@ -166,9 +254,9 @@ pub fn equalize_histogram_mut(image: &mut GrayImage) {
let total = hist[255] as f32;

#[cfg(feature = "rayon")]
let iter = image.par_iter_mut();
let iter = image.par_iter_mut();
#[cfg(not(feature = "rayon"))]
let iter = image.iter_mut();
let iter = image.iter_mut();

iter.for_each(|p| {
// JUSTIFICATION
Expand Down Expand Up @@ -477,21 +565,21 @@ mod tests {
#[test]
fn test_threshold_0_image_0() {
let expected = 0u8;
let actual = threshold(&constant_image(10, 10, 0), 0);
let actual = threshold(&constant_image(10, 10, 0), 0, ThresholdType::ThreshBinary);
assert_pixels_eq!(actual, constant_image(10, 10, expected));
}

#[test]
fn test_threshold_0_image_1() {
let expected = 255u8;
let actual = threshold(&constant_image(10, 10, 1), 0);
let actual = threshold(&constant_image(10, 10, 1), 0, ThresholdType::ThreshBinary);
assert_pixels_eq!(actual, constant_image(10, 10, expected));
}

#[test]
fn test_threshold_threshold_255_image_255() {
let expected = 0u8;
let actual = threshold(&constant_image(10, 10, 255), 255);
let actual = threshold(&constant_image(10, 10, 255), 255, ThresholdType::ThreshBinary);
assert_pixels_eq!(actual, constant_image(10, 10, expected));
}

Expand All @@ -504,7 +592,7 @@ mod tests {

let expected = GrayImage::from_raw(26, 1, expected_contents).unwrap();

let actual = threshold(&original, 125u8);
let actual = threshold(&original, 125u8, ThresholdType::ThreshBinary);
assert_pixels_eq!(expected, actual);
}

Expand All @@ -530,7 +618,7 @@ mod tests {
fn bench_threshold(b: &mut Bencher) {
let image = gray_bench_image(500, 500);
b.iter(|| {
let thresholded = threshold(&image, 125);
let thresholded = threshold(&image, 125, ThresholdType::ThreshBinary);
black_box(thresholded);
});
}
Expand All @@ -539,7 +627,7 @@ mod tests {
fn bench_threshold_mut(b: &mut Bencher) {
let mut image = gray_bench_image(500, 500);
b.iter(|| {
threshold_mut(&mut image, 125);
threshold_mut(&mut image, 125, ThresholdType::ThreshBinary);
black_box(());
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/region_labelling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ pub enum Connectivity {
///
/// // If this behaviour is not what you want then you can first
/// // threshold the input image.
/// use imageproc::contrast::threshold;
/// use imageproc::contrast::{threshold, ThresholdType};
///
/// // Pixels equal to the threshold are treated as background.
/// let thresholded = threshold(&image, 0);
/// let thresholded = threshold(&image, 0,ThresholdType::ThreshBinary);
///
/// let thresholded_components_four = gray_image!(type: u32,
/// 1, 0, 2, 2;
Expand Down
49 changes: 25 additions & 24 deletions tests/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use imageproc::{
utils::load_image_or_panic,
};
use std::{env, f32, path::Path};
use imageproc::contrast::ThresholdType;

/// The directory containing the input images used in regression tests.
const INPUT_DIR: &str = "./tests/data";
Expand Down Expand Up @@ -63,10 +64,10 @@ impl FromDynamic for RgbaImage {

/// Loads an input image, applies a function to it and checks that the result matches a 'truth' image.
fn compare_to_truth<P, F>(input_file_name: &str, truth_file_name: &str, op: F)
where
P: Pixel<Subpixel = u8> + PixelWithColorType,
ImageBuffer<P, Vec<u8>>: FromDynamic,
F: Fn(&ImageBuffer<P, Vec<u8>>) -> ImageBuffer<P, Vec<u8>>,
where
P: Pixel<Subpixel=u8> + PixelWithColorType,
ImageBuffer<P, Vec<u8>>: FromDynamic,
F: Fn(&ImageBuffer<P, Vec<u8>>) -> ImageBuffer<P, Vec<u8>>,
{
compare_to_truth_with_tolerance(input_file_name, truth_file_name, op, 0u8);
}
Expand All @@ -79,7 +80,7 @@ fn compare_to_truth_with_tolerance<P, F>(
op: F,
tol: u8,
) where
P: Pixel<Subpixel = u8> + PixelWithColorType,
P: Pixel<Subpixel=u8> + PixelWithColorType,
ImageBuffer<P, Vec<u8>>: FromDynamic,
F: Fn(&ImageBuffer<P, Vec<u8>>) -> ImageBuffer<P, Vec<u8>>,
{
Expand All @@ -92,9 +93,9 @@ fn compare_to_truth_with_tolerance<P, F>(

/// Checks that an image matches a 'truth' image.
fn compare_to_truth_image<P>(actual: &ImageBuffer<P, Vec<u8>>, truth_file_name: &str)
where
P: Pixel<Subpixel = u8> + PixelWithColorType,
ImageBuffer<P, Vec<u8>>: FromDynamic,
where
P: Pixel<Subpixel=u8> + PixelWithColorType,
ImageBuffer<P, Vec<u8>>: FromDynamic,
{
compare_to_truth_image_with_tolerance(actual, truth_file_name, 0u8);
}
Expand All @@ -105,7 +106,7 @@ fn compare_to_truth_image_with_tolerance<P>(
truth_file_name: &str,
tol: u8,
) where
P: Pixel<Subpixel = u8> + PixelWithColorType,
P: Pixel<Subpixel=u8> + PixelWithColorType,
ImageBuffer<P, Vec<u8>>: FromDynamic,
{
if should_regenerate() {
Expand Down Expand Up @@ -237,12 +238,12 @@ fn test_affine_nearest_rgb() {
fn affine_nearest(image: &RgbImage) -> RgbImage {
let root_two_inv = 1f32 / 2f32.sqrt() * 2.0;
#[rustfmt::skip]
let hom = Projection::from_matrix([
root_two_inv, -root_two_inv, 50.0,
root_two_inv, root_two_inv, -70.0,
0.0, 0.0, 1.0,
let hom = Projection::from_matrix([
root_two_inv, -root_two_inv, 50.0,
root_two_inv, root_two_inv, -70.0,
0.0, 0.0, 1.0,
])
.unwrap();
.unwrap();
warp(image, &hom, Interpolation::Nearest, Rgb::black())
}
compare_to_truth(
Expand All @@ -257,12 +258,12 @@ fn test_affine_bilinear_rgb() {
fn affine_bilinear(image: &RgbImage) -> RgbImage {
let root_two_inv = 1f32 / 2f32.sqrt() * 2.0;
#[rustfmt::skip]
let hom = Projection::from_matrix([
root_two_inv, -root_two_inv, 50.0,
root_two_inv, root_two_inv, -70.0,
0.0, 0.0, 1.0,
let hom = Projection::from_matrix([
root_two_inv, -root_two_inv, 50.0,
root_two_inv, root_two_inv, -70.0,
0.0, 0.0, 1.0,
])
.unwrap();
.unwrap();

warp(image, &hom, Interpolation::Bilinear, Rgb::black())
}
Expand All @@ -279,10 +280,10 @@ fn test_affine_bicubic_rgb() {
fn affine_bilinear(image: &RgbImage) -> RgbImage {
let root_two_inv = 1f32 / 2f32.sqrt() * 2.0;
#[rustfmt::skip]
let hom = Projection::from_matrix([
root_two_inv, -root_two_inv, 50.0,
root_two_inv, root_two_inv, -70.0,
0.0 , 0.0 , 1.0,
let hom = Projection::from_matrix([
root_two_inv, -root_two_inv, 50.0,
root_two_inv, root_two_inv, -70.0,
0.0, 0.0, 1.0,
]).unwrap();

warp(image, &hom, Interpolation::Bicubic, Rgb::black())
Expand Down Expand Up @@ -366,7 +367,7 @@ fn test_otsu_threshold() {
use imageproc::contrast::{otsu_level, threshold};
fn otsu_threshold(image: &GrayImage) -> GrayImage {
let level = otsu_level(image);
threshold(image, level)
threshold(image, level, ThresholdType::ThreshBinary)
}
compare_to_truth("zebra.png", "zebra_otsu.png", otsu_threshold);
}
Expand Down

0 comments on commit 6a100b4

Please sign in to comment.