gpt4 book ai didi

image - 如何加快将巨大的 TIFF 旋转 90 度

转载 作者:行者123 更新时间:2023-12-01 01:06:26 26 4
gpt4 key购买 nike

我正在处理巨大的 TIFF 图像(灰度,8 位或 16 位,最大 4 GB),以用作机器的高分辨率输入数据。每个图像都需要旋转 90 度(顺时针)。输入 TIFF 可以是 LZW 或未压缩的,输出可以是未压缩的。

到目前为止,我在 Objective C 中实现了我自己的 TIFF 阅读器类(包括 LZW 解压缩),它能够处理大文件并在内存中进行一些缓存。目前,TIFF 阅读器类用于图像内部的可视化和测量,它的性能相当不错。

对于我的最新挑战,旋转 TIFF,我需要一种新方法,因为当前的实现非常缓慢。即使对于“中等”尺寸的 TIFF (30.000 x 4.000),也需要大约30 分钟旋转图像。目前,我遍历所有像素并选择 x 和 y 坐标反转的像素,将它们全部放入缓冲区并在一行完成后立即将缓冲区写入磁盘。主要问题是从 TIFF 中读取数据,因为数据是按 strip 组织的,并且不能保证在文件中线性分布(对于 LZW 压缩 strip ,也没有什么是线性的)。

我分析了我的软件,发现大部分时间都花在了复制内存块(memmove)上,并决定绕过我的阅读器类中的缓存进行轮换。现在整个过程快了大约 5%,这并不算多,而且现在所有的时间都花在了 fread() 中。我假设至少我的缓存执行得几乎和系统的 fread() 缓存一样好。

使用 Image Magick 和相同的 30.000 x 4.000 文件进行的另一项测试仅用了大约 10 秒即可完成。 AFAIK Image Magick 将整个文件读入内存,在内存中处理,然后写回磁盘。这适用于数百兆字节的图像数据。

我正在寻找的是某种“元优化”,例如另一种处理像素数据的方法。除了逐个交换像素(并且需要从彼此远离的文件位置读取)之外,还有另一种策略吗?我应该创建一些中间文件来加速这个过程吗?欢迎任何建议。

最佳答案

好的,鉴于您必须进行像素修正,让我们看看您的整体问题。
30000x4000 像素的中等图像是 120M 的 8 位灰度图像数据和 240M 的 16 位图像数据。因此,如果您以这种方式查看数据,您需要问“30 分钟合理吗?”为了进行 90 度旋转,您正在引发一个最坏情况的问题,即内存方面的问题。您正在触摸单列中的每个像素以填充一行。如果您按行工作,至少您不会将内存占用量增加一倍。

所以 - 120M 像素意味着您正在进行 120M 读取和 120M 写入,或 240M 数据访问。这意味着您每秒处理大约 66,667 像素,我认为这太慢了。我认为你应该处理 至少 每秒半百万像素,可能更多。

如果这是我,我会运行我的分析工具,看看瓶颈在哪里,然后把它们去掉。

在不知道您的确切结构并且不必猜测的情况下,我会执行以下操作:

尝试为源图像使用一个连续的内存块

我希望看到这样的旋转功能:

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 扫描线。

如果源图像被分成 block 或者你有一个内存的扫描线抽象,你所做的就是从源图像中获取一条扫描线,然后一次旋转几十列到目标扫描线的缓冲区中。

假设您有一种抽象访问扫描线的机制,您可以在其中获取、释放和写入扫描线。

然后你要做的是弄清楚你愿意一次处理多少个源列,因为你的代码看起来像这样:
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]);
}

在这种情况下,如果您在较大的扫描线 block 中缓冲源像素,则不一定会使堆碎片化,并且您可以选择可能将解码的行刷新到磁盘。您一次处理 n 列,并且您的内存位置应该提高 n 倍。然后它就变成了你的缓存有多昂贵的问题。

可以通过并行处理解决问题吗?

老实说,我认为您的问题应该是 IO 限制,而不是 CPU 限制。我认为您的解码时间将占主导地位,但让我们假装它不是,笑着说。

以这种方式考虑 - 如果您一次读取一整行源图像,您可以将该解码的行扔到一个线程,该线程将其写入目标图像的适当列。所以写你的解码器,让它有一个像 OnRowDecoded(byte *row, int y, int width, int bytesPerPixel); 这样的方法。然后你在解码时旋转。 OnRowDecoded() 打包信息并将其传递给拥有目标图像的线程,并将整个解码行写入正确的目标列。当主线程忙于解码下一行时,该线程将所有写入到 dest。工作线程可能会首先完成,但也可能不会。

您需要将您的 SetPixel() 设置为线程安全的,但除此之外,没有理由这应该是一个串行任务。事实上,如果您的源图像使用 TIFF 将其划分为 strip 或图 block 的功能,您可以并且应该并行解码它们。

关于image - 如何加快将巨大的 TIFF 旋转 90 度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13358919/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com