简体   繁体   English

使用 ImageSharp 跨多个图像并行访问像素的正确方法

[英]Right way to parallelize pixel access across multiple images using ImageSharp

I'm trying to parallelize the processing of an image using ImageSharp.我正在尝试使用 ImageSharp 并行处理图像。 The documentation here: https://docs.sixlabors.com/articles/imagesharp/pixelbuffers.html has an example of processing two images in parallel with the following code:此处的文档: https://docs.sixlabors.com/articles/imagesharp/pixelbuffers.html有一个使用以下代码并行处理两个图像的示例:

// Extract a sub-region of sourceImage as a new image
private static Image<Rgba32> Extract(Image<Rgba32> sourceImage, Rectangle sourceArea)
{
    Image<Rgba32> targetImage = new(sourceArea.Width, sourceArea.Height);
    int height = sourceArea.Height;
    sourceImage.ProcessPixelRows(targetImage, (sourceAccessor, targetAccessor) =>
    {
        for (int i = 0; i < height; i++)
        {
            Span<Rgba32> sourceRow = sourceAccessor.GetRowSpan(sourceArea.Y + i);
            Span<Rgba32> targetRow = targetAccessor.GetRowSpan(i);

            sourceRow.Slice(sourceArea.X, sourceArea.Width).CopyTo(targetRow);
        }
    });

    return targetImage;
}

But that scenario has a key difference to mine, which is that I need to access totally arbitrary pixels from the source image .但这种情况与我的情况有一个关键区别,那就是我需要从源图像访问完全任意的像素 Like so:像这样:

Image<Rgb24> sourceImage = GetImage();
Image<Rgb24> outImage = GetImage();

for (var outY = 0; outY < outImage.Height; outY++)
{
    for (int outX = 0; outX < outImage.Width; outX++)
    {
        var outColor = GetArbitraryPixelFromAnywhereInsideSourceImage(sourceImage, outX, outY); // access arbitrary pixels from the source image based on some calculation, probably a block of between 2x2 and 4x4 pixels
         outImage[outX, outY] = outColor;
    }
}

I've already tried using the ProcessPixelRows method on the outImage, but I suspect that accessing the pixels in the sourceImage while inside that block prevents parallelization.我已经尝试在 outImage 上使用ProcessPixelRows方法,但我怀疑在该块内访问 sourceImage 中的像素会阻止并行化。

Simply replacing the for loops with Parallel.For scrambles the output image.只需将 for 循环替换为 Parallel.For 即可打乱 output 图像。

Note that each outImage pixel is written to exactly once, the sourceImage never changes, and the calculation of the value for the outImage pixel is deterministic based on the source sample.请注意,每个 outImage 像素只写入一次,sourceImage 永远不会更改,并且 outImage 像素值的计算是基于源样本的确定性的。

I would normally recommend using our higher level pixel buffer manipulation for pixel access.我通常会建议使用我们更高级别的像素缓冲区操作来访问像素。 While not parallel by default (the Vector4 variant is) they're extremely efficient.虽然默认情况下不是并行的( Vector4变体是),但它们非常高效。

However, if you want to use parallel processing you should use ParallelRowIterator from the SixLabors.ImageSharp.Advanced namespace.但是,如果要使用并行处理,则应使用SixLabors.ImageSharp.Advanced命名空间中的ParallelRowIterator This splits the processing into blocks based up on the number of available processors applying a user defined IRowOperation<T> instance to the image.这会根据将用户定义的IRowOperation<T>实例应用于图像的可用处理器的数量将处理分成多个块。

Here's a basic example applying random pixels from a source to a destination.这是一个将随机像素从源应用到目标的基本示例。

using Image<Rgba32> source = new(100, 100);
using Image<Rgba32> destination = new(100, 100);

Configuration configuration = Configuration.Default;

// You need access to individual frame pixel buffers in order
// to access some of the advanced APIs
RowOperation operation = new RowOperation(
configuration,
source.Frames[0].PixelBuffer,
destination.Frames[0].PixelBuffer);

// Ensure we don't go out of bounds
var interest = Rectangle.Intersect(source.Bounds(), destination.Bounds());
ParallelRowIterator.IterateRows<RowOperation, Rgba32>(
                configuration,
                interest,
                in operation);

// Save the output.

Your row operation would look something like this.您的行操作看起来像这样。

private readonly struct RowOperation : IRowOperation<Rgba32>
{
    private readonly Random random;
    private readonly Buffer2D<Rgba32> source;
    private readonly Buffer2D<Rgba32> destination;
    private readonly Configuration configuration;

    public RowOperation(
        Configuration configuration,
        Buffer2D<Rgba32> source,
        Buffer2D<Rgba32> destination)
    {

        this.source = source;
        this.destination = destination;
        this.random = new();
        this.configuration = configuration;
    }

    public void Invoke(int y, Span<Rgba32> span)
    {
        Span<Rgba32> destinationRowSpan = this.destination.DangerousGetRowSpan(y);
        for (int x = 0; x < destinationRowSpan.Length; x++)
        {
            destinationRowSpan[x] = this.GetRandomPixel();
        }
    }

    private Rgba32 GetRandomPixel()
    {
        int y = this.random.Next(this.source.Height);
        int x = this.random.Next(this.source.Width);
        return this.source[x, y];
    }
}

Following up on my own question, I had a chance to work through @James's answer and made some discoveries that I thought might be useful to share.跟进我自己的问题,我有机会研究@James 的回答,并发现了一些我认为可能对分享有用的发现。 If you don't give a shit about what is happening and just want code, skip to the end.如果您不关心正在发生的事情并且只想编写代码,请跳到最后。

First, I looked into his suggestion to avoid using the Advanced namespace, and instead consider using the ProcessPixelRowsAsVector4 variant of the higher level pixel buffer manipulation API. I discovered that I couldn't use that because in my case I need the row index (ie. int y ) to do my calculations, and ProcessPixelRowsAsVector4 doesn't provide it.首先,我研究了他避免使用Advanced命名空间的建议,而是考虑使用更高级别像素缓冲区操作 API 的ProcessPixelRowsAsVector4变体。我发现我不能使用它,因为在我的情况下我需要行索引(即int y ) 来进行我的计算,而ProcessPixelRowsAsVector4不提供它。

I opened a discussion here about providing an overload of ProcessPixelRowsAsVector4 that actually does have the row index, so it's possible by the time you're reading this that the library actually has a signature like this that you should try using.在这里讨论了关于提供实际上具有行索引的ProcessPixelRowsAsVector4重载的讨论,因此当您阅读本文时,该库可能实际上具有您应该尝试使用的这样的签名。


Meanwhile, in the present, I went about implementing James's other suggestion, the ParallelRowIterator.IterateRows solution.同时,目前,我着手实施 James 的其他建议,即ParallelRowIterator.IterateRows解决方案。

I did get it working, but as I was putting final touches, I realized that Invoke(int y, Span<Rgba32> span) was giving me a span that I wasn't using.我确实让它工作了,但是当我进行最后的润色时,我意识到Invoke(int y, Span<Rgba32> span)给了我一个我没有使用的跨度。 What is that span?那跨度是多少? Why was I not using it?为什么我没有使用它? Could I use it?我可以使用它吗? Should I discard it?我应该discard它吗?

This mattered to me because in my case these spans could be between 25,000 and 50,000 pixels long, and about that same number of spans could be allocated, so not doing that if it wasn't necessary seemed like a good idea.这对我来说很重要,因为在我的例子中,这些跨度可能在 25,000 到 50,000 像素之间长,并且可以分配大约相同数量的跨度,所以如果没有必要就不要这样做似乎是个好主意。 (It was possible that it's just returning a pointer to a particular place in already-allocated memory, which makes this less of an issue, but I wanted to know.) (有可能它只是返回一个指向已分配的 memory 中特定位置的指针,这使得这不是一个问题,但我想知道。)

My first guess was that the normal usecase for IterateRows involved looping over a source image, so maybe that span was like sourceRowSpan , and if that was true maybe I could somehow get the iterator to loop over the destination image and just return destinationRowSpan without me having to additionally get it inside the loop using DangerousGetRowSpan which sounds like the kind of method call that wears a leather jacket and disrespects your mother.我的第一个猜测是IterateRows的正常用例涉及循环遍历源图像,所以也许那个 span 就像sourceRowSpan ,如果这是真的,也许我可以以某种方式让迭代器循环遍历目标图像并返回destinationRowSpan而无需我另外使用DangerousGetRowSpan将它放入循环中,这听起来像是那种穿着皮夹克并且不尊重你母亲的方法调用。

But I couldn't see how the iterator was even choosing where to get the span from at all, like my source and destination buffers were just members on my custom RowOperation class with no special relationship to the IRowOperation interface.但是我根本看不出迭代器是如何选择从哪里获取跨度的,就像我的源缓冲区和目标缓冲区只是我的自定义RowOperation class 上的成员,与IRowOperation接口没有特殊关系。

So I looked inside the ParallelRowIterator class and followed the rabbit hole into RowOperationWrapper which seemed to be what was actually calling the Invoke , and I discovered 3 things:所以我查看了ParallelRowIterator class并跟随兔子洞进入RowOperationWrapper ,这似乎是真正调用Invoke的东西,我发现了三件事:

  1. The span being passed in was just a newly allocated memory span of whatever pixel type.传入的span只是新分配的 memory 任何像素类型的跨度。 So it WAS, in fact, allocating a bunch of memory for this and not just returning a pointer to the memory.所以实际上,它为此分配了一堆 memory 而不仅仅是返回指向 memory 的指针。
  2. The span wasn't referencing either the source or destination image really, so I could safely ignore it if I wanted, which the originally suggested code de facto did, but what I mean is that I could explicitly discard it with _ if I wanted.跨度并没有真正引用源图像或目标图像,所以如果我愿意,我可以安全地忽略它,最初建议的代码实际上是这样做的,但我的意思是,如果我愿意,我可以用_显式丢弃它。
  3. There is a whole other signature that doesn't allocate or pass a span at all, It just passes the row index, which is all I actually need!还有一个完全不分配或传递跨度的签名,它只是传递行索引,这就是我真正需要的!

So having discovered that, I modified the originally suggested code as follows:所以在发现这一点之后,我修改了最初建议的代码如下:

using Image<Rgba32> source = new(100, 100);
using Image<Rgba32> destination = new(100, 100);

Configuration configuration = Configuration.Default;

// You need access to individual frame pixel buffers in order
// to access some of the advanced APIs
RowOperation operation = new RowOperation(
configuration,
source.Frames[0].PixelBuffer,
destination.Frames[0].PixelBuffer);

// Ensure we don't go out of bounds
var interest = Rectangle.Intersect(source.Bounds(), destination.Bounds());
ParallelRowIterator.IterateRows<RowOperation>(
                configuration,
                interest,
                in operation);

// Save the output.
private readonly struct RowOperation : IRowOperation
{
    private readonly Random random;
    private readonly Buffer2D<Rgba32> source;
    private readonly Buffer2D<Rgba32> destination;
    private readonly Configuration configuration;

    public RowOperation(
        Configuration configuration,
        Buffer2D<Rgba32> source,
        Buffer2D<Rgba32> destination)
    {

        this.source = source;
        this.destination = destination;
        this.random = new();
        this.configuration = configuration;
    }

    public void Invoke(int y)
    {
        Span<Rgba32> destinationRowSpan = this.destination.DangerousGetRowSpan(y);
        for (int x = 0; x < destinationRowSpan.Length; x++)
        {
            destinationRowSpan[x] = this.GetRandomPixel();
        }
    }

    private Rgba32 GetRandomPixel()
    {
        int y = this.random.Next(this.source.Height);
        int x = this.random.Next(this.source.Width);
        return this.source[x, y];
    }
}

Basically I made RowOperation implement IRowOperation instead of IRowOperation<Rgba32> , and invoked it with .IterateRows<RowOperation>() instead of .IterateRows<RowOperation, Rgba32>() , which invokes with just the row index instead of the row index plus Span object.基本上我让RowOperation实现IRowOperation而不是IRowOperation<Rgba32> ,并用.IterateRows<RowOperation>()而不是.IterateRows<RowOperation, Rgba32>()调用它,它只调用行索引而不是行索引加上 Span object。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用 ImageSharp 获取图像中特定像素的颜色数据? - Getting the color data of a specific pixel in an image using ImageSharp? 在多台PC上并行化C#程序的最简单方法是什么 - What is the easiest way to parallelize my C# program across multiple PCs 使用OLEDB将多个记录导出到Access的正确方法 - Right way to export multiple records to Access using OLEDB 使用 ImageSharp 将包含 Bgra32 的传入缓冲区转换为 ImageSharp 图像的最佳方法是什么<rgba24>图片?</rgba24> - Using ImageSharp what's the best way to convert incoming buffer containing Bgra32 to an ImageSharp Image<Rgba24> image? 在 ImageSharp 中使用调色板/索引图像 - Working with palette / indexed images in ImageSharp 在 XNA 中跨多个类处理纹理加载和访问的最佳方法? - Best way to handle texture loading and access across multiple classes in XNA? 如何使用多个 ActionBlock 并行化列表? - How to parallelize a list using multiple ActionBlocks? 无法在 C# 中使用 SixLabors.ImageSharp 处理 .jpg 图像 - 版本 1.0.0-beta0007 - unable to process .jpg images using SixLabors.ImageSharp in c# - Version 1.0.0-beta0007 使用monotouch的像素完美图像 - pixel perfect images using monotouch 使用 ImageSharp 缩小图像文件 - Shrink image file using ImageSharp
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM