Skip to content

Commit

Permalink
color: implement gray from rgb as u8 (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarriba authored Feb 16, 2025
1 parent 22bd5ac commit 00e82d9
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 97 deletions.
103 changes: 14 additions & 89 deletions crates/kornia-imgproc/benches/bench_color.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};

use kornia_image::Image;
use kornia_imgproc::color::gray_from_rgb;
use rayon::prelude::*;
use kornia_imgproc::color::{gray_from_rgb, gray_from_rgb_u8};

// vanilla version
fn gray_vanilla_get_unchecked(
Expand Down Expand Up @@ -53,63 +52,6 @@ fn gray_ndarray_zip_par(
Ok(())
}

fn gray_slice_chunks_pixels(
src: &Image<f32, 3>,
dst: &mut Image<f32, 1>,
) -> Result<(), Box<dyn std::error::Error>> {
src.as_slice()
.chunks_exact(3)
.zip(dst.storage.as_mut_slice().chunks_exact_mut(1))
.for_each(|(src_chunk, dst_chunk)| {
let r = src_chunk[0];
let g = src_chunk[1];
let b = src_chunk[2];
dst_chunk[0] = (76. * r + 150. * g + 29. * b) / 255.;
});

Ok(())
}

fn gray_slice_chunks_pixels_parallel(
src: &Image<f32, 3>,
dst: &mut Image<f32, 1>,
) -> Result<(), Box<dyn std::error::Error>> {
src.as_slice()
.par_chunks_exact(3)
.zip(dst.as_slice_mut().par_chunks_exact_mut(1))
.for_each(|(src_chunk, dst_chunk)| {
let r = src_chunk[0];
let g = src_chunk[1];
let b = src_chunk[2];
dst_chunk[0] = (76. * r + 150. * g + 29. * b) / 255.;
});

Ok(())
}

fn gray_slice_chunks_rows(
src: &Image<f32, 3>,
dst: &mut Image<f32, 1>,
) -> Result<(), Box<dyn std::error::Error>> {
let num_channelsols = src.cols();
src.as_slice()
.chunks_exact(3 * num_channelsols)
.zip(dst.storage.as_mut_slice().chunks_exact_mut(num_channelsols))
.for_each(|(src_chunk, dst_chunk)| {
src_chunk
.chunks_exact(3)
.zip(dst_chunk.chunks_exact_mut(1))
.for_each(|(src_pixel, dst_pixel)| {
let r = src_pixel[0];
let g = src_pixel[1];
let b = src_pixel[2];
dst_pixel[0] = (76. * r + 150. * g + 29. * b) / 255.;
});
});

Ok(())
}

fn gray_image_crate(image: &Image<u8, 3>) -> Image<u8, 1> {
let image_data = image.as_slice();
let rgb = image::RgbImage::from_raw(
Expand Down Expand Up @@ -137,15 +79,16 @@ fn bench_grayscale(c: &mut Criterion) {
let image_data = vec![0u8; width * height * 3];
let image_size = [*width, *height].into();

let image = Image::new(image_size, image_data).unwrap();
let image_f32 = image.clone().cast::<f32>().unwrap();
let image_u8 = Image::new(image_size, image_data).unwrap();
let image_f32 = image_u8.clone().cast::<f32>().unwrap();

// output image
let gray = Image::from_size_val(image.size(), 0.0).unwrap();
let gray_f32 = Image::from_size_val(image_size, 0.0).unwrap();
let gray_u8 = Image::from_size_val(image_size, 0).unwrap();

group.bench_with_input(
BenchmarkId::new("vanilla_unchecked", &parameter_string),
&(&image_f32, &gray),
&(&image_f32, &gray_f32),
|b, i| {
let (src, mut dst) = (i.0, i.1.clone());
b.iter(|| black_box(gray_vanilla_get_unchecked(src, &mut dst)))
Expand All @@ -154,52 +97,34 @@ fn bench_grayscale(c: &mut Criterion) {

group.bench_with_input(
BenchmarkId::new("image_crate", &parameter_string),
&image,
&image_u8,
|b, i| b.iter(|| black_box(gray_image_crate(i))),
);

group.bench_with_input(
BenchmarkId::new("ndarray_zip_par", &parameter_string),
&(&image_f32, &gray),
&(&image_f32, &gray_f32),
|b, i| {
let (src, mut dst) = (i.0, i.1.clone());
b.iter(|| black_box(gray_ndarray_zip_par(src, &mut dst)))
},
);

group.bench_with_input(
BenchmarkId::new("slice_chunks_pixels", &parameter_string),
&(&image_f32, &gray),
BenchmarkId::new("gray_from_rgb", &parameter_string),
&(&image_f32, &gray_f32),
|b, i| {
let (src, mut dst) = (i.0, i.1.clone());
b.iter(|| black_box(gray_slice_chunks_pixels(src, &mut dst)))
},
);

group.bench_with_input(
BenchmarkId::new("slice_chunks_pixels_parallel", &parameter_string),
&(&image_f32, &gray),
|b, i| {
let (src, mut dst) = (i.0, i.1.clone());
b.iter(|| black_box(gray_slice_chunks_pixels_parallel(src, &mut dst)))
},
);

group.bench_with_input(
BenchmarkId::new("slice_chunks_rows", &parameter_string),
&(&image_f32, &gray),
|b, i| {
let (src, mut dst) = (i.0, i.1.clone());
b.iter(|| black_box(gray_slice_chunks_rows(src, &mut dst)))
b.iter(|| black_box(gray_from_rgb(src, &mut dst)))
},
);

group.bench_with_input(
BenchmarkId::new("slice_chunks_rows_parallel", &parameter_string),
&(&image_f32, &gray),
BenchmarkId::new("gray_from_rgb_u8", &parameter_string),
&(&image_u8, &gray_u8),
|b, i| {
let (src, mut dst) = (i.0, i.1.clone());
b.iter(|| black_box(gray_from_rgb(src, &mut dst)))
b.iter(|| black_box(gray_from_rgb_u8(src, &mut dst)))
},
);
}
Expand Down
51 changes: 51 additions & 0 deletions crates/kornia-imgproc/src/color/gray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,38 @@ where
Ok(())
}

/// Convert an RGB8 image to grayscale using the formula:
///
/// Y = 77 * R + 150 * G + 29 * B
///
/// # Arguments
///
/// * `src` - The input RGB8 image.
/// * `dst` - The output grayscale image.
///
/// Precondition: the input image must have 3 channels.
/// Precondition: the output image must have 1 channel.
/// Precondition: the input and output images must have the same size.
pub fn gray_from_rgb_u8(src: &Image<u8, 3>, dst: &mut Image<u8, 1>) -> Result<(), ImageError> {
if src.size() != dst.size() {
return Err(ImageError::InvalidImageSize(
src.cols(),
src.rows(),
dst.cols(),
dst.rows(),
));
}

parallel::par_iter_rows(src, dst, |src_pixel, dst_pixel| {
let r = src_pixel[0] as u16;
let g = src_pixel[1] as u16;
let b = src_pixel[2] as u16;
dst_pixel[0] = ((r * 77 + g * 150 + b * 29) >> 8) as u8;
});

Ok(())
}

/// Convert a grayscale image to an RGB image by replicating the grayscale value across all three channels.
///
/// # Arguments
Expand Down Expand Up @@ -285,4 +317,23 @@ mod tests {

Ok(())
}

#[test]
fn gray_from_rgb_u8() -> Result<(), Box<dyn std::error::Error>> {
let image = Image::new(
ImageSize {
width: 1,
height: 2,
},
vec![0, 128, 255, 128, 0, 128],
)?;

let mut gray = Image::<u8, 1>::from_size_val(image.size(), 0)?;

super::gray_from_rgb_u8(&image, &mut gray)?;

assert_eq!(gray.as_slice(), &[103, 53]);

Ok(())
}
}
2 changes: 1 addition & 1 deletion crates/kornia-imgproc/src/color/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod gray;
mod hsv;

pub use gray::{bgr_from_rgb, gray_from_rgb, rgb_from_gray};
pub use gray::{bgr_from_rgb, gray_from_rgb, gray_from_rgb_u8, rgb_from_gray};
pub use hsv::hsv_from_rgb;
14 changes: 7 additions & 7 deletions examples/imgproc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// read the image
let image: Image<u8, 3> = F::read_image_any_rgb8(args.image_path)?;

// convert the image to f32 and scale it
let mut image_f32 = Image::<f32, 3>::from_size_val(image.size(), 0.0)?;
ops::cast_and_scale(&image, &mut image_f32, 1.0 / 255.0)?;

// convert the image to grayscale
let mut gray = Image::<f32, 1>::from_size_val(image_f32.size(), 0.0)?;
imgproc::color::gray_from_rgb(&image_f32, &mut gray)?;
let mut gray = Image::<u8, 1>::from_size_val(image.size(), 0)?;
imgproc::color::gray_from_rgb_u8(&image, &mut gray)?;

// convert to float
let mut gray_f32 = Image::<f32, 1>::from_size_val(gray.size(), 0.0)?;
ops::cast_and_scale(&gray, &mut gray_f32, 1.0 / 255.0)?;

let new_size = ImageSize {
width: 128,
Expand All @@ -34,7 +34,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let mut gray_resize = Image::<f32, 1>::from_size_val(new_size, 0.0)?;
imgproc::resize::resize_native(
&gray,
&gray_f32,
&mut gray_resize,
imgproc::interpolation::InterpolationMode::Bilinear,
)?;
Expand Down

0 comments on commit 00e82d9

Please sign in to comment.