首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >一个利用XTensor库的简单图像类

一个利用XTensor库的简单图像类
EN

Code Review用户
提问于 2023-04-09 12:40:40
回答 1查看 113关注 0票数 5

我一直在自学C++,最近我创建了一个使用X张量库的简单Image类。使用我的Image类,用户可以轻松地读取和写入PNG或JPG图像,并将RGB图像转换为灰度图像。

如果您感兴趣,您可以在这个存储库中为自己检查代码。请原谅代码中的大量评论--我还在学习C++。

Image

声明

为了便于阅读和查看这里的内容,我从代码中删除了一些广泛的注释。

  • 图像类的声明:
代码语言:javascript
复制
#ifndef IMAGE_XTENSOR_HPP
#define IMAGE_XTENSOR_HPP

#include 
#include 
#include 
#include 
#include 

namespace mypackage::image {

struct ImageXTensor {
  ImageXTensor();

  explicit ImageXTensor(std::string file_path);
  explicit ImageXTensor(int c, int h, int w);
  explicit ImageXTensor(const xt::xtensor &input_matrix);
  explicit ImageXTensor(const ImageXTensor &other);
  ImageXTensor(ImageXTensor &&other);

  ImageXTensor &operator=(const ImageXTensor &other);
  ImageXTensor &operator=(ImageXTensor &&other);
  ~ImageXTensor();
  bool operator==(const ImageXTensor &other) const;

  int channels; // aka comp in std_image
  int height;
  int width;
  int size;
  std::unique_ptr> pixels;

  bool save(std::string file_path);
  void swap(ImageXTensor &other);
};

ImageXTensor rgb_to_grayscale_xtensor(const ImageXTensor &img);
xt::xarray rgb_to_grayscale_xtensor(const xt::xarray &pixels);

} // namespace mypackage::image

#endif

我希望您发现Image类的声明很容易理解。但是,我有一个关于move构造函数ImageXTensor(ImageXTensor &&other);的问题。如果我将explicit关键字附加到移动构造函数(如explicit ImageXTensor(ImageXTensor &&other); ),将导致如下所示的编译错误:

代码语言:javascript
复制
/home/lai/cmake_template/src/mypackage/image/image_xtensor.cpp: In function ‘mypackage::image::ImageXTensor mypackage::image::rgb_to_grayscale_xtensor(const mypackage::image::ImageXTensor&)’:
/home/lai/cmake_template/src/mypackage/image/image_xtensor.cpp:309:10: error: no matching function for call to ‘mypackage::image::ImageXTensor::ImageXTensor(mypackage::image::ImageXTensor&)’
  309 |   return gray;
      |          ^~~~
/home/lai/cmake_template/src/mypackage/image/image_xtensor.cpp:18:1: note: candidate: ‘mypackage::image::ImageXTensor::ImageXTensor()’
   18 | ImageXTensor::ImageXTensor()
      | ^~~~~~~~~~~~
/home/lai/cmake_template/src/mypackage/image/image_xtensor.cpp:18:1: note:   candidate expects 0 arguments, 1 provided
make[2]: *** [src/mypackage/image/CMakeFiles/image_xtensor.dir/build.make:76: src/mypackage/image/CMakeFiles/image_xtensor.dir/image_xtensor.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:506: src/mypackage/image/CMakeFiles/image_xtensor.dir/all] Error 2
make: *** [Makefile:146: all] Error 2

我试图从这个所以问题中理解这个错误,但我仍然不太清楚为什么会发生这种情况。我知道explicit是用来防止编译器进行隐式转换的,而且由于相信显式总是比隐式好,所以我认为在任何地方使用显式都应该是首选的。因此,我将感谢您的任何见解或建议。

图像类的K220定义

以下是Image类的完整定义。

代码语言:javascript
复制
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

namespace mypackage::image {

ImageXTensor::ImageXTensor()
    : channels{0}, height{0}, width{0}, size{0}, pixels{nullptr} {
  std::clog << "The default constructor takes no paramters.\n";
}

ImageXTensor::ImageXTensor(std::string file_path) {
  std::clog << "The constructor takes a file path.\n";

  unsigned char *img_data =
      stbi_load(file_path.c_str(), &width, &height, &channels, 0);
  if (img_data == nullptr) {
    const char *error_msg = stbi_failure_reason();
    std::cerr << "Failed to load image: " << file_path.c_str() << "\n";
    std::cerr << "Error msg (stb_image): " << error_msg << "\n";
    std::exit(1);
  }
  pixels = std::make_unique>(
      xt::zeros({channels, height, width}));
  size = pixels->size();
  std::clog << "The image shape: " << channels << " x " << height << " x "
            << width << '\n';
  assert(size == channels * height * width);

  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      for (int c = 0; c < channels; c++) {
        // PNG's pixels order is mysterious for me.
        std::size_t src_idx = y * width * channels + x * channels + c;
        // Rescale uint8 to float 0-1.
        (*pixels)(c, y, x) = img_data[src_idx] / 255.;
      }
    }
  }
  if (channels == 4)
    channels = 3; // ignore alpha channel

  stbi_image_free(img_data);
}

ImageXTensor::ImageXTensor(int c, int h, int w)
    : channels{c}, height{h}, width{w}, size{c * h * w},
      pixels{std::make_unique>(
          xt::zeros({c, h, w}))} {}

ImageXTensor::ImageXTensor(const xt::xtensor &input_matrix) {
  channels = input_matrix.shape(0);
  height = input_matrix.shape(1);
  width = input_matrix.shape(2);
  size = input_matrix.size();

  pixels = std::make_unique>(input_matrix);
}

ImageXTensor::ImageXTensor(const ImageXTensor &other)
    : channels{other.channels}, height{other.height}, width{other.width},
      size{other.size}, pixels{std::make_unique>(
                            xt::zeros(
                                {other.channels, other.height, other.width}))} {
  std::clog << "Copy Constructor\n";
  *pixels = *other.pixels;
}

ImageXTensor &ImageXTensor::operator=(const ImageXTensor &other) {
  std::clog << "Copy Assignment Operator\n";
  if (this != &other) {
    channels = other.channels;
    height = other.height;
    width = other.width;
    size = other.size;

    pixels = std::make_unique>(
        xt::zeros({other.channels, other.height, other.width}));
    *pixels = *other.pixels;
  }
  return *this;
}

ImageXTensor::ImageXTensor(ImageXTensor &&other)
    : channels{other.channels}, height{other.height}, width{other.width},
      size{other.size}, pixels{std::move(other.pixels)} {
  std::clog << "Move Constructor\n";
  swap(other);
}

ImageXTensor &ImageXTensor::operator=(ImageXTensor &&other) {
  std::clog << "Move Assignment Operator\n";
  swap(other);

  return *this;
}

ImageXTensor::~ImageXTensor() { std::clog << "Destruct Image.\n"; }

bool ImageXTensor::operator==(const ImageXTensor &other) const {
  return (width == other.width) && (height == other.height) &&
         (channels == other.channels) && (size == other.size) &&
         (*pixels == *other.pixels);
}

bool ImageXTensor::save(std::string file_path) {
  auto file_extension = std::filesystem::path(file_path).extension();
  unsigned char *out_data = new unsigned char[width * height * channels];
  /** NOTE: There seems to be no easy way to unfold a 3D array into a 1D array
   * with the desired order.
   */
  for (auto x = 0; x < width; x++) {
    for (auto y = 0; y < height; y++) {
      for (auto c = 0; c < channels; c++) {
        int dst_idx = y * width * channels + x * channels + c;
        // Fill out_data with uint8 values range 0-255.
        out_data[dst_idx] = std::roundf((*pixels)(c, y, x) * 255.);
      }
    }
  }

  bool success{false};
  if (file_extension == std::string(".jpg") ||
      file_extension == std::string(".JPG")) {
    auto quality = 100;
    success = stbi_write_jpg(file_path.c_str(), width, height, channels,
                             out_data, quality);
  } else if (file_extension == std::string(".png") ||
             file_extension == std::string(".png")) {
    auto stride_in_bytes = width * channels;
    success = stbi_write_png(file_path.c_str(), width, height, channels,
                             out_data, stride_in_bytes);
  } else {
    std::cerr << "Unsupported file format: " << file_extension << "\n";
  }
  if (!success)
    std::cerr << "Failed to save image: " << file_path << "\n";

  delete[] out_data;
  return true;
}

void ImageXTensor::swap(ImageXTensor &other) {
  std::swap(channels, other.channels);
  std::swap(height, other.height);
  std::swap(width, other.width);
  std::swap(size, other.size);
  std::swap(pixels, other.pixels);
}

ImageXTensor rgb_to_grayscale_xtensor(const ImageXTensor &img) {
  assert(img.channels >= 3);
  ImageXTensor gray(1, img.height, img.width);

  xt::xarray red = xt::view(*img.pixels, 0, xt::all(), xt::all());
  xt::xarray green = xt::view(*img.pixels, 1, xt::all(), xt::all());
  xt::xarray blue = xt::view(*img.pixels, 2, xt::all(), xt::all());

  xt::view(*gray.pixels, 0, xt::all(), xt::all()) =
      0.299 * red + 0.587 * green + 0.114 * blue;

  return gray;
}

xt::xarray rgb_to_grayscale_xtensor(const xt::xarray &pixels) {
  assert(pixels.shape(0) >= 3);
  auto height = pixels.shape(1);
  auto width = pixels.shape(2);
  xt::xarray::shape_type shape = {1, height, width};
  xt::xarray gray(shape);

  xt::xarray red = xt::view(pixels, 0, xt::all(), xt::all());
  xt::xarray green = xt::view(pixels, 1, xt::all(), xt::all());
  xt::xarray blue = xt::view(pixels, 2, xt::all(), xt::all());

  xt::view(gray, 0, xt::all(), xt::all()) =
      0.299 * red + 0.587 * green + 0.114 * blue;

  return gray;
}

} // namespace mypackage::image

我希望您发现Image类的实现是简单明了的。

使用Image类(您可以找到代码这里)

  • 将图像对象传递给rgb_to_grayscale_xtensor
代码语言:javascript
复制
bool rgb2gray_image_xtensor(std::string input, std::string output) {
  mypackage::image::ImageXTensor in_img{input};
  auto out_img = mypackage::image::rgb_to_grayscale_xtensor(in_img);
  out_img.save(output);
  return 0;
}
  • xt::xarray传递给rgb_to_grayscale_xtensor
代码语言:javascript
复制
bool rgb2gray_image_xtensor_PassByTensor(std::string input,
                                         std::string output) {
  mypackage::image::ImageXTensor in_img{input};
  mypackage::image::ImageXTensor out_img{in_img.channels, in_img.height,
                                         in_img.width};
  *out_img.pixels = mypackage::image::rgb_to_grayscale_xtensor(*in_img.pixels);
  out_img.save(output);
  return 0;
}

单元测试:(您可以找到代码这里)

  • 测试构造函数和赋值操作符:
代码语言:javascript
复制
#include 
#include 
#include 
#include 
#include 

TEST(ImageXTensor, ClassAssertion) {
  mypackage::image::ImageXTensor test_img1(2, 3, 4);
  int counter = 0;
  for (int x = 0; x < test_img1.width; x++) {
    for (int y = 0; y < test_img1.height; y++) {
      for (int c = 0; c < test_img1.channels; c++) {
        (*test_img1.pixels)(c, y, x) = counter;
        ++counter;
      }
    }
  }
  std::clog << "test_img1:\n" << (*test_img1.pixels) << '\n';

  std::clog << "Test Copy Constructor.\n";
  mypackage::image::ImageXTensor test_img2{test_img1};
  std::clog << "test_img2:\n" << (*test_img2.pixels) << '\n';
  EXPECT_EQ(test_img2, test_img1);

  std::clog << "Test Copy Assignment Operator.\n";
  mypackage::image::ImageXTensor test_img3;
  test_img3 = test_img1;
  std::clog << "test_img3\n" << (*test_img3.pixels) << '\n';
  EXPECT_EQ(test_img3, test_img1);

  std::clog << "Test Move Assignment Operator.\n";
  mypackage::image::ImageXTensor test_img4;
  test_img4 = std::move(test_img1);
  // test_img1 becomes unspecified after it's moved from.
  EXPECT_EQ(test_img1.pixels, nullptr);
  EXPECT_EQ(test_img4, test_img2);
}

由于我仍然在学习C++,所以我试图专注于现代C++,并坚持最佳的编码实践。如果不是太麻烦的话,我希望您能够考虑到现代C++ (尤其是来自C++17和其他地方的代码)的代码。此外,任何关于以下C++最佳编码实践的提示都将不胜感激。谢谢。

EN

回答 1

Code Review用户

回答已采纳

发布于 2023-04-10 20:52:52

票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/284388

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档