好的,鉴于您必须进行像素修正,让我们看看您的整体问题。30000x4000 像素的中等图像是 120M 的 8 位灰度图像数据和 240M 的 16 位图像数据。因此,如果您以这种方式查看数据,您需要问“30 分钟合理吗?” 为了进行 90 度旋转,您正在引发一个最坏情况的问题,即内存方面的问题。您正在触摸单列中的每个像素以填充一行。如果您按行工作,至少您不会将内存占用量增加一倍。
所以 - 120M 像素意味着您正在进行 120M 读取和 120M 写入,或 240M 数据访问。这意味着您每秒处理大约 66,667 像素,我认为这太慢了。我认为您应该每秒处理至少50 万像素,可能更多。
如果这是我,我会运行我的分析工具,看看瓶颈在哪里,然后把它们去掉。
在不知道您的确切结构并且不必猜测的情况下,我会执行以下操作:
尝试为源图像使用一个连续的内存块
我希望看到这样的旋转功能:
void RotateColumn(int column, char *sourceImage, int bytesPerRow, int bytesPerPixel, int height, char *destRow)
{
char *src = sourceImage + (bytesPerPixel * column);
if (bytesPerPixel == 1) {
for (int y=0; y < height; y++) {
*destRow++ = *src;
src += bytesPerRow;
}
}
else if (bytesPerPixel == 2) {
for (int y=0; y < height; y++) {
*destRow++ = *src;
*destRow++ = *(src + 1);
src += bytesPerRow;
// although I doubt it would be faster, you could try this:
// *destRow++ = *src++;
// *destRow++ = *src;
// src += bytesPerRow - 1;
}
}
else { /* error out */ }
}
我猜循环内部可能会变成 8 条指令。在 2GHz 处理器上(比方说名义上每条指令 4 个周期,这只是一个猜测),您应该能够在一秒钟内旋转 6.25 亿像素。大致。
如果您不能连续进行,请同时处理多个 dest 扫描线。
如果源图像被分成块或者你有内存的扫描线抽象,你所做的就是从源图像中获取一条扫描线,然后一次旋转几十列到目标扫描线的缓冲区中。
假设您有一种抽象访问扫描线的机制,您可以在其中获取、释放和写入扫描线。
然后你要做的是弄清楚你愿意一次处理多少个源列,因为你的代码看起来像这样:
void RotateNColumns(Pixels &source, Pixels &dest, int startColumn, int nCols)
{
PixelRow &rows[nRows];
for (int i=0; i < nCols; i++)
rows[i] = dest.AcquireRow(i + startColumn);
for (int y=0; y < source.Height(); y++) {
PixelRow &srcRow = source.AcquireRow();
for (int i=0; i < nCols; i++) {
// CopyPixel(int srcX, PixelRow &destRow, int dstX, int nPixels);
sourceRow.CopyPixel(startColumn + i, rows[i], y, 1);
}
source.ReleaseRow(srcRow);
}
for (int i=0; i < nCols; i++)
dest.ReleaseAndWrite(rows[i]);
}
在这种情况下,如果您在较大的扫描线块中缓冲源像素,则不一定会使堆碎片化,并且您可以选择可能将解码的行刷新到磁盘。您一次处理 n 列,并且您的内存位置应该提高 n 倍。然后它就变成了你的缓存有多昂贵的问题。
可以通过并行处理解决问题吗?
老实说,我认为您的问题应该是 IO 限制,而不是 CPU 限制。我认为您的解码时间将占主导地位,但让我们假装它不是,笑着说。
以这种方式考虑 - 如果您一次读取一整行源图像,您可以将该解码的行扔到一个线程,该线程将其写入目标图像的适当列。所以写你的解码器,让它有一个像 OnRowDecoded(byte *row, int y, int width, int bytesPerPixel); 这样的方法。然后你在解码时旋转。OnRowDecoded() 打包信息并将其传递给拥有 dest 图像的线程,并将整个解码的行写入正确的 dest 列。当主线程忙于解码下一行时,该线程将所有写入到 dest。工作线程可能会首先完成,但也可能不会。
您需要将您的 SetPixel() 设置为线程安全的,但除此之外,没有理由这应该是一个串行任务。事实上,如果您的源图像使用 TIFF 将其划分为条带或图块的功能,您可以并且应该并行解码它们。