Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Работа с растром на низком ярусе для начинающих

Anna | 17.06.2014 | нет комментариев
Причиной для данной статьи стал дальнейший пост: «Конвертация bmp изображения в матрицу и обратно для последующей обработки». В свое время, мне много пришлось написать исследовательского кода на C#, тот, что реализовывал разные алгорифмы сжатия, обработки. То, что код исследовательский, я упомянул не нечаянно. У этого кода оригинальные требования. С одной стороны, оптимизация не дюже значима – чай значимо проверить идею. Правда и хочется, Дабы эта проверка не растягивалась на часы и дни (когда идет запуск с разными параметрами, либо обрабатывается огромный корпус тестовых изображений). Примененный в вышеупомянутом посте метод обращения к яркостям пикселов bmp.GetPixel(x, y) – это то, с чего начинался мой 1-й план. Это самый неторопливый, правда и примитивный метод. Стоит ли здесь заморачиваться? Давайте, замерим.

Применять будем типичный Bitmap (System.Drawing.Bitmap). Данный класс комфортен тем, что скрывает от нас детали кодирования растровых форматов – как правило, они нас и не волнуют. При этом поддерживаются все распространенные форматы, типа BMP, GIF, JPEG, PNG.

Кстати, предложу для начинающих первую пользу. У класса Bitmap есть конструктор, тот, что разрешает открыть файл с картинкой. Но у него есть неприятная специфика – он оставляет открытым доступ к этому файлу, следственно повторные обращения к нему приводят к эксепшену. Дабы поправить это поведение, дозволено применять такой способ, принуждающий битмап сразу «отпустить» файл:

public static Bitmap LoadBitmap(string fileName)
{
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
        return new Bitmap(fs);
}

Методология замеров

Замерять будем, перегоняя в массив и обратно в Bitmap классику обработки изображений – Лену (http://en.wikipedia.org/wiki/Lenna). Это свободное изображение, его дозволено встретить в большом числе работ по обработке изображений (и в заголовке данного поста тоже). Размер – 512*512 пикселов.

Немножко о методике – в таких случаях я выбираю не гоняться за сверхточными таймерами, а легко много раз исполнять одно и то же действие. Безусловно, с одной стороны, в этом случае данные и код теснее будут в кэше процессора. Но, но мы вычленяем затраты на 1-й запуск кода, связанный с переводом MSIL-кода в код процессора и другие убыточные расходы. Дабы гарантировать это, заранее запускаем всякий кусок кода один раз – исполняем так называемый «прогрев».

Компилируем код в Release. Запускаем его непременно не из-под студии. Больше того, студию также желанно закрыть – сталкивался со случаями, когда сам факт её «запущенности» изредка сказывается на полученных итогах. Также, желанно закрыть и другие приложения.

Запускаем код несколько раз, добиваясь классических итогов – нужно удостовериться, что на нем не сказывается какой-то непредвиденный процесс. Скажем, проснулся антивирус либо еще что-то. Все эти меры разрешают нам получить стабильные, повторяемые итоги.

«Наивный» способ

Именно данный способ был применен в подлинной статье. Он состоит в том, что применяется способ Bitmap.GetPixel(x, y). Приведем всецело код сходственного способа, тот, что конвертирует содержимое битмапа в трехмерный байтовый массив. При этом первая размерность – это цветовая компонента (от 0 до 2), вторая – позиция y, третья – позиция x. Так сложилось в моих планах, если вам захочется расположить данные напротив – думаю, задач не возникнет.

public static byte[, ,] BitmapToByteRgbNaive(Bitmap bmp)
{
    int width = bmp.Width,
        height = bmp.Height;
    byte[, ,] res = new byte[3, height, width];
    for (int y = 0; y < height;   y)
    {
        for (int x = 0; x < width;   x)
        {
            Color color = bmp.GetPixel(x, y);
            res[0, y, x] = color.R;
            res[1, y, x] = color.G;
            res[2, y, x] = color.B;
        }
    }
    return res;
}

Обратное реформирование подобно, только перенос данных идет в ином направлении. Я не буду приводить его код тут – желающие могут посмотреть в начальных кодах плана по ссылке в конце статьи.

100 реформирований в изображение и обратно на моем ноутбуке с процессором I5-2520M 2.5GHz, требуют 43.90 сек. Получается, что при изображении 512*512, только на перенос данных, расходуется порядка полусекунды!

Прямая работа с данными Bitmap

К счастью, класс Bitmap предоставляет больше стремительный метод обратиться к своим данным. Для этого нам нужно воспользоваться ссылками, предоставляемыми классом BitmapData и адресной арифметикой:

public unsafe static byte[, ,] BitmapToByteRgb(Bitmap bmp)
{
    int width = bmp.Width,
        height = bmp.Height;
    byte[, ,] res = new byte[3, height, width];
    BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
        PixelFormat.Format24bppRgb);
    try
    {
        byte* curpos;
        for (int h = 0; h < height; h  )
        {
            curpos = ((byte*)bd.Scan0)   h * bd.Stride;
            for (int w = 0; w < width; w  )
            {
                res[2, h, w] = *(curpos  );
                res[1, h, w] = *(curpos  );
                res[0, h, w] = *(curpos  );
            }
        }
    }
    finally
    {
        bmp.UnlockBits(bd);
    }
    return res;
}

Такой подход дает нам получить 0.533 секунды на 100 реформирований (ускорились в 82 раза)! Думаю, это теснее отвечает на вопрос – а стоит ли писать больше трудный код реформирования? Но можем ли мы еще ускорить процесс, оставаясь в рамках managed-кода?

Массивы vs указатели

Многомерные массивы являются не самыми стремительными конструкциями данных. Тут производятся проверки на выход за пределы индекса, сам элемент вычисляется, применяя операции умножения и сложения. От того что адресная арифметика теснее дала нам один раз значительное убыстрение при работе с данными Bitmap, то может быть, испробуем её применить и для многомерных массивов? Вот код прямого реформирования:

public unsafe static byte[, ,] BitmapToByteRgbQ(Bitmap bmp)
{
    int width = bmp.Width,
        height = bmp.Height;
    byte[, ,] res = new byte[3, height, width];
    BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
        PixelFormat.Format24bppRgb);
    try
    {
        byte* curpos;
        fixed (byte* _res = res)
        {
            byte* _r = _res, _g = _res   1, _b = _res   2;
            for (int h = 0; h < height; h  )
            {
                curpos = ((byte*)bd.Scan0)   h * bd.Stride;
                for (int w = 0; w < width; w  )
                {
                    *_b = *(curpos  ); _b  = 3;
                    *_g = *(curpos  ); _g  = 3;
                    *_r = *(curpos  ); _r  = 3;
                }
            }
        }
    }
    finally
    {
        bmp.UnlockBits(bd);
    }
    return res;
}

Итог? 0.162 сек на 100 реформирований. Так что ускорились еще в 3.3 раза (270 раз по сопоставлению с «наивной» версией). Именно сходственный код и применялся мной при изысканиях алгорифмов.

Для чего вообще переносить?

Не вовсе видимо, а для чего вообще переносить данные из Bitmap. Может вообще, все реформирования осуществлять именно там? Соглашусь, что это один из допустимых вариантов. Но, дело в том, что многие алгорифмы комфортнее проверять на данных с плавающей запятой. Тогда нет задач с переполнениями, потерей точности на промежуточных этапах. Преобразовать в double/float-массив дозволено аналогичным методом. Обратное реформирование требует проверки при конвертации в byte. Вот легкой код такой проверки:

private static byte Limit(double x)
{
    if (x < 0)
        return 0;
    if (x > 255)
        return 255;
    return (byte)x;
}

Добавление таких проверок и реформирование типов замедляет наш код. Версия с адресной арифметикой на double-массивах исполняется теснее 0.713 сек (на 100 реформирований). Но на фоне «наивного» варианта – она легко молния.

А если необходимо стремительней?

Если необходимо стремительней, то пишем перенос, обработку на C, Asm, используем SIMD-команды. Загружаем растровый формат напрямую, без обертки Bitmap. Безусловно, в этом случае мы выходим за пределы Managed-кода, со всеми вытекающими плюсами и минусами. И делать это имеет толк для теснее отлаженного алгорифма.

Полный код к статье дозволено обнаружить тут: rasterconversion.codeplex.com/SourceControl/latest

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB