【问题标题】:Downscaling a Monochrome xmp image缩小单色 xmp 图像
【发布时间】:2017-04-25 00:02:16
【问题描述】:

我正在使用 Arduino Mega 2560。我有两 (2) 个 GLCD 显示器连接到处理器。显示库为 U2G2。我正在使用 Arduino 的 IDE 作为 C++ 编译器。

使用的 BitMap 文件格式为“xmp”,生成的文件是使用 Gimp 软件包生成的。

我需要能够将单色位图图像的大小从大约 600x300 减小到 128x64。在研究这个问题时,我发现了几种可以使用的方法,包括双线性、三次和双三次插值。这些文章中的大多数都处理彩色 BMP 文件,这些文件的结构与我正在使用的 xbm 文件完全不同。

我尝试使用双线性方法,但根本不起作用。

参考文章Image downscaling algorithm

最近,我在这个网站上发现了一篇文章,涵盖了 Mark Ransom 在 2012 年 3 月所写的问题。这篇文章在我看来是一个不错的选择,因为它使用了一种平均技术,而且似乎正在使用与我正在使用的相同类型的图像。

  1. 在 Mark 的帖子中,他使用二维数组来存储源和目标位图图像。我不明白为什么。我一直在使用的图像都存储在一个单维数组中。我一直在使用的库都使用一个带有两个变量(宽度、高度)的一维数组来告诉库如何使用数据库。

  2. 如果我要使用二维数组,我必须将一维数据库转换为二维数组。运行例程缩小图像后,我必须将数据重新转换回一维数组以供显示驱动程序使用。似乎工作量很大。有什么想法吗??

  3. 在 Mark 的代码中,数组的一维表示“x”轴,另一维表示“y”轴。但是由于我正在使用的数据库从上到下从左到右提供图像数据,并且“宽度”变量提供了回车/换行功能(可以这么说),实际上并不需要“y”轴大批。在我看来,添加第二个维度只会使数据库的大小增加一倍。一个二维数据库将消耗 2.048 字节的 RAM,而目前我只有 8k 可以使用。我将如何加载数组? x1 = 1, y1 = 1 表示“开”像素,x1 = 0, y1 = 0 表示“关”像素?

  4. 据我了解,Mark 文章中的例程是一种平均方法。在我看来,代码一次处理一个字节的图像数据。考虑到 xmp 数据结构中的每个数据字节代表八 (8) 个像素,这是如何工作的?

  5. 由于数据库中的每个位代表一个像素,我真的需要在位级别进行平均吗?如果是这样,我们会做(4)点平均吗?另外,你如何处理从一个字节到另一个字节的交叉?我做了一些拼接,但没有达到这种复杂程度。

我不是程序员,但我确实了解我正在尝试做的一些事情。我愿意付出任何努力和时间来理解这一点(我已经退休了)。

【问题讨论】:

  • 不确定这是否真的适合 StackOverflow。不是真正的编程问题。而且,您实际上已经就相关文章提出了许多问题。我建议考虑提出特定问题的主题方法(每个帖子一个问题)。正如所写,这看起来更像是一个讨论启动(不适合 StackOverflow)。另外,并不是说它会成为话题,但您甚至没有包含您提到的文章中的任何代码或链接
  • 对不起,如果我的问题不符合 Stackoverflow 的政策。这是我第一次在这个网站上寻求帮助。
  • 对不起,如果我的问题不符合 Stackoverflow 的政策。这是我第一次在这个网站上寻求帮助。我确实添加了我在 stackoverflow 上找到的文章的链接。至于很多问题,我对所讨论的过程有很多问题。这是一项非常复杂的任务。我不知道每个帖子政策只有一个问题。
  • 不一定有 1-question-per-post 政策。但这个问题是巨大的。然后,答案需要包含所有 5 个问题。但同样 - 这不是一个特定的编程问题。您要求对某人的实施进行解释。任何答案都只是猜测。或者需要有人学习代码并进行分析。
  • 好点。我有点想看看马克在写有问题的代码时是否会回答这个问题。我还没有找到直接联系他的方法。感谢您的输入。我将设置另一个线程直接寻求有关此问题的帮助。 .

标签: c++ arrays bitmap


【解决方案1】:

懒人前言:

最终(工作)算法可以在最后的源代码列表中找到。

我之前也记录了这些步骤,因为提问者说“不是程序员”。 (顺便说一句。如果没有前面的步骤,我感觉无法解释最终代码......)

简介

几年前,我在德国计算机杂志 c't 上发现一篇关于 RGB 图像的放大和缩小的文章。这些算法成为我个人图书馆的一部分,我不时使用它们,例如用于在我们的软件中调整图像的大小 - 主要是为了准备好 OpenGL 纹理。

本文的基本思想是考虑源像素(想象为正方形)覆盖目标像素(反之亦然)的空间比率。因此作者区分了放大和缩小。部分覆盖像素的考虑是使用浮点值完成的。

在阅读问题时,我意识到两种特殊情况:

  1. 处理位图的要求(由于单色 LCD 输出)

  2. 源与目标的宽度和之比为75/16。

比率 75/16 表示 75×75 源像素映射到 16×16 目标像素,例如4.6875×4.6875 源像素到一个目标像素。因此,源图像中存在部分映射到两个甚至四个相邻目标像素的像素。

关于您的特殊要求,我认为在这种特殊情况下应该可以仅使用整数算术来完成。 (根据您的提示(目标平台是嵌入式 CPU),这应该受到欢迎,因为这些通常不提供本机浮点指令。)

掌握 1D

为了热身,我从

  1. 字节而不是位

  2. 实现了单行图像的缩小:

这个想法是将源像素值累积到 [0,75] 范围内的灰度级,然后使用二进制阈值再次进行二值化。

#include <iostream>

// convenience type for a byte
typedef unsigned char uint8;

// ratio of source image size and destination image size
enum { nR = 75, dR = 16 };

// source image size
enum { wSrc = 1 * nR };

// destination image size
enum { wDst = dR * wSrc / nR };

// binary threshold
enum { tBin = nR / 2 };

// source image
static uint8 imgSrc[wSrc] = {
  1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 0 ... 15
  1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 16 ... 31
  1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 32 ... 47
  1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 48 ... 63
  1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0                 // 64 ... 74
};

// destination image
static uint8 imgDst[wDst];

// returns a source pixel.
inline int getPixel(int x) { return imgSrc[x]; }

// stores a destination pixel
inline void setPixel(int x, int value)
{
  imgDst[x] = !!value; // forces destination value to 0 or 1
}

// prints an image.
void printImg(
  int w, // width of image
  const uint8 *img) // the image data
{
  for (int x = 0; x < w; ++x) std::cout << (char)('0' + img[x]);
  std::cout << std::endl;
}

// main function.
int main()
{
  // print source image for visual check
  std::cout << "Source image (" << wSrc << "):" << std::endl;
  printImg(wSrc, imgSrc);
  // scale x
  int xSrc = 0; int n = 0;
  for (int xDst = 0; xDst < wDst; ++xDst) {
    int value = 0; // destination pixel accumulator
    // process right of cut pixel
    if (n) { value += (dR - n) * getPixel(xSrc); ++xSrc; n -= dR; }
    n += nR;
    // process full pixels
    for (; n >= dR; ++xSrc, n -= dR) value += dR * getPixel(xSrc);
    // process left of cut pixel
    if (n) value += n * getPixel(xSrc);
    // store value: 0 ... tBin -> 0, tBin + 1 ... wSrc -> 1
    setPixel(xDst, value >= tBin);
  }
  // print destination image for visual check
  std::cout << "Destination image (" << wDst << "):"
    << std::endl;
  printImg(wDst, imgDst);
  // done
  return 0;
}

我在 VisualStudio 2013 中编译测试,得到如下输出:

Source image (75):
111111110000000011111111000000001111111100000000111111110000000011111111000
Destination image (16):
1101100110110010

记住大约 5 个源像素映射到 1 个目标像素,输出看起来对我来说已经足够了。

扩展到二维

下一步是扩展二维图像的第一个样本。我很快意识到我的累积方法必须扩展到完整的目标图像行。这是使用values 数组而不是单个value 来实现的。按照我的第一种方法,必须处理两次拆分的源图像行。为了防止代码重复,我为此引入了辅助函数:accuPixel() 和 accuRow()。

#include <cassert>
#include <iostream>

// convenience type for a byte
typedef unsigned char uint8;

// convenience type for an image
struct Image {
  int w, h; // width and height of image
  uint8 *data; // image data

  int getPixel(int x, int y) const
  {
    assert(x >= 0 && x < w);
    assert(y >= 0 && y < h);
    return data[x * w + y];
  }

  void setPixel(int x, int y, int value)
  {
    assert(x >= 0 && x < w);
    assert(y >= 0 && y < h);
    data[x * w + y] = !!value; // '!!' forces dest. value to 0 or 1
  }

  void print() const
  {
    for (int y = 0; y < h; ++y) {
      for (int x = 0; x < w; ++x) {
        std::cout << (char)('0' + data[y * w + x]);
      }
      std::cout << std::endl;
    }
  }
};

// ratio of source image size and destination image size
enum { nR = 75, dR = 16 };

// source image size
enum { wSrc = 1 * nR, hSrc = 1 * nR };

// destination image size
enum { wDst = dR * wSrc / nR, hDst = dR * hSrc / nR };

// binary threshold
enum { tBin = nR * nR / 2 };

// source image
static uint8 dataSrc[wSrc * hSrc];
static Image imgSrc = {
  /* int w, h: */ wSrc, hSrc,
  /* uint8 *data: */ dataSrc
};

// destination image
static uint8 dataDst[wDst * hDst];
static Image imgDst = {
  /* int w, h: */ wDst, hDst,
  /* uint8 *data: */ dataDst
};

/* accumulates value for a destination pixel from the according number
 * of source pixels in one source image row.
 */
void accuPixel(
  int &value, // the accumulation value (updated)
  const Image &imgSrc, // the source image
  int &xSrc, // column index of source pixels (updated)
  int ySrc, // row index of source pixels
  int &n, // counter of accumulated values (updated)
  int fY) // vertical weight of row
{
  // process right part of cut pixel
  if (n) {
    value += fY * (dR - n) * imgSrc.getPixel(xSrc, ySrc);
    ++xSrc; n -= dR;
  }
  n += nR;
  // process full pixels
  for (; n >= dR; ++xSrc, n -= dR) {
    value += fY * dR * imgSrc.getPixel(xSrc, ySrc);
  }
  // process left part of cut pixel
  if (n) value += fY * n * imgSrc.getPixel(xSrc, ySrc);
}

/* accumulates values for one destination image row from one source
 * image row.
 */
void accuRow(
  int wDst, // width of destination image
  int *values, // accumulation values for destination row
  const Image &imgSrc, // the source image
  int ySrc, // row index of source pixels
  int fY) // vertical weight of row
{
  for (int xSrc = 0, n = 0, xDst = 0; xDst < wDst; ++xDst) {
    accuPixel(values[xDst], imgSrc, xSrc, ySrc, n, fY);
  }
}

// main function
int main()
{
  // fill source image with a chess board pattern
  for (int y = 0; y < hSrc; ++y) {
    for (int x = 0; x < wSrc; ++x) {
      imgSrc.setPixel(x, y, (x % 16 < 8) == (y % 16 < 8));
    }
  }
  // print source image for visual check
  std::cout << "Source image (" << wSrc << 'x' << hSrc << "):"
    << std::endl;
  imgSrc.print();
  // scale source image to destination image
  int ySrc = 0; int m = 0;
  for (int yDst = 0; yDst < hDst; ++yDst) {
    int values[wDst];
    for (int &value : values) value = 0; // init accu values
    // process bottom of cut row
    if (m) {
      accuRow(imgDst.w, values, imgSrc, ySrc, dR - m);
      ++ySrc; m -= dR;
    }
    m += nR;
    // process full rows
    for (; m >= dR; ++ySrc, m -= dR) {
      accuRow(imgDst.w, values, imgSrc, ySrc, dR);
    }
    // process top of cut row
    if (m) accuRow(imgDst.w, values, imgSrc, ySrc, m);
    // process accumulated values
    for (int xDst = 0; xDst < wDst; ++xDst) {
      imgDst.setPixel(xDst, yDst, values[xDst] >= tBin);
    }
  }
  // print destination image for visual check
  std::cout << "Destination image (" << wDst << 'x' << hDst << "):"
    << std::endl;
  imgDst.print();
  // done
  return 0;
}

程序的输出(和输入一样)是一个棋盘。但是,由于插值和随后的二进制分离,输出棋盘格没有大小相等的单元格。

Bit 地图的实际缩小比例

缩放达到我的预期后,示例代码完成:

Image 类已修改为支持位图。如果我使用将值打包为位的std::vector&lt;bool&gt;(std::vector&lt;&gt; 的专用版本),这将很容易。这可能简化了部分代码。我决定反对std::vector&lt;bool&gt;,因为我不确定 OP 中如何提供数据。我相信,我的“显式”C++ 示例代码更容易适应提问者平台上现有的数据模型。

我考虑文件 I/O 以使示例更灵活。我不确定 OP 中的图像格式。我的第一个想法是XMP 只是一个拼写错误,意思是XPM。但后来我开始怀疑并用谷歌搜索了一下。因此,我找到了XMP。可以是这个意思吗?如果我理解正确,XMP 是元数据的标准,可能会添加到某些图像格式,如 JPEG 和 TIFF。所以,我还是不确定...

为了解决这个问题,我决定改用一种文件格式,加载和保存只需要几行代码:PBM。

一旦我实施了 PBM I/O,我就在两个问题上苦苦挣扎,恕我直言,这两个问题值得注意:

  1. 如果图像行的长度不是 8 的倍数:行是否字节对齐?因此,我将_bPR // bits per row 成员添加到我的Image 类中。在 PBM 的情况下,行 字节对齐。 (我将带有 GIMP 的 Wikipedia 'J' 示例图像从 ASCII 转换为 RAW 版本以进行检查。)

  2. 第一个工作版本(没有崩溃)产生的输出图像看起来并不完全错误,但不知何故“条纹错误”。因此,我得出的结论是,我以错误的顺序存储了每个字节的位。 (从两个可能的解决方案中,我最初选择了错误的一个。)正确的方法是一个字节中最左边的像素必须存储在它的最高有效位中。 (在相反的情况下,Image::getPixel() 和 Image::setPixel() 中的位移必须更改。我将(在 PBM 的情况下)错误版本保留为禁用代码,只是为了这种情况。)

最终的示例代码:

#include <cassert>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

// convenience type for bytes
typedef unsigned char uint8;

// image helper class
class Image {
  private: // variables:
    int _w, _h; // image size
    int _bPR; // bits per row
    uint8 *_data; // image data

  public: // methods:
    // constructor.
    Image(): _w(0), _h(0), _bPR(0), _data(nullptr) { }
    // destructor.
    ~Image() { free(); }
    // returns width of image.
    int w() const { return _w; }
    // returns height of image.
    int h() const { return _h; }
    // returns data.
    const uint8* data() const { return _data; }
    // returns data size (in bytes).
    size_t size() const { return (_h * _bPR + 7) / 8; }
    // clears image.
    void free()
    {
      delete[] _data; _data = 0; _w = _h = _bPR = 0;
    }
    // allocates image data.
    uint8* alloc( // returns allocated buffer or 0 in case of error
      int w, // image width
      int h, // image height
      int bPR) // bits per row
    {
      assert(w >= 0 && w <= bPR);
      assert(h >= 0);
      free();
      size_t size = (h * bPR + 7) / 8;
      if (size && (_data = new uint8[size])) {
        _w = w; _h = h; _bPR = bPR;
      }
      return _data;
    }
    // returns pixel.
    int getPixel(
      int x, // column
      int y) // row
    const {
      assert(x >= 0 && x < _w);
      assert(y >= 0 && y < _h);
#if 0 // wrong for PBM
      int b = y * _bPR + x, bit = b % 8; // most left pixel is LSB
#else // correct for PBM
      int b = y * _bPR + x, bit = 7 - b % 8; // most left pixel is MSB
#endif // 0
      return _data[b / 8] >> bit & 1;
    }
    // sets pixel.
    void setPixel(
      int x, // column
      int y, // row
      int value) // value (should be 0 or 1)
    {
      assert(x >= 0 && x < _w);
      assert(y >= 0 && y < _h);
      int b = y * _bPR + x;
#if 0 // wrong for PBM
      uint8 *pB = _data + b / 8, bit = b % 8; // most left pixel is LSB
#else // correct for PBM
      uint8 *pB = _data + b / 8, bit = 7 - b % 8; // most left pixel is MSB
#endif // 0
      *pB &= (uint8)~(1 << bit); *pB |= !!value << bit; // bit fiddling
    }
};

// reads a PBM binary file.
void readPBM(
  std::istream &in, // input stream (to read from)
  Image &img) // image to store read data into
{
  std::string buffer;
  std::getline(in, buffer);
  if (buffer != "P4") {
    throw "ERROR! File is not a PBM binary file.";
  }
  do {
    std::getline(in, buffer);
  } while (buffer[0] == '#');
  std::istringstream sIn(buffer);
  int w = 0, h = 0;
  sIn >> w >> h;
  // PBM stores rows aligned to bytes
  int bitsPerRow = (w + 7) & ~0x7;
  // allocate data memory
  char *data = (char*)img.alloc(w, h, bitsPerRow);
  // read rest of file at once
  in.read(data, img.size());
}

// writes a PBM binary file.
void writePBM(
  std::ostream &out, // output stream (to write to)
  const Image &img) // image which shall be written
{
  out << "P4" << std::endl
    << img.w() << ' ' << img.h() << std::endl;
  out.write((const char*)img.data(), img.size());
}

// converts a text to an integer.
int strToI( // returns the integer or throws
  const char *text) // text to convert
{
  const char *end = text; int value = strtol(text, (char**)&end, 0);
  if (end == text || *end != '\0') throw "Not a number.";
  return value;
}

/* accumulates value for a destination pixel from the according number
 * of source pixels in one source image row.
 */
void accuPixel(
  int &value, // the accumulation value (updated)
  const Image &imgSrc, // the source image
  int &xSrc, // column index of source pixels (updated)
  int ySrc, // row index of source pixels
  int &n, // counter of accumulated values (updated)
  int fY, // vertical weight of row
  int nR, // numerator of ratio (source to destination image size)
  int dR) // denominator of ratio (source to destination image size)
{
  // process right part of cut pixel
  if (n) {
    value += fY * (dR - n) * imgSrc.getPixel(xSrc, ySrc);
    ++xSrc; n -= dR;
  }
  n += nR;
  // process full pixels
  for (; n >= dR; ++xSrc, n -= dR) {
    value += fY * dR * imgSrc.getPixel(xSrc, ySrc);
  }
  // process left part of cut pixel
  if (n) value += fY * n * imgSrc.getPixel(xSrc, ySrc);
}

/* accumulates values for one destination image row from one source
 * image row.
 */
void accuRow(
  int wDst, // width of destination image
  int *values, // accumulation values for destination row
  const Image &imgSrc, // the source image
  int ySrc, // row index of source pixels
  int fY, // vertical weight of row
  int nR, // numerator of ratio (source to destination image size)
  int dR) // denominator of ratio (source to destination image size)
{
  for (int xSrc = 0, n = 0, xDst = 0; xDst < wDst; ++xDst) {
    accuPixel(values[xDst], imgSrc, xSrc, ySrc, n, fY, nR, dR);
  }
}

// scales source image to destination image.
void scale(
  const Image &imgSrc, // source image
  Image &imgDst, // destination image
  int nR, // numerator of ratio (source to destination image size)
  int dR, // denominator of ratio (source to destination image size)
  int tBin) // binary threshold e.g. nR * nR / 2
{
  // allocate space for destination image
  const int wDst = dR * imgSrc.w() / nR;
  const int hDst = dR * imgSrc.h() / nR;
  if (!imgDst.alloc(wDst, hDst, wDst + 7 & ~7)) {
    throw "ERROR! Allocation of destination image failed!";
  }
  int *values = new int[wDst]; // aux. buffer to accumulate values
  for (int ySrc = 0, m = 0, yDst = 0; yDst < hDst; ++yDst) {
    // init accu values
    for (int i = 0; i < wDst; ++i) values[i] = 0;
    // process bottom of cut row
    if (m) {
      accuRow(wDst, values, imgSrc, ySrc, dR - m, nR, dR);
      ++ySrc; m -= dR;
    }
    m += nR;
    // process full rows
    for (; m >= dR; ++ySrc, m -= dR) {
      accuRow(wDst, values, imgSrc, ySrc, dR, nR, dR);
    }
    // process top of cut row
    if (m) accuRow(wDst, values, imgSrc, ySrc, m, nR, dR);
    // process accumulated values
    for (int xDst = 0; xDst < wDst; ++xDst) {
      imgDst.setPixel(xDst, yDst, values[xDst] > tBin);
    }
  }
  delete[] values; // free aux. buffer
}

// main function
int main( // returns 0 on success and another value in error case
  int argc, // number of command line arguments
  char **argv) // command line arguments
{
  // check for sufficient number of arguments
  if (argc <= 4) {
    std::cerr << "ERROR! Missing command line arguments." << std::endl;
    std::cout
      << "Usage:" << std::endl
      << argv[0] << " INFILE OUTFILE NR DR" << std::endl
      << "where" << std::endl
      << "INFILE ... file name of PBM input file (must exist)" << std::endl
      << "OUTFILE ... file name of PBM output file (overwritten if existing)" << std::endl
      << "NR ... numerator of ratio (src. to dest. image size)" << std::endl
      << "DR ... denominator of ratio (src. to dest. image size)" << std::endl
      << "NR and DR must be (not too large) positive integers: 0 < DR < NR" << std::endl;
    return 1; // ERROR!
  }
  try {
    // read command line arguments
    const char *fileIn = argv[1];
    const char *fileOut = argv[2];
    int nR;
    try {
      nR = strToI(argv[3]);
    } catch (const char*) {
      throw "ERROR in $3! (Not a number.)";
    }
    int dR;
    try {
      dR = strToI(argv[4]);
    } catch (const char*) {
      throw "ERROR in $4! (Not a number.)";
    }
    int tBin = nR * nR / 2; // might become cmd. line arg. also
    // read input file
    Image imgSrc;
    std::ifstream fIn(fileIn, std::ios::in | std::ios::binary);
    fIn.exceptions(std::ifstream::badbit);
    readPBM(fIn, imgSrc);
    // scale source image to destination image
    Image imgDst;
    scale(imgSrc, imgDst, nR, dR, tBin);
    // write output file
    std::ofstream fOut(fileOut, std::ios::out | std::ios::binary);
    fOut.exceptions(std::ofstream::badbit);
    writePBM(fOut, imgDst);
  } catch (const char *error) {
    std::cerr << error << std::endl;
    return 1; // ERROR!
  } catch (const std::exception &error) {
    std::cerr << error.what() << std::endl;
    return 1; // ERROR!
  }
  // done (probably successfully)
  return 0;
}

为了测试示例代码,我准备了一张我的照片作为示例图像。原图是猫莫里茨在玩螺丝:

我稍微 GIMP 了一下以获得合适的示例图像(主要是因为 GIMP 可以写入、加载和显示 PBM 文件):

虽然我在 VisualStudio 2013 中进行了所有开发和测试,但以下示例会话已使用 g++(在 Windows 10(64 位)上的 cygwin 中)完成:

$ g++ --version
g++ (GCC) 5.4.0

$ g++ -std=c++11 -o scale-bitmap scale-bitmap.cc 

$ ./scale-bitmap cat.bin.pbm out.bin.pbm 75 16

$

这产生了以下输出:

如果我没记错的话,示例代码只是实现了Bilinear filtering,这可能是简单地从源图像中删除行和列之后的第二个最糟糕的方法。

如示例输出所示,输出的质量相当有限。更复杂的处理可能会获得更好的结果:

  1. 更好的插值可能会有所帮助。维基百科文章Image scaling 和Pixel art scaling algorithms 可能是一个好的开始。

  2. 特别是对于单色图像,Dithering 可能是一个选项。

所有这些好东西肯定需要更多的开发工作和代码(如果不在库中使用的话)。

但是,更改二进制阈值tBin 可能会有所改进。我没有尝试过,但我可以想象这是因为我在准备测试图像时使用了 GIMP 中的二进制阈值...

最后但并非最不重要的一点

在写这篇文档的时候。我还发现了一个类似的问题SO: Image downscaling algorithm。 ...并在发送此答案后看到提问者已经提到过...

如果我将nR 和dR 分开用于水平和垂直缩放,该算法也可以应用于非比例缩放。改变它应该不会太难,但在 OP 中不是必需的。

最后,我猜到了目标平台的局限性。关于所描述的 OP 的源图像和目标图像尺寸,最高累积值为 75 * 75 = 5625(将所有源像素缩小为 1 - 一个完整的白色(或黑色?)区域)。这些都是好消息,因为即使 Atmel ATmega 的 C/C++ 编译器仅提供 16 位整数,示例代码也应该可以正常工作。

【讨论】:

  • 首先,我要感谢您的时间和投入。我对您获得的减少质量感到非常沮丧。
  • 感谢您的宝贵时间。我对所获得的还原质量印象深刻。 Atmel 2560 处理器确实提供了 32 位浮点功能,但速度很慢。处理器有 256k 的 RAM 用于程序,但只允许 8k 的 RAM 用于动态变量。我使用的算法平均每个位周围有 9 位,并删除每隔一行和一列。还原的质量很差。我正在使用 Floyd-Steinberg 抖动算法来清理图像。我会试试这种技术。
  • @KenKloster 对于可能的性能改进,我有两个额外的想法: 1. 可以尝试显式使用 16 位整数是否提供更快的代码。 2. 代码可以移植到C。如果您的Atmel-C 编译器支持C11(或至少“动态”本地数组),那么new int[] 可以替换为int[]。 (C++ 不支持“动态”本地数组。)C 端口可能会获得一点额外的加速/节省内存。在这个实现中没有什么应该/必须在 C++ 中完成。我只是按照你的标签/要求。
猜你喜欢
  • 1970-01-01
  • 2012-01-27
  • 1970-01-01
  • 2021-05-01
  • 2014-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多