Siv3D / OpenSiv3D

C++20 framework for creative coding 🎮🎨🎹 / Cross-platform support (Windows, macOS, Linux, and the Web)
https://siv3d.github.io/
MIT License
1.02k stars 139 forks source link

Help with reading and writing images #534

Open ghost opened 4 years ago

ghost commented 4 years ago

Dear team, After integrating Siv3D with Libtorch (https://github.com/QuantScientist/Siv3DTorch) I am now trying to read and write images from and to Siv3D. The way it works is:

  1. An image is read from disk (usually using OpenCV which is easy but I am trying to avoid)
  2. The image is converted to torch::tensor
  3. A DL model is run on the tensor
  4. A tensor is returned from the model
  5. The tensor is converted to an image for display purposes.

This is one example where they used stb_image to this, avoiding the use of OpenCV. https://github.com/prabhuomkar/pytorch-cpp/blob/master/utils/image_io/src/image_io.cpp

I did something similar and was able to read the image, but I am not sure how to proceed and display it on Siv3D. I don't mind using libpng / libjpg that you are linking to if you think I should do so.

Code for reading an image and displaying its dimensions on Siv3D (https://github.com/QuantScientist/Siv3DTorch/blob/master/src/loadmodel003.cpp):

# include <Siv3D.hpp>
#include <torch/script.h>
#include <torch/torch.h>
#include <vector>
#include <typeinfo> 
#include <thread>
#include <future>
#define STB_IMAGE_IMPLEMENTATION
#include "../include/stb_image/stb_image.h"

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "../include/stb_image_write/stb_image_write.h"

#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "../include/stb_image_resize/stb_image_resize.h"

torch::Device device(torch::kCUDA);
torch::Tensor tensor = torch::eye(3).to(device);
torch::data::transforms::Normalize<> normalize_transform({ 0.485, 0.456, 0.406 }, { 0.229, 0.224, 0.225 });

// Loads a tensor from an image file
torch::Tensor load_image(const std::string& file_path,
    torch::IntArrayRef shape, std::function<torch::Tensor(torch::Tensor)> transform) {
    if (!shape.empty() && shape.size() != 1 && shape.size() != 2) {
        throw std::invalid_argument("Shape must be empty or contain exactly one or two elements.");
    }

    int width = 0;
    int height = 0;
    int depth = 0;

    std::unique_ptr<unsigned char, decltype(&stbi_image_free)> image_raw(stbi_load(file_path.c_str(),
        &width, &height, &depth, 0), &stbi_image_free);

    if (!image_raw) {
        throw std::runtime_error("Unable to load image file " + file_path + ".");
    }

    if (shape.empty()) {
        return transform(torch::from_blob(image_raw.get(),
            { height, width, depth }, torch::kUInt8).clone().to(torch::kFloat32).permute({ 2, 0, 1 }).div_(255));
    }

    int new_width = 0;
    int new_height = 0;

    if (shape.size() == 1) {
        double scale = static_cast<double>(shape[0]) / std::max(width, height);
        new_width = width * scale;
        new_height = height * scale;
    }
    else {
        new_width = shape[1];
        new_height = shape[0];
    }

    if (new_width < 0 || new_height < 0) {
        throw std::invalid_argument("Invalid shape.");
    }

    size_t buffer_size = new_width * new_height * depth;

    std::vector<unsigned char> image_resized_buffer(buffer_size);

    stbir_resize_uint8(image_raw.get(), width, height, 0,
        image_resized_buffer.data(), new_width, new_height, 0, depth);

    return transform(torch::from_blob(image_resized_buffer.data(),
        { new_height, new_width, depth }, torch::kUInt8).clone().to(torch::kFloat32).permute({ 2, 0, 1 }).div_(255));
}

void tensorDIMS(const torch::Tensor& tensor) {
    auto t0 = tensor.size(0);
    auto s = tensor.sizes();
    Print (tensor.size(0), U",", tensor.size(1), U",", tensor.size(2));
    //Print(tensor.size(0));
}

void Main()
{
    Window::SetTitle(U"TorchSiv3D C++");
    const Texture icn0(Emoji(U"✡"));
    icn0.draw(0, 0);                
    Scene::SetBackground(Color(90, 81, 95));    

    const std::string modelName = "erfnet_fs.pt";
    const std::string content_image_path = "windmill.png";

    auto module = torch::jit::load(modelName, device);
    //module->to(at::kCUDA);
    if (!std::ifstream(modelName)) {

        Print  (U"ERROR: Could not open the required module file from path:");
    }
    else {
        Print(U"Loaded required module file from path");
    }
    assert(module != nullptr);
    const int64_t max_image_size = 256;
    auto content = load_image(content_image_path, max_image_size, normalize_transform).unsqueeze_(0);

    tensorDIMS(content);
    //auto x = (content).data()[0]; // Move it to the CPU
    //Print(x); //Use it from Siv3D
    while (System::Update())
    {   

    }
}

For reference this is the OpenCV to Libtorch conversion utils:

at::Tensor matToTensor(cv::Mat frame, int h, int w, int c) {
    cv::cvtColor(frame, frame, CV_BGR2RGB);
    frame.convertTo(frame, CV_32FC3, 1.0f / 255.0f);
    auto input_tensor = torch::from_blob(frame.data, {1, h, w, c});
    input_tensor = input_tensor.permute({0, 3, 1, 2});

    torch::DeviceType device_type = torch::kCPU;
//    if (torch::cuda::is_available()) {
    device_type = torch::kCUDA;
//    }
    input_tensor = input_tensor.to(device_type);
    return input_tensor;
}

cv::Mat tensorToOpenCv(at::Tensor out_tensor, int h, int w, int c) {
    out_tensor = out_tensor.squeeze().detach().permute({1, 2, 0});
    out_tensor = out_tensor.mul(255).clamp(0, 255).to(torch::kU8);
    out_tensor = out_tensor.to(torch::kCPU);
    cv::Mat resultImg(h, w, CV_8UC3);
    // cv::Mat resultImg(h, w, CV_8UC1);
    std::memcpy((void *) resultImg.data, out_tensor.data_ptr(), sizeof(torch::kU8) * out_tensor.numel());
    return resultImg;
}

Many thanks,

ghost commented 4 years ago

Done! Working with VC 19 and not CMake is really hard :)

image

Code: https://github.com/QuantScientist/Siv3DTorch/blob/master/src/readpng004.cpp

Reputeless commented 4 years ago

You can use s3d::Image class for image I/O and image processing.

ghost commented 4 years ago

For now, I am converting the tensor to a PNG and writing the PNG to disk and then reading it with siv3d. It is not very fast bu that Is what I have now. I looked at s3d::Image but spent a day trying to figure out how to do s3d::Image to torch::tensor and vice versa. This is my torch to PNG and vice versa code: https://github.com/QuantScientist/PngTorch/blob/master/include/utils/vision_utils.hpp

Thanks,

Reputeless commented 4 years ago

Conversion functions will be like this:

torch::Tensor ImageToTensor(const Image& image)
{
    Array<uint8> buffer(image.num_pixels() * 3);
    uint8* pDst = buffer.data();

    for (const auto& pixel : image)
    {
        *pDst++ = pixel.r;
        *pDst++ = pixel.g;
        *pDst++ = pixel.b;
    }

    const int32 width  = image.width();
    const int32 height = image.height();

    // ...
}

Image TensorToImage(const torch::Tensor& tensor)
{
    size_t width  = ??;
    size_t height = ??;
    const uint8* pSrc = tensor.data_ptr<uint8>();

    Image image(width, height);

    for (auto& pixel : image)
    {
        pixel.r = *pSrc++;
        pixel.g = *pSrc++;
        pixel.b = *pSrc++;
        pixel.a = 255;
    }

    return image;
}