Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Equivalent to OpenCV cv2.INTER_LINEAR #32

Closed
bedapisl opened this issue Jun 24, 2024 · 12 comments
Closed

Equivalent to OpenCV cv2.INTER_LINEAR #32

bedapisl opened this issue Jun 24, 2024 · 12 comments
Assignees

Comments

@bedapisl
Copy link

Hello is it possible to achieve exactly same results with this library as if I was using cv2.resize with interpolation=cv2.INTER_LINEAR from OpenCV?

I tried using Resizer::new(Convolution(Bilinear)) but the pixel values in the resulting image are slightly different.

Thanks

@Cykooz
Copy link
Owner

Cykooz commented Jun 24, 2024

Do you ask about downscaling or upscaling?

@Cykooz
Copy link
Owner

Cykooz commented Jun 24, 2024

As I understand, OpenCV uses "convolution" with fixed kernel size. In this case, downscaling very big image into very small one looks like a result of nearest "interpolation".

fast_image_resize uses convolution with adaptive kernel size. It requires more computations but makes more better result.
Look at results of downscaling big image into small one with use of different OpenCV interpolations.

@bedapisl
Copy link
Author

Downscaling is more important but I would ideally I would like both.

Yes, I see the results are visually better. But my goal is to reproduce a pipeline written in Python that uses OpenCV. I need exactly same results as in the OpenCV, no matter which ones look better.

@Cykooz
Copy link
Owner

Cykooz commented Jun 25, 2024

I can try to implement a fixed kernel size, but I'm not sure if that would be enough to get exactly the same results. The result also depends on how a particular solution handles rounding of numbers, accuracy of intermediate calculations and other things.

@Cykooz Cykooz self-assigned this Jun 25, 2024
@bedapisl
Copy link
Author

Hello, can I ask you about some rough time estimate for this? Alternatively if you don't have time for it, I can try implementing it myself and send it to you for review.

@Cykooz
Copy link
Owner

Cykooz commented Jul 17, 2024

I think I can do it for about 5-7 days. But I don't know what name to give these resizing methods.
ConvolutionWithFixedKernalSize? Interpolation? Do you have any ideas?

@bedapisl
Copy link
Author

bedapisl commented Jul 18, 2024

I think both names you suggested make sense, I don't have any other idea.

@Cykooz
Copy link
Owner

Cykooz commented Jul 18, 2024

I implemented Interpolation resize algorithm. I did the simple test to compare the result of Interpolation(FilterType::Bilinear) with the result of INTER_LINEAR from OpenCV. They look the same.

@bedapisl
Copy link
Author

Perfect thanks. I will try it later.

@Cykooz
Copy link
Owner

Cykooz commented Jul 19, 2024

I will publish this version on crates.org within 1-2 days.

@Cykooz
Copy link
Owner

Cykooz commented Jul 19, 2024

I released version 4.2.0

@bedapisl
Copy link
Author

Hello, I have tested the solution. The results are closer to these of OpenCV, but there are still some small +1/-1 differences. These are probably due to rounding as you said in your previous comment.

I am posting a reproducible example:

create_image.py

import cv2
import numpy as np



def create_image():
    width = 640
    height = 360

    np.random.seed(0)
    image = np.random.randint(0, 256, (height, width), dtype='uint8')

    cv2.imwrite("test_image.png", image)


def main():
    create_image()


if __name__ == "__main__":
    create_image()

resize_python.py

import cv2
import numpy as np


def resize_image(
    image: np.ndarray
) -> np.ndarray:
    new_height = 144
    new_width = 256

    scaled_image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_LINEAR)

    return scaled_image



def main():
    image = cv2.imread("test_image.png")
    resized = resize_image(image)
    print(resized[0:200,0,0].tolist())


if __name__ == "__main__":
    main()

main.rs

use std::fs;
use std::io::{BufWriter, Write};

use image::codecs::png::PngEncoder;
use image::{ImageEncoder, ImageReader};

use fast_image_resize::{FilterType, ResizeAlg, ResizeOptions, Resizer};
use fast_image_resize::images::Image;


fn main() {

    // Load test image
    let img = ImageReader::open("../test_image.png")
        .unwrap()
        .decode()
        .unwrap();

    let src_image = Image::from_vec_u8(img.width(), img.height(), img.into_bytes(), fast_image_resize::PixelType::U8).unwrap();

    // Create an empty destination image
    let new_height: usize = 144;
    let new_width: usize = 256;

    let mut dst_image = Image::from_vec_u8(new_width as u32, new_height as u32, vec![0; new_width*new_height as usize], fast_image_resize::PixelType::U8).unwrap();

    // Create a resizer
    let mut resizer = Resizer::new();
    let resize_options = ResizeOptions::new().resize_alg(ResizeAlg::Interpolation(FilterType::Bilinear));

    // Resize
    resizer.resize(
        &src_image,
        &mut dst_image,
        &resize_options,
    ).unwrap();

    // Print some pixels
    let pixels: Vec<u8> = dst_image.copy().into_vec().into_iter().step_by(new_width).collect();

    let pixel_strings: Vec<String> = pixels.into_iter().map(|pixel_value| pixel_value.to_string()).collect();
    println!("{}", pixel_strings.join(", "));
    
    // Save image
    let mut result_buf = BufWriter::new(Vec::new());
    PngEncoder::new(&mut result_buf)
        .write_image(
            dst_image.buffer(),
            new_width as u32,
            new_height as u32,
            image::ExtendedColorType::L8,
        )
        .unwrap();

    fs::write("../rust_output_image.png", result_buf.buffer()).expect("Unable to write file");
}

After running create_image.py I get following output from resize_image.py`:

[80, 113, 153, 116, 141, 74, 207, 216, 128, 62, 163, 138, 196, 59, 167, 194, 207, 188, 46, 149, 225, 191, 107, 143, 86, 62, 74, 103, 83, 156, 106, 192, 73, 149, 41, 103, 170, 150, 170, 76, 52, 149, 115, 129, 182, 81, 112, 122, 103, 147, 149, 90, 127, 128, 52, 125, 214, 146, 137, 174, 109, 150, 157, 103, 74, 133, 39, 208, 178, 167, 77, 178, 92, 158, 199, 189, 153, 153, 152, 112, 180, 75, 80, 144, 122, 84, 79, 228, 41, 177, 40, 60, 127, 159, 164, 128, 114, 181, 60, 55, 145, 153, 134, 95, 137, 67, 157, 130, 36, 151, 154, 68, 47, 93, 121, 77, 149, 185, 83, 136, 165, 49, 75, 208, 157, 168, 89, 67, 121, 143, 154, 151, 132, 72, 165, 131, 175, 97, 76, 85, 139, 182, 48, 166]

and a sligthly different output from running the rust code:

81, 113, 153, 117, 142, 74, 207, 216, 128, 62, 163, 138, 196, 59, 167, 195, 207, 188, 46, 150, 225, 191, 108, 144, 86, 62, 75, 103, 84, 157, 106, 192, 73, 150, 41, 103, 170, 151, 170, 76, 53, 149, 115, 130, 182, 81, 112, 122, 103, 147, 149, 90, 128, 129, 53, 125, 214, 147, 137, 174, 109, 150, 157, 103, 75, 134, 39, 209, 178, 167, 77, 179, 92, 158, 199, 189, 154, 154, 152, 112, 180, 75, 80, 145, 122, 84, 79, 228, 41, 177, 40, 60, 127, 159, 165, 128, 114, 181, 60, 55, 146, 154, 135, 96, 137, 67, 157, 130, 36, 152, 154, 69, 47, 93, 122, 78, 150, 185, 83, 136, 165, 49, 75, 208, 157, 168, 90, 67, 122, 143, 154, 151, 132, 73, 165, 132, 175, 97, 76, 86, 139, 182, 48, 166

The differences are at most 1.

For my usecase it would be the best to have a complete compatibility but I guess that is not a goal of this library. I don't know yet whether I will use the Interpolation or try some other approach. Anyway thanks for implementing it.

@Cykooz Cykooz closed this as completed Jul 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants