Использование Boost.GIL для преобразования изображения в «сырые» байты - PullRequest
4 голосов
/ 28 ноября 2011

Цель

Я пытаюсь перейти к Boost GIL, чтобы заменить некоторые аналогичные функциональные возможности, реализованные мною, срок службы которых истекает.

У меня есть существующий код, который работает с 24 BPP, 8-битными RGB-изображениями, используя uint8_t*. Я не могу изменить это, поскольку один и тот же интерфейс используется для показа изображений из разных мест (например, буферов OpenGL), и кода уже достаточно много.

Поэтому я пытаюсь использовать GIL небольшими шагами, начиная с чтения файла и копируя пиксель за байтом в std::vector<uint8_t>, который я могу использовать для управления хранилищем, но все равно получаю uint8_t* с помощью &vector[0].

Это можно сделать прозрачно за существующими интерфейсами, пока не наступит момент, когда рефакторинг будет иметь смысл.

Что я пробовал

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

Я собрал минимальный, полный пример, который иллюстрирует сумму того, чего мне удалось достичь, просматривая документы и пробуя что-то:

#include <boost/gil/rgb.hpp>
#include <boost/gil/extension/io/png_dynamic_io.hpp>
#include <stdint.h>
#include <vector>

int main() {
  std::vector<uint8_t> storage;
  {
    using namespace boost::gil;
    rgb8_image_t img;
    png_read_image("test.png", img);

    // what should replace 3 here to be more "generic"?
    storage.resize(img.width()*img.height()*3);

    // doesn't work, the type of the images aren't compatible.
    copy_pixels(const_view(img), 
                interleaved_view(img.width(), img.height(), &storage[0], 3*img.width()));
  }
}

Где я застрял

Это не компилируется:

error: cannot convert ‘const boost::gil::pixel<unsigned char, boost::gil::layout<boost::mpl::vector3<boost::gil::red_t, boost::gil::green_t, boost::gil::blue_t> > >’ to ‘unsigned char’ in assignment

Что само собой разумеется - пиксель RGB не может быть автоматически преобразован в один unsigned char. Я думал, что попробую использовать copy_and_convert_pixels(), чтобы исправить это, но я не вижу способа обойти 3: 1 (т.е. у меня есть 3 unsigned char s на выходе для каждого пикселя в исходное изображение) проблема с этими преобразованиями. Похоже, что конверсия в большей степени нацелена на преобразования цветового пространства (например, RGB-> HSV) или изменения упаковки.

Ответы [ 4 ]

3 голосов
/ 29 ноября 2011

Я бы просто отбрасывал каждый цвет rgb8_pixel_t по отдельности:

struct PixelInserter{
        std::vector<uint8_t>* storage;
        PixelInserter(std::vector<uint8_t>* s) : storage(s) {}
        void operator()(boost::gil::rgb8_pixel_t p) const {
                storage->push_back(boost::gil::at_c<0>(p));
                storage->push_back(boost::gil::at_c<1>(p));
                storage->push_back(boost::gil::at_c<2>(p));
        }
};

int main() {
  std::vector<uint8_t> storage;
  {
    using namespace boost::gil;
    rgb8_image_t img;
    png_read_image("test.png", img);
    storage.reserve(img.width() * img.height() * num_channels<rgb8_image_t>());
    for_each_pixel(const_view(img), PixelInserter(&storage));
  }
...
}

... но я тоже не эксперт по GIL.

1 голос
/ 26 января 2015

Я только что столкнулся с этой же проблемой; здесь для возможного использования в будущем есть ответ, который я могу придумать сейчас, когда я решил это для себя:

Такой подход copy_pixels хорош. Единственная проблема - тип назначения. Если вам известно, что rgb8_pixel_t отформатирован в памяти, как если бы это было три последовательных uint8_t, то все, что вам нужно сделать, это примерно так:

boost::gil::rgba8_image_t image;
boost::gil::png_read_image(filePath, image);
auto view = boost::gil::view(image);
typedef decltype(view)::value_type pixel;
static_assert(sizeof(pixel) == 4, "the glTexImage2D call below assumes this");

pixel imageData[view.width() * view.height()];
boost::gil::copy_pixels(view, boost::gil::interleaved_view(view.width(), view.height(), imageData, view.width() * sizeof(pixel)));

gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, imageData);

Это скопировано из моего проекта, более или менее; Я использую 32-битное изображение, но оно должно работать одинаково для любого другого, жестко закодированного типа изображения. (Я не научился использовать «any_» из GIL, поэтому не могу комментировать динамически определенные типы изображений.)

В приведенном выше коде я несколько грубо подтверждаю, что rgba8_pixel_t - это то, что OpenGL будет видеть как "INT_8_8_8_8" или что-то подобное, выполняя этот static_assert. Я думаю, что, вероятно, лучше взять эту информацию из документации GIL, чем делать предположения и пытаться подтвердить ее утверждением, но я не могу найти там четкого утверждения (я тоже новичок в GIL, так что может я просто скучаю) Но кажется довольно ясным, что это является частью замысла дизайна типов пикселей GIL. Например, руководство по дизайну GIL в какой-то момент говорит: «Наиболее часто используемый пиксель - это однородный пиксель, значения которого находятся вместе в памяти». «Вместе в памяти», кажется, именно то, что я ищу. Сразу после этого руководство рассказывает о «плоских» типах пикселей, в которых значения цветовых каналов для одного пикселя НЕ хранятся вместе в памяти. Было бы странно, если бы все заботы поддерживали это различие так же тщательно, как они это делают, а затем даже не удосужились бы заставить чередующийся тип пикселя плотно объединить свои значения цвета в памяти.

В любом случае, я продемонстрировал в своем собственном проекте, что этот подход работает по крайней мере с версией Boost, которую я использую (1.57), и я утверждаю, что если будущая версия изменит это, то мой static_assert почти наверняка будет поймай это.

(Другой подход к потенциальному отступлению заключается в том, чтобы фактически пойти дальше и использовать планарный пиксель для сопоставления между вашим массивом uint_8_t и rgb8_pixel_t, который for_each_pixel дает вам:

boost::gil::rgba8_image_t image;
boost::gil::png_read_image(filePath, image);
auto view = boost::gil::view(image);
uint8_t data[view.width() * view.height() * view.num_channels()];

using boost::gil::rgba8_pixel_t;
uint8_t* cursor = data;
boost::gil::for_each_pixel(view, std::function<void(rgba8_pixel_t)>(
        [&cursor](rgba8_pixel_t pixel)
        {
            boost::gil::rgba8_planar_ptr_t pixelInData(cursor++, cursor++, cursor++, cursor++);
            *pixelInData = pixel;

            // if data were an array of rgba8_pixel_t's, then we could just do this and be done with it:
            // *cursor++ = pixel;
            // (but in that case we might as well use copy_pixels!)
        }));

gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data);

Но это не совсем лучше стратегии at_c. Думаю, это просто хороший пример. * _planar_ptr_t удивительно умен!)

Также обратите внимание, что в сегодняшнем C ++ вам не нужно создавать отдельный тип для захвата тела вашего цикла «для каждого»; Вы можете использовать анонимную функцию, как указано выше. (Я оборачиваю свой в std :: function, потому что я предполагаю, что GIL выполняет некоторое внутреннее копирование-назначение объекта функции, или что-то в этом роде, и компилятор злится, если передается голая анонимная функция. Я предполагаю, что std :: function Обмотка может немного снизить эффективность, в моем случае это не кажется важным.)

0 голосов
/ 22 июня 2013

Полная, упрощенная форма фактического кода, который я использовал в итоге:

#include <boost/gil/rgb.hpp>
#include <boost/gil/extension/io/png_dynamic_io.hpp>
#include <vector>
#include <string>
#include <cstdint>

struct dimension {
  int w,h;
};

namespace {
struct PixelInserter {
    std::vector<uint8_t>* storage;
    PixelInserter(std::vector<uint8_t>* s) : storage(s) {}
    void operator()(boost::gil::rgb8_pixel_t p) const {
        using boost::gil::at_c;
        storage->push_back(at_c<0>(p));
        storage->push_back(at_c<1>(p));
        storage->push_back(at_c<2>(p));
    }
};

// This could probably share code with the PixelInserter
struct PixelWriter {
    const uint8_t *pixels;
    PixelWriter(const uint8_t *pixels) : pixels(pixels) {}
    void operator()(boost::gil::rgb8_pixel_t& p) {
        using boost::gil::at_c;
        at_c<0>(p) = *pixels++;
        at_c<1>(p) = *pixels++;
        at_c<2>(p) = *pixels++;
    }
};
}

void savePNG(const std::string& filename, const uint8_t *pixels, const dimension& d) {
    boost::gil::rgb8_image_t img(d.w, d.h);
    for_each_pixel(view(img), PixelWriter(pixels));
    boost::gil::png_write_view(filename, view(img));
}

std::vector<uint8_t> readPNG(const std::string& fn, dimension& d) {
    boost::gil::rgb8_image_t image_type;
    image_type img;
    png_read_image(fn, img);
    d.w = img.width();
    d.h = img.height();

    std::vector<uint8_t> storage;
    storage.reserve(d.w*d.h*boost::gil::num_channels<image_type>());
    for_each_pixel(const_view(img), PixelInserter(&storage));
    return storage;
}

int main(int argc, char **argv) {
  dimension d;
  const std::vector<uint8_t> pixels = readPNG(argv[1], d);
  savePNG(argv[2], &pixels[0], d);
}

У меня изначально было следующее, прежде чем я включил любой из заголовков GIL:

#define png_infopp_NULL (png_infopp)NULL
#define int_p_NULL (int*)NULL

Я не уверен точно, какую проблему они исправили с версией буста, которая у меня была в то время, но они, кажется, не требуются с 1.48.

0 голосов
/ 28 ноября 2011

Вот код, который я когда-то использовал:

 unsigned char * buf = new unsigned char[w * h];

 boost::gil::gray8_view_t image =
          boost::gil::interleaved_view(w, h, (boost::gil::gray8_pixel_t*)buf, w);

 for (size_t i = 0; i < ...; ++i)
 {
   boost::gil::gray8_view_t::x_iterator it = image.row_begin(i);

   // use it[j] to access pixel[i][j]
 }

Это только для оттенков серого, но предположительно цветная версия похожа.

...