multithreading - QT多线程QImage改变

标签 multithreading qt qimage

我正在编写一个软件,它对很多(潜在的大)图像进行大量图像操作/合成。
多线程有助于提高速度,但 QT 不允许同时在同一图像上使用多个 QPainter。
所以我必须在副本的每个线程中进行图像操作/合成,然后将其 blit 回,这会大大降低性能(显然取决于用例)。

所以我想出了一个看似可行但感觉非常 hacky 的想法。
我得到目标图像数据 (QImage::bits) 指针,并将其提供给工作线程。
在工作线程中,我从提供的指针重新创建了一个新的 QImage。这意味着,没有复制,没有 blitting。它似乎工作正常,只要我确保每个像素/图 block 仅在一个线程中处理并且我不分离目标图像。

我的问题是:这种方法安全吗?这种方法是否会引发任何其他问题?

示例代码

QImage source = ...;
QImage target = ...;
QPainter::CompositionMode compositionMode = QPainter::CompositionMode_SourceOver;

// calculate tiles
QList<QRect> tiles;
for(int y = rect.top(); y < rect.top() + rect.height(); y += tileSize){
    for(int x = rect.left(); x < rect.left() + rect.width(); x += tileSize){
        QRect tile(
                    x, y,
                    x + tileSize > rect.left() + rect.width() ? rect.left() + rect.width() - x : tileSize,
                    y + tileSize > rect.top() + rect.height() ? rect.top() + rect.height() - y : tileSize
        );
        tiles.append(tile);
    }
}

// Get target pixel pointer and do threaded operation on each tile
uchar *targetPix = target.bits();
auto target_size = target.size();
auto targetFormat = target.format();
QList<int> lol = QtConcurrent::blockingMapped(tiles, [&target_size, &targetFormat, &source, targetPix, &compositionMode](const QRect &r){
    QImage tile_target(targetPix, target_size.width(), target_size.height(), targetFormat);
    QPainter p(&tile_target);
    p.setCompositionMode(compositionMode);
    // do you image operations here. For now we just do a simple draw
    p.drawImage(r.topLeft(), source, r);
    return 1; // In reallity this would return sensible data ;)
});

(顺便说一句,这个例子在我的测试中提高了大约 4.6 倍的速度。当然取决于操作系统和系统。)

最佳答案

简答

这确实很棘手(但当您想要最先进的性能时通常需要这样做),但它应该(可能做到)线程安全(对于某些操作)。当然,这取决于您对 tile_target 执行的操作。

您甚至不能访问分配的图 block 之外的位(即 tile_target 的部分在 rect r 之外) ).

一些注意事项

确保您只访问指定磁贴的位

由于 tile_target 指的是整个图像,因此您需要确保不访问此目标图 block 之外的位。一些有问题的案例:

  • 抗锯齿:您可能会在执行此类操作时改变相邻部分
  • 过滤:操作应该像模糊一样经常读取相邻位以计算“平均”值。如果另一个线程可能同时写入相同的位,则读取也不安全。

可能的解决方案?:允许访问和/或写入图 block 相邻位的一种选择是将图像分成条纹并分两步处理图像:

  • 首先同时处理偶数条纹
  • 然后同时处理凹凸不平的条纹

此过程允许您修改相邻 strip 的一半(对抗锯齿有用)(如果没有人写入)以访问下一个和/或上一个 strip 的所有位(对过滤目的)。

这不会显着降低效率,如果您创建足够多的 strip 以保持所有 CPU 忙碌(即通常是您的 CPU 支持的线程数的两倍)。

我应该担心分离吗?

这不应该成为您当前实现的问题。 QImage::bits 已经从任何其他可能存在的副本中分离(如果需要)图像 (target)。当您通过阻塞调用线程执行并发操作时。至少只要 tile_target 图像存在,原始图像 (target) 就会存在。

更安全的方法

  • 使用专用于多线程图像处理的库,或者至少允许引用子图像。

  • 将图 block 的副本(参见 QImage::copy)传递给每个线程,并将结果写回原始图像(使用互斥锁或通过在调用中执行此操作线)。根据图像操作的计算不敏感度,这个额外的副本可能会也可能不会被忽略。对于 OP,这似乎不是一个可行的选择。

请注意,在抗锯齿或过滤的情况下,那些更安全的方法可能会生成与单线程结果(略有)不同的结果。这些伪影可以通过尽可能大的图 block 来最小化(即创建的图 block 不超过您的 cpu 支持的线程数)

使用GPU

使用 GPU 时,图像处理通常要快得多,尤其是对于过滤之类的事情。但这不是 QImage 开箱即用的支持。

关于multithreading - QT多线程QImage改变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73755281/

相关文章:

qt - 如何将 QImage 裁剪到其不透明区域?

java - 更改线程中 View 的大小,而不是 UI 线程

java - 从线程更新进度条时,SWT 窗口变得无响应

java - 在调用它之前如何确保另一个线程的处理程序不为空?

Python & FreeBSD : threading. currentThread().ident 即使在不同的进程中也返回相同的值

c++ - 带有一个选项卡的 QTabBar 太宽

c++ - 系统托盘图标没有响应

c++ - 是否可以将变量/函数返回作为模板参数传递

c++ - 使用一个 QPainter 一次绘制多个输出 : SVG and QImage

qt - QImage 到 QML