image-rs / imageproc

Image processing operations
MIT License
755 stars 148 forks source link

median_filter is two orders of magnitude slower than opencv::imgproc::median_blur. #530

Open wangm23456 opened 1 year ago

wangm23456 commented 1 year ago
#[cfg(test)]
mod tests{
    use std::{path::PathBuf, time::SystemTime};

    use cv::{imgcodecs::IMREAD_GRAYSCALE, core::CV_8UC1};
    use image::{DynamicImage, GrayImage, buffer::ConvertBuffer};
    use opencv as cv;
    use opencv::prelude::*;
    use tiff::decoder::{Decoder, ifd};
    use imageproc::filter;

    #[test]
    fn test_image(){
        let path = "a.tiff".to_string();
        let pathbuf = PathBuf::from(path);
        let img = image::open(pathbuf).unwrap();
        let img = img.as_luma16().unwrap();
        let img: GrayImage = img.convert();
        let t0 = SystemTime::now();
        let img_median = filter::median_filter(&img, 7, 7);
        let t1 = SystemTime::now();
        println!("{:?}", img_median.dimensions());
        println!(
            "time: {}s",
            t1.duration_since(t0).unwrap().as_millis() as f64 / 1000.
        );
    }
    #[test]
    fn test_opencv(){
        let path = "a.tiff";
        let img = cv::imgcodecs::imread(path, cv::imgcodecs::IMREAD_GRAYSCALE).unwrap();
        let mut img_8 = Mat::default();
        img.convert_to(&mut img_8, CV_8UC1, 1.0/255.0, 0.0).unwrap();
        let t0 = SystemTime::now();
        let mut img_median = Mat::default();
        cv::imgproc::median_blur(&img, &mut img_median, 7).unwrap();
        let t1 = SystemTime::now();
        println!("{:?}", img_median.size());
        println!(
            "time: {}s",
            t1.duration_since(t0).unwrap().as_millis() as f64 / 1000.
        );
    }
}
Ok(Size_ { width: 9720, height: 11340 })
time: 0.924s
test tests::test_opencv ... ok

(9720, 11340)
time: 216.174s
test tests::test_image ... ok
cospectrum commented 8 months ago

The first thing to check is whether opencv is using the GPU in this case or not.

theotherphil commented 6 months ago

OpenCV may well be doing something smarter, but one difference here is that you're using a 7x7 filter for OpenCV and a 15x15 filter for imageproc. OpenCV takes a width parameter that it requires to be odd, whereas imageproc takes a radius parameter and uses a filter of size 2*radius + 1 to ensure it's always odd. This is possibly a confusing API!

theotherphil commented 6 months ago

Having reminded myself of what the code does, the imageproc implementation is linear in kernel width so your accidental difference in parameters shouldn't account for much of the difference between the two libraries!

Quick empirical sanity check for this:

use std::time::SystemTime;
use image::{GrayImage, Luma};
use imageproc::filter::median_filter;

fn main() {
    let image = GrayImage::from_fn(9720, 11340, |_, _| Luma([7]));

    let t0 = SystemTime::now();
    let _f1 = median_filter(&image, 3, 3);
    let t1 = SystemTime::now();
    let _f2 = median_filter(&image, 7, 7);
    let t2 = SystemTime::now();

    println!("{:?}", t1.duration_since(t0));
    println!("{:?}", t2.duration_since(t1));
}

Ok(3.232656s) Ok(6.872397s)

That's still slower than your OpenCV run time, but far faster than your imageproc run time. I suspect you might be running your code in debug mode and so comparing a debug build of imageproc against a release build of OpenCV.

From a quick glance at the source code for OpenCV's implementation, it looks to be doing something roughly similar to what we do in imageproc, but with SIMD acceleration. I wouldn't be too surprised by a 5-10x difference in run time, but 200x looks wrong.

@wangm23456 can you check if you're running imageproc in debug or release mode.