2012-06-21 1 views
2

Я ищу фильтр 1 бит на пиксель с использованием фильтра 3x3: для каждого входного пикселя соответствующий выходной пиксель устанавливается равным 1, если взвешенная сумма окружающих его пикселей (с весами, определяемыми фильтр) превышает некоторый порог.Фильтрация изображений 1bpp

Я надеялся, что это будет более эффективно, чем преобразование в 8 bpp, а затем фильтрацию, но я не могу придумать, как это сделать. Наивный метод состоит в том, чтобы отслеживать девять указателей на байты (три последовательные строки, а также указатели на обе стороны текущего байта в каждой строке, для вычисления вывода для первого и последнего битов в этих байтах) и для каждого вычисления входного пикселя

sum = filter[0] * (lastRowPtr & aMask > 0) + filter[1] * (lastRowPtr & bMask > 0) + ... + filter[8] * (nextRowPtr & hMask > 0),

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

Есть ли хорошие источники для того, как лучше всего делать такие вещи? Решение этой конкретной проблемы было бы потрясающе, но я был бы счастлив указать на любые примеры эффективной обработки изображений на изображениях 1bpp в C/C++. Я хотел бы заменить еще 8 файлов bpp алгоритмами 1 bpp в будущем, чтобы избежать конверсий изображений и копирования, поэтому любые общие соображения по этому поводу будут оценены.

+0

Является ли ваша ширина изображения кратной 8 или строки заполнены так, что каждый начинается с границы байта? Это обычно так, и это делает вещи немного легче. Вам нужно всего 3 маски вместо 9. –

+0

Ширина изображения произвольная, но каждая строка начинается с границы байта. Строки могут быть дополнены к более крупным границам, но гарантированно будут дополняться байтами. – theotherphil

+0

Каков ваш метод заполнения по краям? Повторить или зажать? – Ani

ответ

2

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

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

Дополнительным преимуществом распаковки в отдельный буфер является то, что он дает вам гибкость в отношении того, что вы делаете по краям. Сделав буфер на 2 байта больше, чем вход, вы распаковываете стартовый байт 1, затем устанавливаете байт 0 и n на все, что вам нравится, и петле фильтрации не нужно беспокоиться о граничных условиях вообще.

+0

Хорошо, спасибо. Я надеялся, что смогу сделать что-то более быстрое, не распаковывая, но похоже, что я лучше всего придерживаюсь существующего кода. Код для 8 bpp изображений был достаточно хорошо оптимизирован, так что, похоже, было бы много работы, чтобы победить это с помощью операций 1 bpp. – theotherphil

1

Вместо того, чтобы развернуть все изображение до 1 бит/байт (или 8bpp, по существу, как вы отметили), вы можете просто расширить текущее окно - прочитать первый байт первой строки, сдвиг и маску, затем зачитать три бита, которые вам нужны; сделайте то же самое для двух других строк. Затем для следующего окна вы просто отбрасываете левый столбец и извлекаете еще один бит из каждой строки. Логика и код, чтобы сделать это правильно, не так просто, как простое расширение всего изображения, но это займет намного меньше памяти.

В качестве промежуточного уровня вы можете просто расширить три строки, над которыми вы сейчас работаете. Наверное, проще закодировать этот путь.

2

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

Например, в вашем 3x3 выборки веса и-фильтра случае:

  1. Образец 1x3 (по горизонтали) пикселей в буфер. Это можно сделать изолированно для каждого пикселя, поэтому изображение 1024x1024 может запускать 1024^2 одновременных задания, все из которых выполняют 3 выборки.
  2. Образец 3x1 (по вертикали) пикселей из буфера. Опять же, это можно сделать на каждом пикселе одновременно.
  3. Используйте содержимое буфера, чтобы отбирать пиксели из исходной текстуры.

Преимущество этого подхода, математически, что это сокращает число операций выборки из n^2 в 2n, хотя она требует буферов равного размера к источнику (если вы уже выполняете копию, что может использоваться как буфер, вы просто не можете изменить исходный источник для шага 2). Чтобы сохранить память в 2n, вы можете выполнять шаги 2 и 3 вместе (это немного сложно и не совсем приятно); если память не является проблемой, вы можете потратить 3n на два буфера (источник, hblur, vblur).

Поскольку каждая операция работает в полной изоляции от неизменяемого источника, вы можете выполнять фильтр на каждом пикселе одновременно, если у вас достаточно ядер. Или, в более реалистичном сценарии, вы можете использовать пейджинг и кеширование для загрузки и обработки одного столбца или строки. Это удобно при работе с нечетными шагами, заполнением в конце строки и т. Д. Второй раунд выборок (по вертикали) может зависать с вашим кешем, но в худшем случае один раунд будет кэшировать, и вы обработка разрезов от экспоненциальной к линейной.

Теперь я еще не прикоснулся к случаю хранения данных в битах. Это делает вещи немного сложнее, но не так уж и страшно. Предполагая, что вы можете использовать скатное окно, что-то вроде:

d = s[x-1] + s[x] + s[x+1] 

работы. Интересно, что если вы должны были поворачивать изображение на 90 градусов во время выхода шага 1 (тривиальный, образец от (y,x) при чтении), вы можете уйти с загрузкой не более двух горизонтально смежных байтов для любого образца, и только один байт что-то вроде 75% времени. Это играет немного менее дружелюбно с кешем во время чтения, но значительно упрощает алгоритм (достаточно, чтобы он мог восстановить потерю).

Псевдо-код:

buffer source, dest, vbuf, hbuf; 

for_each (y, x) // Loop over each row, then each column. Generally works better wrt paging 
{ 
    hbuf(x, y) = (source(y, x-1) + source(y, x) + source(y, x+1))/3 // swap x and y to spin 90 degrees 
} 
for_each (y, x) 
{ 
    vbuf(x, 1-y) = (hbuf(y, x-1) + hbuf(y, x) + hbuf(y, x+1))/3 // 1-y to reverse the 90 degree spin 
} 
for_each (y, x) 
{ 
    dest(x, y) = threshold(hbuf(x, y)) 
} 

Доступ битов в байтах (source(x, y) указывает доступ/образец) относительно просто сделать, но вид боли, чтобы написать здесь, так что остается для читателя. Принцип, особенно реализованный таким образом (с 90-градусным вращением), требует только 2 прохода из n выборок каждый и всегда выборки из сразу смежных битов/байтов (никогда не требующих вычисления позиции бит в следующей строке) , В целом, это намного быстрее и проще, чем любая альтернатива.

+0

Очень мало фильтров отделимы, и вряд ли это стоит 3х3. –

+0

Большинство фильтров box, и в случае обработки бит, хранящихся в байтах, очень важно избегать поиска какого-либо произвольного бита из следующей строки. Чистый выигрыш в производительности от разделения фильтра на 3x3 является небольшим (не стоит этого только для этого до 5x5 или около того), но бит помогает сделать его более полезным. – ssube

+0

Я предположил, что, поскольку весы фильтра были упомянуты явно, что это не фильтр коробки. Я полагаю, что каждый вес «1/9' ... –

 Смежные вопросы

  • Нет связанных вопросов^_^