Можно ли распараллелить обработку изображений с помощью PLINQ?

Я хочу сравнить альфа-канал одного изображения с ~ 1000 других изображений. Мой метод сравнения выглядит так:

public static unsafe double Similiarity (Bitmap a, Bitmap b)
{
    BitmapData aData = a.LockBits (
        new Rectangle (0, 0, a.Width, a.Height), 
        System.Drawing.Imaging.ImageLockMode.ReadOnly, a.PixelFormat);
    BitmapData bData = b.LockBits (
        new Rectangle (0, 0, b.Width, b.Height), 
        System.Drawing.Imaging.ImageLockMode.ReadOnly, b.PixelFormat);
    int PixelSize = 4;

    double sum = 0;
    for (int y=0; y<aData.Height; y++) {
        byte* aRow = (byte *)aData.Scan0 + (y * aData.Stride);
        byte* bRow = (byte *)bData.Scan0 + (y * bData.Stride);
        for (int x=0; x<aData.Width; x++) {
            byte aWeight = aRow [x * PixelSize + 3];
            byte bWeight = bRow [x * PixelSize + 3];
            sum += Math.Abs (aWeight - bWeight);
        }
    }
    a.UnlockBits (aData);
    b.UnlockBits (bData);

    return 1 - ((sum / 255) / (a.Width * a.Height));
}

Я думал, что самый простой способ ускорить вычисления — это использовать PLINQ:

var list = from Bitmap img in imageList.AsParallel where (Similiarity (referenceImage, img) > 0.5) select img;

Но при выполнении возникает исключение в gdiplus:

System.InvalidOperationException: The operation is invalid [GDI+ status: Win32Error]
  at System.Drawing.GDIPlus.CheckStatus (Status status) [0x00000] in <filename unknown>:0 
  at System.Drawing.Bitmap.LockBits (Rectangle rect, ImageLockMode flags, PixelFormat format, System.Drawing.Imaging.BitmapData bitmapData) [0x00000] in <filename unknown>:0 

Я понимаю, что gdiplus должен выполняться в разных процессах, но я думал, что это делает PLINQ. Что не так с моими предположениями?


person Rodja    schedule 28.06.2012    source источник
comment
PLINQ, конечно же, не использует разные процессы для выполнения. Он использует потоки.   -  person svick    schedule 28.06.2012


Ответы (3)


Я бы предложил отделить расчет веса от сравнения подобия следующим образом:

public static unsafe byte[,] GetWeight(Bitmap a)
{
    BitmapData aData = a.LockBits(new Rectangle(0, 0, a.Width, a.Height), ImageLockMode.ReadOnly, a.PixelFormat);
    const int pixelSize = 4;

    byte[,] weight = new byte[aData.Width, aData.Height];
    for (int y = 0; y < aData.Height; y++)
    {
        byte* aRow = (byte*)aData.Scan0 + (y * aData.Stride);
        for (int x = 0; x < aData.Width; x++)
        {
            byte aWeight = aRow[x * pixelSize + 3];
            weight[x, y] = aWeight;
        }
    }
    a.UnlockBits(aData);
    return weight;
}

public static double GetSimilarity(byte[,] weightsA, byte[,] weightsB)
{
    double sum = 0;
    int height = weightsA.GetLength(1);
    int width = weightsA.GetLength(0);
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            byte aWeight = weightsA[x,y];
            byte bWeight = weightsB[x, y];
            sum += Math.Abs(aWeight - bWeight);
        }
    }

    return 1 - ((sum / 255) / (width * height));
}

Тогда сам вызов будет выглядеть так:

var referenceWeigth = GetWeight(referenceImage);    
var list =
        imageList
            .AsParallel()
            .Select(image => new {@Image = image, @Weight = GetWeight(image)})
            .Where(imageAndWeight => GetSimilarity(imageAndWeight.Weight, referenceWeigth) > 0.5)
            .Select(imageAndWeight => imageAndWeight.Image);
person George Mamaladze    schedule 28.06.2012
comment
Спасибо за предложенный код. Он работает, как и ожидалось, а также улучшил мое понимание LINQ. - person Rodja; 28.06.2012

Я думаю, проблема может заключаться в том, что вы пытаетесь одновременно заблокировать одни и те же биты в разных потоках. Возможное решение (поскольку вы только читаете) должно состоять в том, чтобы сначала извлечь биты из каждого растрового изображения и использовать эти массивы только для их параллельного сравнения.

person Random Dev    schedule 28.06.2012

Проблема, которую я вижу в этом, заключается в том, что хотя img берется из списка, referenceImage является одним и тем же экземпляром, используемым в нескольких потоках, каждый из которых пытается его заблокировать.

Рассмотрите возможность блокировки эталонного изображения снаружи и передачи BitmapData.

person Asti    schedule 28.06.2012